4 Commits
9 changed files with 38 additions and 18 deletions
+6 -1
View File
@@ -647,7 +647,12 @@ except (LookupError, ValueError, AttributeError) as e:
1. **Read Before Modify**: Always read files before suggesting modifications 1. **Read Before Modify**: Always read files before suggesting modifications
2. **Follow Existing Patterns**: Understand established architecture before changes 2. **Follow Existing Patterns**: Understand established architecture before changes
3. **Test Everything**: Run `uv run pytest` after all changes 3. **Run CI Checks After Every Code Change**: After any code modification, run the same checks as GitHub CI:
```bash
uv run pytest # all tests must pass
uv run mypy src/ --ignore-missing-imports # no type errors
```
Do NOT consider work complete until both commands succeed.
4. **Simplicity First**: Avoid over-engineering solutions 4. **Simplicity First**: Avoid over-engineering solutions
5. **Document Changes**: Update relevant documentation 5. **Document Changes**: Update relevant documentation
+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.4/moma-0.9.4-py3-none-any.whl uv tool install https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-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.4/moma-0.9.4-py3-none-any.whl uv tool install --force https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-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.4/moma-0.9.4-py3-none-any.whl pip install https://github.com/shadoll/moma/releases/download/v0.9.6/moma-0.9.6-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.4" version = "0.9.6"
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"
+3 -3
View File
@@ -211,9 +211,9 @@ class MomaApp(App):
icons = { icons = {
'mkv': '🎥', # Video camera for MKV 'mkv': '🎥', # Video camera for MKV
'mk3d': '🕹️', # Clapper board for 3D 'mk3d': '🕹️', # Clapper board for 3D
'mp4': '🎥', # Video camera 'mp4': '🌐', # Web
'mov': '🎥', # Video camera 'mov': '📽️', # Video camera
'webm': '🎥', # Video camera 'webm': '🌐', # Web
'avi': '💿', # Film frames for AVI 'avi': '💿', # Film frames for AVI
'wmv': '📀', # Video camera 'wmv': '📀', # Video camera
'm4v': '📹', # Video camera 'm4v': '📹', # Video camera
+3 -4
View File
@@ -227,10 +227,9 @@ class FilenameExtractor:
# Check for bare resolution numbers inside brackets (e.g., [720,ukr,eng]) # Check for bare resolution numbers inside brackets (e.g., [720,ukr,eng])
bare_match = re.search(r'[\[,](\d{3,4})(?=[,\]])', normalized_name, re.IGNORECASE) bare_match = re.search(r'[\[,](\d{3,4})(?=[,\]])', normalized_name, re.IGNORECASE)
if bare_match: if bare_match:
height = int(bare_match.group(1)) bare_fc = self._get_frame_class_from_height(int(bare_match.group(1)))
frame_class = self._get_frame_class_from_height(height) if bare_fc:
if frame_class: return bare_fc
return frame_class
# If no specific resolution found, check for non-standard quality indicators # If no specific resolution found, check for non-standard quality indicators
for indicator in NON_STANDARD_QUALITY_INDICATORS: for indicator in NON_STANDARD_QUALITY_INDICATORS:
+13 -4
View File
@@ -195,7 +195,7 @@ class MediaInfoExtractor:
resolution = self.extract_resolution() resolution = self.extract_resolution()
if not resolution: if not resolution:
return None return None
height, width = resolution width, height = resolution
logger.debug( logger.debug(
f"[{self.file_path.name}] Frame class detection - Resolution: {width}x{height}" f"[{self.file_path.name}] Frame class detection - Resolution: {width}x{height}"
@@ -249,11 +249,14 @@ class MediaInfoExtractor:
effective_height = height effective_height = height
# First, try to match width to typical widths # First, try to match width to typical widths
# Use a larger tolerance (10 pixels) to handle cinema/ultrawide aspect ratios # Use proportional tolerance (2% of typical width, min 10px) to handle
# cinema/ultrawide aspect ratios where encoded width may differ slightly
# (e.g. 3820×1592 scope 4K → 2160p, not a non-standard 1592p)
width_matches = [] width_matches = []
for frame_class, info in FRAME_CLASSES.items(): for frame_class, info in FRAME_CLASSES.items():
for tw in info["typical_widths"]: for tw in info["typical_widths"]:
if abs(width - tw) <= 10 and frame_class.endswith(scan_type): width_tolerance = max(10, int(tw * 0.02))
if abs(width - tw) <= width_tolerance and frame_class.endswith(scan_type):
diff = abs(height - info["nominal_height"]) diff = abs(height - info["nominal_height"])
width_matches.append((frame_class, diff)) width_matches.append((frame_class, diff))
@@ -329,7 +332,10 @@ class MediaInfoExtractor:
return None return None
langs = [] langs = []
for a in tracks: for a in tracks:
lang_code = getattr(a, "language", "und") or "und" lang_code = getattr(a, "language", None)
# Skip tracks with no language tag or 'und' (undetermined)
if not lang_code or lang_code.lower() in ("und", "undefined"):
continue
try: try:
# Try to get the 3-letter code # Try to get the 3-letter code
lang_obj = langcodes.Language.get(lang_code.lower()) lang_obj = langcodes.Language.get(lang_code.lower())
@@ -340,6 +346,9 @@ class MediaInfoExtractor:
logger.debug(f"Invalid language code '{lang_code}': {e}") logger.debug(f"Invalid language code '{lang_code}': {e}")
langs.append(lang_code.lower()[:3]) langs.append(lang_code.lower()[:3])
if not langs:
return None # No meaningful language info — let Filename extractor try
lang_counts = Counter(langs) lang_counts = Counter(langs)
audio_langs = [ audio_langs = [
f"{count}{lang}" if count > 1 else lang f"{count}{lang}" if count > 1 else lang
@@ -154,5 +154,12 @@
"interlaced": false, "interlaced": false,
"expected_frame_class": "1080p", "expected_frame_class": "1080p",
"testname": "test-mistakenly-high-height-2" "testname": "test-mistakenly-high-height-2"
},
{
"testname": "test-2160p-scope-240",
"resolution": [3820, 1592],
"interlaced": false,
"expected_frame_class": "2160p",
"description": "4K cinema scope 2.40:1 - width slightly under 3840, height non-standard 1592"
} }
] ]
+1 -1
View File
@@ -39,7 +39,7 @@ def test_frame_class_detection(test_case):
extractor.video_tracks = [mock_track] extractor.video_tracks = [mock_track]
extractor._get_tracks.return_value = [mock_track] # satisfies @requires_tracks_type decorator extractor._get_tracks.return_value = [mock_track] # satisfies @requires_tracks_type decorator
extractor._get_track.return_value = mock_track extractor._get_track.return_value = mock_track
extractor.extract_resolution.return_value = (height, width) extractor.extract_resolution.return_value = (width, height)
extractor.extract_interlaced.return_value = interlaced extractor.extract_interlaced.return_value = interlaced
# Test the method # Test the method
Generated
+1 -1
View File
@@ -208,7 +208,7 @@ wheels = [
[[package]] [[package]]
name = "moma" name = "moma"
version = "0.9.3" version = "0.9.6"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "langcodes" }, { name = "langcodes" },