feat: Update ToDo list with MKV metadata editing capabilities and bump version to 0.7.10
This commit is contained in:
11
ToDo.md
11
ToDo.md
@@ -29,10 +29,13 @@ This file tracks future feature enhancements and improvements.
|
|||||||
|
|
||||||
### Medium Priority
|
### Medium Priority
|
||||||
|
|
||||||
- [ ] **Metadata Editing Capabilities**
|
- [ ] **MKV Metadata Editor with mkvpropedit**
|
||||||
- Edit embedded metadata tags
|
- Fast metadata editing without re-encoding (using mkvpropedit)
|
||||||
- Batch editing support
|
- Edit container title from TMDB data
|
||||||
- Validation and preview
|
- Set audio/subtitle track languages from filename
|
||||||
|
- Set track names and flags
|
||||||
|
- Batch editing support with preview
|
||||||
|
- Validation before applying changes
|
||||||
|
|
||||||
- [ ] **Batch Rename Operations**
|
- [ ] **Batch Rename Operations**
|
||||||
- Select multiple files
|
- Select multiple files
|
||||||
|
|||||||
BIN
dist/renamer-0.7.10-py3-none-any.whl
vendored
Normal file
BIN
dist/renamer-0.7.10-py3-none-any.whl
vendored
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "renamer"
|
name = "renamer"
|
||||||
version = "0.7.9"
|
version = "0.7.10"
|
||||||
description = "Terminal-based media file renamer and metadata viewer"
|
description = "Terminal-based media file renamer and metadata viewer"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -232,6 +232,19 @@ class RenamerApp(App):
|
|||||||
def on_tree_node_highlighted(self, event):
|
def on_tree_node_highlighted(self, event):
|
||||||
node = event.node
|
node = event.node
|
||||||
if node.data and isinstance(node.data, Path):
|
if node.data and isinstance(node.data, Path):
|
||||||
|
# Check if path still exists
|
||||||
|
if not node.data.exists():
|
||||||
|
self._stop_loading_animation()
|
||||||
|
details = self.query_one("#details_technical", Static)
|
||||||
|
details.display = True
|
||||||
|
details_catalog = self.query_one("#details_catalog", Static)
|
||||||
|
details_catalog.display = False
|
||||||
|
details.update(f"[red]Path no longer exists: {node.data.name}[/red]")
|
||||||
|
proposed = self.query_one("#proposed", Static)
|
||||||
|
proposed.update("")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
if node.data.is_dir():
|
if node.data.is_dir():
|
||||||
self._stop_loading_animation()
|
self._stop_loading_animation()
|
||||||
details = self.query_one("#details_technical", Static)
|
details = self.query_one("#details_technical", Static)
|
||||||
@@ -246,6 +259,16 @@ class RenamerApp(App):
|
|||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._extract_and_show_details, args=(node.data,)
|
target=self._extract_and_show_details, args=(node.data,)
|
||||||
).start()
|
).start()
|
||||||
|
except (FileNotFoundError, OSError):
|
||||||
|
# Handle race condition where file was deleted between exists() check and is_file() call
|
||||||
|
self._stop_loading_animation()
|
||||||
|
details = self.query_one("#details_technical", Static)
|
||||||
|
details.display = True
|
||||||
|
details_catalog = self.query_one("#details_catalog", Static)
|
||||||
|
details_catalog.display = False
|
||||||
|
details.update(f"[red]Error accessing path: {node.data.name}[/red]")
|
||||||
|
proposed = self.query_one("#proposed", Static)
|
||||||
|
proposed.update("")
|
||||||
|
|
||||||
def _extract_and_show_details(self, file_path: Path):
|
def _extract_and_show_details(self, file_path: Path):
|
||||||
try:
|
try:
|
||||||
@@ -311,6 +334,16 @@ class RenamerApp(App):
|
|||||||
|
|
||||||
# Get the directory to scan
|
# Get the directory to scan
|
||||||
path = node.data
|
path = node.data
|
||||||
|
|
||||||
|
# Check if the path still exists
|
||||||
|
if not path.exists():
|
||||||
|
self.notify(f"Path no longer exists: {path.name}", severity="error", timeout=3)
|
||||||
|
# Remove the node from the tree since the file/dir is gone
|
||||||
|
if node.parent:
|
||||||
|
node.remove()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
# If it's a file, scan its parent directory
|
# If it's a file, scan its parent directory
|
||||||
path = path.parent
|
path = path.parent
|
||||||
@@ -320,6 +353,11 @@ class RenamerApp(App):
|
|||||||
else:
|
else:
|
||||||
self.notify("Cannot scan root level file", severity="warning", timeout=3)
|
self.notify("Cannot scan root level file", severity="warning", timeout=3)
|
||||||
return
|
return
|
||||||
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
self.notify(f"Error accessing path: {e}", severity="error", timeout=3)
|
||||||
|
if node.parent:
|
||||||
|
node.remove()
|
||||||
|
return
|
||||||
|
|
||||||
# Clear the node and rescan
|
# Clear the node and rescan
|
||||||
node.remove_children()
|
node.remove_children()
|
||||||
@@ -333,11 +371,20 @@ class RenamerApp(App):
|
|||||||
async def action_refresh(self):
|
async def action_refresh(self):
|
||||||
tree = self.query_one("#file_tree", Tree)
|
tree = self.query_one("#file_tree", Tree)
|
||||||
node = tree.cursor_node
|
node = tree.cursor_node
|
||||||
if node and node.data and isinstance(node.data, Path) and node.data.is_file():
|
if node and node.data and isinstance(node.data, Path):
|
||||||
|
# Check if path still exists
|
||||||
|
if not node.data.exists():
|
||||||
|
self.notify(f"Path no longer exists: {node.data.name}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if node.data.is_file():
|
||||||
self._start_loading_animation()
|
self._start_loading_animation()
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._extract_and_show_details, args=(node.data,)
|
target=self._extract_and_show_details, args=(node.data,)
|
||||||
).start()
|
).start()
|
||||||
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
self.notify(f"Error accessing file: {e}", severity="error", timeout=3)
|
||||||
|
|
||||||
async def action_help(self):
|
async def action_help(self):
|
||||||
self.push_screen(HelpScreen())
|
self.push_screen(HelpScreen())
|
||||||
@@ -410,7 +457,14 @@ By Category:"""
|
|||||||
async def action_rename(self):
|
async def action_rename(self):
|
||||||
tree = self.query_one("#file_tree", Tree)
|
tree = self.query_one("#file_tree", Tree)
|
||||||
node = tree.cursor_node
|
node = tree.cursor_node
|
||||||
if node and node.data and isinstance(node.data, Path) and node.data.is_file():
|
if node and node.data and isinstance(node.data, Path):
|
||||||
|
# Check if file exists
|
||||||
|
if not node.data.exists():
|
||||||
|
self.notify(f"File no longer exists: {node.data.name}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if node.data.is_file():
|
||||||
# Get the proposed name from the extractor
|
# Get the proposed name from the extractor
|
||||||
extractor = MediaExtractor(node.data)
|
extractor = MediaExtractor(node.data)
|
||||||
proposed_formatter = ProposedFilenameView(extractor)
|
proposed_formatter = ProposedFilenameView(extractor)
|
||||||
@@ -419,16 +473,31 @@ By Category:"""
|
|||||||
# Always open rename dialog, even if names are the same (user might want to manually edit)
|
# Always open rename dialog, even if names are the same (user might want to manually edit)
|
||||||
if new_name:
|
if new_name:
|
||||||
self.push_screen(RenameConfirmScreen(node.data, new_name))
|
self.push_screen(RenameConfirmScreen(node.data, new_name))
|
||||||
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
self.notify(f"Error accessing file: {e}", severity="error", timeout=3)
|
||||||
|
|
||||||
async def action_convert(self):
|
async def action_convert(self):
|
||||||
"""Convert AVI/MPG/MPEG/WebM/MP4 file to MKV with metadata preservation."""
|
"""Convert AVI/MPG/MPEG/WebM/MP4 file to MKV with metadata preservation."""
|
||||||
tree = self.query_one("#file_tree", Tree)
|
tree = self.query_one("#file_tree", Tree)
|
||||||
node = tree.cursor_node
|
node = tree.cursor_node
|
||||||
|
|
||||||
if not (node and node.data and isinstance(node.data, Path) and node.data.is_file()):
|
if not (node and node.data and isinstance(node.data, Path)):
|
||||||
self.notify("Please select a file first", severity="warning", timeout=3)
|
self.notify("Please select a file first", severity="warning", timeout=3)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not node.data.exists():
|
||||||
|
self.notify(f"File no longer exists: {node.data.name}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not node.data.is_file():
|
||||||
|
self.notify("Please select a file first", severity="warning", timeout=3)
|
||||||
|
return
|
||||||
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
self.notify(f"Error accessing file: {e}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
file_path = node.data
|
file_path = node.data
|
||||||
conversion_service = ConversionService()
|
conversion_service = ConversionService()
|
||||||
|
|
||||||
@@ -466,10 +535,23 @@ By Category:"""
|
|||||||
tree = self.query_one("#file_tree", Tree)
|
tree = self.query_one("#file_tree", Tree)
|
||||||
node = tree.cursor_node
|
node = tree.cursor_node
|
||||||
|
|
||||||
if not (node and node.data and isinstance(node.data, Path) and node.data.is_file()):
|
if not (node and node.data and isinstance(node.data, Path)):
|
||||||
self.notify("Please select a file first", severity="warning", timeout=3)
|
self.notify("Please select a file first", severity="warning", timeout=3)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not node.data.exists():
|
||||||
|
self.notify(f"File no longer exists: {node.data.name}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not node.data.is_file():
|
||||||
|
self.notify("Please select a file first", severity="warning", timeout=3)
|
||||||
|
return
|
||||||
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
self.notify(f"Error accessing file: {e}", severity="error", timeout=3)
|
||||||
|
return
|
||||||
|
|
||||||
file_path = node.data
|
file_path = node.data
|
||||||
|
|
||||||
# Show confirmation screen
|
# Show confirmation screen
|
||||||
|
|||||||
Reference in New Issue
Block a user