mnamer 2.6.1.dev18__tar.gz → 2.7.1__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.
Files changed (81) hide show
  1. mnamer-2.7.1/.dockerignore +8 -0
  2. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/init/action.yml +2 -2
  3. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/dependabot.yml +1 -1
  4. mnamer-2.7.1/.github/workflows/publish-docker.yml +251 -0
  5. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/push.yml +39 -0
  6. mnamer-2.7.1/AGENTS.md +123 -0
  7. mnamer-2.7.1/CLAUDE.md +1 -0
  8. mnamer-2.7.1/Dockerfile +20 -0
  9. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/PKG-INFO +10 -2
  10. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/README.md +8 -0
  11. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__version__.py +1 -1
  12. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/PKG-INFO +10 -2
  13. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/SOURCES.txt +4 -0
  14. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/requires.txt +1 -1
  15. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/pyproject.toml +1 -1
  16. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/uv.lock +196 -176
  17. mnamer-2.6.1.dev18/Dockerfile +0 -10
  18. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/lint/action.yml +0 -0
  19. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/test/action.yml +0 -0
  20. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/publish.yml +0 -0
  21. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/pull_request.yml +0 -0
  22. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.gitignore +0 -0
  23. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.python-version +0 -0
  24. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.vscode/settings.json +0 -0
  25. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/LICENSE.txt +0 -0
  26. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/MANIFEST.in +0 -0
  27. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/design.eps +0 -0
  28. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo-2.png +0 -0
  29. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo-3.png +0 -0
  30. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo.png +0 -0
  31. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/recording.mov +0 -0
  32. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/screenshot.eps +0 -0
  33. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/screenshot.png +0 -0
  34. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/makefile +0 -0
  35. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__init__.py +0 -0
  36. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__main__.py +0 -0
  37. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/argument.py +0 -0
  38. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/const.py +0 -0
  39. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/endpoints.py +0 -0
  40. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/exceptions.py +0 -0
  41. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/frontends.py +0 -0
  42. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/language.py +0 -0
  43. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/metadata.py +0 -0
  44. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/providers.py +0 -0
  45. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/py.typed +0 -0
  46. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/setting_spec.py +0 -0
  47. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/setting_store.py +0 -0
  48. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/target.py +0 -0
  49. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/tty.py +0 -0
  50. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/types.py +0 -0
  51. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/utils.py +0 -0
  52. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/dependency_links.txt +0 -0
  53. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/entry_points.txt +0 -0
  54. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/top_level.txt +0 -0
  55. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/pytest.ini +0 -0
  56. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/setup.cfg +0 -0
  57. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/__init__.py +0 -0
  58. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/conftest.py +0 -0
  59. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/__init__.py +0 -0
  60. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/conftest.py +0 -0
  61. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_directives.py +0 -0
  62. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_errors.py +0 -0
  63. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_moving.py +0 -0
  64. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/__init__.py +0 -0
  65. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_argument.py +0 -0
  66. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_language.py +0 -0
  67. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_metadata.py +0 -0
  68. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_setting_spec.py +0 -0
  69. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_setting_store.py +0 -0
  70. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_target.py +0 -0
  71. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_tty.py +0 -0
  72. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_utils.py +0 -0
  73. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/__init__.py +0 -0
  74. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__omdb.py +0 -0
  75. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tmdb.py +0 -0
  76. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tvdb.py +0 -0
  77. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tvmaze.py +0 -0
  78. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__omdb.py +0 -0
  79. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tmdb.py +0 -0
  80. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tvdb.py +0 -0
  81. {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tvmaze.py +0 -0
@@ -0,0 +1,8 @@
1
+ .mypy_cache
2
+ .pytest_cache
3
+ .ruff_cache
4
+ .venv
5
+ build
6
+ dist
7
+ *.egg-info
8
+ __pycache__
@@ -7,7 +7,7 @@ runs:
7
7
  - uses: actions/setup-python@v5
8
8
  with:
9
9
  python-version: '3.12'
10
-
10
+
11
11
  - name: Install uv
12
12
  uses: astral-sh/setup-uv@v4
13
13
  with:
@@ -16,4 +16,4 @@ runs:
16
16
 
17
17
  - name: Install dependencies
18
18
  shell: sh
19
- run: uv sync --dev
19
+ run: uv sync --locked --dev
@@ -5,7 +5,7 @@
5
5
 
6
6
  version: 2
7
7
  updates:
8
- - package-ecosystem: "pip"
8
+ - package-ecosystem: "uv"
9
9
  directory: "/"
10
10
  schedule:
11
11
  interval: "weekly"
@@ -0,0 +1,251 @@
1
+ name: publish docker
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ target:
7
+ required: true
8
+ type: string
9
+ release_tag:
10
+ required: false
11
+ type: string
12
+ default: ""
13
+ is_prerelease:
14
+ required: false
15
+ type: boolean
16
+ default: false
17
+ secrets:
18
+ DOCKERHUB_USERNAME:
19
+ required: true
20
+ DOCKERHUB_TOKEN:
21
+ required: true
22
+
23
+ workflow_dispatch:
24
+ inputs:
25
+ target:
26
+ description: What Docker image set to publish
27
+ required: true
28
+ type: choice
29
+ options:
30
+ - latest sha
31
+ - latest release
32
+
33
+ env:
34
+ DOCKERHUB_IMAGE: jkwill87/mnamer
35
+ GHCR_IMAGE: ghcr.io/jkwill87/mnamer/mnamer
36
+
37
+ jobs:
38
+ publish-docker:
39
+ runs-on: ubuntu-latest
40
+
41
+ permissions:
42
+ contents: read
43
+ packages: write
44
+
45
+ steps:
46
+ - name: Resolving Source
47
+ id: resolve
48
+ env:
49
+ GH_TOKEN: ${{ github.token }}
50
+ RAW_TARGET: ${{ inputs.target || github.event.inputs.target }}
51
+ RELEASE_TAG: ${{ inputs.release_tag }}
52
+ IS_PRERELEASE: ${{ inputs.is_prerelease }}
53
+ run: |
54
+ set -euo pipefail
55
+
56
+ target="$(printf '%s' "$RAW_TARGET" | tr ' ' '-')"
57
+ main_sha="$(git ls-remote "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" refs/heads/main | awk '{print $1}')"
58
+
59
+ case "$target" in
60
+ latest-sha)
61
+ checkout_ref="$main_sha"
62
+ release_tag=""
63
+ is_prerelease="false"
64
+ ;;
65
+ latest-release)
66
+ release_json="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName,isPrerelease)"
67
+ release_tag="$(jq -r '.tagName' <<<"$release_json")"
68
+ is_prerelease="$(jq -r '.isPrerelease' <<<"$release_json")"
69
+ checkout_ref="$release_tag"
70
+ ;;
71
+ event-release)
72
+ release_tag="$RELEASE_TAG"
73
+ is_prerelease="$IS_PRERELEASE"
74
+ checkout_ref="$release_tag"
75
+ ;;
76
+ *)
77
+ echo "Unsupported Docker publish target: $RAW_TARGET" >&2
78
+ exit 1
79
+ ;;
80
+ esac
81
+
82
+ if [ -z "$checkout_ref" ]; then
83
+ echo "Could not resolve a checkout ref for target: $RAW_TARGET" >&2
84
+ exit 1
85
+ fi
86
+
87
+ {
88
+ echo "target=$target"
89
+ echo "checkout_ref=$checkout_ref"
90
+ echo "main_sha=$main_sha"
91
+ echo "release_tag=$release_tag"
92
+ echo "is_prerelease=$is_prerelease"
93
+ } >> "$GITHUB_OUTPUT"
94
+
95
+ - uses: actions/checkout@v6
96
+ with:
97
+ ref: ${{ steps.resolve.outputs.checkout_ref }}
98
+ fetch-depth: 0
99
+
100
+ - name: Finalizing Tags
101
+ id: tags
102
+ env:
103
+ TARGET: ${{ steps.resolve.outputs.target }}
104
+ MAIN_SHA: ${{ steps.resolve.outputs.main_sha }}
105
+ RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
106
+ IS_PRERELEASE: ${{ steps.resolve.outputs.is_prerelease }}
107
+ run: |
108
+ set -euo pipefail
109
+
110
+ source_sha="$(git rev-parse HEAD)"
111
+ aliases=()
112
+
113
+ if [ "$TARGET" = "latest-sha" ]; then
114
+ python -m pip install --quiet setuptools-scm[toml]
115
+ version="$(python -m setuptools_scm)"
116
+ aliases+=("dev")
117
+ else
118
+ version="${RELEASE_TAG#v}"
119
+ aliases+=("$version")
120
+
121
+ if [ "$IS_PRERELEASE" != "true" ]; then
122
+ aliases+=("latest")
123
+ fi
124
+
125
+ if [ "$TARGET" = "latest-release" ] && [ "$source_sha" = "$MAIN_SHA" ]; then
126
+ aliases+=("dev")
127
+ fi
128
+ fi
129
+
130
+ {
131
+ echo "source_sha=$source_sha"
132
+ echo "version=$version"
133
+ echo "aliases<<EOF"
134
+ printf '%s\n' "${aliases[@]}"
135
+ echo "EOF"
136
+ } >> "$GITHUB_OUTPUT"
137
+
138
+ - name: Logging In To GHCR
139
+ uses: docker/login-action@v4
140
+ with:
141
+ registry: ghcr.io
142
+ username: ${{ github.actor }}
143
+ password: ${{ secrets.GITHUB_TOKEN }}
144
+
145
+ - name: Logging In To Docker Hub
146
+ uses: docker/login-action@v4
147
+ with:
148
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
149
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
150
+ scope: jkwill87/mnamer@push
151
+
152
+ - name: Setting Up QEMU
153
+ uses: docker/setup-qemu-action@v3
154
+
155
+ - name: Setting Up Docker Buildx
156
+ uses: docker/setup-buildx-action@v3
157
+
158
+ - name: Planning Docker Push
159
+ id: plan
160
+ env:
161
+ SOURCE_SHA: ${{ steps.tags.outputs.source_sha }}
162
+ ALIASES: ${{ steps.tags.outputs.aliases }}
163
+ run: |
164
+ set -euo pipefail
165
+
166
+ tag_is_current() {
167
+ local ref="$1"
168
+ local output
169
+ local error_file
170
+ error_file="$(mktemp)"
171
+
172
+ if ! output="$(docker buildx imagetools inspect "$ref" --raw 2>"$error_file")"; then
173
+ if grep -Eiq 'not found|manifest unknown|name unknown' "$error_file"; then
174
+ rm -f "$error_file"
175
+ return 1
176
+ fi
177
+
178
+ cat "$error_file" >&2
179
+ rm -f "$error_file"
180
+ return 2
181
+ fi
182
+
183
+ rm -f "$error_file"
184
+ revision="$(
185
+ jq -r '
186
+ .annotations["org.opencontainers.image.revision"]
187
+ // (.manifests[]?.annotations["org.opencontainers.image.revision"] // empty)
188
+ ' <<<"$output" | head -n 1
189
+ )"
190
+ [ "$revision" = "$SOURCE_SHA" ]
191
+ }
192
+
193
+ tags=()
194
+ while IFS= read -r alias; do
195
+ [ -n "$alias" ] || continue
196
+
197
+ tags+=("$GHCR_IMAGE:$alias")
198
+ tags+=("$DOCKERHUB_IMAGE:$alias")
199
+ done <<<"$ALIASES"
200
+
201
+ publish_tags=()
202
+ for tag in "${tags[@]}"; do
203
+ if tag_is_current "$tag"; then
204
+ echo "$tag is already current"
205
+ else
206
+ echo "$tag needs publishing"
207
+ publish_tags+=("$tag")
208
+ fi
209
+ done
210
+
211
+ if [ "${#publish_tags[@]}" -eq 0 ]; then
212
+ echo "All requested Docker tags are already current."
213
+ echo "should_push=false" >> "$GITHUB_OUTPUT"
214
+ else
215
+ echo "should_push=true" >> "$GITHUB_OUTPUT"
216
+ {
217
+ echo "tags<<EOF"
218
+ printf '%s\n' "${publish_tags[@]}"
219
+ echo "EOF"
220
+ } >> "$GITHUB_OUTPUT"
221
+ fi
222
+
223
+ - name: Extracting Docker Metadata
224
+ if: steps.plan.outputs.should_push == 'true'
225
+ id: meta
226
+ uses: docker/metadata-action@v6
227
+ with:
228
+ images: |
229
+ ${{ env.GHCR_IMAGE }}
230
+ ${{ env.DOCKERHUB_IMAGE }}
231
+ labels: |
232
+ org.opencontainers.image.title=mnamer
233
+ org.opencontainers.image.description=A command-line utility for organizing media files.
234
+ org.opencontainers.image.revision=${{ steps.tags.outputs.source_sha }}
235
+ org.opencontainers.image.version=${{ steps.tags.outputs.version }}
236
+
237
+ - name: Building And Publishing Docker Image
238
+ if: steps.plan.outputs.should_push == 'true'
239
+ uses: docker/build-push-action@v7
240
+ with:
241
+ context: .
242
+ platforms: linux/amd64,linux/arm64
243
+ push: true
244
+ tags: ${{ steps.plan.outputs.tags }}
245
+ labels: ${{ steps.meta.outputs.labels }}
246
+ annotations: |
247
+ org.opencontainers.image.revision=${{ steps.tags.outputs.source_sha }}
248
+ org.opencontainers.image.version=${{ steps.tags.outputs.version }}
249
+ org.opencontainers.image.source=https://github.com/${{ github.repository }}
250
+ cache-from: type=gha
251
+ cache-to: type=gha,mode=max
@@ -49,3 +49,42 @@ jobs:
49
49
  inherit
50
50
 
51
51
  uses: ./.github/workflows/publish.yml
52
+
53
+ publish-docker-main:
54
+ if: >-
55
+ success()
56
+ && github.event_name == 'push'
57
+ && github.ref == 'refs/heads/main'
58
+
59
+ needs:
60
+ - lint
61
+ - test
62
+
63
+ secrets:
64
+ inherit
65
+
66
+ uses: ./.github/workflows/publish-docker.yml
67
+ with:
68
+ target: latest-sha
69
+
70
+ publish-docker-release:
71
+ if: >-
72
+ success()
73
+ && github.event_name == 'release'
74
+ && (
75
+ github.event.action == 'published'
76
+ || github.event.action == 'prereleased'
77
+ )
78
+
79
+ needs:
80
+ - lint
81
+ - test
82
+
83
+ secrets:
84
+ inherit
85
+
86
+ uses: ./.github/workflows/publish-docker.yml
87
+ with:
88
+ target: event-release
89
+ release_tag: ${{ github.event.release.tag_name }}
90
+ is_prerelease: ${{ github.event.release.prerelease }}
mnamer-2.7.1/AGENTS.md ADDED
@@ -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.
mnamer-2.7.1/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,20 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM python:alpine AS builder
4
+
5
+ RUN apk add --no-cache git
6
+ WORKDIR /src
7
+ COPY . .
8
+ RUN pip wheel --no-cache-dir --wheel-dir /wheels .
9
+
10
+ FROM python:alpine
11
+ ARG UID=1000
12
+ ARG GID=1000
13
+ RUN addgroup mnamer -g "$GID"
14
+ RUN adduser mnamer -u "$UID" -G mnamer --disabled-password
15
+ COPY --from=builder /wheels/*.whl /tmp/
16
+ RUN pip3 install --no-cache-dir --upgrade pip /tmp/*.whl \
17
+ && rm -f /tmp/*.whl
18
+ USER mnamer
19
+ ENTRYPOINT ["python", "-m", "mnamer"]
20
+ CMD ["--batch", "/mnt"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mnamer
3
- Version: 2.6.1.dev18
3
+ Version: 2.7.1
4
4
  Summary: A command-line utility for organizing media files.
5
5
  Author-email: Jessy Williams <jessy@jessywilliams.com>
6
6
  Maintainer-email: Jessy Williams <jessy@jessywilliams.com>
@@ -33,7 +33,7 @@ License-File: LICENSE.txt
33
33
  Requires-Dist: appdirs~=1.4.4
34
34
  Requires-Dist: babelfish~=0.6.1
35
35
  Requires-Dist: guessit~=3.8.0
36
- Requires-Dist: requests~=2.33.1
36
+ Requires-Dist: requests<2.35.0,>=2.33.1
37
37
  Requires-Dist: requests-cache~=1.3.1
38
38
  Requires-Dist: setuptools-scm>=10.0.0
39
39
  Requires-Dist: teletype~=1.3.4
@@ -134,3 +134,11 @@ Parameters can either by entered as command line arguments or from a config file
134
134
  Community contributions are a welcome addition to the project. In order to be merged upstream any additions will need to be formatted with [ruff](https://docs.astral.sh/ruff/) for consistency with the rest of the project and pass the continuous integration tests run against each PR. Before introducing any major features or changes to the configuration api please consider opening [an issue](https://github.com/jkwill87/mnamer/issues) to outline your proposal.
135
135
 
136
136
  Bug reports are also welcome on the [issue page](https://github.com/jkwill87/mnamer/issues). Please include any generated crash reports if applicable. Feature requests are welcome but consider checking out [if it is in the works](https://github.com/jkwill87/mnamer/issues?q=label%3Arequest) first to avoid duplication.
137
+
138
+ ## Star History
139
+
140
+ <picture>
141
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&theme=dark&legend=top-left" />
142
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
143
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
144
+ </picture>
@@ -93,3 +93,11 @@ Parameters can either by entered as command line arguments or from a config file
93
93
  Community contributions are a welcome addition to the project. In order to be merged upstream any additions will need to be formatted with [ruff](https://docs.astral.sh/ruff/) for consistency with the rest of the project and pass the continuous integration tests run against each PR. Before introducing any major features or changes to the configuration api please consider opening [an issue](https://github.com/jkwill87/mnamer/issues) to outline your proposal.
94
94
 
95
95
  Bug reports are also welcome on the [issue page](https://github.com/jkwill87/mnamer/issues). Please include any generated crash reports if applicable. Feature requests are welcome but consider checking out [if it is in the works](https://github.com/jkwill87/mnamer/issues?q=label%3Arequest) first to avoid duplication.
96
+
97
+ ## Star History
98
+
99
+ <picture>
100
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&theme=dark&legend=top-left" />
101
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
102
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
103
+ </picture>
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __version__ = "2.6.1.dev18"
4
+ __version__ = "2.7.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mnamer
3
- Version: 2.6.1.dev18
3
+ Version: 2.7.1
4
4
  Summary: A command-line utility for organizing media files.
5
5
  Author-email: Jessy Williams <jessy@jessywilliams.com>
6
6
  Maintainer-email: Jessy Williams <jessy@jessywilliams.com>
@@ -33,7 +33,7 @@ License-File: LICENSE.txt
33
33
  Requires-Dist: appdirs~=1.4.4
34
34
  Requires-Dist: babelfish~=0.6.1
35
35
  Requires-Dist: guessit~=3.8.0
36
- Requires-Dist: requests~=2.33.1
36
+ Requires-Dist: requests<2.35.0,>=2.33.1
37
37
  Requires-Dist: requests-cache~=1.3.1
38
38
  Requires-Dist: setuptools-scm>=10.0.0
39
39
  Requires-Dist: teletype~=1.3.4
@@ -134,3 +134,11 @@ Parameters can either by entered as command line arguments or from a config file
134
134
  Community contributions are a welcome addition to the project. In order to be merged upstream any additions will need to be formatted with [ruff](https://docs.astral.sh/ruff/) for consistency with the rest of the project and pass the continuous integration tests run against each PR. Before introducing any major features or changes to the configuration api please consider opening [an issue](https://github.com/jkwill87/mnamer/issues) to outline your proposal.
135
135
 
136
136
  Bug reports are also welcome on the [issue page](https://github.com/jkwill87/mnamer/issues). Please include any generated crash reports if applicable. Feature requests are welcome but consider checking out [if it is in the works](https://github.com/jkwill87/mnamer/issues?q=label%3Arequest) first to avoid duplication.
137
+
138
+ ## Star History
139
+
140
+ <picture>
141
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&theme=dark&legend=top-left" />
142
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
143
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=jkwill87/mnamer&type=date&legend=top-left" />
144
+ </picture>
@@ -1,5 +1,8 @@
1
+ .dockerignore
1
2
  .gitignore
2
3
  .python-version
4
+ AGENTS.md
5
+ CLAUDE.md
3
6
  Dockerfile
4
7
  LICENSE.txt
5
8
  MANIFEST.in
@@ -12,6 +15,7 @@ uv.lock
12
15
  .github/actions/init/action.yml
13
16
  .github/actions/lint/action.yml
14
17
  .github/actions/test/action.yml
18
+ .github/workflows/publish-docker.yml
15
19
  .github/workflows/publish.yml
16
20
  .github/workflows/pull_request.yml
17
21
  .github/workflows/push.yml
@@ -1,7 +1,7 @@
1
1
  appdirs~=1.4.4
2
2
  babelfish~=0.6.1
3
3
  guessit~=3.8.0
4
- requests~=2.33.1
4
+ requests<2.35.0,>=2.33.1
5
5
  requests-cache~=1.3.1
6
6
  setuptools-scm>=10.0.0
7
7
  teletype~=1.3.4
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "appdirs~=1.4.4",
14
14
  "babelfish~=0.6.1",
15
15
  "guessit~=3.8.0",
16
- "requests~=2.33.1",
16
+ "requests>=2.33.1,<2.35.0",
17
17
  "requests-cache~=1.3.1",
18
18
  "setuptools-scm>=10.0.0",
19
19
  "teletype~=1.3.4",