From 274ce4d451838dba47a0846fb0563316b0579372 Mon Sep 17 00:00:00 2001 From: sHa Date: Mon, 13 Apr 2026 18:15:37 +0300 Subject: [PATCH] fix: Add 3D layout extraction from filename and update proposed filename generation --- src/extractors/extractor.py | 1 + src/extractors/filename_extractor.py | 27 +++++++++++++++++++++++++++ src/extractors/mediainfo_extractor.py | 5 +++++ src/views/proposed_filename.py | 8 +++++++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/extractors/extractor.py b/src/extractors/extractor.py index 3e371e9..12736d9 100644 --- a/src/extractors/extractor.py +++ b/src/extractors/extractor.py @@ -123,6 +123,7 @@ class MediaExtractor: "3d_layout": { "sources": [ ("MediaInfo", "extract_3d_layout"), + ("Filename", "extract_3d_layout"), ("Default", "extract_3d_layout"), ], }, diff --git a/src/extractors/filename_extractor.py b/src/extractors/filename_extractor.py index 514c651..e10bea8 100644 --- a/src/extractors/filename_extractor.py +++ b/src/extractors/filename_extractor.py @@ -251,6 +251,33 @@ class FilenameExtractor: return None + @cached_method() + def extract_3d_layout(self) -> str | None: + """Extract 3D stereoscopic layout from filename or extension""" + # Ordered by specificity: most specific patterns first + patterns = [ + (r'\b3D-SBS\b', '3D-SBS'), + (r'\b3D-OU\b', '3D-OU'), + (r'\b3D-HSBS\b', '3D-HSBS'), + (r'\b3D-HOU\b', '3D-HOU'), + (r'\bHalf[-.]?SBS\b', '3D-HSBS'), + (r'\bHalf[-.]?OU\b', '3D-HOU'), + (r'\bHSBS\b', '3D-HSBS'), + (r'\bHOU\b', '3D-HOU'), + (r'\bSBS\b', '3D-SBS'), + (r'\bOU\b', '3D-OU'), + (r'\b3D\b', '3D'), + ] + for pattern, result in patterns: + if re.search(pattern, self.file_name, re.IGNORECASE): + return result + + # Fall back to mk3d extension as a 3D indicator + if self.file_path.suffix.lower() == '.mk3d': + return '3D' + + return None + @cached_method() def extract_movie_db(self) -> list[str] | None: """Extract movie database identifier from filename""" diff --git a/src/extractors/mediainfo_extractor.py b/src/extractors/mediainfo_extractor.py index 717c33a..d9b733f 100644 --- a/src/extractors/mediainfo_extractor.py +++ b/src/extractors/mediainfo_extractor.py @@ -389,6 +389,11 @@ class MediaInfoExtractor: # Use the constants function to get extension from format ext = get_extension_from_format(format_) + # Preserve mk3d extension when the source file already has it, + # even if MediaInfo doesn't report stereoscopic metadata + if self.file_path.suffix.lower() == '.mk3d': + return 'mk3d' + # Special case: Matroska 3D uses mk3d extension if ext == "mkv" and self.is_3d(): return "mk3d" diff --git a/src/views/proposed_filename.py b/src/views/proposed_filename.py index 8f2fa96..c3bb155 100644 --- a/src/views/proposed_filename.py +++ b/src/views/proposed_filename.py @@ -48,6 +48,12 @@ class ProposedFilenameView: """Get the frame class""" return self._extractor.get("frame_class") or "" + @property + @conditional_decorators.wrap(",") + def _3d_layout(self) -> str: + """Get the 3D layout formatted with a leading comma if present""" + return self._extractor.get("3d_layout") + @property @conditional_decorators.wrap(",") def _hdr(self) -> str: @@ -81,7 +87,7 @@ class ProposedFilenameView: @property def rename_line(self) -> str: """Generate the proposed filename.""" - result = f"{self._order}{self._title}{self._year}{self._special_info}{self._source} [{self._frame_class}{self._hdr},{self._audio_langs}]{self._db_info}.{self._extension}" + result = f"{self._order}{self._title}{self._year}{self._special_info}{self._source} [{self._frame_class}{self._3d_layout}{self._hdr},{self._audio_langs}]{self._db_info}.{self._extension}" return result.replace("/", "-").replace("\\", "-") def rename_line_formatted(self, file_path) -> str: