diff --git a/dist/renamer-0.4.7-py3-none-any.whl b/dist/renamer-0.4.7-py3-none-any.whl new file mode 100644 index 0000000..026e6ef Binary files /dev/null and b/dist/renamer-0.4.7-py3-none-any.whl differ diff --git a/pyproject.toml b/pyproject.toml index 1e1ccdd..8a94e18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "renamer" -version = "0.4.6" +version = "0.4.7" description = "Terminal-based media file renamer and metadata viewer" readme = "README.md" requires-python = ">=3.11" diff --git a/renamer/extractors/default_extractor.py b/renamer/extractors/default_extractor.py index e9c7223..9b1834f 100644 --- a/renamer/extractors/default_extractor.py +++ b/renamer/extractors/default_extractor.py @@ -23,7 +23,7 @@ class DefaultExtractor: return None def extract_special_info(self): - return [] + return None def extract_audio_langs(self): return None @@ -55,6 +55,12 @@ class DefaultExtractor: def extract_subtitle_tracks(self): return [] + def extract_anamorphic(self): + return None + + def extract_extension(self): + return None + def extract_tmdb_url(self): return None diff --git a/renamer/extractors/extractor.py b/renamer/extractors/extractor.py index 2dfd438..05d10d7 100644 --- a/renamer/extractors/extractor.py +++ b/renamer/extractors/extractor.py @@ -76,6 +76,18 @@ class MediaExtractor: ("Default", "extract_hdr"), ], }, + "anamorphic": { + "sources": [ + ("MediaInfo", "extract_anamorphic"), + ("Default", "extract_anamorphic"), + ], + }, + "3d_layout": { + "sources": [ + ("MediaInfo", "extract_3d_layout"), + ("Default", "extract_3d_layout"), + ], + }, "movie_db": { "sources": [ ("TMDB", "extract_movie_db"), @@ -128,6 +140,7 @@ class MediaExtractor: }, "extension": { "sources": [ + ("MediaInfo", "extract_extension"), ("FileInfo", "extract_extension"), ("Default", "extract_extension"), ], diff --git a/renamer/extractors/mediainfo_extractor.py b/renamer/extractors/mediainfo_extractor.py index 0266d6f..79b62a6 100644 --- a/renamer/extractors/mediainfo_extractor.py +++ b/renamer/extractors/mediainfo_extractor.py @@ -1,7 +1,7 @@ from pathlib import Path from pymediainfo import MediaInfo from collections import Counter -from ..constants import FRAME_CLASSES +from ..constants import FRAME_CLASSES, MEDIA_TYPES import langcodes @@ -21,6 +21,15 @@ class MediaInfoExtractor: self.audio_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: """Get frame class from video height, finding closest match if exact not found""" if not height: @@ -182,4 +191,51 @@ class MediaInfoExtractor: 'format': getattr(s, 'format', None) or getattr(s, 'codec', None) or 'unknown', } tracks.append(track_data) - return tracks \ No newline at end of file + 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 \ No newline at end of file diff --git a/renamer/formatters/media_formatter.py b/renamer/formatters/media_formatter.py index 12e766d..ac82445 100644 --- a/renamer/formatters/media_formatter.py +++ b/renamer/formatters/media_formatter.py @@ -253,6 +253,25 @@ class MediaFormatter: or "Not extracted", "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) diff --git a/renamer/test/test_mediainfo_extractor.py b/renamer/test/test_mediainfo_extractor.py index a1de9d1..5d3665d 100644 --- a/renamer/test/test_mediainfo_extractor.py +++ b/renamer/test/test_mediainfo_extractor.py @@ -29,4 +29,22 @@ class TestMediaInfoExtractor: """Test extracting audio languages""" langs = extractor.extract_audio_langs() # Text files don't have audio tracks - assert langs is None \ No newline at end of file + 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 \ No newline at end of file diff --git a/uv.lock b/uv.lock index c38f3a1..156c340 100644 --- a/uv.lock +++ b/uv.lock @@ -255,7 +255,7 @@ wheels = [ [[package]] name = "renamer" -version = "0.4.6" +version = "0.4.7" source = { editable = "." } dependencies = [ { name = "langcodes" },