15 Commits
Author SHA1 Message Date
sha 27fae023bf fix: Refactor resolution highlighting in MomaApp to improve label rendering 2026-04-13 16:15:30 +03:00
sha d3d5a8c5c1 fix: Enhance file label display with resolution highlighting and improve icon handling in MomaApp 2026-04-13 15:24:39 +03:00
sha 4b90abb457 fix: Improve width matching tolerance for cinema/ultrawide aspect ratios in MediaInfoExtractor; add test for 2160p scope resolution 2026-04-13 14:56:38 +03:00
sha b3c8580789 fix: Update icons for video file types in MomaApp 2026-04-13 14:44:49 +03:00
sha d26cbacc03 fix: Update version to 0.9.5 in INSTALL.md, pyproject.toml, and uv.lock 2026-04-13 14:37:53 +03:00
sha 1f87337bb4 fix: Update version to 0.9.4 in uv.lock; improve CI checks and code clarity in AGENTS.md, filename_extractor.py, and mediainfo_extractor.py 2026-04-13 14:37:37 +03:00
sha cbcb349c9b fix: Update version to 0.9.4 in INSTALL.md and pyproject.toml 2026-04-13 14:04:06 +03:00
sha 6194e5e168 test: Enhance filename extraction tests with dataset-based cases 2026-04-13 14:03:45 +03:00
sha e3c31f02f3 fix: Update version to 0.9.3 in INSTALL.md, pyproject.toml, and uv.lock 2026-04-13 12:51:46 +03:00
sha eb46b3c7ed style: Clean up button styles in RenameConfirmScreen 2026-04-13 12:44:53 +03:00
sha cf250c4089 fix: Update version to 0.9.2 in INSTALL.md, pyproject.toml, and uv.lock 2026-04-13 11:30:13 +03:00
sha d19fb730d2 feat: Implement git-release command to manage version tagging and bumping 2026-04-13 11:29:43 +03:00
sha 60302e18d3 feat: Add justfile for version bumping and release process automation 2026-04-13 01:26:24 +03:00
sha db39c02e30 fix: Update version to 0.9.1 in INSTALL.md and pyproject.toml, and bump version in uv.lock 2026-04-13 01:01:02 +03:00
sha f7270e7afc fix: Update versioning in INSTALL.md and bump version in pyproject.toml 2026-04-13 01:00:34 +03:00
15 changed files with 356 additions and 71 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
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
5. **Document Changes**: Update relevant documentation
+1 -1
View File
@@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- **GitHub Actions CI** (`ci.yml`): runs tests + mypy on Python 3.12 for every push to `main` and all PRs
- **GitHub Actions Release** (`release.yml`): triggered by `v*.*.*` tags — runs tests, builds wheel + tarball, publishes packages as GitHub Release assets (`moma-X.Y.Z.whl`, `moma-latest.whl`, etc.)
- **GitHub Actions Release** (`release.yml`): triggered by `v*.*.*` tags — runs tests, builds wheel + tarball, publishes packages as GitHub Release assets (`moma-X.Y.Z-py3-none-any.whl`, `moma-X.Y.Z.tar.gz`, `moma-latest.tar.gz`)
- **TMDB credentials in Settings**: `tmdb_api_key` and `tmdb_access_token` are now stored in `~/.config/moma/config.json` and editable via the Settings screen (`p`)
### Changed
+16 -10
View File
@@ -24,19 +24,25 @@ powershell -c "irm https://astral.sh/uv/install.sh | iex"
#### Install moma
```bash
# Always-latest stable URL (recommended)
uv tool install https://github.com/shadoll/moma/releases/latest/download/moma-latest.whl
# Always-latest (recommended, no version to update)
uv tool install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# Specific version
uv tool install https://github.com/shadoll/moma/releases/download/v0.8.11/moma-0.8.11.whl
# From a locally downloaded wheel
uv tool install moma-latest.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)
uv tool install moma
```
#### Reinstall / Upgrade moma
```bash
# Reinstall (same URL — force overwrite)
uv tool install --force https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# Upgrade to a newer specific version
uv tool install --force https://github.com/shadoll/moma/releases/download/v0.9.7/moma-0.9.7-py3-none-any.whl
```
#### Usage
```bash
moma # Scan current directory
@@ -46,11 +52,11 @@ moma /path/to/directory # Scan specific directory
### Method 2: pip Install from Wheel
```bash
# Always-latest stable URL
pip install https://github.com/shadoll/moma/releases/latest/download/moma-latest.whl
# Always-latest (recommended, no version to update)
pip install https://github.com/shadoll/moma/releases/latest/download/moma-latest.tar.gz
# From a locally downloaded wheel
pip install moma-latest.whl
# Specific version
pip install https://github.com/shadoll/moma/releases/download/v0.9.7/moma-0.9.7-py3-none-any.whl
```
### Method 3: Development Installation
+106
View File
@@ -0,0 +1,106 @@
# Bump patch version in pyproject.toml and INSTALL.md
bump:
#!/usr/bin/env python3
import re
with open('pyproject.toml', 'r') as f:
content = f.read()
m = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', content)
if m:
old = m.group(1) + '.' + m.group(2) + '.' + m.group(3)
major, minor, patch = map(int, m.groups())
new = f'{major}.{minor}.{patch + 1}'
with open('pyproject.toml', 'w') as f:
f.write(content.replace(m.group(0), f'version = "{new}"'))
with open('INSTALL.md', 'r') as f:
install = f.read()
with open('INSTALL.md', 'w') as f:
f.write(install.replace(old, new))
print(f'Version bumped to {new}')
else:
print('Version not found')
# Bump version, sync dependencies, and build package
release: bump
uv sync
uv build
@echo "Release process completed successfully!"
# Run tests
test:
uv run pytest
# Tag the current version from pyproject.toml and push to origin
git-release:
#!/usr/bin/env python3
import re
import subprocess
import sys
def read_version():
with open('pyproject.toml', 'r') as f:
content = f.read()
m = re.search(r'version = "(\d+\.\d+\.\d+)"', content)
return m.group(1) if m else None
def tag_exists(tag):
local = subprocess.run(['git', 'tag', '-l', tag], capture_output=True, text=True)
if local.stdout.strip():
return True
remote = subprocess.run(['git', 'ls-remote', '--tags', 'origin', f'refs/tags/{tag}'], capture_output=True, text=True)
return bool(remote.stdout.strip())
def do_bump():
with open('pyproject.toml', 'r') as f:
content = f.read()
m = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', content)
if not m:
print('Version not found in pyproject.toml')
sys.exit(1)
old = m.group(0).split('"')[1]
major, minor, patch = map(int, m.groups())
new = f'{major}.{minor}.{patch + 1}'
with open('pyproject.toml', 'w') as f:
f.write(content.replace(m.group(0), f'version = "{new}"'))
with open('INSTALL.md', 'r') as f:
install = f.read()
with open('INSTALL.md', 'w') as f:
f.write(install.replace(old, new))
print(f'Version bumped to {new}')
return new
version = read_version()
if not version:
print('Could not read version from pyproject.toml')
sys.exit(1)
tag = f'v{version}'
if tag_exists(tag):
print(f'Tag {tag} already exists.')
print(' [c] Cancel')
print(' [b] Bump version and continue')
print(' [r] Replace tag (delete and re-create)')
choice = input('Your choice (c/b/r): ').strip().lower()
if choice == 'c' or choice == '':
print('Cancelled.')
sys.exit(0)
elif choice == 'b':
version = do_bump()
tag = f'v{version}'
elif choice == 'r':
print(f'Deleting tag {tag} locally and on origin...')
subprocess.run(['git', 'tag', '-d', tag])
subprocess.run(['git', 'push', 'origin', f':refs/tags/{tag}'])
else:
print('Unknown choice, cancelling.')
sys.exit(1)
print(f'Creating tag {tag}...')
subprocess.run(['git', 'tag', tag], check=True)
print(f'Pushing tag {tag} to origin...')
subprocess.run(['git', 'push', 'origin', tag], check=True)
print(f'Done! Tag {tag} pushed.')
# Run the application
run *ARGS:
uv run moma {{ARGS}}
+10 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "moma"
version = "0.9.0"
version = "0.9.7"
description = "Terminal-based media file renamer and metadata viewer"
readme = "README.md"
requires-python = ">=3.12"
@@ -22,9 +22,15 @@ dev = [
]
[project.scripts]
moma = "main:main"
bump-version = "bump:main"
release = "release:main"
moma = "src.main:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["."]
include = ["src*"]
[tool.uv]
package = true
+53 -15
View File
@@ -4,6 +4,7 @@ from textual.containers import Horizontal, Container, ScrollableContainer, Verti
from textual.widget import Widget
from textual.command import Provider, Hit
from rich.markup import escape
from rich.text import Text
from pathlib import Path
from functools import partial
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 .views import OpenScreen, HelpScreen, RenameConfirmScreen, SettingsScreen, ConvertConfirmScreen, DeleteConfirmScreen
from .extractors.extractor import MediaExtractor
from .extractors.filename_extractor import FilenameExtractor
from .views import MediaPanelView, ProposedFilenameView
from .formatters.text_formatter import TextFormatter
from .formatters.catalog_formatter import CatalogFormatter
@@ -211,18 +213,56 @@ class MomaApp(App):
icons = {
'mkv': '🎥', # Video camera for MKV
'mk3d': '🕹️', # Clapper board for 3D
'mp4': '🎥', # Video camera
'mov': '🎥', # Video camera
'webm': '🎥', # Video camera
'avi': '💿', # Film frames for AVI
'wmv': '📀', # Video camera
'mp4': '🌐', # Web
'mov': '📽️', # Video camera
'webm': '🌐', # Web
'avi': '💿', # Silver compact disk
'wmv': '📀', # Gold compact disk
'm4v': '📹', # Video camera
'mpg': '📼', # Video camera
'mpeg': '📼', # Video camera
'mpg': '📼', # Video cassette
'mpeg': '📼', # Video cassette
}
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):
try:
for item in sorted(path.iterdir()):
@@ -231,16 +271,14 @@ class MomaApp(App):
if item.name.startswith(".") or item.name == "lost+found":
continue
# Add folder icon before directory name
label = f"󰉋 {escape(item.name)}"
subnode = node.add(label, data=item)
dir_label: str = f"󰉋 {escape(item.name)}"
subnode = node.add(dir_label, data=item)
self.build_tree(item, subnode)
elif item.is_file() and item.suffix.lower() in {
f".{ext}" for ext in MEDIA_TYPES
}:
# Add file type icon before filename
icon = self._get_file_icon(item)
label = f"{icon} {escape(item.name)}"
node.add(label, data=item)
# Add file type icon before filename with resolution colour
node.add(self._make_file_label(item), data=item)
except PermissionError:
pass
except PermissionError:
@@ -645,7 +683,7 @@ By Category:"""
logging.info(f"Found node for {old_path}, updating to {new_path.name}")
# Update label with icon
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
logging.info(f"After update: node.data = {node.data}, node.label = {node.label}")
# Ensure cursor stays on the renamed file
@@ -708,7 +746,7 @@ By Category:"""
# Get icon for the file
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
new_node = None
+7
View File
@@ -4,12 +4,19 @@ def main():
content = f.read()
match = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', content)
if match:
old_version = match.group(1) + '.' + match.group(2) + '.' + match.group(3)
major, minor, patch = map(int, match.groups())
patch += 1
new_version = f'{major}.{minor}.{patch}'
content = content.replace(match.group(0), f'version = "{new_version}"')
with open('pyproject.toml', 'w') as f:
f.write(content)
# Update version references in INSTALL.md
with open('INSTALL.md', 'r') as f:
install = f.read()
install = install.replace(old_version, new_version)
with open('INSTALL.md', 'w') as f:
f.write(install)
print(f'Version bumped to {new_version}')
else:
print('Version not found')
+42 -15
View File
@@ -63,6 +63,13 @@ class FilenameExtractor:
dot_match = re.search(r'\.(\d{4})\.', self.file_name)
if dot_match:
year_pos = dot_match.start()
else:
# Try year between mixed separators (like .1967_ or _1967.)
sep_match = re.search(r'(?<=[.\-_\s])(\d{4})(?=[.\-_\s])', self.file_name)
if sep_match:
year_val = int(sep_match.group(1))
if is_valid_year(year_val):
year_pos = sep_match.start(1)
else:
# Last resort: any 4-digit number
any_match = re.search(r'\b(\d{4})\b', self.file_name)
@@ -76,7 +83,7 @@ class FilenameExtractor:
source = self.extract_source()
if source:
for alias in SOURCE_DICT[source]:
match = re.search(r'\b' + re.escape(alias) + r'\b', self.file_name, re.IGNORECASE)
match = re.search(r'(?<![a-zA-Z])' + re.escape(alias) + r'(?![a-zA-Z])', self.file_name, re.IGNORECASE)
if match:
source_pos = match.start()
break
@@ -108,26 +115,23 @@ class FilenameExtractor:
# Remove bracketed prefixes like [01.1], [1], etc.
title = re.sub(r'^\s*\[[^\]]+\]\s*', '', title)
# Remove order number prefixes like 01., 1., 1.1 followed by space/underscore
# Only remove if the number is multi-digit or has decimal (to avoid removing single digit titles)
match = re.match(r'^\s*(\d+(?:\.\d+)?)\.(?=\s|_)', title)
if match:
order = match.group(1)
if len(order) > 1 or '.' in order:
title = re.sub(r'^\s*(\d+(?:\.\d+)?)\.(?=\s|_)', '', title)
# Remove order like 1.9 where 1 is order, 9 is title
# Remove order prefix (order followed by dot or space)
order = self.extract_order()
if order:
match = re.match(r'^' + re.escape(order) + r'\.(.+)', title)
match = re.match(r'^' + re.escape(order) + r'[.\s]+(.+)', title)
if match:
title = match.group(1)
# Clean up any remaining leading separators
title = title.lstrip('_ \t')
# Clean up title: remove leading/trailing brackets and dots
title = title.strip('[](). ')
# Clean up title: remove leading/trailing brackets and orphaned dots
title = title.strip('[]. ')
# Only strip unmatched leading/trailing parens
if title.endswith(')') and title.count('(') < title.count(')'):
title = title.rstrip(')')
if title.startswith('(') and title.count('(') > title.count(')'):
title = title.lstrip('(')
# Replace dots with spaces if they appear to be word separators
# Only replace dots that are surrounded by letters/digits (not at edges)
@@ -151,6 +155,13 @@ class FilenameExtractor:
if dot_match:
return dot_match.group(1)
# Try year between mixed separators (like .1967_ or _1967.)
sep_match = re.search(r'(?<=[.\-_\s])(\d{4})(?=[.\-_\s])', self.file_name)
if sep_match:
year = int(sep_match.group(1))
if is_valid_year(year):
return str(year)
# Last resort: any 4-digit number (but this is less reliable)
any_match = re.search(r'\b(\d{4})\b', self.file_name)
if any_match:
@@ -213,6 +224,13 @@ class FilenameExtractor:
# Fallback to height-based if not in constants
return self._get_frame_class_from_height(height)
# Check for bare resolution numbers inside brackets (e.g., [720,ukr,eng])
bare_match = re.search(r'[\[,](\d{3,4})(?=[,\]])', normalized_name, re.IGNORECASE)
if bare_match:
bare_fc = self._get_frame_class_from_height(int(bare_match.group(1)))
if bare_fc:
return bare_fc
# If no specific resolution found, check for non-standard quality indicators
for indicator in NON_STANDARD_QUALITY_INDICATORS:
if re.search(r'\b' + re.escape(indicator) + r'\b', self.file_name, re.IGNORECASE):
@@ -334,8 +352,17 @@ class FilenameExtractor:
# Remove bracketed content first
text_without_brackets = re.sub(r'\[([^\]]+)\]', '', self.file_name)
# Split on dots, spaces, and underscores
parts = re.split(r'[.\s_]+', text_without_brackets)
# Find start of metadata section (after title) to avoid title words being
# misdetected as language codes (e.g. "War" from "The.War.Wagon")
metadata_start = 0
year_m = (re.search(r'\(\d{4}\)', text_without_brackets) or
re.search(r'\.\d{4}\.', text_without_brackets) or
re.search(r'(?<=[.\-_\s])\d{4}(?=[.\-_\s])', text_without_brackets))
if year_m:
metadata_start = year_m.start()
# Split on dots, spaces, and underscores (only in the post-title portion)
parts = re.split(r'[.\s_]+', text_without_brackets[metadata_start:])
for part in parts:
part = part.strip()
+13 -4
View File
@@ -195,7 +195,7 @@ class MediaInfoExtractor:
resolution = self.extract_resolution()
if not resolution:
return None
height, width = resolution
width, height = resolution
logger.debug(
f"[{self.file_path.name}] Frame class detection - Resolution: {width}x{height}"
@@ -249,11 +249,14 @@ class MediaInfoExtractor:
effective_height = height
# 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 = []
for frame_class, info in FRAME_CLASSES.items():
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"])
width_matches.append((frame_class, diff))
@@ -329,7 +332,10 @@ class MediaInfoExtractor:
return None
langs = []
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 to get the 3-letter code
lang_obj = langcodes.Language.get(lang_code.lower())
@@ -340,6 +346,9 @@ class MediaInfoExtractor:
logger.debug(f"Invalid language code '{lang_code}': {e}")
langs.append(lang_code.lower()[:3])
if not langs:
return None # No meaningful language info — let Filename extractor try
lang_counts = Counter(langs)
audio_langs = [
f"{count}{lang}" if count > 1 else lang
@@ -2,6 +2,24 @@
"description": "Comprehensive test dataset for filename metadata extraction",
"version": "2.0",
"test_cases": [
{
"filename": "The.War.Wagon.1967_BDRip Ukr_Eng[Hurtom].mkv",
"expected": {
"order": null,
"title": "The War Wagon",
"year": "1967",
"source": "BDRip",
"frame_class": null,
"hdr": null,
"movie_db": null,
"special_info": null,
"audio_langs": "ukr,eng",
"extension": "mkv"
},
"testname": "edge-multi-lang-001",
"category": "edge_cases",
"description": "Multiple languages without brackets"
},
{
"filename": "Le Jaguar.(1996).[1080i,3ukr,fra].mkv",
"expected": {
@@ -799,7 +817,7 @@
"filename": "Movie.Title (2020) BDRip [1080p,ukr,eng].mkv",
"expected": {
"order": null,
"title": "Movie.Title",
"title": "Movie Title",
"year": "2020",
"source": "BDRip",
"frame_class": "1080p",
@@ -810,7 +828,7 @@
"extension": "mkv"
},
"category": "edge_cases",
"description": "Title with dots"
"description": "Title with dot separator (dot replaced with space by extractor)"
},
{
"testname": "edge-no-brackets-001",
@@ -154,5 +154,12 @@
"interlaced": false,
"expected_frame_class": "1080p",
"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"
}
]
+62
View File
@@ -15,6 +15,22 @@ def load_test_filenames():
return []
def load_test_cases():
"""Load full test cases (testname, filename, expected) from dataset"""
dataset_file = Path(__file__).parent / "datasets" / "filenames" / "filename_patterns.json"
if dataset_file.exists():
with open(dataset_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return [
pytest.param(
case['filename'],
case['expected'],
id=case.get('testname', case['filename'])
)
for case in data['test_cases']
]
return []
@pytest.mark.parametrize("filename", load_test_filenames())
def test_extract_title(filename):
"""Test title extraction from filename"""
@@ -129,3 +145,49 @@ def test_extract_audio_tracks(filename):
for track in audio_tracks:
assert isinstance(track, dict)
assert 'language' in track
# ---------------------------------------------------------------------------
# Dataset-based value-checking tests (check against filename_patterns.json)
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_title(filename, expected):
"""Test that extracted title matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_title() == expected['title']
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_year(filename, expected):
"""Test that extracted year matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_year() == expected['year']
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_source(filename, expected):
"""Test that extracted source matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_source() == expected['source']
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_frame_class(filename, expected):
"""Test that extracted frame_class matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_frame_class() == expected['frame_class']
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_audio_langs(filename, expected):
"""Test that extracted audio_langs matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_audio_langs() == expected['audio_langs']
@pytest.mark.parametrize("filename,expected", load_test_cases())
def test_expected_movie_db(filename, expected):
"""Test that extracted movie_db matches the expected value from dataset."""
extractor = FilenameExtractor(Path(filename), use_cache=False)
assert extractor.extract_movie_db() == expected['movie_db']
+1 -1
View File
@@ -39,7 +39,7 @@ def test_frame_class_detection(test_case):
extractor.video_tracks = [mock_track]
extractor._get_tracks.return_value = [mock_track] # satisfies @requires_tracks_type decorator
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
# Test the method
-6
View File
@@ -11,14 +11,8 @@ class RenameConfirmScreen(Screen):
#confirm_content {
text-align: center;
}
# Button {
# background: $surface;
# border: solid $surface;
}
Button:focus {
background: $primary;
# color: $text-primary;
# border: solid $primary;
}
#buttons {
align: center middle;
Generated
+1 -1
View File
@@ -208,7 +208,7 @@ wheels = [
[[package]]
name = "moma"
version = "0.8.11"
version = "0.9.7"
source = { editable = "." }
dependencies = [
{ name = "langcodes" },