mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
13 lines
5.5 KiB
Markdown
13 lines
5.5 KiB
Markdown
---
|
|
paths:
|
|
- "roboco/services/video*.py"
|
|
- "roboco/services/x_video_client.py"
|
|
- "roboco/services/tiktok_client.py"
|
|
- "motion/**"
|
|
- "docker/video-renderer.Dockerfile"
|
|
---
|
|
|
|
# RoboCo video engine
|
|
|
|
**RoboCo video engine (default-off).** Bespoke motion-graphics videos (release announcements, feature spotlights, on-demand CEO briefs) authored by a UX/UI dev and distributed to X/TikTok — nothing renders or posts without the flags on, and nothing posts without an explicit CEO approval. Gated by `ROBOCO_VIDEO_ENGINE_ENABLED` (+ sub-switches `ROBOCO_VIDEO_ON_RELEASE` / `ROBOCO_VIDEO_ON_SPOTLIGHT`, and `_MAX_OPEN_POSTS` / `_RENDER_INTERVAL_SECONDS` / `_RENDER_TIMEOUT_SECONDS` / `_REQUEST_TIMEOUT_SECONDS` / `_OUTPUT_DIR`); a CEO on-demand brief rides `POST /video/request` regardless of the release/spotlight sub-switches. A project opts in via `projects.video_engine_enabled` (migration 063, mirroring `ci_watch_enabled`): the global flag arms the subsystem, the per-project flag opts a repo into authoring against its `motion/` — `VideoEngine._opted_in_project` no-ops `open_video_task` until the operator flips it on the project settings page's budget/ops card (Wave C — was the edit-project dialog). Two task kinds mirror the XEngine/ReleaseManagerEngine "originate a CEO-scoped artifact" shape but split across the real delivery lifecycle: `VideoEngine.open_video_task` (`roboco/services/video_engine.py`) opens a normal, ASSIGNED **authoring task** (`source=video`, `confirmed_by_human=True`, team=UX/UI, balanced across `ux-dev-1`/`ux-dev-2` by open-task count) that dispatches like any other pre-assigned code task — NOT held, NOT in any dispatcher's skip bucket. The assigned dev builds a HyperFrames HTML composition under `motion/compositions/<id>/` and calls the UX/UI-team-gated `propose_video` do-tool (metadata-only: composition id, input props, per-platform captions — every developer role carries the tool on their manifest, but the runtime `_caller_team` check rejects a be-dev/fe-dev) to stamp the task's `video_draft` marker, then commits + `open_pr` through the normal PR-review gate. Once that authoring task reaches `completed`, the orchestrator's `_video_render_loop` (bounded retry, `_MAX_VIDEO_RENDER_ATTEMPTS`) tars the merged `motion/` dir from the project's read-clone and POSTs it to the credential-free **video-renderer sidecar** (`VideoRenderer` in `roboco/services/video_renderer_client.py`, `ROBOCO_VIDEO_RENDERER_BASE_URL`) to render both the 9:16 and 1:1 cuts to MP4 (`video_output_dir`); on success `VideoEngine._originate_video_post` materializes a held **video-post draft** (`source=video_post`, `confirmed_by_human=False`, Secretary-owned, skipped by every dispatcher) carrying `mp4_paths` (`{vertical, square}` absolute paths) + the per-platform captions. The CEO reviews it in the panel's video queue (`video-post-queue.tsx`; `GET /video/posts` lists drafts including `mp4_paths` so the panel knows which cuts exist, `GET /video/posts/{id}/media?cut=vertical|square` streams the MP4 bytes for the preview player, CEO-gated throughout) and edits captions / approves / rejects. `VideoPostService.approve` (`roboco/services/video_post_service.py`) is the ONLY caller of the X-v2 (`XVideoPoster` in `x_video_client.py`) and TikTok inbox-upload (`TikTokPoster` in `tiktok_client.py`) posters; because a video upload + transcode/poll can run well past a minute, the critical section runs under a heartbeat-renewed Redis mutex (`heartbeat_mutex.py`, mirroring `ReleaseProposalService`'s release-execute lock shape) rather than a flat lock, commits each platform's posted-id durably before attempting the next (a partial failure never re-posts an already-succeeded platform on retry), and is idempotent (an already-`COMPLETED` draft returns the stored ids without calling a poster again). TikTok's four OAuth2 secrets live Fernet-encrypted in a singleton `tiktok_credentials` row (mirroring the git-token / `x_credentials` pattern; the API only ever returns `has_credentials`) — set via the panel's TikTok credentials card. `NullVideoRenderer` / `NullXVideoPoster` / `NullTikTokPoster` make every unconfigured leg a graceful no-op rather than a crash. **Artifact verification (2026-07-16):** authoring is gated on the RENDERED clip, not its source — the `request_render` do-verb (developer/QA, mirroring `request_sandbox`'s shape) renders the caller's actual composition via the sidecar's new frames mode (`POST /render` with `frames=N` → ffprobe'd duration + N midpoint-sampled PNGs, tar.gz back) and extracts them to the container-shared `{workspaces_root}/{project}/.previews/{task8}/{orientation}/`; the dev renders their own working tree (worktree-aware, `head_sha`/`dirty` provenance), QA a read-only `git archive` export of the assembled branch (`WorkspaceService.export_branch_motion` — the read clone's pinned HEAD undisturbed). Success stamps the `render_preview` marker; `i_am_done` on a `source=video` task refuses without it (`Requirement.RENDER_VERIFIED` in the tracing gate, mirrored in the possibilities-matrix fast path — the canonical source string lives in foundation as `markers.VIDEO_TASK_SOURCE`), the dev spawn prompt orders Read-every-frame verification, and QA's `claim_review` evidence carries a `video_context` block so the reviewer checks output, not source. A CEO reject of a held video-post draft with a non-empty reason now also re-enters the flow: `VideoPostService.reject` → `VideoEngine.reauthor_from_rejection` opens a fresh authoring task carrying the verbatim feedback + a revise-in-place pointer at the existing composition (best-effort, never fails the reject).
|