tv-recorder 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.
- tv_recorder-0.1.0/.codex/skills/tv-recorder-add-channel/SKILL.md +94 -0
- tv_recorder-0.1.0/.codex/skills/tv-recorder-add-channel/agents/openai.yaml +4 -0
- tv_recorder-0.1.0/.codex/skills/tv-recorder-add-channel/references/channel-recipes.md +105 -0
- tv_recorder-0.1.0/.github/workflows/publish.yml +30 -0
- tv_recorder-0.1.0/.gitignore +7 -0
- tv_recorder-0.1.0/LICENSE +21 -0
- tv_recorder-0.1.0/PKG-INFO +182 -0
- tv_recorder-0.1.0/README.md +129 -0
- tv_recorder-0.1.0/pyproject.toml +58 -0
- tv_recorder-0.1.0/src/tv_recorder/__init__.py +1 -0
- tv_recorder-0.1.0/src/tv_recorder/cli.py +98 -0
- tv_recorder-0.1.0/src/tv_recorder/config.py +59 -0
- tv_recorder-0.1.0/src/tv_recorder/defaults.yaml +644 -0
- tv_recorder-0.1.0/src/tv_recorder/duration.py +36 -0
- tv_recorder-0.1.0/src/tv_recorder/recorder.py +366 -0
- tv_recorder-0.1.0/src/tv_recorder/stream_finder.py +463 -0
- tv_recorder-0.1.0/tests/test_duration.py +26 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tv-recorder-add-channel
|
|
3
|
+
description: Add or repair a channel entry in a Python tv-recorder project's defaults.yaml. Use when the user asks to add a public TV/news/live channel, discover or stabilize an HLS m3u8 URL, choose between direct URL/API/browser discovery strategies, configure video/audio selection, or run smoke tests for a tv-recorder channel.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TV Recorder Add Channel
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
Use this skill when editing `src/tv_recorder/defaults.yaml` for a `tv-recorder` project.
|
|
11
|
+
|
|
12
|
+
1. Read `defaults.yaml`, `config.py`, `stream_finder.py`, and `recorder.py` before editing. Preserve the existing schema and local style.
|
|
13
|
+
2. Read the requested channel URL or channel list. If a companion file such as `chaines.md` exists, inspect the relevant lines.
|
|
14
|
+
3. Prefer the least fragile working strategy:
|
|
15
|
+
- Stable direct HLS URL or public API returning HLS.
|
|
16
|
+
- Browser discovery with Playwright when the URL is tokenized, embedded, or only emitted after consent/play clicks.
|
|
17
|
+
- Alternate public HLS only when the official site is geoblocked, stale, DRM-protected, or unusable from the current location.
|
|
18
|
+
4. Validate candidates with a real short ffmpeg capture, not only HTTP status or dry-run. A master manifest can return `200` while every child variant fails.
|
|
19
|
+
5. Patch `defaults.yaml` only after a candidate records successfully.
|
|
20
|
+
6. Run one focused smoke test for the new/changed channel. Use 1 minute unless the user asks otherwise.
|
|
21
|
+
7. Report the source key, strategy used, test result, output path, and any caveat such as geoblocking or third-party alternate feed.
|
|
22
|
+
|
|
23
|
+
## Discovery
|
|
24
|
+
|
|
25
|
+
Use these options in order, stopping once one gives a reliable recording:
|
|
26
|
+
|
|
27
|
+
- **Direct manifest/API**: Check the live page source, network calls, JSON APIs, and public stream indexes for `.m3u8`. Configure `stream_request_urls` and `steps: []`.
|
|
28
|
+
- **Browser discovery**: Configure `start_url`, `stream_url_pattern`, `steps: [{action: wait_for_stream}]`, and add reject patterns for ads, analytics, VOD, or redirector manifests.
|
|
29
|
+
- **JSON response extraction**: Configure `stream_response_url_patterns` and `stream_response_json_keys` when a validation/API endpoint returns the stream URL.
|
|
30
|
+
- **Separate audio HLS**: If the master manifest uses `#EXT-X-MEDIA` audio groups, set `recording.hls.separate_audio: true`, `video.direct_variant: false`, and configure `audio.language`.
|
|
31
|
+
|
|
32
|
+
Read `references/channel-recipes.md` for concrete recipes and failure patterns before repairing a difficult channel.
|
|
33
|
+
|
|
34
|
+
## Validation
|
|
35
|
+
|
|
36
|
+
For each new or repaired channel:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
.\.venv\Scripts\tv-recorder.exe <source-key> now 1m --output-dir recordings\smoke-<stamp> --timeout-ms 60000
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If validating several channels, cap parallelism. Previous project smoke tests used 3 or 5 concurrent recordings.
|
|
43
|
+
|
|
44
|
+
After recording, inspect the file with the bundled ffmpeg:
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
@'
|
|
48
|
+
import subprocess, imageio_ffmpeg
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
|
|
51
|
+
path = Path(r"<recorded-file.mp4>")
|
|
52
|
+
subprocess.run([ffmpeg, "-hide_banner", "-i", str(path)])
|
|
53
|
+
'@ | .\.venv\Scripts\python.exe -
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A successful entry should produce a playable MP4 with nonzero size, about the requested duration, and expected video/audio streams.
|
|
57
|
+
|
|
58
|
+
## Defaults YAML Pattern
|
|
59
|
+
|
|
60
|
+
Use stable, lowercase source keys. Keep display names human-readable.
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
source-key:
|
|
64
|
+
display_name: Channel Name
|
|
65
|
+
start_url: "https://example.com/live"
|
|
66
|
+
stream_url_pattern: "\\.m3u8(\\?|$)"
|
|
67
|
+
stream_request_urls:
|
|
68
|
+
- "https://example.com/live/master.m3u8"
|
|
69
|
+
stream_url_reject_patterns:
|
|
70
|
+
- "dai\\.google\\.com"
|
|
71
|
+
output_extension: "mp4"
|
|
72
|
+
recording:
|
|
73
|
+
video:
|
|
74
|
+
height: 720
|
|
75
|
+
audio:
|
|
76
|
+
language: "eng"
|
|
77
|
+
steps: []
|
|
78
|
+
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125 Safari/537.36"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For browser discovery, omit `stream_request_urls` and use:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
steps:
|
|
85
|
+
- action: wait_for_stream
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Guardrails
|
|
89
|
+
|
|
90
|
+
- Do not trust a candidate because `Invoke-WebRequest` or `context.request.get()` returns `200`; run ffmpeg for a few seconds.
|
|
91
|
+
- Reject ad manifests such as `dai.google.com` and Dailymotion redirectors when they do not record.
|
|
92
|
+
- Prefer official sources, but accept that a stable direct alternate can be as reasonable as browsing a fragile page.
|
|
93
|
+
- If DRM or HLS SAMPLE-AES appears, do not try to bypass protection. Mark it as unsupported.
|
|
94
|
+
- Keep changes scoped to channel config unless the project lacks a capability needed by multiple channels.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Channel Recipes
|
|
2
|
+
|
|
3
|
+
Use this reference when a channel is not obvious from `defaults.yaml`.
|
|
4
|
+
|
|
5
|
+
## Direct HLS
|
|
6
|
+
|
|
7
|
+
Use `stream_request_urls` with `steps: []` when a manifest records directly. This is fastest and most stable when the URL is not tokenized per session.
|
|
8
|
+
|
|
9
|
+
Validate with ffmpeg, because a master manifest can be alive while its variants return `400`, `403`, or `404`.
|
|
10
|
+
|
|
11
|
+
Examples observed in this project:
|
|
12
|
+
- Global News regional feeds: direct Corus `.isml/.m3u8` manifests.
|
|
13
|
+
- CPAC: direct master with separate audio tracks.
|
|
14
|
+
- NHK: old master returned `200` but children failed; `https://masterpl.hls.nhkworld.jp/hls/w/live/smarttv.m3u8` recorded successfully.
|
|
15
|
+
|
|
16
|
+
## API-Returned Stream
|
|
17
|
+
|
|
18
|
+
Use `stream_response_url_patterns` and `stream_response_json_keys` when a public endpoint returns a stream URL. Keep browser steps empty if the API is enough.
|
|
19
|
+
|
|
20
|
+
Example pattern:
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
stream_request_urls:
|
|
24
|
+
- "https://service.example/api/live?output=json"
|
|
25
|
+
stream_response_url_patterns:
|
|
26
|
+
- "service\\.example/api/live"
|
|
27
|
+
stream_response_json_keys:
|
|
28
|
+
- "url"
|
|
29
|
+
steps: []
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Browser Discovery
|
|
33
|
+
|
|
34
|
+
Use browser discovery when:
|
|
35
|
+
- the manifest is tokenized per session,
|
|
36
|
+
- a consent or play action is required,
|
|
37
|
+
- the source is embedded in an iframe/player,
|
|
38
|
+
- the direct URL expires quickly.
|
|
39
|
+
|
|
40
|
+
Start with:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
steps:
|
|
44
|
+
- action: wait_for_stream
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The project's auto-start logic tries common consent and play controls. Add explicit steps only when required.
|
|
48
|
+
|
|
49
|
+
## Reject Patterns
|
|
50
|
+
|
|
51
|
+
Add `stream_url_reject_patterns` for candidates that are detected but not recordable or not the desired program:
|
|
52
|
+
|
|
53
|
+
- `dai\\.google\\.com` for ad-insertion manifests.
|
|
54
|
+
- `originpath=/linear/hls` when a provider exposes an ad/linear wrapper instead of the clean live stream.
|
|
55
|
+
- `dmxleo\\.dailymotion\\.com` for Dailymotion manifest wrappers that are not final media.
|
|
56
|
+
- `cdndirector\\.dailymotion\\.com` when ffmpeg receives `403`; prefer the final `live.*.dmcdn.net/...m3u8` variant if discovered.
|
|
57
|
+
|
|
58
|
+
## Video and Audio Selection
|
|
59
|
+
|
|
60
|
+
Set `recording.video.height` when the stream has multiple variants. Use `direct_variant: false` when ffmpeg should receive the master manifest, especially when separate audio groups are involved.
|
|
61
|
+
|
|
62
|
+
For master manifests with separate audio:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
recording:
|
|
66
|
+
hls:
|
|
67
|
+
separate_audio: true
|
|
68
|
+
video:
|
|
69
|
+
height: 720
|
|
70
|
+
direct_variant: false
|
|
71
|
+
audio:
|
|
72
|
+
language: "en"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For French audio, observed language codes may be `fre`, `fra`, or `fr`; use what ffmpeg or the HLS manifest reports.
|
|
76
|
+
|
|
77
|
+
When audio-description tracks exist, use `reject_comments` if ffmpeg metadata exposes the descriptive track:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
audio:
|
|
81
|
+
language: "fre"
|
|
82
|
+
comment: "Français"
|
|
83
|
+
reject_comments:
|
|
84
|
+
- "audio_dv"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Failure Meanings
|
|
88
|
+
|
|
89
|
+
- `403 Forbidden` on the manifest or variants: likely geoblocking, missing headers, expired token, or a redirector that should be rejected.
|
|
90
|
+
- `Output file does not contain any stream`: master was reachable but variants failed or were empty.
|
|
91
|
+
- `SAMPLE-AES` or `KEYFORMAT`: protected HLS; do not attempt to bypass.
|
|
92
|
+
- A small partial file with nonzero size and rc=1: often a late stream interruption, timestamp issue, or too-short capture; retest before changing config.
|
|
93
|
+
- A live endpoint returning `false`, `is_on_air: false`, or no player data can mean the channel is event-based rather than broken.
|
|
94
|
+
- YouTube embeds can expose no HLS and return `EMBEDDER_IDENTITY_DENIED`; do not treat that as a normal HLS channel unless the project intentionally adds a YouTube resolver.
|
|
95
|
+
- Do not replace a channel with a different but similarly named feed just to make a smoke test pass. Keep `TV5MONDE Europe` as Europe, `UK Parliament` as UK Parliament, etc., unless the user explicitly accepts a substitute.
|
|
96
|
+
|
|
97
|
+
## Smoke Testing
|
|
98
|
+
|
|
99
|
+
Use 1-minute tests for confidence:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
.\.venv\Scripts\tv-recorder.exe <source-key> now 1m --output-dir recordings\channel-smoke --timeout-ms 60000
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For many channels, use a small worker pool. This project previously used 3 or 5 simultaneous recordings. Treat repeated failures across 3 attempts as real configuration problems; treat one failure followed by successes as a possible transient.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
|
|
23
|
+
- name: Install build tools
|
|
24
|
+
run: python -m pip install build
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Publish package
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tv-recorder contributors
|
|
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,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tv-recorder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line recorder for public live TV streams.
|
|
5
|
+
Project-URL: Homepage, https://github.com/onclefranck/tv-recorder
|
|
6
|
+
Project-URL: Repository, https://github.com/onclefranck/tv-recorder
|
|
7
|
+
Project-URL: Issues, https://github.com/onclefranck/tv-recorder/issues
|
|
8
|
+
Author: tv-recorder contributors
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 tv-recorder contributors
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: cli,ffmpeg,hls,playwright,recorder,tv
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Multimedia :: Video
|
|
42
|
+
Classifier: Topic :: Utilities
|
|
43
|
+
Requires-Python: >=3.11
|
|
44
|
+
Requires-Dist: imageio-ffmpeg>=0.6.0
|
|
45
|
+
Requires-Dist: playwright>=1.44
|
|
46
|
+
Requires-Dist: pytimeparse2>=1.7.1
|
|
47
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# tv-recorder
|
|
55
|
+
|
|
56
|
+
Command-line recorder for public live TV streams.
|
|
57
|
+
|
|
58
|
+
## Local Installation
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
python -m pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Chromium is installed automatically on first use if Playwright does not already have it.
|
|
65
|
+
|
|
66
|
+
The recorder uses the `ffmpeg` binary provided by `imageio-ffmpeg`.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
tv-recorder radio-canada.ca now 2h
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The general form is:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
tv-recorder SOURCE START DURATION
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
tv-recorder radio-canada.ca now 30m
|
|
84
|
+
tv-recorder radio-canada.ca 2026-05-24T20:00:00 2h --output-dir recordings
|
|
85
|
+
tv-recorder tvaplus.ca now 30m
|
|
86
|
+
tv-recorder telequebec.tv now 30m
|
|
87
|
+
tv-recorder cpac-english now 30m
|
|
88
|
+
tv-recorder cpac-francais now 30m
|
|
89
|
+
tv-recorder canal-assemblee-nationale now 30m
|
|
90
|
+
tv-recorder globalnews-national now 30m
|
|
91
|
+
tv-recorder globalnews-montreal now 30m
|
|
92
|
+
tv-recorder radio-canada.ca now 10m --headful
|
|
93
|
+
tv-recorder radio-canada.ca now 10m --dry-run
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`START` accepts `now` or a local ISO date. `DURATION` accepts values such as `90s`, `30m`, `2h`, or `01:30:00`.
|
|
97
|
+
|
|
98
|
+
## Configuration YAML
|
|
99
|
+
|
|
100
|
+
The package includes a default configuration. To replace it, provide your own YAML file:
|
|
101
|
+
|
|
102
|
+
```powershell
|
|
103
|
+
tv-recorder radio-canada.ca now 2h --config my-file.yaml
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Each source can define:
|
|
107
|
+
|
|
108
|
+
- `start_url`: page to open with Playwright.
|
|
109
|
+
- `stream_url_pattern`: regular expression used to identify stream URLs.
|
|
110
|
+
- `stream_request_urls`: optional JSON endpoints to call before browser steps.
|
|
111
|
+
- `stream_response_url_patterns`: response URL patterns whose JSON can contain stream URLs.
|
|
112
|
+
- `stream_response_json_keys`: JSON keys whose values should be treated as stream URLs.
|
|
113
|
+
- `stream_url_reject_patterns`: stream URL patterns to ignore.
|
|
114
|
+
- `recording`: video/audio track selection.
|
|
115
|
+
- `steps`: browser interaction recipe before stream detection.
|
|
116
|
+
- `output_extension`: output file extension.
|
|
117
|
+
- `user_agent`: optional user-agent for browser and recorder requests.
|
|
118
|
+
|
|
119
|
+
Track selection:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
recording:
|
|
123
|
+
video:
|
|
124
|
+
height: 720
|
|
125
|
+
audio:
|
|
126
|
+
language: "fre"
|
|
127
|
+
reject_comments:
|
|
128
|
+
- "audio_dv"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If no video track matches the requested height, the highest available resolution is used. For audio, rejected comments are filtered first, then language and comment preferences are used to choose the main track.
|
|
132
|
+
|
|
133
|
+
Supported steps:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
steps:
|
|
137
|
+
- action: wait_for_stream
|
|
138
|
+
- action: wait_for_selector
|
|
139
|
+
selector: "button.vjs-big-play-button"
|
|
140
|
+
- action: click_by_text
|
|
141
|
+
text: "EN DIRECT"
|
|
142
|
+
- action: click_by_selector
|
|
143
|
+
selector: "button:has-text('Play')"
|
|
144
|
+
fallback:
|
|
145
|
+
action: click_by_text
|
|
146
|
+
text: "Play"
|
|
147
|
+
required: true
|
|
148
|
+
timeout_ms: 30000
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Notes
|
|
152
|
+
|
|
153
|
+
The recorder uses browser automation to discover streams when a channel does not expose a stable feed URL, then records the selected audio/video tracks without re-encoding. This version starts the recording at the requested time in the current process. A real system scheduler can be layered on top of the same command later.
|
|
154
|
+
|
|
155
|
+
## Publishing
|
|
156
|
+
|
|
157
|
+
Install packaging tools:
|
|
158
|
+
|
|
159
|
+
```powershell
|
|
160
|
+
python -m pip install -e ".[dev]"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Build and check the package:
|
|
164
|
+
|
|
165
|
+
```powershell
|
|
166
|
+
python -m build
|
|
167
|
+
python -m twine check dist/*
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Publish to TestPyPI first:
|
|
171
|
+
|
|
172
|
+
```powershell
|
|
173
|
+
python -m twine upload --repository testpypi dist/*
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Publish to PyPI:
|
|
177
|
+
|
|
178
|
+
```powershell
|
|
179
|
+
python -m twine upload dist/*
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Before publishing, update `version` in `pyproject.toml` and `__version__` in `src/tv_recorder/__init__.py`, then rebuild from a clean `dist` directory.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# tv-recorder
|
|
2
|
+
|
|
3
|
+
Command-line recorder for public live TV streams.
|
|
4
|
+
|
|
5
|
+
## Local Installation
|
|
6
|
+
|
|
7
|
+
```powershell
|
|
8
|
+
python -m pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Chromium is installed automatically on first use if Playwright does not already have it.
|
|
12
|
+
|
|
13
|
+
The recorder uses the `ffmpeg` binary provided by `imageio-ffmpeg`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
tv-recorder radio-canada.ca now 2h
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The general form is:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
tv-recorder SOURCE START DURATION
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
tv-recorder radio-canada.ca now 30m
|
|
31
|
+
tv-recorder radio-canada.ca 2026-05-24T20:00:00 2h --output-dir recordings
|
|
32
|
+
tv-recorder tvaplus.ca now 30m
|
|
33
|
+
tv-recorder telequebec.tv now 30m
|
|
34
|
+
tv-recorder cpac-english now 30m
|
|
35
|
+
tv-recorder cpac-francais now 30m
|
|
36
|
+
tv-recorder canal-assemblee-nationale now 30m
|
|
37
|
+
tv-recorder globalnews-national now 30m
|
|
38
|
+
tv-recorder globalnews-montreal now 30m
|
|
39
|
+
tv-recorder radio-canada.ca now 10m --headful
|
|
40
|
+
tv-recorder radio-canada.ca now 10m --dry-run
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`START` accepts `now` or a local ISO date. `DURATION` accepts values such as `90s`, `30m`, `2h`, or `01:30:00`.
|
|
44
|
+
|
|
45
|
+
## Configuration YAML
|
|
46
|
+
|
|
47
|
+
The package includes a default configuration. To replace it, provide your own YAML file:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
tv-recorder radio-canada.ca now 2h --config my-file.yaml
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each source can define:
|
|
54
|
+
|
|
55
|
+
- `start_url`: page to open with Playwright.
|
|
56
|
+
- `stream_url_pattern`: regular expression used to identify stream URLs.
|
|
57
|
+
- `stream_request_urls`: optional JSON endpoints to call before browser steps.
|
|
58
|
+
- `stream_response_url_patterns`: response URL patterns whose JSON can contain stream URLs.
|
|
59
|
+
- `stream_response_json_keys`: JSON keys whose values should be treated as stream URLs.
|
|
60
|
+
- `stream_url_reject_patterns`: stream URL patterns to ignore.
|
|
61
|
+
- `recording`: video/audio track selection.
|
|
62
|
+
- `steps`: browser interaction recipe before stream detection.
|
|
63
|
+
- `output_extension`: output file extension.
|
|
64
|
+
- `user_agent`: optional user-agent for browser and recorder requests.
|
|
65
|
+
|
|
66
|
+
Track selection:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
recording:
|
|
70
|
+
video:
|
|
71
|
+
height: 720
|
|
72
|
+
audio:
|
|
73
|
+
language: "fre"
|
|
74
|
+
reject_comments:
|
|
75
|
+
- "audio_dv"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If no video track matches the requested height, the highest available resolution is used. For audio, rejected comments are filtered first, then language and comment preferences are used to choose the main track.
|
|
79
|
+
|
|
80
|
+
Supported steps:
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
steps:
|
|
84
|
+
- action: wait_for_stream
|
|
85
|
+
- action: wait_for_selector
|
|
86
|
+
selector: "button.vjs-big-play-button"
|
|
87
|
+
- action: click_by_text
|
|
88
|
+
text: "EN DIRECT"
|
|
89
|
+
- action: click_by_selector
|
|
90
|
+
selector: "button:has-text('Play')"
|
|
91
|
+
fallback:
|
|
92
|
+
action: click_by_text
|
|
93
|
+
text: "Play"
|
|
94
|
+
required: true
|
|
95
|
+
timeout_ms: 30000
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Notes
|
|
99
|
+
|
|
100
|
+
The recorder uses browser automation to discover streams when a channel does not expose a stable feed URL, then records the selected audio/video tracks without re-encoding. This version starts the recording at the requested time in the current process. A real system scheduler can be layered on top of the same command later.
|
|
101
|
+
|
|
102
|
+
## Publishing
|
|
103
|
+
|
|
104
|
+
Install packaging tools:
|
|
105
|
+
|
|
106
|
+
```powershell
|
|
107
|
+
python -m pip install -e ".[dev]"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Build and check the package:
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
python -m build
|
|
114
|
+
python -m twine check dist/*
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Publish to TestPyPI first:
|
|
118
|
+
|
|
119
|
+
```powershell
|
|
120
|
+
python -m twine upload --repository testpypi dist/*
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Publish to PyPI:
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
python -m twine upload dist/*
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Before publishing, update `version` in `pyproject.toml` and `__version__` in `src/tv_recorder/__init__.py`, then rebuild from a clean `dist` directory.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tv-recorder"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Command-line recorder for public live TV streams."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "tv-recorder contributors" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["cli", "ffmpeg", "hls", "playwright", "recorder", "tv"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: End Users/Desktop",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Multimedia :: Video",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"imageio-ffmpeg>=0.6.0",
|
|
31
|
+
"playwright>=1.44",
|
|
32
|
+
"pytimeparse2>=1.7.1",
|
|
33
|
+
"PyYAML>=6.0.1",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"build>=1.2",
|
|
39
|
+
"pytest>=8.0",
|
|
40
|
+
"twine>=5.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
tv-recorder = "tv_recorder.cli:main"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/onclefranck/tv-recorder"
|
|
48
|
+
Repository = "https://github.com/onclefranck/tv-recorder"
|
|
49
|
+
Issues = "https://github.com/onclefranck/tv-recorder/issues"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/tv_recorder"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
55
|
+
"src/tv_recorder/defaults.yaml" = "tv_recorder/defaults.yaml"
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|