mirror of
https://github.com/shadoll/moma.git
synced 2026-08-28 19:43:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27fae023bf | ||
|
|
d3d5a8c5c1 |
+3
-3
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user