diff --git a/dist/renamer-0.3.1-py3-none-any.whl b/dist/renamer-0.3.1-py3-none-any.whl new file mode 100644 index 0000000..1a53e57 Binary files /dev/null and b/dist/renamer-0.3.1-py3-none-any.whl differ diff --git a/pyproject.toml b/pyproject.toml index 2265cd1..569a3c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "renamer" -version = "0.2.12" +version = "0.3.1" description = "Terminal-based media file renamer and metadata viewer" readme = "README.md" requires-python = ">=3.11" diff --git a/renamer/app.py b/renamer/app.py index 19a65f5..85601eb 100644 --- a/renamer/app.py +++ b/renamer/app.py @@ -185,8 +185,11 @@ class RenamerApp(App): extractor = MediaExtractor(node.data) proposed_formatter = ProposedNameFormatter(extractor) new_name = str(proposed_formatter) + logging.info(f"Proposed new name: {new_name!r} for file: {node.data}") if new_name and new_name != node.data.name: self.push_screen(RenameConfirmScreen(node.data, new_name)) + else: + self.notify("Proposed name is the same as current name; no rename needed.", severity="information", timeout=3) async def action_expand(self): tree = self.query_one("#file_tree", Tree) diff --git a/renamer/extractors/filename_extractor.py b/renamer/extractors/filename_extractor.py index ffea6d4..f9302f0 100644 --- a/renamer/extractors/filename_extractor.py +++ b/renamer/extractors/filename_extractor.py @@ -355,10 +355,16 @@ class FilenameExtractor: if not langs: return '' - # Count occurrences and format like mediainfo: "2ukr,eng" - lang_counts = Counter(langs) + # Count occurrences while preserving order of first appearance + lang_counts = {} + for lang in langs: + if lang not in lang_counts: + lang_counts[lang] = 0 + lang_counts[lang] += 1 + + # Format like mediainfo: "2ukr,eng" preserving order audio_langs = [f"{count}{lang}" if count > 1 else lang for lang, count in lang_counts.items()] - return ','.join(sorted(audio_langs)) + return ','.join(audio_langs) def extract_audio_tracks(self) -> list[dict]: """Extract audio track data from filename (simplified version with only language)""" diff --git a/renamer/formatters/proposed_name_formatter.py b/renamer/formatters/proposed_name_formatter.py index b768dc3..139e8ca 100644 --- a/renamer/formatters/proposed_name_formatter.py +++ b/renamer/formatters/proposed_name_formatter.py @@ -17,7 +17,7 @@ class ProposedNameFormatter: self.__frame_class = extractor.get("frame_class") or None self.__hdr = f",{extractor.get('hdr')}" if extractor.get("hdr") else "" self.__audio_langs = extractor.get("audio_langs") or None - self.__special_info = f" [{SpecialInfoFormatter.format_special_info(extractor.get('special_info'))}]" if extractor.get("special_info") else "" + self.__special_info = f" \[{SpecialInfoFormatter.format_special_info(extractor.get('special_info'))}]" if extractor.get("special_info") else "" self.__db_info = f" [{SpecialInfoFormatter.format_database_info(extractor.get('movie_db'))}]" if extractor.get("movie_db") else "" self.__extension = extractor.get("extension") or "ext" diff --git a/renamer/screens.py b/renamer/screens.py index 06cf3b5..5ffd3c9 100644 --- a/renamer/screens.py +++ b/renamer/screens.py @@ -1,7 +1,7 @@ from textual.screen import Screen from textual.widgets import Input, Button, Static from textual.containers import Vertical, Horizontal, Center, Container -from rich.markup import escape +from textual.markup import escape from pathlib import Path @@ -97,18 +97,30 @@ class RenameConfirmScreen(Screen): #confirm_content { text-align: center; } - Button { - background: $surface; - border: solid $surface; - } + # Button { + # background: $surface; + # border: solid $surface; + # } Button:focus { - background: $primary; - color: $text-primary; - border: solid $primary; + background: $primary; + # color: $text-primary; + # border: solid $primary; } #buttons { align: center middle; } + #new_name_input { + width: 100%; + margin: 1 0; + } + #new_name_display { + text-align: center; + margin-bottom: 1; + } + #warning_content { + text-align: center; + margin-bottom: 0; + } """ def __init__(self, old_path: Path, new_name: str): @@ -123,17 +135,25 @@ class RenameConfirmScreen(Screen): confirm_text = f""" {TextFormatter.bold(TextFormatter.red("RENAME CONFIRMATION"))} -Current name: {TextFormatter.cyan(escape(self.old_path.name))} -New name: {TextFormatter.green(escape(self.new_name))} +RAW name: {escape(self.old_path.name)} -{TextFormatter.yellow("This action cannot be undone!")} +Current name: {TextFormatter.cyan(escape(self.old_path.name))} +Proposed name: {TextFormatter.green(escape(self.new_name))} +{TextFormatter.yellow("Edit the new name below:")} + """.strip() + + warning_text = f""" +{TextFormatter.bold(TextFormatter.red("This action cannot be undone!"))} Do you want to proceed with renaming? """.strip() with Center(): with Vertical(): yield Static(confirm_text, id="confirm_content", markup=True) + yield Input(value=self.new_name, id="new_name_input", placeholder="New file name") + yield Static(f"{TextFormatter.green(escape(self.new_name))}", id="new_name_display", markup=True) + yield Static(warning_text, id="warning_content", markup=True) with Horizontal(id="buttons"): yield Button("Rename (y)", id="rename") yield Button("Cancel (n)", id="cancel") @@ -141,6 +161,15 @@ Do you want to proceed with renaming? def on_mount(self): self.set_focus(self.query_one("#rename")) + def on_input_changed(self, event): + if event.input.id == "new_name_input": + self.new_name = event.input.value + self.new_path = self.old_path.parent / self.new_name + # Update the display + from .formatters.text_formatter import TextFormatter + display = self.query_one("#new_name_display", Static) + display.update(f"{TextFormatter.green(escape(self.new_name))}") + def on_button_pressed(self, event): if event.button.id == "rename": try: @@ -156,10 +185,37 @@ Do you want to proceed with renaming? self.app.pop_screen() def on_key(self, event): - if event.key == "left": - self.set_focus(self.query_one("#rename")) - elif event.key == "right": - self.set_focus(self.query_one("#cancel")) + current = self.focused + if current and hasattr(current, 'id'): + if current.id == "new_name_input": + # When input is focused, let left/right move cursor, use up/down to change focus + if event.key == "up": + self.set_focus(self.query_one("#cancel")) + elif event.key == "down": + self.set_focus(self.query_one("#rename")) + elif current.id in ("rename", "cancel"): + if event.key == "left": + if current.id == "rename": + self.set_focus(self.query_one("#new_name_input")) + elif current.id == "cancel": + self.set_focus(self.query_one("#rename")) + elif event.key == "right": + if current.id == "new_name_input": + self.set_focus(self.query_one("#rename")) + elif current.id == "rename": + self.set_focus(self.query_one("#cancel")) + elif event.key == "up": + if current.id == "rename": + self.set_focus(self.query_one("#new_name_input")) + elif current.id == "cancel": + self.set_focus(self.query_one("#rename")) + elif event.key == "down": + if current.id == "new_name_input": + self.set_focus(self.query_one("#rename")) + elif current.id == "rename": + self.set_focus(self.query_one("#cancel")) + elif current.id == "cancel": + self.set_focus(self.query_one("#new_name_input")) elif event.key == "y": # Trigger rename try: diff --git "a/renamer/test/filenames/Big Mommas. Like Father, Like Son (2011) \\[Theatrical Cut] BDRemux [1080p,ukr,3eng].mkv" "b/renamer/test/filenames/Big Mommas. Like Father, Like Son (2011) \\[Theatrical Cut] BDRemux [1080p,ukr,3eng].mkv" new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock index a0c92fa..d5064b0 100644 --- a/uv.lock +++ b/uv.lock @@ -164,7 +164,7 @@ wheels = [ [[package]] name = "renamer" -version = "0.2.12" +version = "0.3.1" source = { editable = "." } dependencies = [ { name = "langcodes" },