mediathekwatch 0.1.0__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 (45) hide show
  1. mediathekwatch-0.1.0/.forgejo/workflows/ci.yml +40 -0
  2. mediathekwatch-0.1.0/.forgejo/workflows/publish.yml +60 -0
  3. mediathekwatch-0.1.0/.gitignore +17 -0
  4. mediathekwatch-0.1.0/.pre-commit-config.yaml +13 -0
  5. mediathekwatch-0.1.0/.python-version +1 -0
  6. mediathekwatch-0.1.0/LICENSE +21 -0
  7. mediathekwatch-0.1.0/Makefile +83 -0
  8. mediathekwatch-0.1.0/PKG-INFO +219 -0
  9. mediathekwatch-0.1.0/README.md +207 -0
  10. mediathekwatch-0.1.0/pyproject.toml +78 -0
  11. mediathekwatch-0.1.0/src/mediathekwatch/__init__.py +3 -0
  12. mediathekwatch-0.1.0/src/mediathekwatch/api/__init__.py +11 -0
  13. mediathekwatch-0.1.0/src/mediathekwatch/api/ard.py +248 -0
  14. mediathekwatch-0.1.0/src/mediathekwatch/api/arte.py +206 -0
  15. mediathekwatch-0.1.0/src/mediathekwatch/api/zdf.py +359 -0
  16. mediathekwatch-0.1.0/src/mediathekwatch/cli.py +594 -0
  17. mediathekwatch-0.1.0/src/mediathekwatch/database.py +237 -0
  18. mediathekwatch-0.1.0/src/mediathekwatch/download.py +92 -0
  19. mediathekwatch-0.1.0/src/mediathekwatch/main.py +125 -0
  20. mediathekwatch-0.1.0/src/mediathekwatch/models.py +39 -0
  21. mediathekwatch-0.1.0/src/mediathekwatch/statics.py +33 -0
  22. mediathekwatch-0.1.0/tests/unit/__init__.py +0 -0
  23. mediathekwatch-0.1.0/tests/unit/test_api.py +62 -0
  24. mediathekwatch-0.1.0/tests/unit/test_api_arte.py +175 -0
  25. mediathekwatch-0.1.0/tests/unit/test_api_errors.py +152 -0
  26. mediathekwatch-0.1.0/tests/unit/test_api_fetch.py +176 -0
  27. mediathekwatch-0.1.0/tests/unit/test_api_parse.py +77 -0
  28. mediathekwatch-0.1.0/tests/unit/test_api_zdf.py +304 -0
  29. mediathekwatch-0.1.0/tests/unit/test_cli.py +263 -0
  30. mediathekwatch-0.1.0/tests/unit/test_cli_check.py +125 -0
  31. mediathekwatch-0.1.0/tests/unit/test_cli_check_full.py +88 -0
  32. mediathekwatch-0.1.0/tests/unit/test_cli_download_arte.py +132 -0
  33. mediathekwatch-0.1.0/tests/unit/test_cli_mark.py +139 -0
  34. mediathekwatch-0.1.0/tests/unit/test_cli_redownload_full.py +93 -0
  35. mediathekwatch-0.1.0/tests/unit/test_database.py +111 -0
  36. mediathekwatch-0.1.0/tests/unit/test_database_full.py +40 -0
  37. mediathekwatch-0.1.0/tests/unit/test_download.py +29 -0
  38. mediathekwatch-0.1.0/tests/unit/test_download_errors.py +52 -0
  39. mediathekwatch-0.1.0/tests/unit/test_download_full.py +36 -0
  40. mediathekwatch-0.1.0/tests/unit/test_download_output.py +44 -0
  41. mediathekwatch-0.1.0/tests/unit/test_groups_cli.py +232 -0
  42. mediathekwatch-0.1.0/tests/unit/test_groups_edge.py +64 -0
  43. mediathekwatch-0.1.0/tests/unit/test_main.py +109 -0
  44. mediathekwatch-0.1.0/tests/unit/test_models.py +53 -0
  45. mediathekwatch-0.1.0/uv.lock +730 -0
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: codeberg-small
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install system tools
15
+ env:
16
+ DEBIAN_FRONTEND: noninteractive
17
+ run: |
18
+ apt-get update
19
+ apt-get install -y make curl
20
+
21
+ - name: Install uv
22
+ run: |
23
+ curl -LsSf https://astral.sh/uv/install.sh | env UV_UNMANAGED_INSTALL="/usr/local/bin" sh
24
+ uv --version
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ uv sync --all-extras --dev
29
+
30
+ - name: Validate and test
31
+ run: |
32
+ uv run make all
33
+
34
+ - name: Build package
35
+ run: |
36
+ uv build
37
+
38
+ - name: Check package artifacts
39
+ run: |
40
+ uv tool run twine check dist/*
@@ -0,0 +1,60 @@
1
+ name: Publish Python package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: codeberg-small
11
+ environment: release
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install system tools
17
+ env:
18
+ DEBIAN_FRONTEND: noninteractive
19
+ run: |
20
+ apt-get update
21
+ apt-get install -y make curl
22
+
23
+ - name: Install uv
24
+ run: |
25
+ curl -LsSf https://astral.sh/uv/install.sh | env UV_UNMANAGED_INSTALL="/usr/local/bin" sh
26
+ uv --version
27
+
28
+ - name: Install dependencies
29
+ run: |
30
+ uv sync --all-extras --dev
31
+
32
+ - name: Validate and test
33
+ run: |
34
+ uv run make all
35
+
36
+ - name: Build package
37
+ run: |
38
+ uv build
39
+
40
+ - name: Check package artifacts
41
+ run: |
42
+ uv tool run twine check dist/*
43
+
44
+ - name: Publish to PyPI
45
+ env:
46
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
47
+ run: |
48
+ uv publish
49
+
50
+ # Optional: publish a mirror to Codeberg's Forgejo package registry.
51
+ # Enable this only after adding CODEBERG_USERNAME and CODEBERG_TOKEN secrets.
52
+ #
53
+ # - name: Publish to Codeberg package registry
54
+ # env:
55
+ # TWINE_USERNAME: ${{ secrets.CODEBERG_USERNAME }}
56
+ # TWINE_PASSWORD: ${{ secrets.CODEBERG_TOKEN }}
57
+ # run: |
58
+ # uv tool run twine upload \
59
+ # --repository-url https://codeberg.org/api/packages/smeinecke/pypi \
60
+ # dist/*
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .ruff_cache
9
+ .coverage
10
+ coverage.xml
11
+ .pytest_cache
12
+ *.pyc
13
+ .vscode
14
+ .crush
15
+
16
+ # Virtual environments
17
+ .venv
@@ -0,0 +1,13 @@
1
+ default_stages: [pre-push]
2
+ repos:
3
+ - repo: https://github.com/astral-sh/ruff-pre-commit
4
+ rev: v0.11.2
5
+ hooks:
6
+ - id: ruff
7
+ args: [--fix]
8
+ - id: ruff-format
9
+
10
+ - repo: https://github.com/RobertCraigie/pyright-python
11
+ rev: v1.1.397
12
+ hooks:
13
+ - id: pyright
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Stefan Meinecke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,83 @@
1
+ # Makefile for disposable email domain generator
2
+
3
+ .PHONY: all format check validate test test-unit test-integration test-all help reformat-ruff fix-ruff fix vulture complexity xenon bandit pyright
4
+
5
+ # Default target: runs format and check
6
+ all: validate test-unit
7
+
8
+ # Format the code using ruff
9
+ format:
10
+ ruff format --check --diff .
11
+
12
+ reformat-ruff:
13
+ ruff format .
14
+
15
+ # Check the code using ruff
16
+ check:
17
+ ruff check .
18
+
19
+ fix-ruff:
20
+ ruff check . --fix
21
+
22
+ fix: reformat-ruff fix-ruff
23
+ @echo "Updated code."
24
+
25
+ vulture:
26
+ vulture src --exclude .venv,__pycache__ --min-confidence 80
27
+
28
+ complexity:
29
+ radon cc src -a -nc
30
+
31
+ xenon:
32
+ -xenon -b C -m C -a C src || true
33
+
34
+ bandit:
35
+ bandit -c pyproject.toml -r src
36
+
37
+ pyright:
38
+ pyright
39
+
40
+ test:
41
+ pytest -m "not integration"
42
+
43
+ test-unit:
44
+ pytest tests/unit/ --cov-fail-under=85
45
+
46
+ test-integration:
47
+ pytest -m integration
48
+
49
+ test-all:
50
+ pytest --tb=short
51
+
52
+ # Validate the code (format + check)
53
+ validate: format check bandit pyright vulture complexity xenon
54
+ @echo "Validation passed. Your code is ready to push."
55
+
56
+ build:
57
+ uv build
58
+
59
+ package-check:
60
+ uv tool run twine check dist/*
61
+
62
+ ci: all build package-check
63
+
64
+ # Help target
65
+ help:
66
+ @echo "Available targets:"
67
+ @echo " all - Run validation and unit tests (default)"
68
+ @echo " format - Check code formatting with ruff"
69
+ @echo " reformat-ruff - Format code with ruff"
70
+ @echo " check - Run ruff linting"
71
+ @echo " fix-ruff - Auto-fix ruff issues"
72
+ @echo " fix - Run reformat-ruff and fix-ruff"
73
+ @echo " vulture - Run dead code detection"
74
+ @echo " complexity - Run complexity analysis"
75
+ @echo " xenon - Run xenon complexity check"
76
+ @echo " bandit - Run security analysis"
77
+ @echo " pyright - Run type checking"
78
+ @echo " test - Run all tests"
79
+ @echo " test-unit - Run unit tests only"
80
+ @echo " test-integration - Run integration tests only"
81
+ @echo " test-all - Run all tests with short traceback"
82
+ @echo " validate - Run all validation checks"
83
+ @echo " help - Show this help message"
@@ -0,0 +1,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: mediathekwatch
3
+ Version: 0.1.0
4
+ Summary: Monitor ARD Mediathek series for new episodes and download them via yt-dlp
5
+ Author: Stefan Meinecke
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: <4.0,>=3.10
9
+ Requires-Dist: duckdb>=1.0
10
+ Requires-Dist: requests>=2.32
11
+ Description-Content-Type: text/markdown
12
+
13
+ # MediathekWatch
14
+
15
+ A Python CLI tool to monitor ARD Mediathek, ARTE.tv, and ZDF Mediathek series for new episodes and automatically download them using yt-dlp. State is tracked in a local DuckDB database.
16
+
17
+ ## Requirements
18
+
19
+ - Python 3.10+
20
+ - [uv](https://docs.astral.sh/uv/)
21
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp) installed and available in `$PATH`
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ # Clone or navigate to the project
27
+ cd /path/to/mediathekwatch
28
+
29
+ # Install with uv
30
+ uv sync
31
+
32
+ # Or install as editable
33
+ uv pip install -e .
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ The tool is installed as `mediathekwatch` command:
39
+
40
+ ### Add a series to monitor
41
+
42
+ ```bash
43
+ mediathekwatch add "https://www.ardmediathek.de/serie/feuer-und-flamme/staffel-11/Y3JpZDovL3dkci5kZS9mZXVlcnVuZGZsYW1tZQ/11" --name "Feuer und Flamme"
44
+ ```
45
+
46
+ ZDF Mediathek URLs are also supported:
47
+
48
+ ```bash
49
+ mediathekwatch add "https://www.zdf.de/serien/die-anstalt" --name "Die Anstalt"
50
+ ```
51
+
52
+ Assign to a group (see [Groups](#groups)):
53
+
54
+ ```bash
55
+ mediathekwatch add "https://www.ardmediathek.de/serie/..." --name "My Show" --group "TVShows"
56
+ ```
57
+
58
+ ### List monitored series
59
+
60
+ ```bash
61
+ mediathekwatch list
62
+ ```
63
+
64
+ ### Check for new episodes (intended for cron)
65
+
66
+ ```bash
67
+ mediathekwatch check --target /mnt/media/TVShows
68
+
69
+ # Silent mode (suppress all output)
70
+ mediathekwatch check --target /mnt/media/TVShows --silent
71
+ ```
72
+
73
+ When groups are configured, each group downloads to its own target folder and `--target` is not allowed:
74
+
75
+ ```bash
76
+ mediathekwatch check --silent
77
+ ```
78
+
79
+ ### Force redownload of a specific episode
80
+
81
+ ```bash
82
+ # By series ID
83
+ mediathekwatch redownload 1 11 5 --target /mnt/media/TVShows
84
+
85
+ # By series name
86
+ mediathekwatch redownload "Feuer und Flamme" 11 5 --target /mnt/media/TVShows
87
+ ```
88
+
89
+ If `--target` is omitted, the episode downloads to the series' group folder.
90
+
91
+ ### Download all episodes from a series URL
92
+
93
+ ```bash
94
+ mediathekwatch download "https://www.ardmediathek.de/serie/feuer-und-flamme/staffel-11/Y3JpZDovL3dkci5kZS9mZXVlcnVuZGZsYW1tZQ/11" --target /mnt/media/TVShows
95
+ ```
96
+
97
+ Works with ZDF Mediathek too:
98
+
99
+ ```bash
100
+ mediathekwatch download "https://www.zdf.de/serien/die-anstalt" --target /mnt/media/TVShows
101
+ ```
102
+
103
+ ### Remove a series
104
+
105
+ ```bash
106
+ # By ID
107
+ mediathekwatch remove 1
108
+
109
+ # By name
110
+ mediathekwatch remove "Feuer und Flamme"
111
+ ```
112
+
113
+ ## Groups
114
+
115
+ Groups let you organize series into separate download destinations. Each group has its own target folder. When any non-default group exists, `check` uses group folders instead of `--target`.
116
+
117
+ ### Create a group
118
+
119
+ ```bash
120
+ mediathekwatch group create "TVShows" --target /mnt/media/TVShows
121
+ ```
122
+
123
+ ### List groups
124
+
125
+ ```bash
126
+ mediathekwatch group list
127
+ ```
128
+
129
+ ### Update a group
130
+
131
+ ```bash
132
+ mediathekwatch group update "TVShows" --name "TV Shows" --target /mnt/media/TV
133
+ ```
134
+
135
+ ### Delete a group
136
+
137
+ ```bash
138
+ mediathekwatch group delete "TVShows"
139
+ ```
140
+
141
+ ### Move a series to a group
142
+
143
+ ```bash
144
+ mediathekwatch set-group "Feuer und Flamme" "TVShows"
145
+ ```
146
+
147
+ ## Global Options
148
+
149
+ - `--db PATH` — Path to DuckDB database (default: `~/.config/mediathekwatch/mediathekwatch.db`)
150
+ - `--target PATH` — Target folder for downloads (default: current directory)
151
+
152
+ ## Cron Setup
153
+
154
+ Add to your crontab (e.g., every 6 hours):
155
+
156
+ ```cron
157
+ 0 */6 * * * /path/to/uv run mediathekwatch check --target /mnt/media/TVShows
158
+ ```
159
+
160
+ Or if installed globally:
161
+
162
+ ```cron
163
+ 0 */6 * * * /home/user/.local/bin/mediathekwatch check --target /mnt/media/TVShows
164
+ ```
165
+
166
+ ## Filename Format
167
+
168
+ Downloaded episodes are organized as:
169
+
170
+ ```
171
+ <Target Folder>/<Series Name>/S<Season>/<Series> - S<season>E<episode> - <episode name>.mkv
172
+ ```
173
+
174
+ Example:
175
+
176
+ ```
177
+ /mnt/media/TVShows/Feuer und Flamme/S11/Feuer und Flamme - S11E05 - Der neue Fall.mkv
178
+ ```
179
+
180
+ ## Database Schema
181
+
182
+ Three tables are maintained in DuckDB:
183
+
184
+ - `groups` — download groups with target folders
185
+ - `monitored_series` — series being tracked
186
+ - `downloaded_episodes` — episodes already downloaded
187
+
188
+ ## Project Structure
189
+
190
+ ```
191
+ src/mediathekwatch/
192
+ ├── __init__.py # Package init
193
+ ├── main.py # CLI entry point
194
+ ├── cli.py # Command handlers
195
+ ├── models.py # Episode/Series dataclasses
196
+ ├── database.py # DuckDB interface
197
+ ├── api/ # API clients (ARD, ARTE, ZDF)
198
+ └── download.py # yt-dlp wrapper & filename utils
199
+ ```
200
+
201
+ ## Notes
202
+
203
+ - The tool queries ARD Mediathek, ARTE.tv EMAC, and ZDF GraphQL APIs to discover episodes.
204
+ - yt-dlp is invoked with flags for embedding thumbnails, subtitles, metadata, chapters, and info-json.
205
+ - Episodes are skipped if they already exist in the `downloaded_episodes` table; use `redownload` to override.
206
+
207
+ ## Acknowledgments
208
+
209
+ - Thanks to the [MediathekView](https://mediathekview.de/) project for providing valuable insights into the structure of German public media APIs.
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ # Run tests
215
+ uv run pytest
216
+
217
+ # Type check
218
+ uv run pyright
219
+ ```
@@ -0,0 +1,207 @@
1
+ # MediathekWatch
2
+
3
+ A Python CLI tool to monitor ARD Mediathek, ARTE.tv, and ZDF Mediathek series for new episodes and automatically download them using yt-dlp. State is tracked in a local DuckDB database.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.10+
8
+ - [uv](https://docs.astral.sh/uv/)
9
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp) installed and available in `$PATH`
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ # Clone or navigate to the project
15
+ cd /path/to/mediathekwatch
16
+
17
+ # Install with uv
18
+ uv sync
19
+
20
+ # Or install as editable
21
+ uv pip install -e .
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ The tool is installed as `mediathekwatch` command:
27
+
28
+ ### Add a series to monitor
29
+
30
+ ```bash
31
+ mediathekwatch add "https://www.ardmediathek.de/serie/feuer-und-flamme/staffel-11/Y3JpZDovL3dkci5kZS9mZXVlcnVuZGZsYW1tZQ/11" --name "Feuer und Flamme"
32
+ ```
33
+
34
+ ZDF Mediathek URLs are also supported:
35
+
36
+ ```bash
37
+ mediathekwatch add "https://www.zdf.de/serien/die-anstalt" --name "Die Anstalt"
38
+ ```
39
+
40
+ Assign to a group (see [Groups](#groups)):
41
+
42
+ ```bash
43
+ mediathekwatch add "https://www.ardmediathek.de/serie/..." --name "My Show" --group "TVShows"
44
+ ```
45
+
46
+ ### List monitored series
47
+
48
+ ```bash
49
+ mediathekwatch list
50
+ ```
51
+
52
+ ### Check for new episodes (intended for cron)
53
+
54
+ ```bash
55
+ mediathekwatch check --target /mnt/media/TVShows
56
+
57
+ # Silent mode (suppress all output)
58
+ mediathekwatch check --target /mnt/media/TVShows --silent
59
+ ```
60
+
61
+ When groups are configured, each group downloads to its own target folder and `--target` is not allowed:
62
+
63
+ ```bash
64
+ mediathekwatch check --silent
65
+ ```
66
+
67
+ ### Force redownload of a specific episode
68
+
69
+ ```bash
70
+ # By series ID
71
+ mediathekwatch redownload 1 11 5 --target /mnt/media/TVShows
72
+
73
+ # By series name
74
+ mediathekwatch redownload "Feuer und Flamme" 11 5 --target /mnt/media/TVShows
75
+ ```
76
+
77
+ If `--target` is omitted, the episode downloads to the series' group folder.
78
+
79
+ ### Download all episodes from a series URL
80
+
81
+ ```bash
82
+ mediathekwatch download "https://www.ardmediathek.de/serie/feuer-und-flamme/staffel-11/Y3JpZDovL3dkci5kZS9mZXVlcnVuZGZsYW1tZQ/11" --target /mnt/media/TVShows
83
+ ```
84
+
85
+ Works with ZDF Mediathek too:
86
+
87
+ ```bash
88
+ mediathekwatch download "https://www.zdf.de/serien/die-anstalt" --target /mnt/media/TVShows
89
+ ```
90
+
91
+ ### Remove a series
92
+
93
+ ```bash
94
+ # By ID
95
+ mediathekwatch remove 1
96
+
97
+ # By name
98
+ mediathekwatch remove "Feuer und Flamme"
99
+ ```
100
+
101
+ ## Groups
102
+
103
+ Groups let you organize series into separate download destinations. Each group has its own target folder. When any non-default group exists, `check` uses group folders instead of `--target`.
104
+
105
+ ### Create a group
106
+
107
+ ```bash
108
+ mediathekwatch group create "TVShows" --target /mnt/media/TVShows
109
+ ```
110
+
111
+ ### List groups
112
+
113
+ ```bash
114
+ mediathekwatch group list
115
+ ```
116
+
117
+ ### Update a group
118
+
119
+ ```bash
120
+ mediathekwatch group update "TVShows" --name "TV Shows" --target /mnt/media/TV
121
+ ```
122
+
123
+ ### Delete a group
124
+
125
+ ```bash
126
+ mediathekwatch group delete "TVShows"
127
+ ```
128
+
129
+ ### Move a series to a group
130
+
131
+ ```bash
132
+ mediathekwatch set-group "Feuer und Flamme" "TVShows"
133
+ ```
134
+
135
+ ## Global Options
136
+
137
+ - `--db PATH` — Path to DuckDB database (default: `~/.config/mediathekwatch/mediathekwatch.db`)
138
+ - `--target PATH` — Target folder for downloads (default: current directory)
139
+
140
+ ## Cron Setup
141
+
142
+ Add to your crontab (e.g., every 6 hours):
143
+
144
+ ```cron
145
+ 0 */6 * * * /path/to/uv run mediathekwatch check --target /mnt/media/TVShows
146
+ ```
147
+
148
+ Or if installed globally:
149
+
150
+ ```cron
151
+ 0 */6 * * * /home/user/.local/bin/mediathekwatch check --target /mnt/media/TVShows
152
+ ```
153
+
154
+ ## Filename Format
155
+
156
+ Downloaded episodes are organized as:
157
+
158
+ ```
159
+ <Target Folder>/<Series Name>/S<Season>/<Series> - S<season>E<episode> - <episode name>.mkv
160
+ ```
161
+
162
+ Example:
163
+
164
+ ```
165
+ /mnt/media/TVShows/Feuer und Flamme/S11/Feuer und Flamme - S11E05 - Der neue Fall.mkv
166
+ ```
167
+
168
+ ## Database Schema
169
+
170
+ Three tables are maintained in DuckDB:
171
+
172
+ - `groups` — download groups with target folders
173
+ - `monitored_series` — series being tracked
174
+ - `downloaded_episodes` — episodes already downloaded
175
+
176
+ ## Project Structure
177
+
178
+ ```
179
+ src/mediathekwatch/
180
+ ├── __init__.py # Package init
181
+ ├── main.py # CLI entry point
182
+ ├── cli.py # Command handlers
183
+ ├── models.py # Episode/Series dataclasses
184
+ ├── database.py # DuckDB interface
185
+ ├── api/ # API clients (ARD, ARTE, ZDF)
186
+ └── download.py # yt-dlp wrapper & filename utils
187
+ ```
188
+
189
+ ## Notes
190
+
191
+ - The tool queries ARD Mediathek, ARTE.tv EMAC, and ZDF GraphQL APIs to discover episodes.
192
+ - yt-dlp is invoked with flags for embedding thumbnails, subtitles, metadata, chapters, and info-json.
193
+ - Episodes are skipped if they already exist in the `downloaded_episodes` table; use `redownload` to override.
194
+
195
+ ## Acknowledgments
196
+
197
+ - Thanks to the [MediathekView](https://mediathekview.de/) project for providing valuable insights into the structure of German public media APIs.
198
+
199
+ ## Development
200
+
201
+ ```bash
202
+ # Run tests
203
+ uv run pytest
204
+
205
+ # Type check
206
+ uv run pyright
207
+ ```