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.
- mediathekwatch-0.1.0/.forgejo/workflows/ci.yml +40 -0
- mediathekwatch-0.1.0/.forgejo/workflows/publish.yml +60 -0
- mediathekwatch-0.1.0/.gitignore +17 -0
- mediathekwatch-0.1.0/.pre-commit-config.yaml +13 -0
- mediathekwatch-0.1.0/.python-version +1 -0
- mediathekwatch-0.1.0/LICENSE +21 -0
- mediathekwatch-0.1.0/Makefile +83 -0
- mediathekwatch-0.1.0/PKG-INFO +219 -0
- mediathekwatch-0.1.0/README.md +207 -0
- mediathekwatch-0.1.0/pyproject.toml +78 -0
- mediathekwatch-0.1.0/src/mediathekwatch/__init__.py +3 -0
- mediathekwatch-0.1.0/src/mediathekwatch/api/__init__.py +11 -0
- mediathekwatch-0.1.0/src/mediathekwatch/api/ard.py +248 -0
- mediathekwatch-0.1.0/src/mediathekwatch/api/arte.py +206 -0
- mediathekwatch-0.1.0/src/mediathekwatch/api/zdf.py +359 -0
- mediathekwatch-0.1.0/src/mediathekwatch/cli.py +594 -0
- mediathekwatch-0.1.0/src/mediathekwatch/database.py +237 -0
- mediathekwatch-0.1.0/src/mediathekwatch/download.py +92 -0
- mediathekwatch-0.1.0/src/mediathekwatch/main.py +125 -0
- mediathekwatch-0.1.0/src/mediathekwatch/models.py +39 -0
- mediathekwatch-0.1.0/src/mediathekwatch/statics.py +33 -0
- mediathekwatch-0.1.0/tests/unit/__init__.py +0 -0
- mediathekwatch-0.1.0/tests/unit/test_api.py +62 -0
- mediathekwatch-0.1.0/tests/unit/test_api_arte.py +175 -0
- mediathekwatch-0.1.0/tests/unit/test_api_errors.py +152 -0
- mediathekwatch-0.1.0/tests/unit/test_api_fetch.py +176 -0
- mediathekwatch-0.1.0/tests/unit/test_api_parse.py +77 -0
- mediathekwatch-0.1.0/tests/unit/test_api_zdf.py +304 -0
- mediathekwatch-0.1.0/tests/unit/test_cli.py +263 -0
- mediathekwatch-0.1.0/tests/unit/test_cli_check.py +125 -0
- mediathekwatch-0.1.0/tests/unit/test_cli_check_full.py +88 -0
- mediathekwatch-0.1.0/tests/unit/test_cli_download_arte.py +132 -0
- mediathekwatch-0.1.0/tests/unit/test_cli_mark.py +139 -0
- mediathekwatch-0.1.0/tests/unit/test_cli_redownload_full.py +93 -0
- mediathekwatch-0.1.0/tests/unit/test_database.py +111 -0
- mediathekwatch-0.1.0/tests/unit/test_database_full.py +40 -0
- mediathekwatch-0.1.0/tests/unit/test_download.py +29 -0
- mediathekwatch-0.1.0/tests/unit/test_download_errors.py +52 -0
- mediathekwatch-0.1.0/tests/unit/test_download_full.py +36 -0
- mediathekwatch-0.1.0/tests/unit/test_download_output.py +44 -0
- mediathekwatch-0.1.0/tests/unit/test_groups_cli.py +232 -0
- mediathekwatch-0.1.0/tests/unit/test_groups_edge.py +64 -0
- mediathekwatch-0.1.0/tests/unit/test_main.py +109 -0
- mediathekwatch-0.1.0/tests/unit/test_models.py +53 -0
- 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,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
|
+
```
|