feat: Add order extraction from filename and integrate into proposed name formatting
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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"""
|
||||||
|
|||||||
Reference in New Issue
Block a user