feat: Update version to 0.4.7 and add new extraction methods for anamorphic, extension, and 3D layout
This commit is contained in:
BIN
dist/renamer-0.4.7-py3-none-any.whl
vendored
Normal file
BIN
dist/renamer-0.4.7-py3-none-any.whl
vendored
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "renamer"
|
name = "renamer"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
description = "Terminal-based media file renamer and metadata viewer"
|
description = "Terminal-based media file renamer and metadata viewer"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class DefaultExtractor:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def extract_special_info(self):
|
def extract_special_info(self):
|
||||||
return []
|
return None
|
||||||
|
|
||||||
def extract_audio_langs(self):
|
def extract_audio_langs(self):
|
||||||
return None
|
return None
|
||||||
@@ -55,6 +55,12 @@ class DefaultExtractor:
|
|||||||
def extract_subtitle_tracks(self):
|
def extract_subtitle_tracks(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def extract_anamorphic(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_extension(self):
|
||||||
|
return None
|
||||||
|
|
||||||
def extract_tmdb_url(self):
|
def extract_tmdb_url(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,18 @@ class MediaExtractor:
|
|||||||
("Default", "extract_hdr"),
|
("Default", "extract_hdr"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"anamorphic": {
|
||||||
|
"sources": [
|
||||||
|
("MediaInfo", "extract_anamorphic"),
|
||||||
|
("Default", "extract_anamorphic"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"3d_layout": {
|
||||||
|
"sources": [
|
||||||
|
("MediaInfo", "extract_3d_layout"),
|
||||||
|
("Default", "extract_3d_layout"),
|
||||||
|
],
|
||||||
|
},
|
||||||
"movie_db": {
|
"movie_db": {
|
||||||
"sources": [
|
"sources": [
|
||||||
("TMDB", "extract_movie_db"),
|
("TMDB", "extract_movie_db"),
|
||||||
@@ -128,6 +140,7 @@ class MediaExtractor:
|
|||||||
},
|
},
|
||||||
"extension": {
|
"extension": {
|
||||||
"sources": [
|
"sources": [
|
||||||
|
("MediaInfo", "extract_extension"),
|
||||||
("FileInfo", "extract_extension"),
|
("FileInfo", "extract_extension"),
|
||||||
("Default", "extract_extension"),
|
("Default", "extract_extension"),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pymediainfo import MediaInfo
|
from pymediainfo import MediaInfo
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from ..constants import FRAME_CLASSES
|
from ..constants import FRAME_CLASSES, MEDIA_TYPES
|
||||||
import langcodes
|
import langcodes
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +21,15 @@ class MediaInfoExtractor:
|
|||||||
self.audio_tracks = []
|
self.audio_tracks = []
|
||||||
self.sub_tracks = []
|
self.sub_tracks = []
|
||||||
|
|
||||||
|
# Build mapping from meta_type to extensions
|
||||||
|
self._format_to_extensions = {}
|
||||||
|
for ext, info in MEDIA_TYPES.items():
|
||||||
|
meta_type = info.get('meta_type')
|
||||||
|
if meta_type:
|
||||||
|
if meta_type not in self._format_to_extensions:
|
||||||
|
self._format_to_extensions[meta_type] = []
|
||||||
|
self._format_to_extensions[meta_type].append(ext)
|
||||||
|
|
||||||
def _get_frame_class_from_height(self, height: int) -> str | None:
|
def _get_frame_class_from_height(self, height: int) -> str | None:
|
||||||
"""Get frame class from video height, finding closest match if exact not found"""
|
"""Get frame class from video height, finding closest match if exact not found"""
|
||||||
if not height:
|
if not height:
|
||||||
@@ -182,4 +191,51 @@ class MediaInfoExtractor:
|
|||||||
'format': getattr(s, 'format', None) or getattr(s, 'codec', None) or 'unknown',
|
'format': getattr(s, 'format', None) or getattr(s, 'codec', None) or 'unknown',
|
||||||
}
|
}
|
||||||
tracks.append(track_data)
|
tracks.append(track_data)
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
def is_3d(self) -> bool:
|
||||||
|
"""Check if the video is 3D"""
|
||||||
|
if not self.video_tracks:
|
||||||
|
return False
|
||||||
|
multi_view = getattr(self.video_tracks[0], 'multi_view_count', None)
|
||||||
|
if multi_view and int(multi_view) > 1:
|
||||||
|
return True
|
||||||
|
stereoscopic = getattr(self.video_tracks[0], 'stereoscopic', None)
|
||||||
|
if stereoscopic == 'Yes':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def extract_anamorphic(self) -> str | None:
|
||||||
|
"""Extract anamorphic info for 3D videos"""
|
||||||
|
if not self.video_tracks:
|
||||||
|
return None
|
||||||
|
anamorphic = getattr(self.video_tracks[0], 'anamorphic', None)
|
||||||
|
if anamorphic == 'Yes' and self.is_3d():
|
||||||
|
return 'Anamorphic:Yes'
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_extension(self) -> str | None:
|
||||||
|
"""Extract file extension based on container format"""
|
||||||
|
if not self.media_info:
|
||||||
|
return None
|
||||||
|
general_track = next((t for t in self.media_info.tracks if t.track_type == 'General'), None)
|
||||||
|
if not general_track:
|
||||||
|
return None
|
||||||
|
format_ = getattr(general_track, 'format', None)
|
||||||
|
if format_ in self._format_to_extensions:
|
||||||
|
exts = self._format_to_extensions[format_]
|
||||||
|
if format_ == 'Matroska':
|
||||||
|
if self.is_3d() and 'mk3d' in exts:
|
||||||
|
return 'mk3d'
|
||||||
|
else:
|
||||||
|
return 'mkv'
|
||||||
|
else:
|
||||||
|
return exts[0] if exts else None
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_3d_layout(self) -> str | None:
|
||||||
|
"""Extract 3D stereoscopic layout from MediaInfo"""
|
||||||
|
if not self.is_3d():
|
||||||
|
return None
|
||||||
|
stereoscopic = getattr(self.video_tracks[0], 'stereoscopic', None)
|
||||||
|
return stereoscopic if stereoscopic else None
|
||||||
@@ -253,6 +253,25 @@ class MediaFormatter:
|
|||||||
or "Not extracted",
|
or "Not extracted",
|
||||||
"display_formatters": [TextFormatter.grey],
|
"display_formatters": [TextFormatter.grey],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Anamorphic",
|
||||||
|
"label_formatters": [TextFormatter.bold],
|
||||||
|
"value": self.extractor.get("anamorphic", "MediaInfo") or "Not extracted",
|
||||||
|
"display_formatters": [TextFormatter.grey],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Extension",
|
||||||
|
"label_formatters": [TextFormatter.bold],
|
||||||
|
"value": self.extractor.get("extension", "MediaInfo") or "Not extracted",
|
||||||
|
"value_formatters": [ExtensionFormatter.format_extension_info],
|
||||||
|
"display_formatters": [TextFormatter.grey],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3D Layout",
|
||||||
|
"label_formatters": [TextFormatter.bold],
|
||||||
|
"value": self.extractor.get("3d_layout", "MediaInfo") or "Not extracted",
|
||||||
|
"display_formatters": [TextFormatter.grey],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return FormatterApplier.format_data_items(data)
|
return FormatterApplier.format_data_items(data)
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,22 @@ class TestMediaInfoExtractor:
|
|||||||
"""Test extracting audio languages"""
|
"""Test extracting audio languages"""
|
||||||
langs = extractor.extract_audio_langs()
|
langs = extractor.extract_audio_langs()
|
||||||
# Text files don't have audio tracks
|
# Text files don't have audio tracks
|
||||||
assert langs is None
|
assert langs is None
|
||||||
|
|
||||||
|
def test_extract_anamorphic(self, extractor, test_file):
|
||||||
|
"""Test extracting anamorphic info"""
|
||||||
|
anamorphic = extractor.extract_anamorphic()
|
||||||
|
# Text files don't have video tracks
|
||||||
|
assert anamorphic is None
|
||||||
|
|
||||||
|
def test_extract_extension(self, extractor, test_file):
|
||||||
|
"""Test extracting extension"""
|
||||||
|
extension = extractor.extract_extension()
|
||||||
|
# For text file, should return None since no media info
|
||||||
|
assert extension is None
|
||||||
|
|
||||||
|
def test_is_3d(self, extractor, test_file):
|
||||||
|
"""Test checking if video is 3D"""
|
||||||
|
is_3d = extractor.is_3d()
|
||||||
|
# Text files don't have video tracks
|
||||||
|
assert is_3d is False
|
||||||
Reference in New Issue
Block a user