176 lines
5.4 KiB
Python
176 lines
5.4 KiB
Python
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 pathlib import Path
|
|
|
|
|
|
class OpenScreen(Screen):
|
|
def compose(self):
|
|
yield Input(placeholder="Enter directory path", value=".", id="dir_input")
|
|
yield Button("OK", id="ok")
|
|
|
|
def on_button_pressed(self, event):
|
|
if event.button.id == "ok":
|
|
self.submit_path()
|
|
|
|
def on_input_submitted(self, event):
|
|
self.submit_path()
|
|
|
|
def submit_path(self):
|
|
path_str = self.query_one("#dir_input", Input).value
|
|
path = Path(path_str)
|
|
if not path.exists():
|
|
# Show error
|
|
self.query_one("#dir_input", Input).value = f"Path does not exist: {path_str}"
|
|
return
|
|
if not path.is_dir():
|
|
self.query_one("#dir_input", Input).value = f"Not a directory: {path_str}"
|
|
return
|
|
self.app.scan_dir = path
|
|
self.app.scan_files()
|
|
self.app.pop_screen()
|
|
|
|
|
|
class HelpScreen(Screen):
|
|
def compose(self):
|
|
try:
|
|
from importlib.metadata import version
|
|
app_version = version("renamer")
|
|
except Exception:
|
|
app_version = "unknown"
|
|
|
|
help_text = f"""
|
|
Media File Renamer v{app_version}
|
|
|
|
A powerful tool for analyzing and renaming media files with intelligent metadata extraction.
|
|
|
|
NAVIGATION:
|
|
• Use arrow keys to navigate the file tree
|
|
• Right arrow: Expand directory
|
|
• Left arrow: Collapse directory
|
|
• Enter/Space: Select file
|
|
|
|
ACTIONS:
|
|
• o: Open directory - Change the scan directory
|
|
• s: Scan - Refresh the current directory
|
|
• f: Refresh - Reload metadata for selected file
|
|
• r: Rename - Rename selected file with proposed name
|
|
• p: Expand/Collapse - Toggle expansion of selected directory
|
|
• h: Help - Show this help screen
|
|
• q: Quit - Exit the application
|
|
|
|
FEATURES:
|
|
• Automatic metadata extraction from filenames
|
|
• MediaInfo integration for technical details
|
|
• Intelligent title, year, and format detection
|
|
• Support for special editions and collections
|
|
• Real-time file analysis and renaming suggestions
|
|
|
|
FILE ANALYSIS:
|
|
The app extracts various metadata including:
|
|
• Movie/series titles and years
|
|
• Video resolution and frame rates
|
|
• Audio languages and formats
|
|
• Special edition information
|
|
• Collection order numbers
|
|
• HDR and source information
|
|
|
|
Press any key to close this help screen.
|
|
""".strip()
|
|
|
|
with Vertical():
|
|
yield Static(help_text, id="help_content")
|
|
yield Button("Close", id="close")
|
|
|
|
def on_button_pressed(self, event):
|
|
if event.button.id == "close":
|
|
self.app.pop_screen()
|
|
|
|
def on_key(self, event):
|
|
# Close on any key press
|
|
self.app.pop_screen()
|
|
|
|
|
|
class RenameConfirmScreen(Screen):
|
|
CSS = """
|
|
#confirm_content {
|
|
text-align: center;
|
|
}
|
|
Button {
|
|
background: $surface;
|
|
border: solid $surface;
|
|
}
|
|
Button:focus {
|
|
background: $primary;
|
|
color: $text-primary;
|
|
border: solid $primary;
|
|
}
|
|
#buttons {
|
|
align: center middle;
|
|
}
|
|
"""
|
|
|
|
def __init__(self, old_path: Path, new_name: str):
|
|
super().__init__()
|
|
self.old_path = old_path
|
|
self.new_name = new_name
|
|
self.new_path = old_path.parent / new_name
|
|
|
|
def compose(self):
|
|
from .formatters.text_formatter import TextFormatter
|
|
|
|
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))}
|
|
|
|
{TextFormatter.yellow("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)
|
|
with Horizontal(id="buttons"):
|
|
yield Button("Rename (y)", id="rename")
|
|
yield Button("Cancel (n)", id="cancel")
|
|
|
|
def on_mount(self):
|
|
self.set_focus(self.query_one("#rename"))
|
|
|
|
def on_button_pressed(self, event):
|
|
if event.button.id == "rename":
|
|
try:
|
|
self.old_path.rename(self.new_path)
|
|
# Update the tree node
|
|
self.app.update_renamed_file(self.old_path, self.new_path)
|
|
self.app.pop_screen()
|
|
except Exception as e:
|
|
# Show error
|
|
content = self.query_one("#confirm_content", Static)
|
|
content.update(f"Error renaming file: {str(e)}")
|
|
elif event.button.id == "cancel":
|
|
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"))
|
|
elif event.key == "y":
|
|
# Trigger rename
|
|
try:
|
|
self.old_path.rename(self.new_path)
|
|
# Update the tree node
|
|
self.app.update_renamed_file(self.old_path, self.new_path)
|
|
self.app.pop_screen()
|
|
except Exception as e:
|
|
# Show error
|
|
content = self.query_one("#confirm_content", Static)
|
|
content.update(f"Error renaming file: {str(e)}")
|
|
elif event.key == "n":
|
|
# Cancel
|
|
self.app.pop_screen() |