From b21308c7b87644491f0f41570ac6bcfee6268f69 Mon Sep 17 00:00:00 2001 From: sHa Date: Fri, 26 Dec 2025 20:42:52 +0000 Subject: [PATCH] feat: Add order extraction from filename and integrate into proposed name formatting --- renamer/app.py | 10 +++++++ renamer/extractor.py | 6 ++++ renamer/extractors/default_extractor.py | 3 ++ renamer/extractors/filename_extractor.py | 29 +++++++++++++++++++ renamer/formatters/media_formatter.py | 6 ++++ renamer/formatters/proposed_name_formatter.py | 3 +- 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/renamer/app.py b/renamer/app.py index f62f278..dea546f 100644 --- a/renamer/app.py +++ b/renamer/app.py @@ -30,6 +30,7 @@ class RenamerApp(App): ("q", "quit", "Quit"), ("o", "open", "Open directory"), ("s", "scan", "Scan"), + ("r", "refresh", "Refresh"), ] def __init__(self, scan_dir): @@ -148,6 +149,15 @@ class RenamerApp(App): if self.scan_dir: self.scan_files() + async def action_refresh(self): + tree = self.query_one("#file_tree", Tree) + node = tree.cursor_node + if node and node.data and isinstance(node.data, Path) and node.data.is_file(): + self._start_loading_animation() + threading.Thread( + target=self._extract_and_show_details, args=(node.data,) + ).start() + def on_key(self, event): if event.key == "right": tree = self.query_one("#file_tree", Tree) diff --git a/renamer/extractor.py b/renamer/extractor.py index ea2e3f6..6e73377 100644 --- a/renamer/extractor.py +++ b/renamer/extractor.py @@ -46,6 +46,12 @@ class MediaExtractor: ("Default", "extract_source"), ], }, + "order": { + "sources": [ + ("Filename", "extract_order"), + ("Default", "extract_order"), + ], + }, "frame_class": { "sources": [ ("MediaInfo", "extract_frame_class"), diff --git a/renamer/extractors/default_extractor.py b/renamer/extractors/default_extractor.py index 736f940..b20d933 100644 --- a/renamer/extractors/default_extractor.py +++ b/renamer/extractors/default_extractor.py @@ -10,6 +10,9 @@ class DefaultExtractor: def extract_source(self): return None + def extract_order(self): + return None + def extract_resolution(self): return None diff --git a/renamer/extractors/filename_extractor.py b/renamer/extractors/filename_extractor.py index f86a4bf..2c6eb01 100644 --- a/renamer/extractors/filename_extractor.py +++ b/renamer/extractors/filename_extractor.py @@ -73,6 +73,13 @@ class FilenameExtractor: # Remove bracketed prefixes like [01.1], [1], etc. title = re.sub(r'^\s*\[[^\]]+\]\s*', '', title) + # Remove order number prefixes like 01., 1., 1.1 followed by space/underscore + title = re.sub(r'^\s*(\d+(?:\.\d+)?)\.(?=\s|_|$)', '', title) + title = re.sub(r'^\s*(\d+(?:\.\d+)?)(?=\s|_)', '', title) + + # Clean up any remaining leading separators + title = title.lstrip('_ \t') + # Clean up title: remove leading/trailing brackets and dots title = title.strip('[](). ') @@ -114,6 +121,28 @@ class FilenameExtractor: return src return None + def extract_order(self) -> str | None: + """Extract collection order number from filename (at the beginning)""" + # Look for order patterns at the start of filename + # Patterns: [01], [01.1], 01., 1., 1.1 followed by space or underscore + + # Check for bracketed patterns: [01], [01.1], etc. + bracket_match = re.match(r'^\[(\d+(?:\.\d+)?)\]', self.file_name) + if bracket_match: + return bracket_match.group(1) + + # Check for dot patterns: 01., 1., 1.1 followed by space, underscore, or end of string + dot_match = re.match(r'^(\d+(?:\.\d+)?)\.(?=\s|_|$)', self.file_name) + if dot_match: + return dot_match.group(1) + + # Check for number followed by space or underscore (like "1.1 " at start) + space_match = re.match(r'^(\d+(?:\.\d+)?)(?=\s|_)', self.file_name) + if space_match: + return space_match.group(1) + + return None + def extract_frame_class(self) -> str | None: """Extract frame class from filename (480p, 720p, 1080p, 2160p, etc.)""" # First check for specific numeric resolutions diff --git a/renamer/formatters/media_formatter.py b/renamer/formatters/media_formatter.py index ab46eca..b3f1d00 100644 --- a/renamer/formatters/media_formatter.py +++ b/renamer/formatters/media_formatter.py @@ -263,6 +263,12 @@ class MediaFormatter: "label": "Filename Extracted Data", "label_formatters": [TextFormatter.bold, TextFormatter.uppercase], }, + { + "label": "Order", + "label_formatters": [TextFormatter.bold], + "value": self.extractor.get("order", "Filename") or "Not extracted", + "display_formatters": [TextFormatter.grey], + }, { "label": "Movie title", "label_formatters": [TextFormatter.bold], diff --git a/renamer/formatters/proposed_name_formatter.py b/renamer/formatters/proposed_name_formatter.py index 17732cd..7793bc3 100644 --- a/renamer/formatters/proposed_name_formatter.py +++ b/renamer/formatters/proposed_name_formatter.py @@ -8,6 +8,7 @@ class ProposedNameFormatter: def __init__(self, extractor): """Initialize with media extractor data""" + self.__order = f"[{extractor.get('order')}] " if extractor.get("order") else "" self.__title = extractor.get("title") or "Unknown Title" self.__year = DateFormatter.format_year(extractor.get("year")) self.__source = f" {extractor.get('source')}" if extractor.get("source") else "" @@ -21,7 +22,7 @@ class ProposedNameFormatter: return self.rename_line() def rename_line(self) -> str: - return f"{self.__title} {self.__year}{self.__source} [{self.__frame_class}{self.__hdr},{self.__audio_langs}].{self.__extension}" + return f"{self.__order}{self.__title} {self.__year}{self.__source} [{self.__frame_class}{self.__hdr},{self.__audio_langs}].{self.__extension}" def rename_line_formatted(self) -> str: """Format the proposed name for display with color"""