2 Commits
4 changed files with 55 additions and 17 deletions
+3 -3
View File
@@ -28,7 +28,7 @@ powershell -c "irm https://astral.sh/uv/install.sh | iex"
uv tool install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz uv tool install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# Specific version # Specific version
uv tool install https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-py3-none-any.whl uv tool install https://github.com/shadoll/moma/releases/download/v0.9.7/moma-0.9.7-py3-none-any.whl
# From PyPI (when published) # From PyPI (when published)
uv tool install moma uv tool install moma
@@ -40,7 +40,7 @@ uv tool install moma
uv tool install --force https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz uv tool install --force https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# Upgrade to a newer specific version # Upgrade to a newer specific version
uv tool install --force https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-py3-none-any.whl uv tool install --force https://github.com/shadoll/moma/releases/download/v0.9.7/moma-0.9.7-py3-none-any.whl
``` ```
#### Usage #### Usage
@@ -56,7 +56,7 @@ moma /path/to/directory # Scan specific directory
pip install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz pip install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# Specific version # Specific version
pip install https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-py3-none-any.whl pip install https://github.com/shadoll/moma/releases/download/v0.9.7/moma-0.9.7-py3-none-any.whl
``` ```
### Method 3: Development Installation ### Method 3: Development Installation
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "moma" name = "moma"
version = "0.9.6" version = "0.9.7"
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.12" requires-python = ">=3.12"
+50 -12
View File
@@ -4,6 +4,7 @@ from textual.containers import Horizontal, Container, ScrollableContainer, Verti
from textual.widget import Widget from textual.widget import Widget
from textual.command import Provider, Hit from textual.command import Provider, Hit
from rich.markup import escape from rich.markup import escape
from rich.text import Text
from pathlib import Path from pathlib import Path
from functools import partial from functools import partial
from typing import TYPE_CHECKING, cast, Any from typing import TYPE_CHECKING, cast, Any
@@ -14,6 +15,7 @@ from .logging_config import LoggerConfig # Initialize logging singleton
from .constants import MEDIA_TYPES from .constants import MEDIA_TYPES
from .views import OpenScreen, HelpScreen, RenameConfirmScreen, SettingsScreen, ConvertConfirmScreen, DeleteConfirmScreen from .views import OpenScreen, HelpScreen, RenameConfirmScreen, SettingsScreen, ConvertConfirmScreen, DeleteConfirmScreen
from .extractors.extractor import MediaExtractor from .extractors.extractor import MediaExtractor
from .extractors.filename_extractor import FilenameExtractor
from .views import MediaPanelView, ProposedFilenameView from .views import MediaPanelView, ProposedFilenameView
from .formatters.text_formatter import TextFormatter from .formatters.text_formatter import TextFormatter
from .formatters.catalog_formatter import CatalogFormatter from .formatters.catalog_formatter import CatalogFormatter
@@ -214,15 +216,53 @@ class MomaApp(App):
'mp4': '🌐', # Web 'mp4': '🌐', # Web
'mov': '📽️', # Video camera 'mov': '📽️', # Video camera
'webm': '🌐', # Web 'webm': '🌐', # Web
'avi': '💿', # Film frames for AVI 'avi': '💿', # Silver compact disk
'wmv': '📀', # Video camera 'wmv': '📀', # Gold compact disk
'm4v': '📹', # Video camera 'm4v': '📹', # Video camera
'mpg': '📼', # Video camera 'mpg': '📼', # Video cassette
'mpeg': '📼', # Video camera 'mpeg': '📼', # Video cassette
} }
return icons.get(ext, '📄') # Default to document icon return icons.get(ext, '📄') # Default to document icon
@staticmethod
def _frame_class_color(frame_class: str | None) -> str | None:
"""Return a Rich colour name for a frame class, or None if no highlight needed."""
if not frame_class:
return None
fc = frame_class.lower()
if fc in ("4320p",):
return "bright_green"
if fc in ("2160p",):
return "green"
if fc in ("1440p", "1080p", "1080i"):
return "yellow1"
if fc in ("720p",):
return "orange1"
# 576p, 480p, 480i, 360p and anything lower
return "red"
def _make_file_label(self, file_path: Path) -> Text:
"""Build a Rich Text label with resolution highlighted by quality tier."""
import re
icon = self._get_file_icon(file_path)
name = file_path.name
frame_class = FilenameExtractor(file_path, use_cache=False).extract_frame_class()
color = self._frame_class_color(frame_class)
label = Text(f"{icon} ")
if color and frame_class:
# Highlight the exact resolution token (e.g. "1080p") in the filename
m = re.search(re.escape(frame_class), name, re.IGNORECASE)
if m:
label.append(name[:m.start()])
label.append(name[m.start():m.end()], style=color)
label.append(name[m.end():])
return label
label.append(name)
return label
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()):
@@ -231,16 +271,14 @@ class MomaApp(App):
if item.name.startswith(".") or item.name == "lost+found": if item.name.startswith(".") or item.name == "lost+found":
continue continue
# Add folder icon before directory name # Add folder icon before directory name
label = f"󰉋 {escape(item.name)}" dir_label: str = f"󰉋 {escape(item.name)}"
subnode = node.add(label, data=item) subnode = node.add(dir_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
}: }:
# Add file type icon before filename # Add file type icon before filename with resolution colour
icon = self._get_file_icon(item) node.add(self._make_file_label(item), data=item)
label = f"{icon} {escape(item.name)}"
node.add(label, data=item)
except PermissionError: except PermissionError:
pass pass
except PermissionError: except PermissionError:
@@ -645,7 +683,7 @@ By Category:"""
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}")
# Update label with icon # Update label with icon
icon = self._get_file_icon(new_path) icon = self._get_file_icon(new_path)
node.label = f"{icon} {escape(new_path.name)}" node.label = self._make_file_label(new_path) # type: ignore[assignment]
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
@@ -708,7 +746,7 @@ By Category:"""
# Get icon for the file # Get icon for the file
icon = self._get_file_icon(file_path) icon = self._get_file_icon(file_path)
label = f"{icon} {escape(file_path.name)}" label = self._make_file_label(file_path)
# Add the new file node in alphabetically sorted position # Add the new file node in alphabetically sorted position
new_node = None new_node = None
Generated
+1 -1
View File
@@ -208,7 +208,7 @@ wheels = [
[[package]] [[package]]
name = "moma" name = "moma"
version = "0.9.6" version = "0.9.7"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "langcodes" }, { name = "langcodes" },