feat: Add genre extraction to media properties and icons to tree
This commit is contained in:
BIN
dist/renamer-0.6.9-py3-none-any.whl
vendored
Normal file
BIN
dist/renamer-0.6.9-py3-none-any.whl
vendored
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "renamer"
|
name = "renamer"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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"
|
||||||
|
|||||||
@@ -160,6 +160,31 @@ class RenamerApp(App):
|
|||||||
self.tree_expanded = False # Sub-levels are collapsed
|
self.tree_expanded = False # Sub-levels are collapsed
|
||||||
self.set_focus(tree)
|
self.set_focus(tree)
|
||||||
|
|
||||||
|
def _get_file_icon(self, file_path: Path) -> str:
|
||||||
|
"""Get icon for file based on extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Icon character for the file type
|
||||||
|
"""
|
||||||
|
ext = file_path.suffix.lower().lstrip('.')
|
||||||
|
|
||||||
|
# File type icons
|
||||||
|
icons = {
|
||||||
|
'mkv': '📹', # Video camera for MKV
|
||||||
|
'mk3d': '🎬', # Clapper board for 3D
|
||||||
|
'avi': '🎞️', # Film frames for AVI
|
||||||
|
'mp4': '📹', # Video camera
|
||||||
|
'mov': '📹', # Video camera
|
||||||
|
'wmv': '📹', # Video camera
|
||||||
|
'webm': '📹', # Video camera
|
||||||
|
'm4v': '📹', # Video camera
|
||||||
|
}
|
||||||
|
|
||||||
|
return icons.get(ext, '📄') # Default to document icon
|
||||||
|
|
||||||
def build_tree(self, path: Path, node):
|
def build_tree(self, path: Path, node):
|
||||||
try:
|
try:
|
||||||
for item in sorted(path.iterdir()):
|
for item in sorted(path.iterdir()):
|
||||||
@@ -167,13 +192,18 @@ class RenamerApp(App):
|
|||||||
if item.is_dir():
|
if item.is_dir():
|
||||||
if item.name.startswith(".") or item.name == "lost+found":
|
if item.name.startswith(".") or item.name == "lost+found":
|
||||||
continue
|
continue
|
||||||
subnode = node.add(escape(item.name), data=item)
|
# Add folder icon before directory name
|
||||||
|
label = f"📁 {escape(item.name)}"
|
||||||
|
subnode = node.add(label, data=item)
|
||||||
self.build_tree(item, subnode)
|
self.build_tree(item, subnode)
|
||||||
elif item.is_file() and item.suffix.lower() in {
|
elif item.is_file() and item.suffix.lower() in {
|
||||||
f".{ext}" for ext in MEDIA_TYPES
|
f".{ext}" for ext in MEDIA_TYPES
|
||||||
}:
|
}:
|
||||||
logging.info(f"Adding file to tree: {item.name!r} (full path: {item})")
|
logging.info(f"Adding file to tree: {item.name!r} (full path: {item})")
|
||||||
node.add(escape(item.name), data=item)
|
# Add file type icon before filename
|
||||||
|
icon = self._get_file_icon(item)
|
||||||
|
label = f"{icon} {escape(item.name)}"
|
||||||
|
node.add(label, data=item)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
pass
|
pass
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
@@ -452,7 +482,9 @@ By Category:"""
|
|||||||
node = find_node(tree.root)
|
node = find_node(tree.root)
|
||||||
if node:
|
if node:
|
||||||
logging.info(f"Found node for {old_path}, updating to {new_path.name}")
|
logging.info(f"Found node for {old_path}, updating to {new_path.name}")
|
||||||
node.label = escape(new_path.name)
|
# Update label with icon
|
||||||
|
icon = self._get_file_icon(new_path)
|
||||||
|
node.label = f"{icon} {escape(new_path.name)}"
|
||||||
node.data = new_path
|
node.data = new_path
|
||||||
logging.info(f"After update: node.data = {node.data}, node.label = {node.label}")
|
logging.info(f"After update: node.data = {node.data}, node.label = {node.label}")
|
||||||
# Ensure cursor stays on the renamed file
|
# Ensure cursor stays on the renamed file
|
||||||
@@ -513,6 +545,10 @@ By Category:"""
|
|||||||
if parent_node:
|
if parent_node:
|
||||||
logging.info(f"Found parent node for {parent_dir}, adding file {file_path.name}")
|
logging.info(f"Found parent node for {parent_dir}, adding file {file_path.name}")
|
||||||
|
|
||||||
|
# Get icon for the file
|
||||||
|
icon = self._get_file_icon(file_path)
|
||||||
|
label = f"{icon} {escape(file_path.name)}"
|
||||||
|
|
||||||
# Add the new file node in alphabetically sorted position
|
# Add the new file node in alphabetically sorted position
|
||||||
new_node = None
|
new_node = None
|
||||||
inserted = False
|
inserted = False
|
||||||
@@ -522,14 +558,14 @@ By Category:"""
|
|||||||
# Compare filenames for sorting
|
# Compare filenames for sorting
|
||||||
if child.data.name > file_path.name:
|
if child.data.name > file_path.name:
|
||||||
# Insert before this child
|
# Insert before this child
|
||||||
new_node = parent_node.add(escape(file_path.name), data=file_path, before=i)
|
new_node = parent_node.add(label, data=file_path, before=i)
|
||||||
inserted = True
|
inserted = True
|
||||||
logging.info(f"Inserted file before {child.data.name}")
|
logging.info(f"Inserted file before {child.data.name}")
|
||||||
break
|
break
|
||||||
|
|
||||||
# If not inserted, add at the end
|
# If not inserted, add at the end
|
||||||
if not inserted:
|
if not inserted:
|
||||||
new_node = parent_node.add(escape(file_path.name), data=file_path)
|
new_node = parent_node.add(label, data=file_path)
|
||||||
logging.info(f"Added file at end of directory")
|
logging.info(f"Added file at end of directory")
|
||||||
|
|
||||||
# Select the new node and show its details
|
# Select the new node and show its details
|
||||||
|
|||||||
@@ -200,6 +200,12 @@ class MediaExtractor:
|
|||||||
("Default", "extract_subtitle_tracks"),
|
("Default", "extract_subtitle_tracks"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"genres": {
|
||||||
|
"sources": [
|
||||||
|
("TMDB", "extract_genres"),
|
||||||
|
("Default", "extract_genres"),
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(self, key: str, source: str | None = None):
|
def get(self, key: str, source: str | None = None):
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class MediaPanelView:
|
|||||||
self._props.title("Media Info Summary"),
|
self._props.title("Media Info Summary"),
|
||||||
self._props.media_title,
|
self._props.media_title,
|
||||||
self._props.media_year,
|
self._props.media_year,
|
||||||
|
self._props.media_genres,
|
||||||
self._props.media_duration,
|
self._props.media_duration,
|
||||||
self._props.media_file_size,
|
self._props.media_file_size,
|
||||||
self._props.media_file_extension,
|
self._props.media_file_extension,
|
||||||
@@ -70,6 +71,7 @@ class MediaPanelView:
|
|||||||
self._props.tmdb_title,
|
self._props.tmdb_title,
|
||||||
self._props.tmdb_original_title,
|
self._props.tmdb_original_title,
|
||||||
self._props.tmdb_year,
|
self._props.tmdb_year,
|
||||||
|
self._props.tmdb_genres,
|
||||||
self._props.tmdb_database_info,
|
self._props.tmdb_database_info,
|
||||||
self._props.tmdb_url,
|
self._props.tmdb_url,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ class MediaPanelProperties:
|
|||||||
def tmdb_year(self) -> str:
|
def tmdb_year(self) -> str:
|
||||||
"""Get TMDB year formatted with label."""
|
"""Get TMDB year formatted with label."""
|
||||||
return self._extractor.get("year", "TMDB")
|
return self._extractor.get("year", "TMDB")
|
||||||
|
|
||||||
|
@property
|
||||||
|
@text_decorators.blue()
|
||||||
|
@conditional_decorators.wrap("Genres: ")
|
||||||
|
@text_decorators.yellow()
|
||||||
|
@conditional_decorators.default("<None>")
|
||||||
|
def tmdb_genres(self) -> str:
|
||||||
|
"""Get TMDB genres formatted with label."""
|
||||||
|
return self._extractor.get("genres", "TMDB")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@text_decorators.blue()
|
@text_decorators.blue()
|
||||||
@@ -328,7 +337,7 @@ class MediaPanelProperties:
|
|||||||
return self._extractor.get("movie_db", "Filename")
|
return self._extractor.get("movie_db", "Filename")
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Joined Data Properties
|
# Media Data Properties
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -367,6 +376,15 @@ class MediaPanelProperties:
|
|||||||
"""Get selected year formatted with label."""
|
"""Get selected year formatted with label."""
|
||||||
return self._extractor.get("year")
|
return self._extractor.get("year")
|
||||||
|
|
||||||
|
@property
|
||||||
|
@text_decorators.blue()
|
||||||
|
@conditional_decorators.wrap("Genres: ")
|
||||||
|
@text_decorators.cyan()
|
||||||
|
@conditional_decorators.default("<None>")
|
||||||
|
def media_genres(self) -> str:
|
||||||
|
"""Get TMDB genres formatted with label."""
|
||||||
|
return self._extractor.get("genres")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@text_decorators.blue()
|
@text_decorators.blue()
|
||||||
@conditional_decorators.wrap("File size: ")
|
@conditional_decorators.wrap("File size: ")
|
||||||
|
|||||||
Reference in New Issue
Block a user