helios-broadcast 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 (50) hide show
  1. helios_broadcast-0.1.0/LICENSE +21 -0
  2. helios_broadcast-0.1.0/PKG-INFO +256 -0
  3. helios_broadcast-0.1.0/README.md +229 -0
  4. helios_broadcast-0.1.0/helios/__init__.py +3 -0
  5. helios_broadcast-0.1.0/helios/audit.py +31 -0
  6. helios_broadcast-0.1.0/helios/backfill.py +119 -0
  7. helios_broadcast-0.1.0/helios/calliope.py +555 -0
  8. helios_broadcast-0.1.0/helios/cli.py +360 -0
  9. helios_broadcast-0.1.0/helios/config.py +227 -0
  10. helios_broadcast-0.1.0/helios/director.py +85 -0
  11. helios_broadcast-0.1.0/helios/health.py +46 -0
  12. helios_broadcast-0.1.0/helios/ingest.py +61 -0
  13. helios_broadcast-0.1.0/helios/knowledge/__init__.py +5 -0
  14. helios_broadcast-0.1.0/helios/knowledge/aegis.py +38 -0
  15. helios_broadcast-0.1.0/helios/knowledge/mnemosyne.py +172 -0
  16. helios_broadcast-0.1.0/helios/llm.py +207 -0
  17. helios_broadcast-0.1.0/helios/notify.py +54 -0
  18. helios_broadcast-0.1.0/helios/queue.py +267 -0
  19. helios_broadcast-0.1.0/helios/renderer/__init__.py +45 -0
  20. helios_broadcast-0.1.0/helios/renderer/episode.py +88 -0
  21. helios_broadcast-0.1.0/helios/renderer/subtitles.py +72 -0
  22. helios_broadcast-0.1.0/helios/renderer/tts.py +64 -0
  23. helios_broadcast-0.1.0/helios/renderer/visuals.py +100 -0
  24. helios_broadcast-0.1.0/helios/security.py +121 -0
  25. helios_broadcast-0.1.0/helios/worker.py +227 -0
  26. helios_broadcast-0.1.0/helios/youtube/__init__.py +1 -0
  27. helios_broadcast-0.1.0/helios/youtube/auth.py +46 -0
  28. helios_broadcast-0.1.0/helios/youtube/stats.py +81 -0
  29. helios_broadcast-0.1.0/helios/youtube/upload.py +113 -0
  30. helios_broadcast-0.1.0/helios_broadcast.egg-info/PKG-INFO +256 -0
  31. helios_broadcast-0.1.0/helios_broadcast.egg-info/SOURCES.txt +48 -0
  32. helios_broadcast-0.1.0/helios_broadcast.egg-info/dependency_links.txt +1 -0
  33. helios_broadcast-0.1.0/helios_broadcast.egg-info/entry_points.txt +2 -0
  34. helios_broadcast-0.1.0/helios_broadcast.egg-info/requires.txt +12 -0
  35. helios_broadcast-0.1.0/helios_broadcast.egg-info/top_level.txt +1 -0
  36. helios_broadcast-0.1.0/pyproject.toml +43 -0
  37. helios_broadcast-0.1.0/setup.cfg +4 -0
  38. helios_broadcast-0.1.0/tests/test_audit.py +20 -0
  39. helios_broadcast-0.1.0/tests/test_backfill.py +109 -0
  40. helios_broadcast-0.1.0/tests/test_calliope.py +53 -0
  41. helios_broadcast-0.1.0/tests/test_calliope_queue.py +41 -0
  42. helios_broadcast-0.1.0/tests/test_config.py +34 -0
  43. helios_broadcast-0.1.0/tests/test_director.py +76 -0
  44. helios_broadcast-0.1.0/tests/test_editorial.py +121 -0
  45. helios_broadcast-0.1.0/tests/test_ingest.py +30 -0
  46. helios_broadcast-0.1.0/tests/test_mnemosyne.py +53 -0
  47. helios_broadcast-0.1.0/tests/test_queue.py +68 -0
  48. helios_broadcast-0.1.0/tests/test_scout.py +96 -0
  49. helios_broadcast-0.1.0/tests/test_security.py +93 -0
  50. helios_broadcast-0.1.0/tests/test_youtube_stats.py +49 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AICOM (alexar76)
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,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: helios-broadcast
3
+ Version: 0.1.0
4
+ Summary: HELIOS — self-hosted broadcast pipeline for the AIMarket ecosystem. Template in, voiced video out, queued to YouTube — private until you approve.
5
+ Author: AICOM (alexar76)
6
+ License: MIT
7
+ Keywords: youtube,broadcast,devrel,aimarket,aicom,ffmpeg,tts
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: typer>=0.12
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: google-api-python-client>=2.100
19
+ Requires-Dist: google-auth-oauthlib>=1.2
20
+ Requires-Dist: google-auth-httplib2>=0.2
21
+ Requires-Dist: pillow>=10.0
22
+ Requires-Dist: edge-tts>=6.1
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8.0; extra == "dev"
25
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # HELIOS — broadcast layer of the AIMarket ecosystem
29
+
30
+ <!-- aicom-readme-badges -->
31
+ <p align="center">
32
+ <a href="https://github.com/alexar76/helios/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/helios/ci.yml?branch=main&label=CI" alt="CI" /></a>
33
+ <a href="https://github.com/alexar76/helios/actions/workflows/pages.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/helios/pages.yml?branch=main&label=Pages" alt="Pages deploy" /></a>
34
+ <a href="https://alexar76.github.io/helios/"><img src="https://img.shields.io/badge/landing-gallery-e84a1a" alt="Landing gallery" /></a>
35
+ <a href="https://www.youtube.com/@My-AI-Factory"><img src="https://img.shields.io/badge/YouTube-@My--AI--Factory-ff0000" alt="YouTube channel" /></a>
36
+ <img src="https://img.shields.io/badge/python-%3E%3D3.11-brightgreen.svg" alt="Python >=3.11" />
37
+ <img src="https://img.shields.io/badge/tests-45%20passed-brightgreen" alt="45 tests passed" />
38
+ <a href="docs/badges/coverage.svg"><img src="docs/badges/coverage.svg" alt="Test coverage" /></a>
39
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
40
+ </p>
41
+ <!-- /aicom-readme-badges -->
42
+
43
+ > **Template in, voiced video out, queued to your YouTube — private until you approve.**
44
+ > MIT · self-hosted · POST-only · no engagement bots.
45
+
46
+ 🌐 **Language:** **English** · [Русский](README-ru.md) · [Español](README-es.md)
47
+
48
+ In the myth, **Helios** drives the chariot of the sun across the sky — light, rhythm, visibility.
49
+ **HELIOS** is the broadcast satellite of the [AICOM / AIMarket ecosystem](https://magic-ai-factory.com): render ecosystem explainers, upload to [@My-AI-Factory](https://www.youtube.com/@My-AI-Factory), keep everything **private** until a human says go.
50
+
51
+ **Channel:** [@My-AI-Factory](https://www.youtube.com/@My-AI-Factory) · [playlist](https://www.youtube.com/playlist?list=PLQAJcI3MYJxM)
52
+ **Landing:** [alexar76.github.io/helios](https://alexar76.github.io/helios/) · source: [`landing/index.html`](landing/index.html)
53
+ **Repo:** [github.com/alexar76/helios](https://github.com/alexar76/helios)
54
+ **Monitor:** [Alien Monitor](https://magic-ai-factory.com/monitor/) — click the **HELIOS** node for cached YouTube stats
55
+ **Integration guide:** [helios-integration.md](https://github.com/alexar76/aicom/blob/main/docs/ecosystem/helios-integration.md)
56
+
57
+ | | |
58
+ |---|---|
59
+ | **Role** | Render MP4 → upload YouTube (private) → operator approve → public |
60
+ | **Screenwriter** | **CALLIOPE** — LLM scripts grounded in **MNEMOSYNE** (shared with DIOSCURI) |
61
+ | **Ops** | **Director** — LLM metadata review + queue priority (DeepSeek default) |
62
+
63
+ ## Why HELIOS exists
64
+
65
+ The ecosystem ships constantly — releases, oracles, courses, live demos. Manual `build_series.py` + `upload_youtube.py` in PromoMaterials does not scale. **HELIOS** is the dedicated broadcast engine: one job queue, one audit trail, one human gate before anything goes public.
66
+
67
+ ## Three minds (who does what)
68
+
69
+ | Entity | Myth | Job | Touches voiceover? |
70
+ |--------|------|-----|------------------|
71
+ | **CALLIOPE** | Muse of epic poetry | Pitch topics, write scripts from GitHub + demo corpus | **Yes** (new videos) |
72
+ | **Director** | Broadcast ops | Review title/description, prioritize queue | No |
73
+ | **Operator** | Human | Studio review → `helios approve` | No |
74
+
75
+ Backfill episodes (PromoMaterials S1 E10+, Season 2) use **human-authored yaml** — CALLIOPE does not rewrite them.
76
+
77
+ ## Charter (non-negotiable)
78
+
79
+ 1. **POST-only** — our channel only; no likes, comments, or engagement bots.
80
+ 2. **Grounded creation** — CALLIOPE cites **MNEMOSYNE** (READMEs, releases, live demos); backfill stays human yaml.
81
+ 3. **Private-first** — every upload starts `private`; `helios approve` for public.
82
+ 4. **Human gate** — operator watches every video in YouTube Studio before approve.
83
+ 5. **Fail-soft** — if HELIOS is down, Factory and DIOSCURI keep running.
84
+
85
+ ## Features
86
+
87
+ | Feature | What it means |
88
+ |---------|---------------|
89
+ | Job queue | Idempotent jobs, daily upload cap (~9/day), crash-safe worker lock |
90
+ | Renderer | TTS (`say` on macOS, edge-tts on Linux) + ffmpeg + burned-in captions + SRT |
91
+ | YouTube API | Resumable upload, captions, playlist routing |
92
+ | Backfill | Upload existing PromoMaterials renders without re-encoding |
93
+ | **CALLIOPE** | `helios calliope pitch/write/enqueue` — MNEMOSYNE-grounded scripts |
94
+ | Director | LLM metadata review (DeepSeek default) |
95
+ | DIOSCURI hook | Release events → shared `HELIOS_QUEUE_PATH` jsonl |
96
+ | Alien Monitor | `GET /health` — cached YouTube stats for the graph node |
97
+ | Audit | Append-only `data/audit.jsonl` for every state transition |
98
+
99
+ ## Architecture
100
+
101
+ ```mermaid
102
+ flowchart TB
103
+ subgraph sources["Content & knowledge (read-only)"]
104
+ GH["GitHub READMEs / releases"]
105
+ DEMO["Live demo endpoints"]
106
+ PM["PromoMaterials MP4 + yaml"]
107
+ MN["MNEMOSYNE — mnemosyne.json<br/>(DIOSCURI sync)"]
108
+ GH --> MN
109
+ DEMO --> MN
110
+ end
111
+
112
+ subgraph triggers["Triggers"]
113
+ CLI["CLI — helios enqueue / calliope"]
114
+ DC["DIOSCURI — release event"]
115
+ CRON["cron — helios worker"]
116
+ end
117
+
118
+ subgraph helios["HELIOS core"]
119
+ Q["Queue store<br/>queue.json"]
120
+ ING["Ingest — HELIOS_QUEUE_PATH jsonl"]
121
+ CAL["CALLIOPE — screenwriter<br/>BM25 + AEGIS + LLM"]
122
+ DIR["Director — metadata QA"]
123
+ WK["Worker — render → upload"]
124
+ REN["Renderer — TTS + ffmpeg"]
125
+ YTU["YouTube client — OAuth"]
126
+ AUD["audit.jsonl"]
127
+ HC["GET /health"]
128
+ end
129
+
130
+ subgraph output["Output"]
131
+ YT["YouTube @My-AI-Factory<br/>(private)"]
132
+ OP["Operator — Studio + approve"]
133
+ PUB["public video"]
134
+ MON["Alien Monitor"]
135
+ end
136
+
137
+ MN --> CAL
138
+ PM -->|backfill| Q
139
+ CLI --> CAL
140
+ CAL -->|script.yaml| Q
141
+ DC -->|jsonl job| ING
142
+ ING --> Q
143
+ CRON --> WK
144
+ Q --> DIR
145
+ DIR --> WK
146
+ WK --> REN
147
+ REN --> YTU
148
+ YTU --> YT
149
+ YT --> OP
150
+ OP -->|helios approve| PUB
151
+ WK --> AUD
152
+ WK --> HC
153
+ HC --> MON
154
+ ```
155
+
156
+ **Job phases:** `backfill` (PromoMaterials backlog) → `creator` (CALLIOPE) → `steady` (release-shorts).
157
+
158
+ **Status flow:** `pending` → `rendering` → `uploading` → `awaiting_approval` → `published`
159
+
160
+ ## Quick start (pip)
161
+
162
+ ```bash
163
+ cd helios
164
+ pip install -e ".[dev]"
165
+ cp helios.config.example.yaml helios.config.yaml
166
+ cp .env.example .env
167
+ # Required: YOUTUBE_CLIENT_SECRET, YOUTUBE_TOKEN, DEEPSEEK_API_KEY
168
+ # CALLIOPE: DIOSCURI_DATA_DIR=/path/to/dioscuri/data (reads mnemosyne.json)
169
+
170
+ helios auth
171
+ helios calliope stats # MNEMOSYNE loaded?
172
+ helios calliope editorial-status # weekly quota + scout cadence
173
+ helios calliope pitch # topic ideas (manual)
174
+ helios worker # also runs editorial scout when due
175
+ helios approve job_...
176
+ ```
177
+
178
+ ### Backfill (existing renders)
179
+
180
+ ```bash
181
+ helios backfill-scan
182
+ helios backfill-enqueue -n 10 # queue only; upload when quota allows
183
+ helios worker --max-jobs 4 # conservative if YouTube quota is tight
184
+ ```
185
+
186
+ ## Quick start (Docker)
187
+
188
+ ```bash
189
+ cp helios.config.example.yaml helios.config.yaml
190
+ cp .env.example .env
191
+ docker compose up -d --build
192
+ docker compose exec helios helios worker
193
+ ```
194
+
195
+ Container: non-root, read-only rootfs, `cap_drop: ALL`, health on `:8791/health`.
196
+
197
+ ## Configuration
198
+
199
+ Secrets in `.env`; tuning in `helios.config.yaml` (never commit real values).
200
+
201
+ | Variable | Purpose |
202
+ |----------|---------|
203
+ | `YOUTUBE_CLIENT_SECRET` | OAuth client JSON path |
204
+ | `YOUTUBE_TOKEN` | OAuth token JSON path |
205
+ | `HELIOS_LLM_PROVIDER` | `deepseek` (default) · `anthropic` · `openai-compatible` |
206
+ | `DEEPSEEK_API_KEY` | LLM for Director + CALLIOPE |
207
+ | `DIOSCURI_DATA_DIR` | Path to DIOSCURI data → reads `mnemosyne.json` |
208
+ | `HELIOS_MNEMOSYNE_PATH` | Override MNEMOSYNE file path |
209
+ | `HELIOS_QUEUE_PATH` | Shared jsonl ingest (DIOSCURI syndication) |
210
+ | `HELIOS_DATA_DIR` | Queue, audit, scripts, renders |
211
+ | `HELIOS_DRY_RUN` | `1` = no YouTube upload |
212
+
213
+ | `helios.config.yaml` key | Default | Purpose |
214
+ |----------------------------|---------|---------|
215
+ | `limits.max_uploads_per_day` | `9` | YouTube quota margin |
216
+ | `calliope.enabled` | `true` | CALLIOPE screenwriter |
217
+ | `calliope.editorial_enabled` | `true` | Auto-scout on worker cron |
218
+ | `calliope.scout_interval_days` | `3` | Days between scout runs |
219
+ | `calliope.weekly_enqueue_quota` | `3` | Max new scripts/week |
220
+ | `calliope.backfill_pause_threshold` | `8` | Pause editorial while backfill > N |
221
+ | `director.enabled` | `true` | Metadata review before upload |
222
+
223
+ ## Ecosystem position
224
+
225
+ ```
226
+ aicom → builds products
227
+ dioscuri → community Q&A + MNEMOSYNE + release → HELIOS queue
228
+ PromoMaterials → human episode scripts & rendered MP4 (backfill)
229
+ helios → render + YouTube delivery ← you are here
230
+ alien-monitor → 3D graph + HELIOS / DIOSCURI live stats
231
+ ```
232
+
233
+ ## Documentation
234
+
235
+ | Doc | Description |
236
+ |-----|-------------|
237
+ | [landing/index.html](landing/index.html) | Source for [alexar76.github.io/helios](https://alexar76.github.io/helios/) — video gallery |
238
+ | [docs/setup.md](docs/setup.md) | Install, OAuth, Docker |
239
+ | [docs/usage.md](docs/usage.md) | CLI — CALLIOPE, Director, worker |
240
+ | [docs/architecture.md](docs/architecture.md) | Components, data model |
241
+ | [docs/security.md](docs/security.md) | Threat model |
242
+ | [docs/runbook.md](docs/runbook.md) | Operator playbook |
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ pip install -e ".[dev]"
248
+ PYTHONPATH=. pytest tests/ -q
249
+ bash scripts/ci_coverage_badge.sh # refresh docs/badges/coverage.svg
250
+ ```
251
+
252
+ CI: [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (pytest + coverage badge + Docker) · Pages: [`.github/workflows/pages.yml`](.github/workflows/pages.yml) → [alexar76.github.io/helios](https://alexar76.github.io/helios/)
253
+
254
+ ## License
255
+
256
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,229 @@
1
+ # HELIOS — broadcast layer of the AIMarket ecosystem
2
+
3
+ <!-- aicom-readme-badges -->
4
+ <p align="center">
5
+ <a href="https://github.com/alexar76/helios/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/helios/ci.yml?branch=main&label=CI" alt="CI" /></a>
6
+ <a href="https://github.com/alexar76/helios/actions/workflows/pages.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/helios/pages.yml?branch=main&label=Pages" alt="Pages deploy" /></a>
7
+ <a href="https://alexar76.github.io/helios/"><img src="https://img.shields.io/badge/landing-gallery-e84a1a" alt="Landing gallery" /></a>
8
+ <a href="https://www.youtube.com/@My-AI-Factory"><img src="https://img.shields.io/badge/YouTube-@My--AI--Factory-ff0000" alt="YouTube channel" /></a>
9
+ <img src="https://img.shields.io/badge/python-%3E%3D3.11-brightgreen.svg" alt="Python >=3.11" />
10
+ <img src="https://img.shields.io/badge/tests-45%20passed-brightgreen" alt="45 tests passed" />
11
+ <a href="docs/badges/coverage.svg"><img src="docs/badges/coverage.svg" alt="Test coverage" /></a>
12
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
13
+ </p>
14
+ <!-- /aicom-readme-badges -->
15
+
16
+ > **Template in, voiced video out, queued to your YouTube — private until you approve.**
17
+ > MIT · self-hosted · POST-only · no engagement bots.
18
+
19
+ 🌐 **Language:** **English** · [Русский](README-ru.md) · [Español](README-es.md)
20
+
21
+ In the myth, **Helios** drives the chariot of the sun across the sky — light, rhythm, visibility.
22
+ **HELIOS** is the broadcast satellite of the [AICOM / AIMarket ecosystem](https://magic-ai-factory.com): render ecosystem explainers, upload to [@My-AI-Factory](https://www.youtube.com/@My-AI-Factory), keep everything **private** until a human says go.
23
+
24
+ **Channel:** [@My-AI-Factory](https://www.youtube.com/@My-AI-Factory) · [playlist](https://www.youtube.com/playlist?list=PLQAJcI3MYJxM)
25
+ **Landing:** [alexar76.github.io/helios](https://alexar76.github.io/helios/) · source: [`landing/index.html`](landing/index.html)
26
+ **Repo:** [github.com/alexar76/helios](https://github.com/alexar76/helios)
27
+ **Monitor:** [Alien Monitor](https://magic-ai-factory.com/monitor/) — click the **HELIOS** node for cached YouTube stats
28
+ **Integration guide:** [helios-integration.md](https://github.com/alexar76/aicom/blob/main/docs/ecosystem/helios-integration.md)
29
+
30
+ | | |
31
+ |---|---|
32
+ | **Role** | Render MP4 → upload YouTube (private) → operator approve → public |
33
+ | **Screenwriter** | **CALLIOPE** — LLM scripts grounded in **MNEMOSYNE** (shared with DIOSCURI) |
34
+ | **Ops** | **Director** — LLM metadata review + queue priority (DeepSeek default) |
35
+
36
+ ## Why HELIOS exists
37
+
38
+ The ecosystem ships constantly — releases, oracles, courses, live demos. Manual `build_series.py` + `upload_youtube.py` in PromoMaterials does not scale. **HELIOS** is the dedicated broadcast engine: one job queue, one audit trail, one human gate before anything goes public.
39
+
40
+ ## Three minds (who does what)
41
+
42
+ | Entity | Myth | Job | Touches voiceover? |
43
+ |--------|------|-----|------------------|
44
+ | **CALLIOPE** | Muse of epic poetry | Pitch topics, write scripts from GitHub + demo corpus | **Yes** (new videos) |
45
+ | **Director** | Broadcast ops | Review title/description, prioritize queue | No |
46
+ | **Operator** | Human | Studio review → `helios approve` | No |
47
+
48
+ Backfill episodes (PromoMaterials S1 E10+, Season 2) use **human-authored yaml** — CALLIOPE does not rewrite them.
49
+
50
+ ## Charter (non-negotiable)
51
+
52
+ 1. **POST-only** — our channel only; no likes, comments, or engagement bots.
53
+ 2. **Grounded creation** — CALLIOPE cites **MNEMOSYNE** (READMEs, releases, live demos); backfill stays human yaml.
54
+ 3. **Private-first** — every upload starts `private`; `helios approve` for public.
55
+ 4. **Human gate** — operator watches every video in YouTube Studio before approve.
56
+ 5. **Fail-soft** — if HELIOS is down, Factory and DIOSCURI keep running.
57
+
58
+ ## Features
59
+
60
+ | Feature | What it means |
61
+ |---------|---------------|
62
+ | Job queue | Idempotent jobs, daily upload cap (~9/day), crash-safe worker lock |
63
+ | Renderer | TTS (`say` on macOS, edge-tts on Linux) + ffmpeg + burned-in captions + SRT |
64
+ | YouTube API | Resumable upload, captions, playlist routing |
65
+ | Backfill | Upload existing PromoMaterials renders without re-encoding |
66
+ | **CALLIOPE** | `helios calliope pitch/write/enqueue` — MNEMOSYNE-grounded scripts |
67
+ | Director | LLM metadata review (DeepSeek default) |
68
+ | DIOSCURI hook | Release events → shared `HELIOS_QUEUE_PATH` jsonl |
69
+ | Alien Monitor | `GET /health` — cached YouTube stats for the graph node |
70
+ | Audit | Append-only `data/audit.jsonl` for every state transition |
71
+
72
+ ## Architecture
73
+
74
+ ```mermaid
75
+ flowchart TB
76
+ subgraph sources["Content & knowledge (read-only)"]
77
+ GH["GitHub READMEs / releases"]
78
+ DEMO["Live demo endpoints"]
79
+ PM["PromoMaterials MP4 + yaml"]
80
+ MN["MNEMOSYNE — mnemosyne.json<br/>(DIOSCURI sync)"]
81
+ GH --> MN
82
+ DEMO --> MN
83
+ end
84
+
85
+ subgraph triggers["Triggers"]
86
+ CLI["CLI — helios enqueue / calliope"]
87
+ DC["DIOSCURI — release event"]
88
+ CRON["cron — helios worker"]
89
+ end
90
+
91
+ subgraph helios["HELIOS core"]
92
+ Q["Queue store<br/>queue.json"]
93
+ ING["Ingest — HELIOS_QUEUE_PATH jsonl"]
94
+ CAL["CALLIOPE — screenwriter<br/>BM25 + AEGIS + LLM"]
95
+ DIR["Director — metadata QA"]
96
+ WK["Worker — render → upload"]
97
+ REN["Renderer — TTS + ffmpeg"]
98
+ YTU["YouTube client — OAuth"]
99
+ AUD["audit.jsonl"]
100
+ HC["GET /health"]
101
+ end
102
+
103
+ subgraph output["Output"]
104
+ YT["YouTube @My-AI-Factory<br/>(private)"]
105
+ OP["Operator — Studio + approve"]
106
+ PUB["public video"]
107
+ MON["Alien Monitor"]
108
+ end
109
+
110
+ MN --> CAL
111
+ PM -->|backfill| Q
112
+ CLI --> CAL
113
+ CAL -->|script.yaml| Q
114
+ DC -->|jsonl job| ING
115
+ ING --> Q
116
+ CRON --> WK
117
+ Q --> DIR
118
+ DIR --> WK
119
+ WK --> REN
120
+ REN --> YTU
121
+ YTU --> YT
122
+ YT --> OP
123
+ OP -->|helios approve| PUB
124
+ WK --> AUD
125
+ WK --> HC
126
+ HC --> MON
127
+ ```
128
+
129
+ **Job phases:** `backfill` (PromoMaterials backlog) → `creator` (CALLIOPE) → `steady` (release-shorts).
130
+
131
+ **Status flow:** `pending` → `rendering` → `uploading` → `awaiting_approval` → `published`
132
+
133
+ ## Quick start (pip)
134
+
135
+ ```bash
136
+ cd helios
137
+ pip install -e ".[dev]"
138
+ cp helios.config.example.yaml helios.config.yaml
139
+ cp .env.example .env
140
+ # Required: YOUTUBE_CLIENT_SECRET, YOUTUBE_TOKEN, DEEPSEEK_API_KEY
141
+ # CALLIOPE: DIOSCURI_DATA_DIR=/path/to/dioscuri/data (reads mnemosyne.json)
142
+
143
+ helios auth
144
+ helios calliope stats # MNEMOSYNE loaded?
145
+ helios calliope editorial-status # weekly quota + scout cadence
146
+ helios calliope pitch # topic ideas (manual)
147
+ helios worker # also runs editorial scout when due
148
+ helios approve job_...
149
+ ```
150
+
151
+ ### Backfill (existing renders)
152
+
153
+ ```bash
154
+ helios backfill-scan
155
+ helios backfill-enqueue -n 10 # queue only; upload when quota allows
156
+ helios worker --max-jobs 4 # conservative if YouTube quota is tight
157
+ ```
158
+
159
+ ## Quick start (Docker)
160
+
161
+ ```bash
162
+ cp helios.config.example.yaml helios.config.yaml
163
+ cp .env.example .env
164
+ docker compose up -d --build
165
+ docker compose exec helios helios worker
166
+ ```
167
+
168
+ Container: non-root, read-only rootfs, `cap_drop: ALL`, health on `:8791/health`.
169
+
170
+ ## Configuration
171
+
172
+ Secrets in `.env`; tuning in `helios.config.yaml` (never commit real values).
173
+
174
+ | Variable | Purpose |
175
+ |----------|---------|
176
+ | `YOUTUBE_CLIENT_SECRET` | OAuth client JSON path |
177
+ | `YOUTUBE_TOKEN` | OAuth token JSON path |
178
+ | `HELIOS_LLM_PROVIDER` | `deepseek` (default) · `anthropic` · `openai-compatible` |
179
+ | `DEEPSEEK_API_KEY` | LLM for Director + CALLIOPE |
180
+ | `DIOSCURI_DATA_DIR` | Path to DIOSCURI data → reads `mnemosyne.json` |
181
+ | `HELIOS_MNEMOSYNE_PATH` | Override MNEMOSYNE file path |
182
+ | `HELIOS_QUEUE_PATH` | Shared jsonl ingest (DIOSCURI syndication) |
183
+ | `HELIOS_DATA_DIR` | Queue, audit, scripts, renders |
184
+ | `HELIOS_DRY_RUN` | `1` = no YouTube upload |
185
+
186
+ | `helios.config.yaml` key | Default | Purpose |
187
+ |----------------------------|---------|---------|
188
+ | `limits.max_uploads_per_day` | `9` | YouTube quota margin |
189
+ | `calliope.enabled` | `true` | CALLIOPE screenwriter |
190
+ | `calliope.editorial_enabled` | `true` | Auto-scout on worker cron |
191
+ | `calliope.scout_interval_days` | `3` | Days between scout runs |
192
+ | `calliope.weekly_enqueue_quota` | `3` | Max new scripts/week |
193
+ | `calliope.backfill_pause_threshold` | `8` | Pause editorial while backfill > N |
194
+ | `director.enabled` | `true` | Metadata review before upload |
195
+
196
+ ## Ecosystem position
197
+
198
+ ```
199
+ aicom → builds products
200
+ dioscuri → community Q&A + MNEMOSYNE + release → HELIOS queue
201
+ PromoMaterials → human episode scripts & rendered MP4 (backfill)
202
+ helios → render + YouTube delivery ← you are here
203
+ alien-monitor → 3D graph + HELIOS / DIOSCURI live stats
204
+ ```
205
+
206
+ ## Documentation
207
+
208
+ | Doc | Description |
209
+ |-----|-------------|
210
+ | [landing/index.html](landing/index.html) | Source for [alexar76.github.io/helios](https://alexar76.github.io/helios/) — video gallery |
211
+ | [docs/setup.md](docs/setup.md) | Install, OAuth, Docker |
212
+ | [docs/usage.md](docs/usage.md) | CLI — CALLIOPE, Director, worker |
213
+ | [docs/architecture.md](docs/architecture.md) | Components, data model |
214
+ | [docs/security.md](docs/security.md) | Threat model |
215
+ | [docs/runbook.md](docs/runbook.md) | Operator playbook |
216
+
217
+ ## Development
218
+
219
+ ```bash
220
+ pip install -e ".[dev]"
221
+ PYTHONPATH=. pytest tests/ -q
222
+ bash scripts/ci_coverage_badge.sh # refresh docs/badges/coverage.svg
223
+ ```
224
+
225
+ CI: [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (pytest + coverage badge + Docker) · Pages: [`.github/workflows/pages.yml`](.github/workflows/pages.yml) → [alexar76.github.io/helios](https://alexar76.github.io/helios/)
226
+
227
+ ## License
228
+
229
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """HELIOS — broadcast layer of the AIMarket ecosystem."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,31 @@
1
+ """Append-only audit log — one JSON line per event."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ def _now_iso() -> str:
12
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
13
+
14
+
15
+ class AuditLog:
16
+ def __init__(self, path: Path) -> None:
17
+ self.path = path
18
+ self.path.parent.mkdir(parents=True, exist_ok=True)
19
+
20
+ def append(self, kind: str, *, job_id: str | None = None, actor: str = "system", data: dict[str, Any] | None = None) -> None:
21
+ entry = {
22
+ "ts": _now_iso(),
23
+ "kind": kind,
24
+ "actor": actor,
25
+ }
26
+ if job_id:
27
+ entry["job_id"] = job_id
28
+ if data:
29
+ entry["data"] = data
30
+ with self.path.open("a", encoding="utf-8") as f:
31
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
@@ -0,0 +1,119 @@
1
+ """Scan PromoMaterials backlog and enqueue backfill upload jobs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import yaml
10
+
11
+ from helios.config import HeliosConfig, file_roots
12
+ from helios.queue import QueueStore
13
+
14
+
15
+ def _series_dir(cfg: HeliosConfig) -> Path:
16
+ if cfg.promo_materials_dir:
17
+ return cfg.promo_materials_dir / "youtube" / "ecosystem-series"
18
+ for root in cfg.asset_roots:
19
+ candidate = root / "youtube" / "ecosystem-series"
20
+ if candidate.exists():
21
+ return candidate
22
+ candidate = root / "PromoMaterials" / "youtube" / "ecosystem-series"
23
+ if candidate.exists():
24
+ return candidate
25
+ raise FileNotFoundError("PromoMaterials ecosystem-series not found — set promo_materials_dir or asset_roots")
26
+
27
+
28
+ def _load_meta(series: Path, season: int) -> dict[str, Any]:
29
+ name = "upload_metadata.yaml" if season == 1 else "upload_metadata-season2.yaml"
30
+ return yaml.safe_load((series / name).read_text(encoding="utf-8"))
31
+
32
+
33
+ def _load_state(series: Path, season: int) -> dict[str, Any]:
34
+ name = "upload_state.json" if season == 1 else "upload_state_season2.json"
35
+ path = series / name
36
+ if path.exists():
37
+ return json.loads(path.read_text(encoding="utf-8"))
38
+ return {"videos": {}, "playlists": {}}
39
+
40
+
41
+ def _migrate_playlists(store: QueueStore, series: Path) -> None:
42
+ """Import S1 playlist IDs into helios upload_state."""
43
+ s1_state = series / "upload_state.json"
44
+ if not s1_state.exists():
45
+ return
46
+ s1 = json.loads(s1_state.read_text(encoding="utf-8"))
47
+ state = store.load_upload_state()
48
+ for key, pid in (s1.get("playlists") or {}).items():
49
+ state.setdefault("playlists", {})[key] = pid
50
+ store.save_upload_state(state)
51
+
52
+
53
+ def scan_backlog(cfg: HeliosConfig, store: QueueStore) -> list[dict[str, Any]]:
54
+ """Return pending episodes not yet in helios queue or upload state."""
55
+ series = _series_dir(cfg)
56
+ _migrate_playlists(store, series)
57
+ helios_state = store.load_upload_state()
58
+ uploaded = set(helios_state.get("videos", {}).keys())
59
+ existing_jobs = {j.idempotency_key for j in store.list_jobs()}
60
+
61
+ pending: list[dict[str, Any]] = []
62
+ for season in (1, 2):
63
+ meta_path = series / ("upload_metadata.yaml" if season == 1 else "upload_metadata-season2.yaml")
64
+ if not meta_path.exists():
65
+ continue
66
+ meta = _load_meta(series, season)
67
+ promo_state = _load_state(series, season)
68
+ promo_uploaded = set(promo_state.get("videos", {}).keys())
69
+
70
+ for eid in meta.get("upload_order", []):
71
+ if eid in uploaded or eid in promo_uploaded:
72
+ continue
73
+ ep = meta["episodes"].get(eid)
74
+ if not ep:
75
+ continue
76
+ video = series / ep["video"]
77
+ srt = series / ep["srt"]
78
+ if not video.exists():
79
+ continue
80
+ ikey = f"backfill:s{season}:{eid}"
81
+ if ikey in existing_jobs:
82
+ continue
83
+ pending.append({
84
+ "episode": eid,
85
+ "season": season,
86
+ "video": str(video),
87
+ "srt": str(srt) if srt.exists() else None,
88
+ "youtube": {
89
+ "title": ep["title"],
90
+ "description": ep.get("description", ""),
91
+ "tags": ep.get("tags", []),
92
+ "playlist": ep.get("playlist"),
93
+ "privacy": "private",
94
+ },
95
+ "idempotency_key": ikey,
96
+ })
97
+ return pending
98
+
99
+
100
+ def enqueue_backfill(cfg: HeliosConfig, store: QueueStore, *, limit: int | None = None) -> list[str]:
101
+ """Enqueue all pending PromoMaterials episodes as backfill jobs."""
102
+ pending = scan_backlog(cfg, store)
103
+ if limit:
104
+ pending = pending[:limit]
105
+ ids: list[str] = []
106
+ for item in pending:
107
+ job = store.enqueue(
108
+ template="promo-backfill",
109
+ vars={"episode": item["episode"], "season": str(item["season"])},
110
+ youtube=item["youtube"],
111
+ idempotency_key=item["idempotency_key"],
112
+ source="promo-materials",
113
+ render_path=item["video"],
114
+ srt_path=item.get("srt"),
115
+ phase="backfill",
116
+ job_id=f"job_backfill_{item['episode'].lower()}",
117
+ )
118
+ ids.append(job.id)
119
+ return ids