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.
- helios_broadcast-0.1.0/LICENSE +21 -0
- helios_broadcast-0.1.0/PKG-INFO +256 -0
- helios_broadcast-0.1.0/README.md +229 -0
- helios_broadcast-0.1.0/helios/__init__.py +3 -0
- helios_broadcast-0.1.0/helios/audit.py +31 -0
- helios_broadcast-0.1.0/helios/backfill.py +119 -0
- helios_broadcast-0.1.0/helios/calliope.py +555 -0
- helios_broadcast-0.1.0/helios/cli.py +360 -0
- helios_broadcast-0.1.0/helios/config.py +227 -0
- helios_broadcast-0.1.0/helios/director.py +85 -0
- helios_broadcast-0.1.0/helios/health.py +46 -0
- helios_broadcast-0.1.0/helios/ingest.py +61 -0
- helios_broadcast-0.1.0/helios/knowledge/__init__.py +5 -0
- helios_broadcast-0.1.0/helios/knowledge/aegis.py +38 -0
- helios_broadcast-0.1.0/helios/knowledge/mnemosyne.py +172 -0
- helios_broadcast-0.1.0/helios/llm.py +207 -0
- helios_broadcast-0.1.0/helios/notify.py +54 -0
- helios_broadcast-0.1.0/helios/queue.py +267 -0
- helios_broadcast-0.1.0/helios/renderer/__init__.py +45 -0
- helios_broadcast-0.1.0/helios/renderer/episode.py +88 -0
- helios_broadcast-0.1.0/helios/renderer/subtitles.py +72 -0
- helios_broadcast-0.1.0/helios/renderer/tts.py +64 -0
- helios_broadcast-0.1.0/helios/renderer/visuals.py +100 -0
- helios_broadcast-0.1.0/helios/security.py +121 -0
- helios_broadcast-0.1.0/helios/worker.py +227 -0
- helios_broadcast-0.1.0/helios/youtube/__init__.py +1 -0
- helios_broadcast-0.1.0/helios/youtube/auth.py +46 -0
- helios_broadcast-0.1.0/helios/youtube/stats.py +81 -0
- helios_broadcast-0.1.0/helios/youtube/upload.py +113 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/PKG-INFO +256 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/SOURCES.txt +48 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/dependency_links.txt +1 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/entry_points.txt +2 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/requires.txt +12 -0
- helios_broadcast-0.1.0/helios_broadcast.egg-info/top_level.txt +1 -0
- helios_broadcast-0.1.0/pyproject.toml +43 -0
- helios_broadcast-0.1.0/setup.cfg +4 -0
- helios_broadcast-0.1.0/tests/test_audit.py +20 -0
- helios_broadcast-0.1.0/tests/test_backfill.py +109 -0
- helios_broadcast-0.1.0/tests/test_calliope.py +53 -0
- helios_broadcast-0.1.0/tests/test_calliope_queue.py +41 -0
- helios_broadcast-0.1.0/tests/test_config.py +34 -0
- helios_broadcast-0.1.0/tests/test_director.py +76 -0
- helios_broadcast-0.1.0/tests/test_editorial.py +121 -0
- helios_broadcast-0.1.0/tests/test_ingest.py +30 -0
- helios_broadcast-0.1.0/tests/test_mnemosyne.py +53 -0
- helios_broadcast-0.1.0/tests/test_queue.py +68 -0
- helios_broadcast-0.1.0/tests/test_scout.py +96 -0
- helios_broadcast-0.1.0/tests/test_security.py +93 -0
- 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,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
|