feat: Add order extraction from filename and integrate into proposed name formatting

This commit is contained in:
sHa
2025-12-26 20:42:52 +00:00
parent 2dce807984
commit b21308c7b8
6 changed files with 56 additions and 1 deletions

View File

@@ -30,6 +30,7 @@ class RenamerApp(App):
("q", "quit", "Quit"), ("q", "quit", "Quit"),
("o", "open", "Open directory"), ("o", "open", "Open directory"),
("s", "scan", "Scan"), ("s", "scan", "Scan"),
("r", "refresh", "Refresh"),
] ]
def __init__(self, scan_dir): def __init__(self, scan_dir):
@@ -148,6 +149,15 @@ class RenamerApp(App):
if self.scan_dir: if self.scan_dir:
self.scan_files() 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): def on_key(self, event):
if event.key == "right": if event.key == "right":
tree = self.query_one("#file_tree", Tree) tree = self.query_one("#file_tree", Tree)

View File

@@ -46,6 +46,12 @@ class MediaExtractor:
("Default", "extract_source"), ("Default", "extract_source"),
], ],
}, },
"order": {
"sources": [
("Filename", "extract_order"),
("Default", "extract_order"),
],
},
"frame_class": { "frame_class": {
"sources": [ "sources": [
("MediaInfo", "extract_frame_class"), ("MediaInfo", "extract_frame_class"),

View File

@@ -10,6 +10,9 @@ class DefaultExtractor:
def extract_source(self): def extract_source(self):
return None return None
def extract_order(self):
return None
def extract_resolution(self): def extract_resolution(self):
return None return None

View File

@@ -73,6 +73,13 @@ class FilenameExtractor:
# Remove bracketed prefixes like [01.1], [1], etc. # Remove bracketed prefixes like [01.1], [1], etc.
title = re.sub(r'^\s*\[[^\]]+\]\s*', '', title) 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 # Clean up title: remove leading/trailing brackets and dots
title = title.strip('[](). ') title = title.strip('[](). ')
@@ -114,6 +121,28 @@ class FilenameExtractor:
return src return src
return None 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: def extract_frame_class(self) -> str | None:
"""Extract frame class from filename (480p, 720p, 1080p, 2160p, etc.)""" """Extract frame class from filename (480p, 720p, 1080p, 2160p, etc.)"""
# First check for specific numeric resolutions # First check for specific numeric resolutions

View File

@@ -263,6 +263,12 @@ class MediaFormatter:
"label": "Filename Extracted Data", "label": "Filename Extracted Data",
"label_formatters": [TextFormatter.bold, TextFormatter.uppercase], "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": "Movie title",
"label_formatters": [TextFormatter.bold], "label_formatters": [TextFormatter.bold],

View File

@@ -8,6 +8,7 @@ class ProposedNameFormatter:
def __init__(self, extractor): def __init__(self, extractor):
"""Initialize with media extractor data""" """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.__title = extractor.get("title") or "Unknown Title"
self.__year = DateFormatter.format_year(extractor.get("year")) self.__year = DateFormatter.format_year(extractor.get("year"))
self.__source = f" {extractor.get('source')}" if extractor.get("source") else "" self.__source = f" {extractor.get('source')}" if extractor.get("source") else ""
@@ -21,7 +22,7 @@ class ProposedNameFormatter:
return self.rename_line() return self.rename_line()
def rename_line(self) -> str: 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: def rename_line_formatted(self) -> str:
"""Format the proposed name for display with color""" """Format the proposed name for display with color"""