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.
- mnamer-2.7.1/.dockerignore +8 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/init/action.yml +2 -2
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/dependabot.yml +1 -1
- mnamer-2.7.1/.github/workflows/publish-docker.yml +251 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/push.yml +39 -0
- mnamer-2.7.1/AGENTS.md +123 -0
- mnamer-2.7.1/CLAUDE.md +1 -0
- mnamer-2.7.1/Dockerfile +20 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/PKG-INFO +10 -2
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/README.md +8 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__version__.py +1 -1
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/PKG-INFO +10 -2
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/SOURCES.txt +4 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/requires.txt +1 -1
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/pyproject.toml +1 -1
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/uv.lock +196 -176
- mnamer-2.6.1.dev18/Dockerfile +0 -10
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/lint/action.yml +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/actions/test/action.yml +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/publish.yml +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.github/workflows/pull_request.yml +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.gitignore +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.python-version +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/.vscode/settings.json +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/LICENSE.txt +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/MANIFEST.in +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/design.eps +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo-2.png +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo-3.png +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/logo.png +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/recording.mov +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/screenshot.eps +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/assets/screenshot.png +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/makefile +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__init__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/__main__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/argument.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/const.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/endpoints.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/exceptions.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/frontends.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/language.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/metadata.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/providers.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/py.typed +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/setting_spec.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/setting_store.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/target.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/tty.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/types.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer/utils.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/dependency_links.txt +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/entry_points.txt +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/mnamer.egg-info/top_level.txt +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/pytest.ini +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/setup.cfg +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/__init__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/conftest.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/__init__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/conftest.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_directives.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_errors.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/e2e/test_moving.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/__init__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_argument.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_language.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_metadata.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_setting_spec.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_setting_store.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_target.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_tty.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/local/test_utils.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/__init__.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__omdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tmdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tvdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_endpoints__tvmaze.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__omdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tmdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tvdb.py +0 -0
- {mnamer-2.6.1.dev18 → mnamer-2.7.1}/tests/network/test_providers__tvmaze.py +0 -0
|
@@ -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
|
|
@@ -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
|
mnamer-2.7.1/Dockerfile
ADDED
|
@@ -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.
|
|
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
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mnamer
|
|
3
|
-
Version: 2.
|
|
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
|
|
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
|