mnamer 2.7.1.dev19__tar.gz → 2.7.1.dev21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mnamer-2.7.1.dev21/AGENTS.md +123 -0
- mnamer-2.7.1.dev21/CLAUDE.md +1 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/PKG-INFO +1 -1
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/__version__.py +1 -1
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/PKG-INFO +1 -1
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/SOURCES.txt +2 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/actions/init/action.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/actions/lint/action.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/actions/test/action.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/dependabot.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/workflows/publish.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/workflows/pull_request.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.github/workflows/push.yml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.gitignore +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.python-version +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/.vscode/settings.json +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/Dockerfile +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/LICENSE.txt +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/MANIFEST.in +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/README.md +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/design.eps +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/logo-2.png +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/logo-3.png +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/logo.png +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/recording.mov +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/screenshot.eps +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/assets/screenshot.png +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/makefile +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/__init__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/__main__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/argument.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/const.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/endpoints.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/exceptions.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/frontends.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/language.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/metadata.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/providers.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/py.typed +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/setting_spec.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/setting_store.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/target.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/tty.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/types.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer/utils.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/dependency_links.txt +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/entry_points.txt +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/requires.txt +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/mnamer.egg-info/top_level.txt +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/pyproject.toml +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/pytest.ini +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/setup.cfg +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/__init__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/conftest.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/e2e/__init__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/e2e/conftest.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/e2e/test_directives.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/e2e/test_errors.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/e2e/test_moving.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/__init__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_argument.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_language.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_metadata.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_setting_spec.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_setting_store.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_target.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_tty.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/local/test_utils.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/__init__.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_endpoints__omdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_endpoints__tmdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_endpoints__tvdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_endpoints__tvmaze.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_providers__omdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_providers__tmdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_providers__tvdb.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/tests/network/test_providers__tvmaze.py +0 -0
- {mnamer-2.7.1.dev19 → mnamer-2.7.1.dev21}/uv.lock +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to LLMs when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
mnamer (media renamer) is a command-line utility for organizing media files. It parses filenames for metadata using guessit, queries metadata providers (TMDb, OMDb, TVDb, TvMaze), and intelligently renames/moves files based on configurable templates.
|
|
8
|
+
|
|
9
|
+
## Development Setup
|
|
10
|
+
|
|
11
|
+
This project requires Python 3.12+ and uses `uv` as the package manager:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install dependencies
|
|
15
|
+
uv sync --dev
|
|
16
|
+
|
|
17
|
+
# Run mnamer locally
|
|
18
|
+
uv run mnamer [args]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Common Commands
|
|
22
|
+
|
|
23
|
+
### Testing
|
|
24
|
+
|
|
25
|
+
The test suite is organized by pytest markers. Keep local tests free of network
|
|
26
|
+
access; network and e2e tests can be flaky because they exercise providers and
|
|
27
|
+
the CLI workflow.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Run local unit tests (no network)
|
|
31
|
+
uv run pytest -m local
|
|
32
|
+
|
|
33
|
+
# Run network tests (requires internet, may be flaky)
|
|
34
|
+
uv run pytest -m network --reruns 3
|
|
35
|
+
|
|
36
|
+
# Run end-to-end tests
|
|
37
|
+
uv run pytest -m e2e --reruns 3
|
|
38
|
+
|
|
39
|
+
# Run all tests with coverage
|
|
40
|
+
uv run pytest --cov=./ --cov-report=term-missing
|
|
41
|
+
|
|
42
|
+
# Run a specific test file
|
|
43
|
+
uv run pytest tests/local/test_metadata.py
|
|
44
|
+
|
|
45
|
+
# Run a specific test function
|
|
46
|
+
uv run pytest tests/local/test_metadata.py::test_function_name
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Registered markers are declared in `pytest.ini`: `local`, `network`, `e2e`,
|
|
50
|
+
plus provider markers `omdb`, `tmdb`, `tvdb`, and `tvmaze`.
|
|
51
|
+
|
|
52
|
+
### Linting and Formatting
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Check code with ruff
|
|
56
|
+
uv run ruff check mnamer tests
|
|
57
|
+
|
|
58
|
+
# Format code with ruff
|
|
59
|
+
uv run ruff format mnamer tests
|
|
60
|
+
|
|
61
|
+
# Type check with mypy
|
|
62
|
+
uv run mypy mnamer tests
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Architecture
|
|
66
|
+
|
|
67
|
+
### Core Data Flow
|
|
68
|
+
|
|
69
|
+
1. **Entry Point** (`__main__.py:main`): Loads settings and initializes the CLI frontend
|
|
70
|
+
2. **Frontend** (`frontends.py:Cli`): Orchestrates the file processing workflow
|
|
71
|
+
3. **Target** (`target.py:Target`): Represents a media file, manages its metadata and relocation
|
|
72
|
+
4. **Metadata** (`metadata.py`): Dataclasses for storing parsed and enriched metadata
|
|
73
|
+
- `MetadataMovie`: Movie-specific fields (name, year, id_imdb, id_tmdb)
|
|
74
|
+
- `MetadataEpisode`: TV episode fields (series, season, episode, id_tvdb, id_tvmaze)
|
|
75
|
+
5. **Providers** (`providers.py`): High-level interface for querying metadata APIs
|
|
76
|
+
- `Tmdb`, `Omdb`: Movie providers
|
|
77
|
+
- `Tvdb`, `Tvmaze`: TV episode providers
|
|
78
|
+
6. **Endpoints** (`endpoints.py`): Low-level API request functions and response TypedDicts
|
|
79
|
+
|
|
80
|
+
### Key Architectural Patterns
|
|
81
|
+
|
|
82
|
+
- **Metadata Parsing**: Uses `guessit` library to extract metadata from filenames. The `Target._parse()` method converts guessit output into `Metadata` objects.
|
|
83
|
+
|
|
84
|
+
- **Target Discovery**: `Target.populate_paths()` crawls positional targets, applies `--recurse`, `--ignore`, `--mask`, de-duplicates paths, and filters by `--media` when supplied.
|
|
85
|
+
|
|
86
|
+
- **Provider System**: Providers are registered per-provider in a class variable cache (`Target._providers`). `Provider.provider_factory()` instantiates providers based on the `ProviderType` enum and settings.
|
|
87
|
+
|
|
88
|
+
- **Settings Management**: `SettingStore` loads configuration from both CLI arguments (`argument.py:ArgLoader`) and JSON config files (`.mnamer-v2.json`). CLI args take precedence over config files. Config-only fields include API keys and replacement maps.
|
|
89
|
+
|
|
90
|
+
- **Template Formatting**: `MetadataMovie.__format__()` and `MetadataEpisode.__format__()` use `_MetaFormatter` to substitute template variables like `{name}`, `{season:02}`, and `{extension}`. Templates are configured per media type via settings.
|
|
91
|
+
|
|
92
|
+
- **File Relocation**: `Target.destination` combines the optional `movie_directory` or `episode_directory` setting with the matching format template, then applies replacement, scene/lowercase settings, and filename sanitization. `Target.relocate()` creates destination directories and moves the file.
|
|
93
|
+
|
|
94
|
+
### Important Implementation Details
|
|
95
|
+
|
|
96
|
+
- **Subtitle Handling**: Subtitle files (`.srt`, `.idx`, `.sub`) are detected via `is_subtitle()`. They use the same format pattern as their media type and, when subtitle language is known, prefix the extension with the 2-letter language code (e.g., `.en.srt`).
|
|
97
|
+
|
|
98
|
+
- **Language Support**: The `Language` class (in `language.py`) wraps babelfish for language code conversion. Providers return metadata in the language specified by `--language` setting.
|
|
99
|
+
|
|
100
|
+
- **Request Caching**: API requests go through `utils.get_session()`, a `requests-cache` `CachedSession` stored under the user cache directory for six days. Cache can be cleared with the `--clear-cache` directive or bypassed per run with `--no-cache`.
|
|
101
|
+
|
|
102
|
+
- **Error Handling**: Custom exceptions in `exceptions.py` distinguish between network errors (`MnamerNetworkException`), missing results (`MnamerNotFoundException`), and user actions (`MnamerSkipException`, `MnamerAbortException`).
|
|
103
|
+
|
|
104
|
+
- **Test Organization**:
|
|
105
|
+
- `tests/local/`: Pure unit tests, no network or filesystem side effects
|
|
106
|
+
- `tests/network/`: Integration tests hitting real APIs (may be flaky)
|
|
107
|
+
- `tests/e2e/`: End-to-end tests with actual file operations
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
- **Config File**: `.mnamer-v2.json` in the current or parent directories, or the explicit path from `--config-path`
|
|
112
|
+
- **Settings Precedence**: CLI arguments > config file > defaults
|
|
113
|
+
- **Directives vs Parameters**: Directives (like `--test`, `--id-tmdb`) are one-time overrides not stored in config files
|
|
114
|
+
- **Config-only Fields**: `api_key_omdb`, `api_key_tmdb`, `api_key_tvdb`, `api_key_tvmaze`, `replace_before`, and `replace_after` are only loaded from config/defaults, not CLI flags
|
|
115
|
+
|
|
116
|
+
## API Keys
|
|
117
|
+
|
|
118
|
+
Providers load API keys through `Provider.from_settings()` using config-only
|
|
119
|
+
fields named `api_key_<provider>`. If unset, provider classes fall back to
|
|
120
|
+
environment variables (`API_KEY_OMDB`, `API_KEY_TMDB`, `API_KEY_TVDB`,
|
|
121
|
+
`API_KEY_TVMAZE`) and then bundled defaults where present.
|
|
122
|
+
|
|
123
|
+
Check provider classes in `providers.py` for API key handling via `from_settings()` class method.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|