diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index af2f583..d1a458e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "writing-method", - "version": "0.1.0", - "description": "Reader-oriented writing, documentation workflows and local PDF delivery.", + "version": "0.2.0", + "description": "Reader-oriented writing, documentation workflows, local PDF delivery and narrated tutorial videos.", "author": { "name": "Peter Hense" }, @@ -11,7 +11,9 @@ "writing", "documentation", "review", - "pdf" + "pdf", + "video", + "tutorial" ], "displayName": "Peter's Writing Method", "homepage": "https://github.com/phense/writing-method", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 089cdf6..cac0fb9 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "writing-method", - "version": "0.1.0", - "description": "Reader-oriented writing, documentation workflows and local PDF delivery.", + "version": "0.2.0", + "description": "Reader-oriented writing, documentation workflows, local PDF delivery and narrated tutorial videos.", "author": { "name": "Peter Hense" }, @@ -10,7 +10,9 @@ "writing", "documentation", "review", - "pdf" + "pdf", + "video", + "tutorial" ], "skills": "./skills/", "interface": { diff --git a/.gitignore b/.gitignore index 25be9a1..58f171f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,11 @@ htmlcov/ .method-core-transaction/ .method-core-stage-*/ .method-core-retired-*/ + +# credentials: only *.env.example is tracked +env/*.env + +# a project's own video profile and pronunciation list hold house choices +# (voice, hosts, storage paths); only the *.example.json files are tracked +video-profile.json +pronunciation.json diff --git a/BACKLOG.md b/BACKLOG.md new file mode 100644 index 0000000..6ad520b --- /dev/null +++ b/BACKLOG.md @@ -0,0 +1,18 @@ +Generated GitHub Issues cache — do not edit +# Backlog + + +GitHub Issues are canonical. Refresh this cache; do not make local canonical mutations. + +## Status legend + +- `⭕` Open +- `🔄` In progress +- `✅` Complete +- `❌` Blocked + +## Tasks + + +- 🔄 `WM-001` **P1** Narrated presentation videos: scripting, production and review skills + - Notes: Spec approved 2026-11-13. Skills, guides, project template, credential examples and tests implemented on branch wm-001-presentation-video (uncommitted); 235 tests pass; rehearsed end to end on the sample video (docs/specs/WM-001-presentation-video/rehearsal.md). Open: maintainer listening check, playbook review, live host trigger evaluations, commit and pull request. diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000..635984c --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,5 @@ +# Features + +Peter's Engineering Method is currently pre-release. No plugin capability has been implemented or released yet. + +This file records working, user-visible or architectural capabilities after they exist. Planned work belongs only in `BACKLOG.md`. diff --git a/README.md b/README.md index efc7f72..7caf6fd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ --- type: reference audience: Authors and maintainers familiar with their coding assistant and Git -version: 0.1.0 +version: 0.2.0 --- # Writing Method -Writing Method provides reader-oriented writing, documentation planning, review and local PDF delivery for Claude, Codex and OpenCode. Install it on its own or alongside [Engineering Method](https://github.com/phense/engineering-method). Both bundle shared support from [Method Core](https://github.com/phense/method-core); no sibling checkout is needed at runtime. +Writing Method provides reader-oriented writing, documentation planning, review, local PDF delivery and narrated tutorial videos for Claude, Codex and OpenCode. Install it on its own or alongside [Engineering Method](https://github.com/phense/engineering-method). Both bundle shared support from [Method Core](https://github.com/phense/method-core); no sibling checkout is needed at runtime. The three repositories remain under `phense`, share the `agent-methods` topic and have independent Issues and pull requests. Report a writing behavior here, an engineering behavior in Engineering Method, and shared adapter/state/playbook behavior in Method Core. @@ -17,14 +17,39 @@ The three repositories remain under `phense`, share the `agent-methods` topic an | Produce reviewed PDFs | documentation-pdf | | Explain and instruct | explaining-concepts, writing-procedures, empathic-troubleshooting | | Keep prose and documents consistent | human-prose-drafting, terminology-guard, visual-placeholders | +| Script, produce and review narrated tutorial videos | tutorial-video-scripting, presentation-video-production, presentation-video-review | | Shared support from Core | project-backlog, verification-before-completion, requesting-code-review, receiving-code-review, using-git-worktrees, dispatching-parallel-agents, playbook-authoring, playbook-review | -Describe the outcome to your assistant. A small wording edit needs focused editing; a standalone document uses authoring and self-review; a coherent document set uses planning, authoring and review. PDF generation follows content review. Playbooks support concrete publication/export operations when their decisions or recovery needs warrant one; ordinary writing does not automatically create one. +Describe the outcome to your assistant. A small wording edit needs focused editing; a standalone document uses authoring and self-review; a coherent document set uses planning, authoring and review. PDF generation follows content review. A tutorial video is scripted, produced and reviewed in that order; see [Narrated tutorial videos](#narrated-tutorial-videos). Playbooks support concrete publication/export operations when their decisions or recovery needs warrant one; ordinary writing does not automatically create one. ## Terms A **method** is this plugin's workflow and supporting skills. A **host adapter** maps shared operations to a coding assistant. **Core** is the shared source copied into the plugin. A **core lock** records that copy's revision, hashes and executable modes. A **work ID** identifies persistent project work. A **playbook** is a scenario-bound procedure whose draft, review and rehearsal are separate evidence. +## Narrated tutorial videos + +A **presentation video** is composed from designed scenes and synthesized narration; nothing is filmed. A **tutorial video** is a presentation video that teaches. Marketing clips, filmed material and screen recordings are out of scope. + +| Phase | Skill | Result | +|---|---|---| +| Script | tutorial-video-scripting | A reviewed script: one audience, one message per scene, narration written for listening, a source for every claim. The script check must pass before anything is voiced. | +| Produce | presentation-video-production | Voiced narration, a timeline derived from the measured sound, check stills, the rendered file and its measurement | +| Review | presentation-video-review | Two gates: before voicing, and before delivery (every still, a listening check, the measured file) | + +Each video is a copy of the [project template](templates/presentation-video/README.md), which carries its own tools. House choices (voice, pauses, loudness, opening sequence, render and delivery location) live once in a `video-profile.json` of the target project, never in this plugin. Rules that hold for every provider are in [speech synthesis](shared/media/speech-synthesis.md); rules for the wording are in [writing for listening](shared/media/spoken-style.md). + +Two speech providers are supported. Azure AI Speech is voiced line by line with a key from an env file and can run unattended. ElevenLabs is voiced scene by scene through its connector in the assistant session; this method stores no ElevenLabs key. [Credential examples](env/README.md) contain variable names only. + +What the method enforces, from measurements of earlier videos: + +- No time is entered by hand. The pause between sentences is one profile value (default 0.65 s) and is measured in the rendered file. +- The voice must speak the target language and English. Key terms are voiced and verified before the whole video is voiced, and read back by speech recognition afterwards. +- A provider setting counts only after its effect was measured for the voice. +- A tutorial video has no target length; above the profile's maximum (default 45 minutes) the script is split at a chapter boundary. +- Resolution, frame rate, loudness and peak are measured on the delivered file. + +The video tools need Node with npm, `ffmpeg` and `ffprobe` in addition to Python. They are checked and named when missing, not installed. + ## Installation Prerequisites: a supported Claude or Codex CLI, Git, and Python 3.11 or newer for state tools. Clone this repository to a retained directory. These commands register the local checkout as a marketplace; run the pair for your host: @@ -41,7 +66,7 @@ claude plugin install writing-method@writing-method For a session-scoped Claude test use `claude --plugin-dir /absolute/path/to/writing-method`. For OpenCode, run `python3 scripts/install-opencode --check` from this checkout, inspect the proposed path, then repeat without `--check`. It installs an owned `writing-method.js` loader pointing to this retained directory. Gemini support is deferred until its adapter is verified. -Restart the assistant session after installation and inspect its plugin inventory for `writing-method` 0.1.0. An installed listing proves discovery; a live authoring request is separate behavior evidence. With both methods installed, use the invoking method's namespace for shared support, such as `writing-method:playbook-authoring`, and run one copy. +Restart the assistant session after installation and inspect its plugin inventory for `writing-method` 0.2.0. An installed listing proves discovery; a live authoring request is separate behavior evidence. With both methods installed, use the invoking method's namespace for shared support, such as `writing-method:playbook-authoring`, and run one copy. ## Project state and updates @@ -59,6 +84,6 @@ python3 scripts/validate-plugin python3 -m unittest discover -s tests -t . -v ``` -These checks cover installed-relative resources, source integrity, state behavior, isolated package use and OpenCode coexistence. PDF generation additionally needs the tools/fonts described by `documentation-pdf`; this plugin does not install them automatically. Source tests and native plugin installation do not prove model routing, content quality or provider availability. No live Claude result is claimed during an outage. +These checks cover installed-relative resources, source integrity, state behavior, isolated package use and OpenCode coexistence. PDF generation additionally needs the tools/fonts described by `documentation-pdf`; this plugin does not install them automatically. Video tests that need `ffmpeg` are skipped where it is absent; the timing, script-check and pronunciation logic is tested without it. A rendered sample video and a provider run are separate evidence, recorded in `docs/specs/WM-001-presentation-video/rehearsal.md`. Source tests and native plugin installation do not prove model routing, content quality or provider availability. No live Claude result is claimed during an outage. Original code uses the [MIT License](LICENSE). Adapted Humanizer material is listed in [third-party notices](THIRD_PARTY_NOTICES.md); shared adaptations retain [Core notices](CORE_NOTICES.md). The project is independent of the named assistant providers. diff --git a/docs/specs/WM-001-presentation-video/plan.md b/docs/specs/WM-001-presentation-video/plan.md new file mode 100644 index 0000000..0952d26 --- /dev/null +++ b/docs/specs/WM-001-presentation-video/plan.md @@ -0,0 +1,200 @@ +# Implementation Plan: Narrated presentation videos + +## Stable work ID + +- Feature ID: `WM-001` +- Specification: `docs/specs/WM-001-presentation-video/spec.md` (approved by + the maintainer on 2026-11-13) +- Artifact path: `docs/specs/WM-001-presentation-video/plan.md` + +## Summary + +Add three method skills, one shared speech guide, one project template with +its tools, two credential examples and one playbook template. The template is +copied into a target project once per video. Its tools are small programs with +one job each; every numeric gate reads its limit from the video profile. + +The main technical change against the eleven existing projects: **one +timeline implementation**. Until now the timing rule existed twice (in the +composition and in the check-still tool) and ignored silence inside the sound +files. A single tool now writes `timeline.json`; the composition, check +stills, captions and measurements all read it. Measured defaults are in +[research.md](research.md). + +## Project gates + +| Gate | Evidence | Result | +|---|---|---| +| Core-managed files unchanged | `python3 scripts/sync-core --check` | Must pass; no export is edited (research R-5) | +| Plugin validity | `python3 scripts/validate-plugin` | Must pass with three more method skills | +| Exact skill inventory | `tests/test_writing_contracts.py` | Updated: thirteen writing skills plus eight shared | +| Existing triggers unchanged (SC-006) | `tests/fixtures/trigger-cases/matrix.json`, `evals/` | Existing cases untouched; new cases added | +| No organization-specific facts (FR-029) | New test scanning skills, templates and examples for host names, share paths and voice names in use | Must pass | +| No credential values (SC-005) | New test: example files hold no values; `env/*.env` ignored and absent from the package | Must pass | +| English, reader-oriented documentation | Writing skills applied to README, usage and skill text | Reviewed before handoff | + +## Technical context + +- Languages and versions: Python 3.11 standard library for all tools and + tests (no new Python dependency). The template uses Node 20 or newer with + Remotion 4 and TypeScript, as in the existing projects. +- Relevant components and paths: `skills/`, `shared/media/`, + `shared/policies/lifecycle-handoffs.md`, `templates/presentation-video/`, + `env/`, `method.json`, `tests/`, `evals/`, `README.md`, `docs/usage.md`. +- Storage and external systems: Azure AI Speech (HTTPS, key in an env file), + ElevenLabs (connector in the assistant session), a speech recognizer for + read-back (command named in the profile), `ffmpeg`/`ffprobe`, optionally a + render machine reached over SSH. +- Constraints and scale: videos up to 45 minutes, about 300 narration lines; + one request per line on the Azure path; free-tier rate limits. + +## Compatibility boundaries + +- Preserved behavior: the ten writing skills and all Core files. The + narration data keeps its existing fields (`text`, `speak`, `show`, `min`), + so an existing project can adopt the template tools by adding a profile. +- Migration and rollback: no migration. Rollback removes the added paths and + reverts `method.json`, the handoff graph, the inventory test and the + documentation. +- Deployment or versioning boundary: version 0.2.0 in all six places listed + in research R-5. Release and reinstallation on the hosts stay a maintainer + step. + +## Design decisions + +| ID | Decision | Reason | Alternative rejected | +|---|---|---|---| +| D-01 | Tools live in the template and are copied into the video project | A video project must render months later without the plugin version that created it | Tools inside the plugin only: later plugin changes would alter old videos | +| D-02 | `timeline.json` is generated by one Python tool and read by everything else | Removes the duplicated timing rule; makes captions and measurements trivial | Keep timing in the composition code: stays duplicated | +| D-03 | Pause accounting by measured silence, files untouched | Exact audible pause, provider-independent, no re-encoding of speech | Trim silence from files: destructive, re-encodes | +| D-04 | Profile and pronunciation list are JSON | Readable by Python and Node without dependencies | YAML: needs a parser | +| D-05 | Two profile levels: project-wide `video-profile.json` and per-video `video.json` | House choices once, video facts per video | One file per video: house choices drift | +| D-06 | Script is two files: `script/script.md` for people (audience, goal, chapters, sources, evidenced/approximated/invented) and `src/narration.json` for machines | Reviewable prose and stable machine input; checks cross-validate both | One Markdown file parsed for narration: fragile | +| D-07 | Read-back recognition is a configurable command producing word timings; the connector's transcription is the alternative | No bundled recognizer; projects already differ | Bundle a recognizer: heavy dependency | +| D-08 | Provider settings are probed: each setting is voiced twice with and twice without, and counts as effective only beyond the spread | Research R-3: a setting was trusted for weeks without effect | Trust provider documentation | +| D-09 | ElevenLabs is a procedure in the skill plus a splitting tool; no key | Maintainer decision; connector spends credits per call | API client with stored key | +| D-10 | Missing-character defects stay a visual check on stills; the template states its fonts' coverage and warns on on-screen characters outside it | A reliable automatic glyph check needs a font library | Add a font-parsing dependency | + +## Interface contracts + +| Contract ID | Producer | Consumer | Contract change | Failure behavior | +|---|---|---|---|---| +| IC-001 | `tutorial-video-scripting` | `tools/check_script.py`, voicing | New: `script/script.md` sections and `src/narration.json` (`{scene: [{text, speak?, show?, min?}]}`), `src/scenes.json` (order, chapter, message) | Check lists violations with scene and line; exit 1 | +| IC-002 | Author, review | `tools/speech.py` | New: `pronunciation.json` entries `{term, speak?, ipa?, lang?, key?}`; project-wide file merged with a per-video file | Duplicate or conflicting term: exit 1 | +| IC-003 | `tools/voice_azure.py`, `tools/split_takes.py` | Timeline | New: `public/audio/--.mp3`, `src/audio-manifest.json` with `duration`, `lead_silence`, `trail_silence`, `words` | Rate limit: wait and retry within budget; other errors stop, completed files kept; key never printed | +| IC-004 | `tools/timeline.py` | Composition, stills, captions, measurements | New: `src/timeline.json` (per line `at`, `speech_start`, `speech_end`, `slot`; per scene and video length; chapter marks) and `out/captions.vtt` | Missing or stale manifest: exit 1, nothing written | +| IC-005 | `tools/stills.mjs`, `tools/frame.mjs` | Review | New: `/-.png` and `stills.json` listing each still as uninspected | Stale timeline: exit 1 | +| IC-006 | `tools/measure.py` | Review, `verification-before-completion` | New: `out/measure.json`: resolution, frame rate, duration, loudness, peak, pace per scene, pauses between lines; pass or fail against the profile | Any deviation: exit 1 | +| IC-007 | Author | All tools | New: `video-profile.json` (found by walking up from the video project, or `VIDEO_PROFILE`) and `video.json` | Unknown key or missing required key: exit 1 with the key name | +| IC-008 | Repository | New user | New: `env/azure-speech.env.example`, `env/elevenlabs.env.example`; `tools/voice_azure.py --check` lists voices without synthesizing | Missing file, empty or rejected key: names file or variable, exit 1 | +| IC-009 | `presentation-video-review` | Author, completion | New: `review.md` from the template: findings with severity, scene, line, evidence; two gate states | Ready only with no open Critical or Important finding and all stills inspected | + +## Dependencies + +| Dependency | Why needed | Availability or ordering | +|---|---|---| +| Profile and schema (IC-007) | Every tool reads limits from it | First slice | +| Timeline tool (IC-004) | Stills, captions, measurements, composition | After manifest format; before the template composition | +| `ffmpeg`, `ffprobe` | Lengths, silence, loudness, joining | External; tools check presence and name the missing program | +| Remotion and Node | Composition, stills, render | External; installed in the copied project, not in the plugin | +| Azure key | Voicing sample and probing | Present on the development machine; never in the repository | +| ElevenLabs connector | Second provider path | Available in the session; exercised once in the rehearsal | +| Speech recognizer | Read-back (FR-034) | Existing remote recognizer on the development side; contract is word-timing JSON | + +## Tests + +- Targeted (`tests/test_presentation_video.py`, no media tools): timeline + arithmetic including silence accounting, minimum slot, scene pauses and + chapter marks; caption output; script check for each rule in FR-001 to + FR-006 and the length rule (AC-017); pronunciation merge and conflicts; + profile validation; multilingual check (AC-016); cache naming; retry and + budget handling against a fake provider (AC-005); credential check failures + without network (AC-010). +- Contract: narration fixtures taken from an existing project load unchanged; + manifest, timeline and measurement files validated against the documented + fields; skill inventory, handoff graph and trigger matrix assertions. +- Integration (skipped without `ffmpeg`): generated tones with known silence + give the expected audible pause (AC-013); a generated clip with wrong + loudness fails the delivery check (AC-007); splitting a generated take at + word boundaries; the seeded-defect project yields its four automatic + findings. +- Host evaluation: trigger cases for the three skills and the negative cases + (AC-012); the fifth seeded defect (missing character) is found in the still + inspection. +- End to end (once, recorded as rehearsal evidence): sample video from the + template, voiced with Azure, stills, render, measurements, captions; one + scene voiced through the ElevenLabs connector and split. +- Migration or rollback: one existing project is run with the new tools + using configuration changes only; its measured pause is compared before and + after. + +## Architecture findings + +The architecture gate is not applicable: the change is additive inside one +plugin, crosses no system boundary and alters no existing component. The +design-time findings below take its place. + +| Finding ID | Evidence | Required response | Status | +|---|---|---|---| +| AF-001 | Timing rule duplicated in nine projects (`narration.ts`, `stills.py`) | Single timeline tool (D-02) | Open | +| AF-002 | Audible pause 1.0 to 1.2 s against a configured 0.6 s (research R-2) | Silence accounting (D-03), measurement in delivery check | Open | +| AF-003 | Rate setting without effect, break ignored, style rejected (research R-3) | Setting probe (D-08); pauses owned by the timeline | Open | +| AF-004 | Packaging and validation tools are Core-managed | Enforce credential exclusion through ignore rules and a writing-owned test | Open | +| AF-005 | Only four of five seeded defects are automatically detectable | Fifth covered by still inspection and host evaluation (D-10) | Open | +| AF-006 | Issue label `engineering-method` is hard-coded in the Core migration tool | Report to Method Core; outside this feature | Open | +| AF-007 | Independent review, 2026-11-13: take splitting could delete accepted sound and accept a take without its last line; key-term read-back matched inside other words and rejected respelled terms; picture limits of the profile were not read by the composition; the still-inspection gate was described, not enforced | Fixed with tests: staged replacement and per-line match in `split_takes.py`; token matching in `verify_speech.py`; picture block in `timeline.json`; `tools/check_review.py`; shell-bound profile values validated | Resolved | + +## Supporting artifacts + +- `research.md`: created. Measured pace, pauses and provider behaviour. +- `data-model.md`, `contracts/`, `quickstart.md`: omitted. The file formats + are small and specified in the interface table; the end-to-end procedure is + the system playbook. + +## Implementation structure + +``` +skills/tutorial-video-scripting/SKILL.md +skills/presentation-video-production/SKILL.md +skills/presentation-video-review/SKILL.md +shared/media/speech-synthesis.md provider-neutral voicing guide +shared/media/spoken-style.md writing for listening; friendly tone +shared/policies/lifecycle-handoffs.md three nodes, edges, one retry edge +templates/presentation-video/ + README.md package.json tsconfig.json video.json + video-profile.example.json pronunciation.example.json + script/script.md script template with sample content + review.md playbook.md review record; PB-WM-VIDEO-001 template + src/ composition: Root, Video, timeline reader, + Scene frame, Caption, sample scenes + tools/profile.py check_script.py speech.py voice_azure.py + split_takes.py timeline.py probe_settings.py verify_speech.py + measure.py stills.mjs frame.mjs render.sh render-remote.sh prepend.sh +env/azure-speech.env.example env/elevenlabs.env.example env/README.md +tests/test_presentation_video.py +tests/fixtures/presentation-video/ sample narration, seeded defects +tests/fixtures/trigger-cases/matrix.json new cases +evals/shared/triggers/*.json evals/*/expected.json +method.json README.md docs/usage.md .gitignore version strings +``` + +Owner of all paths: Writing Method. No path is a Core export. + +Slices, in order: + +1. Profile, pronunciation list, script format and script check. +2. Voicing (Azure), manifest with silence, timeline, captions. +3. Measurement, setting probe, read-back verification, take splitting. +4. Template composition, stills, render scripts, sample video. +5. Skills, shared guides, handoff graph, triggers, credential examples. +6. Documentation, version, playbook template, review record. +7. Rehearsal on the sample video and one existing project; evidence. + +## Playbook obligations + +| ID | Class | Scenario | Origin | Owner | Canonical path | Dependent task | Deadline | Review and rehearsal | +|---|---|---|---|---|---|---|---|---| +| `PB-WM-VIDEO-001` | System | Produce and deliver a presentation video, including resumption after interrupted voicing or render, a rejected take and a failed delivery check | Specification | Writing Method maintainer | `templates/presentation-video/playbook.md` (instantiated in a target project as `docs/playbooks/system/PB-WM-VIDEO-001.md`) | Slices 6 and 7 | Before the feature is reported complete | Reviewed with `playbook-review`; rehearsed once end to end on the sample video on the development machine, evidence in `docs/specs/WM-001-presentation-video/rehearsal.md` | + +Development operations: none. The change is additive, reaches users only +through an ordinary pull request and release, and is reverted by Git. diff --git a/docs/specs/WM-001-presentation-video/rehearsal.md b/docs/specs/WM-001-presentation-video/rehearsal.md new file mode 100644 index 0000000..2ae89fb --- /dev/null +++ b/docs/specs/WM-001-presentation-video/rehearsal.md @@ -0,0 +1,75 @@ +# WM-001 Rehearsal evidence + +Rehearsal of `PB-WM-VIDEO-001` on the sample video of the project template. + +- Date: 2026-11-13 +- Artifact revision: working tree of branch `wm-001-presentation-video` + (template tools as committed with this file) +- Environment: authoring machine Linux (Python 3, Node, ffmpeg); separate + render machine (macOS, Apple Silicon) over SSH; Azure AI Speech free tier; + ElevenLabs connector in the assistant session; remote speech recognizer +- Method: real execution, no simulation. The project was a copy of + `templates/presentation-video/` outside the repository. + +## Steps and results + +| Step | Action | Result | +|---|---|---| +| Before starting | `tools/profile.py`, `tools/check_script.py` | Profile valid; `0 error(s), 0 warning(s); estimated length 0.9 min` | +| Before starting | `tools/voice_azure.py --check` | `credentials accepted, 1015 voices available`; languages of the voice listed; nothing synthesized | +| P2a | `tools/voice_azure.py` | `8 lines, 37.5 s of speech, 8 new, 553 characters voiced` | +| P3 | `tools/timeline.py`, `tools/measure.py pace` | 3 scenes, 3 chapters. First run **failed**: 167 to 177 wpm against a range that had been measured for German only. Response: the pace range now defaults by language; second run passed | +| P1 / P4 | `tools/verify_speech.py --samples`, then read-back of the key-term line with real recognition | Sample line `steps:2` named for the key term; `1 lines checked, 0 with an unrecognized key term` | +| P5 | `node tools/stills.mjs stills` | 8 stills; two opened and inspected (title card, four-step scene): text complete, chapter row and caption present | +| P6b | `tools/render.sh` on the authoring machine | File rendered in 2 min 41 s | +| P8 | `tools/measure.py all out/sample-video.mp4` | 1920x1080, 30 fps, loudness −16.03 LUFS, true peak −2.26 dBTP, duration 41.58 s against 41.57 s expected, 5 gaps with a mean pause of 0.66 s, none outside ± 0.15 s: `PASSED` | +| One sentence changed (US-004) | Edited `steps:1`; `tools/timeline.py` | Refused: `steps:1: the text changed after voicing; voice it again` | +| | `tools/voice_azure.py` | `8 lines … 1 new, 63 characters voiced`; file count unchanged at 8 | +| P6a | `tools/render-remote.sh ` | `done in 60 s`; measurement `PASSED` | +| P7 | `tools/prepend.sh` with a 7 s opening sequence of matching stream format | `joined without re-encoding` | +| P8 with opening | `tools/measure.py all … --offset 7` | First run **failed**: true peak −1.49 dBTP against a limit of −1.5; the peak comes from the opening sequence. Response: a peak tolerance of 0.1 dB as a profile value for encoder overshoot and rounding; second run `PASSED` (loudness −16.13 LUFS, pauses 0.66 s) | +| P2b | One generation of scene `steps` through the ElevenLabs connector (one take requested, 272 credits), stored at once, transcribed, `tools/split_takes.py steps` | `steps: steps-take1.mp3 (match 1.00), 4 lines` | + +## Existing project with the new timeline + +The timeline tool was applied to an existing 117-line, 21-scene project +(narration and sound unchanged; a profile and silence measurement added). + +| | Before | After | +|---|---|---| +| Audible pause between lines (lines without a minimum display time) | mean 1.17 s, range 0.87 to 1.35 s | 0.65 s | +| Total length | 18.05 min | 16.64 min (85 s shorter) | + +This is a calculation from the measured sound files. The project was not +re-rendered, so the figures were not confirmed in a rendered file. + +## Branches not rehearsed + +| Branch | Why | Covered by | +|---|---|---| +| R1 rate limit during voicing | The provider did not limit the run | Unit tests with a fake provider (wait, continue, stop with the line reached) | +| R2 connector failure or timeout | Did not occur | Procedure only | +| R3 rejected take | The take matched 1.00 | Unit test with a skipped sentence | +| R4 interrupted render | Not provoked | Procedure only | +| R5 failed measurement | Occurred twice (pace, peak), see above | Rehearsed | +| P10 delivery copy with checksum | No delivery location in the sample profile | Procedure only | +| Listening check by a person | Not possible for the assistant | Open: the maintainer listens to the sample and confirms the pause default of 0.65 s and the tone | +| Host trigger evaluation | Not run | Trigger matrix and expected results added; live host runs are open | + +## After the independent review + +An independent review of the change (2026-11-13) reported four Important and +eight Minor findings and no Critical one. All were corrected with tests +(plan, finding AF-007). Re-run on the sample project with the corrected +tools: voicing (all 8 lines again, because the line identity now includes the +language), timeline, type check, check stills of all scenes, and +`tools/check_review.py`, which reported `4 of 8 stills inspected without +finding` after one scene's stills were inspected and marked: the gate stays +open until the rest are. The render was not repeated after the review; the +render scripts changed only by a draft guard and an extra exclusion. + +## What the rehearsal changed + +- Pace range defaults by language (German 120 to 155, English 140 to 180). +- Peak tolerance of 0.1 dB. +- Chapter labels raised to the minimum font size. diff --git a/docs/specs/WM-001-presentation-video/research.md b/docs/specs/WM-001-presentation-video/research.md new file mode 100644 index 0000000..7e5f916 --- /dev/null +++ b/docs/specs/WM-001-presentation-video/research.md @@ -0,0 +1,112 @@ +# WM-001 Research: measured defaults and provider behaviour + +Measured on 2026-11-13. Sources: eleven existing video projects (narration +data, sound files and length manifests) and nine short test syntheses with +Azure AI Speech. No rendered video was needed for these numbers. + +## R-1 Speaking pace + +Words of caption text divided by speech time (pauses between lines excluded). +German narration. + +| Project (provider) | Lines | Speech | Pace | Line length median / max | +|---|---|---|---|---| +| AD basics (Azure) | 161 | 31.8 min | 116 wpm | 23 / 39 words | +| M365 basics (Azure) | 233 | 39.9 min | 119 wpm | 20 / 34 | +| Server landscape (Azure) | 45 | 7.8 min | 118 wpm | 21 / 33 | +| GitHub for IT (Azure) | 56 | 8.7 min | 123 wpm | 19 / 32 | +| Git explained (Azure) | 44 | 4.5 min | 125 wpm | 13 / 17 | +| PIM guide (Azure) | 58 | 8.3 min | 128 wpm | 18 / 29 | +| Password safe (Azure) | 117 | 15.5 min | 130 wpm | 17 / 29 | +| herdr explained (Azure) | 30 | 3.8 min | 132 wpm | 17 / 21 | +| Claude Desktop SSH (Azure) | 80 | 11.6 min | 133 wpm | 19 / 31 | +| Website guide (Azure) | 100 | 13.6 min | 138 wpm | 19 / 29 | +| Welcome to IT (ElevenLabs) | 263 | 30.6 min | 152 wpm | 18 / 32 | + +- Decision: default pace range **120 to 155 words per minute** per scene, + measured the same way. Published guidance for English gives 100 to 150 for + presentations and about 155 for audiobooks; German words are longer, so the + numeric range is taken from our own accepted videos, not from that guidance. +- Decision: default line limit **28 words**, sentence warning above + **22 words**. The three slowest videos are also the ones with the longest + lines. + +## R-2 Pause between lines + +All nine timeline implementations use the same constants: 0.7 s lead, 8 frames +(0.27 s) start delay, 0.6 s gap, 1.4 s tail. Silence inside the sound files +(threshold −40 dB): + +| Provider path | Leading silence | Trailing silence | Audible pause between lines | +|---|---|---|---| +| Azure, per line | 0.02 s | 0.31 s | 0.31 + 0.60 + 0.27 + 0.02 ≈ **1.20 s** | +| ElevenLabs, split takes | 0.05 s | 0.09 s | 0.09 + 0.60 + 0.27 + 0.05 ≈ **1.01 s** | + +- Finding: the audible pause was twice the configured gap, and differed by + provider, because file silence and start delay were not accounted for. +- Decision: the manifest records leading and trailing silence per file. The + timeline places the next line's first sound exactly `pause` after the + previous line's last sound. Defaults: **0.65 s** between lines, **1.0 s** + at a scene change, **0.5 s** before a scene's first line. The maintainer + asked for roughly half a second less than before, never zero; a listening + comparison (0.45 / 0.65 / 0.85 s) on the sample video confirms or adjusts + the default during implementation. + +## R-3 Azure voice behaviour (voice in current use, MAI family) + +One two-sentence test text, synthesized nine times. + +| Variant | Length | Finding | +|---|---|---| +| Plain, run 1 | 8.04 s | | +| Plain, run 2 | 7.13 s | Same input, 11 % shorter: output is not deterministic | +| `prosody rate="+7%"` | 7.54 s | Inside the run-to-run spread: no measurable effect | +| `prosody rate="+25%"` | 5.86 s | Effective | +| `break time="1500ms"` between sentences | 6.65 s | Ignored: pause 0.29 s instead of 1.5 s | +| `lang xml:lang` around one word | 7.80 s | Accepted | +| `phoneme alphabet="ipa"` | 7.94 s | Accepted; audible effect to be judged by ear | +| `mstts:express-as style="friendly"` | HTTP 400 | Rejected | +| Different HD family, plain | 7.22 s | Works with the same request | + +- The rate setting used so far (+7 %) had no reliable effect. Small rate + values must not be trusted; FR-035's "measure before relying" is necessary. +- Pauses cannot be set through markup with this voice. Pauses are therefore + owned by the timeline (R-2), which is provider-independent. +- A style cannot be requested with this voice. Friendliness comes from the + wording; the voice infers tone from the text. +- Because output varies between runs, an accepted sound file must be kept and + never regenerated without a text change. The content-addressed cache + already guarantees this. +- Provider documentation (read 2026-11-13) lists, for its HD voices: + `prosody`, `emphasis`, `mstts:silence` unsupported; `phoneme`, `sub`, + `lexicon` (alias only), `lang`, `break` supported; a `temperature` parameter + for output variation. The test shows the documented table does not transfer + to every voice family, which is why settings are probed per voice. + +## R-4 ElevenLabs behaviour (provider documentation, read 2026-11-13) + +- Current models take no SSML pauses. Pacing follows punctuation, line breaks + and bracketed audio tags. Ellipses add hesitation and are unsuitable here. +- Tone can be directed with bracketed tags; results depend on the voice and + must be tested. Decision: at most one mild tone tag at the start of a scene + passage, recorded in the profile, never per sentence. +- Pronunciation: phonetic notation is supported by some models; a phonetic + respelling is the general fallback. The provider states that words shared + between related languages may be read with the wrong accent depending on the + voice. This matches the recorded mispronunciation and supports sample + sentences before full voicing (FR-034). +- Access is through the connector in the assistant session; a generation is + started once and its status is polled, never repeated as a retry. + +## R-5 Repository mechanics + +- `scripts/package_plugin.py` and `scripts/validate_plugin.py` are + Core-managed files and must not be edited here. Packaging reads files + through Git, so an ignored `env/*.env` is not packaged. A writing-owned test + asserts this. +- CI runs `validate-plugin` and the unit tests on a runner without media + tools. Tests that need `ffmpeg` generate their own short sounds and are + skipped when it is absent; all timeline, script-check and pronunciation + logic is tested without media tools. +- The version string appears in `method.json`, `pyproject.toml`, both plugin + manifests, `README.md` and `docs/usage.md`. diff --git a/docs/specs/WM-001-presentation-video/spec.md b/docs/specs/WM-001-presentation-video/spec.md new file mode 100644 index 0000000..473e24d --- /dev/null +++ b/docs/specs/WM-001-presentation-video/spec.md @@ -0,0 +1,443 @@ +# Feature Specification: Narrated presentation videos + +## Stable work ID + +- Feature ID: `WM-001` +- Artifact path: `docs/specs/WM-001-presentation-video/spec.md` +- Status: Draft +- Source request: Formalize in Writing Method how narrated tutorial videos are + scripted, voiced, produced and reviewed, so that future videos reach a + higher and repeatable quality. Ship credential examples without real keys. + Name the parts so that other video forms can be added later without + renaming. + +## Terms + +| Term | Meaning | +|---|---| +| Presentation video | A video composed programmatically from designed scenes and synthesized narration. It contains no camera footage and no screen recording. | +| Tutorial video | A presentation video whose purpose is to teach: it explains a concept or walks through a procedure. | +| Script | The reviewed text of a video: chapters, scenes and narration lines. | +| Narration line | One spoken sentence or short sentence group, with the on-screen caption it belongs to. The smallest unit of voicing, timing and checking. | +| Scene | A visual unit that carries one message and owns an ordered list of narration lines. | +| Check still | A single rendered frame, taken while a given narration line is spoken, used to inspect the picture without rendering the video. | +| Video profile | A file in the target project that records its house choices: voice, speech provider, branding, opening sequence, render location and delivery location. | +| Speech provider | A text-to-speech service. Azure AI Speech and ElevenLabs are the two supported ones. | +| Recreated interface | A drawn imitation of a real application window, used in place of a screenshot. | + +**Purpose** and **form** are separate axes. `tutorial` names a purpose and stays +a writing concern. `presentation-video` names a form and owns production. A +later form (for example short vertical marketing clips) adds its own +form-bound skills and reuses the provider-neutral speech guidance. + +## Evidence base + +Eleven existing projects (October to November 2026) were inspected: nine +tutorial videos between 4 and 36 minutes, one opening sequence and one +template. Practices that held up, and failures that occurred, are the source of +the requirements below. + +| Observation | Source | Effect on this specification | +|---|---|---| +| Hand-written start times drifted whenever a sentence changed. Deriving the timeline from the measured audio lengths removed the problem. | Earliest project versus all later ones | FR-010 | +| Marker frames and special characters were missing in the picture and were only noticed after a full render. | Several projects | FR-014, FR-021 | +| Voicing per line with a content-addressed cache made a one-sentence change cost one request. | Azure path, nine projects | FR-007 | +| Voicing a whole scene in one request gave a more natural delivery, but a one-sentence change forced the whole scene to be re-voiced and re-split, and the download links expired. | ElevenLabs path, one project | FR-008, edge cases | +| Pronunciation overrides were kept per line and repeated inconsistently across scenes. | Largest project | FR-005 | +| The free speech tier answered with rate-limit errors during long runs. | Azure path | FR-009 | +| Rendered sound was quiet and uneven between videos until loudness was normalized. | Render scripts | FR-016 | +| Viewers could mistake recreated interfaces and invented people for real ones. | Project notes | FR-004, FR-019 | +| One project needed two audiences and was split into two videos late. | Password-safe project | FR-001 | +| Joining an opening sequence re-encoded the video until stream formats were matched. | Opening-sequence project | FR-017 | +| The pause between sentences (a fixed 0.6 s plus a start delay and the silence inside each sound file) felt slightly long; the maintainer wants it shorter, not removed, for a crisper rhythm. | Maintainer feedback, 2026-11-13 | FR-030 | +| A multilingual voice repeatedly mispronounced a plain target-language word that is central to the video ("Tresor" in a German video). A multilingual model alone does not guarantee correct pronunciation; it can read a native word as foreign. | Password-safe project; maintainer feedback, 2026-11-13 | FR-033, FR-034 | +| The speaking-rate setting used so far relies on markup that the provider documents as unsupported for its high-definition voices; whether it had any effect was never measured. | Provider documentation, read 2026-11-13 | FR-035 | +| Research recommends learning videos of six to nine minutes. The maintainer decided that this does not apply to tutorial videos here: length follows the content, with one upper limit. | Brame 2016; maintainer decision, 2026-11-13 | FR-036 | +| The narration sounded neutral. The maintainer wants somewhat more friendliness and positive energy, without overdoing it. | Maintainer feedback, 2026-11-13 | FR-031 | + +## User scenarios + +### US-001: Script a tutorial video from source material (Priority: P1) + +An author states the topic, audience and source material. The assistant +produces a reviewed script: chapters, scenes with one message each, narration +lines written for listening, and a list of every claim with its source. + +- Why this priority: every later step consumes the script, and most quality + defects found so far were script defects (wrong audience, unsupported claim, + sentence too long to follow by ear). +- Independent test: given a short source document and a named audience, the + assistant delivers a script that passes the script checks in FR-006 without + any production tooling installed. + +### US-002: Produce a video from an accepted script (Priority: P1) + +The assistant turns an accepted script into a rendered video: it voices the +narration, derives the timeline from the audio, builds the scenes, inspects +check stills and renders. + +- Why this priority: this is the deliverable the user asked for. +- Independent test: starting from the bundled sample script and the project + template, with one configured speech provider, a short video is rendered + and its measured properties satisfy FR-016. + +### US-003: Review a video before delivery (Priority: P1) + +A reviewer, human or assistant, checks the script, the check stills, the sound +and the rendered file against a fixed checklist and records findings. + +- Why this priority: the requested gain in quality comes from gates that are + applied every time, not from better tooling. +- Independent test: a prepared project containing five seeded defects (missing + character, caption differs from speech, unsupported claim, unmarked recreated + interface, loudness out of range) yields five findings. + +### US-004: Change one sentence without redoing the video (Priority: P2) + +The author corrects a single narration line. Only that line is voiced again, +all later timings follow by themselves, and only the affected check stills are +inspected again. + +- Why this priority: videos are maintained for months; a costly correction + path means errors stay in. +- Independent test: after editing one line, the number of provider requests is + one, and no hand-written time value was edited. + +### US-005: Set up credentials safely (Priority: P2) + +A new user copies an example file, enters a key and runs a check that reports +whether the provider answers, without the key appearing in output, logs or the +repository. + +- Why this priority: the method is published; a leaked key or an unusable + first run both harm trust. +- Independent test: from a fresh clone, following only the written steps, the + credential check passes, and a repository-wide search finds no key value. + +### US-006: Apply house choices without changing the method (Priority: P2) + +A project records its voice, branding, opening sequence, render location and +delivery location once in its video profile. Every later video follows it +without being told again. + +- Why this priority: keeps organization-specific facts out of the public + method while preserving consistency between videos. +- Independent test: two projects with different profiles produce videos that + differ in voice and opening sequence, using an unchanged plugin. + +## Acceptance criteria + +- AC-001 — Given a topic, an audience and source material, when the author asks + for a tutorial video script, then the script names exactly one audience, + gives each scene one stated message, and lists a source for every factual + claim or marks it as unverified. +- AC-002 — Given a script with a narration line longer than the configured + limit, a term used in two spellings, or a caption that differs in meaning + from its spoken form, when the script check runs, then each is reported with + scene and line. +- AC-003 — Given an accepted script and a configured provider, when production + runs, then one sound file exists per narration line, the timeline is derived + from the measured lengths, and no start time was entered by hand. +- AC-004 — Given a voiced project, when one line changes and production runs + again, then only that line is requested from the provider (per-line mode) or + only its scene (per-scene mode), and unused sound files are removed. +- AC-005 — Given a provider that answers with a rate-limit error, when + production runs, then it waits as instructed and continues; after the retry + budget is spent it stops, keeps completed files and reports the line reached. +- AC-006 — Given a voiced project, when check stills are requested, then one + still per narration line is produced, and the review cannot be recorded as + passed until each still is marked inspected. +- AC-007 — Given a rendered video, when delivery checks run, then resolution, + frame rate, loudness, peak level and duration are measured from the file and + compared with the profile; any deviation blocks the "ready" claim. +- AC-008 — Given a script that uses recreated interfaces or invented persons + and data, when the script check runs, then it requires one spoken or visible + notice near the start and a written list of what is evidenced, approximated + and invented. +- AC-009 — Given a fresh clone, when the repository is searched, then it + contains example credential files with variable names and comments only. + Real credential files are ignored by version control; packaging ships + tracked files only, and a test fails if any credential file is tracked. +- AC-010 — Given no credential file, an empty key or a rejected key, when the + credential check runs, then it names the missing file or variable, never + prints a key, and exits with failure before any paid request. +- AC-011 — Given a project without a video profile, when a video is requested, + then the assistant creates the profile from the template, asks only for + values it cannot infer, and uses it from then on. +- AC-013 — Given two consecutive narration lines in a rendered video, when the + silence between them is measured, then it equals the profile's pause within + a stated tolerance and is greater than zero. +- AC-014 — Given the ElevenLabs path, when a generation fails or times out, + then the procedure checks the existing generation's status and does not + start a second paid generation without the author's decision. +- AC-015 — Given a script whose key term the voice mispronounces, when the + pronunciation check runs before full voicing, then the term is reported, + and after a correction is entered in the pronunciation list every + occurrence in the voiced result is recognized correctly. +- AC-016 — Given a profile naming a voice that does not cover both the target + language and English, when the profile is checked, then it is rejected with + the reason. +- AC-017 — Given a script whose estimated length exceeds the profile's + maximum, when the script check runs, then it blocks voicing and proposes a + split at a chapter boundary; a script below the maximum is never reported + for its length. +- AC-018 — Given a profile with a rate setting that the voice ignores, when + the setting is checked, then the measured pace with and without it is + shown and the setting is reported as ineffective. +- AC-012 — Given a request for a marketing clip, a screen recording or a + filmed video, when routing happens, then none of the three new skills + claims it. + +## Functional requirements + +Scripting (`tutorial-video-scripting`): + +- FR-001: A script must record one audience, one form of address, the learning + goal and the estimated length. A second audience requires a second script. +- FR-002: A script must be organized as chapters containing scenes; each scene + must state its single message and what the viewer sees while it is spoken. +- FR-003: Narration must be written for listening and must apply the existing + prose, explanation, procedure and terminology skills from the first draft. + Spoken limits must be stated. Sentence and line length, abbreviations and + numbers without a spoken form are checked automatically; "one new term per + line" is checked by the reviewer at the first gate. +- FR-004: Every factual claim must be traceable to a recorded source with its + date, or be marked unverified. The script must list what in the picture is + evidenced, approximated and invented, and must carry a notice to the viewer + when interfaces are recreated. +- FR-005: Pronunciation overrides must be kept once per project in a + pronunciation list and applied to every occurrence. A caption and its spoken + form may differ in spelling, never in meaning. +- FR-006: A script check must run before any paid voicing and report + violations of FR-001 to FR-005 with scene and line. Secrets, personal data of + real people and live credentials must not appear in narration or picture. + +Voicing (`shared/media/speech-synthesis.md`, used by production): + +- FR-007: Per-line voicing must store each line under a name derived from its + exact spoken content and voice settings, reuse existing files, and remove + files no longer referenced. +- FR-008: Per-scene voicing must be supported for providers that deliver more + natural speech from longer passages. It must split the result into lines at + measured word boundaries, verify that the recognized words match the script + above a stated threshold, and reject a take that does not. +- FR-009: Provider errors must be handled explicitly: wait and retry on rate + limits within a budget, stop on authentication or quota errors, keep + completed work, and report characters voiced so cost is visible. +- FR-030: The pause between narration lines must be a single profile value + that already accounts for silence contained in the sound files, so the + audible pause equals the stated one. It must be shorter than in the existing + videos and must never be zero. A longer pause may be set at chapter and + scene changes. +- FR-031: The narration must sound friendly and moderately positive. The + script contributes wording (direct address, encouragement where a step + succeeds, no hype and no exclamation chains); the profile contributes the + provider's voice-style settings. The tone must stay the same across scenes + and across videos of one profile. +- FR-032: ElevenLabs must be used through its connector in the assistant + session, not through a stored API key. The procedure must cover choosing the + voice from the connector's list or the profile, starting a generation once, + waiting for completion, storing the result immediately and never repeating + a generation call as a retry. +- FR-033: The voice named in a profile must be multilingual: it must speak + the target language and English, because narration regularly contains + English product names and technical terms. A voice that covers only one + language must be rejected when the profile is checked. +- FR-034: Pronunciation must be verified before full voicing and after it. + Before: the recurring key terms of the script (title term, product names, + abbreviations, foreign words) are voiced in short sample sentences with the + profile's voice and checked. After: every voiced line is transcribed by + speech recognition and compared with its spoken form; a key term that is + not recognized is reported with scene and line. A failing term is corrected + in this order: pronunciation list entry (phonetic spelling or the + provider's pronunciation markup), explicit language marking, a different + voice. The working correction is recorded in the pronunciation list so it + holds for every later occurrence and video. +- FR-035: Speaking pace must be measured on the voiced result as words per + minute per scene and compared with the profile's range. A provider setting + for rate, pause or style counts as effective only after its effect was + measured once for the profile's voice; a setting the voice ignores must be + reported, not silently kept. Where the voice offers no pace control, pace + is adjusted through wording and the pause between lines. +- FR-036: A tutorial video has no recommended length; brevity targets from + learning research are not applied. The profile sets one maximum length + (default 45 minutes). The script check estimates the length from the word + count and the profile's pace; a script above the maximum must be split into + two or more videos at a chapter boundary, each complete on its own, before + voicing. Every video must carry chapter marks. +- FR-010: The measured length of every sound file must be recorded in a + manifest. All timing must derive from this manifest. A line may declare a + minimum display time; no other time value is authored. + +Production (`presentation-video-production`): + +- FR-011: A project template must provide a working minimal video: narration + data, timeline derivation, a scene frame with chapter display, a caption + component, check-still tooling and a render command. +- FR-012: Scenes must synchronize their animations to line starts, not to + absolute times. +- FR-013: The picture must meet stated legibility rules: minimum text size at + the delivery resolution, minimum contrast, a safe margin, and no line of + on-screen text that changes while it is being read. Resolution, minimum + size and margin are profile values that the composition reads; the + template's own frame and caption obey them. Whether a scene obeys them, and + contrast, is checked by eye on the check stills, not by a tool. +- FR-014: Check stills must be produced per narration line, late in the line's + display time, at reduced size, for all scenes or a named subset. A single + frame at a named second must also be obtainable. +- FR-015: Rendering must be possible locally and on a separate render machine. + Voicing must complete before transfer so that credentials never leave the + authoring machine. +- FR-016: The delivered file must meet the profile's resolution, frame rate, + integrated loudness and peak level. These must be measured after rendering. +- FR-017: An opening sequence named in the profile must be placed in front + without re-encoding when stream formats match, and the video must never be + encoded twice on the standard path. +- FR-018: A caption file with line timings must be produced from the same + timeline, so that subtitles never drift from speech. +- FR-019: Each project must keep a short record beside the video: chapters + with time marks, sources, the evidenced/approximated/invented list, voice + and provider used, and what change in the real world makes the video stale. + +Review (`presentation-video-review`): + +- FR-020: Review must have two gates. Before voicing: script check and source + check. Before delivery: check stills, listening check, measured file + properties. +- FR-021: The still inspection must look for missing characters, missing + markers, clipped or overlapping text, a pointer or highlight on the wrong + element, and a picture that contradicts the spoken line. +- FR-022: The listening check must cover mispronounced terms, wrong emphasis, + audible cuts between lines, speaking pace outside the stated range, pauses + that feel long or missing, and a tone that is flat or exaggerated. +- FR-023: Findings must be recorded with severity, scene, line and evidence. A + video is not ready while a Critical or Important finding is open. + +Configuration and credentials: + +- FR-024: The repository must contain `env/azure-speech.env.example` with + variable names, purpose, where to obtain the value and no values, and + `env/elevenlabs.env.example` with the non-secret settings of that provider + (voice, model, style) and a note that access runs through the connector. Real credential files must be ignored by version + control and excluded from packaging. +- FR-025: The location of the credential file must be configurable; the + default must not assume any particular project or home directory layout. +- FR-026: A video profile template must cover: provider and voice, voice + style, speaking rate, pause between lines, language, pace range, maximum length, resolution and frame rate, loudness target, branding, + opening sequence, render location, delivery location and retention of + intermediate files. + +Method integration: + +- FR-027: The three skills must be registered as method skills, appear in the + plugin inventory and documentation, and carry trigger descriptions that + exclude one another and the existing documentation skills. +- FR-028: Trigger cases must cover: tutorial video from sources, production + from an accepted script, review of a finished video, a one-line correction, + and the negative cases in AC-012. +- FR-029: Organization-specific facts (voice names in use, host names, storage + paths, opening sequences) must not appear in the plugin; they belong in the + target project's video profile. + +## Compatibility boundaries + +- Existing behavior that must remain unchanged: the ten existing writing + skills, their triggers and artifacts; all Core-managed files and the core + lock; installation and coexistence with Engineering Method. +- Migration and rollback expectations: the eleven existing projects are not + migrated by this feature. The template must be able to host one of them + with changes limited to configuration, shown once as evidence. Rollback is + removal of the new skills, templates and examples; no state format changes. +- Data, security, performance, and platform constraints: no credential, key + fragment or provider response containing one may be written to the + repository, logs or events. Paid requests happen only after the script gate. + External tools (composition framework, media tools, speech providers) are + dependencies the plugin names and checks for, and does not install. + Supported hosts are those Writing Method already supports. + +## Interface contracts + +| Contract ID | Boundary | Inputs | Outputs | Errors or invariants | +|---|---|---|---|---| +| IC-001 | Script artifact | Audience, goal, sources | Chapters, scenes, narration lines with caption, optional spoken form, optional caption-hidden flag, optional minimum display time | One audience; every scene has one message; line order is stable | +| IC-002 | Pronunciation list | Term, spoken form or pronunciation markup, language, key-term flag | Applied spoken form for every occurrence; sample sentences for key terms; per-line recognition result | One entry per term; conflicts are an error; a key term failing recognition blocks the delivery gate | +| IC-003 | Voicing | Narration lines, pronunciation list, profile, credential file | One sound file per line, manifest of measured lengths, count of characters voiced | Same input yields no new request; failure keeps completed files; never prints credentials; the connector path never repeats a generation as a retry | +| IC-004 | Timeline | Narration data, manifest | Start and length per line, length per scene and video, caption file | Pure function of its inputs; no authored start times | +| IC-005 | Check stills | Scene subset or all, output directory | One image per line, named by scene and line | Fails if manifest is missing or stale | +| IC-006 | Delivery check | Rendered file, profile | Measured resolution, frame rate, loudness, peak, duration; pass or fail | Measures the file, never trusts the render exit status | +| IC-007 | Video profile | Target project | House choices listed in FR-026 | Absent profile triggers creation; unknown keys are reported | +| IC-008 | Credential examples | Fresh clone | Example files, written setup steps, credential check | No values in the repository; real files ignored and unpackaged | +| IC-009 | Review record | Script, stills, file measurements | Findings with severity, scene, line, evidence; gate status | Ready only with no open Critical or Important finding | + +## Edge cases + +- A line's animation needs longer than its speech: the minimum display time + extends the slot; later lines shift. +- A provider mispronounces a term despite an override: the listening check + records it; the override is corrected in the pronunciation list, not in the + line. +- Per-scene voicing returns a take that skips or repeats a sentence: the match + threshold rejects it and a new take is requested; the stored download is + kept because provider links expire. +- A font lacks a character used on screen: the still inspection must catch it; + the template states which character ranges its fonts cover. +- The render machine is unreachable: local rendering remains available and is + slower; no partial file is reported as a result. +- A video serves two audiences: two scripts and two outputs may share one + project and its scenes. +- The source system changes after delivery: the project record names the + trigger; the correction path is US-004. +- Both providers are configured: the profile decides; the choice is recorded + per video. +- A sound file exists but its line was deleted: it is removed on the next run. +- The opening sequence has a different stream format: it is re-encoded once + and the user is told why. + +## Assumptions and unresolved decisions + +- The reference composition framework is Remotion with ffmpeg for + measurement and joining, because all eleven projects use it. Requirements + are written so another framework could satisfy them. Effect: planning may + bind the template to these tools. +- ElevenLabs is reached through its connector in the assistant session + (maintainer decision, 2026-11-13); no ElevenLabs key is stored by this + method. Consequence: that path needs an interactive session with the + connector authorized, while the Azure path can run unattended. Whether the + connector's own timing data can replace separate speech recognition for + splitting is left to planning. +- Default speaking pace, pause between lines, sentence-length limit, minimum + text size and loudness target will be proposed in planning from measurements of the existing + videos (current practice: integrated loudness near −16 LUFS, 1920×1080 at + 30 frames per second). Effect: numeric gates are profile values with + defaults, not constants. +- GitHub Issues of this repository are the canonical backlog (maintainer + decision, 2026-11-13). `WM-001` is its first work ID. +- The folder for credential examples is `env/` (maintainer decision, + 2026-11-13). +- Out of scope: marketing and short-form video, filmed material, screen + recording, music composition, publishing to video platforms, automatic + installation of tools, migration of existing projects. + +## Success measures + +- SC-001: A person who has not built a video before produces the sample video + from a fresh clone by following the written steps only. +- SC-002: Correcting one sentence in a finished ten-minute video takes one + provider request and no manual timing change. +- SC-003: The seeded-defect project of US-003 yields all five findings. +- SC-004: Three consecutive videos from one profile measure within the + loudness tolerance of each other. +- SC-005: A repository-wide and package-wide search for credential values + finds none. +- SC-006: None of the existing trigger cases changes its selected skill. + +## Playbook obligations + +| ID | Class | Scenario and trigger | Source | Audience | Acceptance criteria | +|---|---|---|---|---|---| +| `PB-WM-VIDEO-001` | System | Produce and deliver a presentation video from an accepted script, including resumption after interrupted voicing, an interrupted render, a rejected take and a failed delivery check | US-002, US-004, AC-005, AC-007 | An author or assistant who has the project and a configured provider, without knowledge of earlier videos | Delivered as a template that a target project instantiates with its video profile; covers decisions (provider mode, local or separate render), failure paths and verification; reviewed with `playbook-review`; rehearsed once end to end on the sample video | + +Credential setup (US-005) is a short linear procedure with a built-in check +and needs no playbook. Scripting and review are ordinary skill use and need +none. Development-operation playbooks are assessed in planning. Policy: +[shared playbooks](../../../shared/policies/playbooks.md). diff --git a/docs/specs/WM-001-presentation-video/tasks.md b/docs/specs/WM-001-presentation-video/tasks.md new file mode 100644 index 0000000..c9d4308 --- /dev/null +++ b/docs/specs/WM-001-presentation-video/tasks.md @@ -0,0 +1,105 @@ +# Tasks: Narrated presentation videos + +## Stable work ID + +- Feature ID: `WM-001` +- Specification: `docs/specs/WM-001-presentation-video/spec.md` +- Plan: `docs/specs/WM-001-presentation-video/plan.md` +- Artifact path: `docs/specs/WM-001-presentation-video/tasks.md` + +Template root below: `T/` = `templates/presentation-video/`. +Test file: `tests/test_presentation_video.py`. + +## Architecture findings + +| Finding | Affected interface | Resolution task | +|---|---|---| +| AF-001 duplicated timing rule | IC-004 | T008 | +| AF-002 audible pause twice the configured one | IC-003, IC-004, IC-006 | T007, T008, T011 | +| AF-003 provider settings without effect | IC-003 | T012 | +| AF-004 packaging is Core-managed | IC-008 | T021 | +| AF-005 fifth seeded defect not automatic | IC-009 | T018, T026 | +| AF-006 hard-coded issue label in Core | none | T030 (report only) | + +## Tests + +Command for all slices: `python3 -m unittest discover -s tests -t . -v`, plus +`python3 scripts/validate-plugin` and `python3 scripts/sync-core --check`. +Each slice writes its failing tests first and ends green. + +## Implementation slices + +### Slice S1: Profile, pronunciation list, script format and script check + +- [x] T001 [US-006] [FR-026] [IC-007] Tests then implementation of profile loading and validation: required keys, unknown keys, defaults from research, multilingual voice rule — files: `T/tools/profile.py`, `T/video-profile.example.json`, `T/video.json`; catches: missing key, monolingual voice (AC-016) +- [x] T002 [FR-005] [IC-002] Tests then implementation of the pronunciation list: merge project and video lists, conflicts, application to spoken text, provider markup — files: `T/tools/speech.py`, `T/pronunciation.example.json`; catches: term corrected in one place only; depends on: T001 +- [x] T003 [US-001] [FR-001–FR-006] [FR-036] [AC-001] [AC-002] [AC-008] [AC-017] Tests then implementation of the script check: one audience, scene message, line and sentence limits, caption against spoken form, sources, recreated-interface notice, secrets pattern, estimated length and split proposal — files: `T/tools/check_script.py`, `T/script/script.md`, `tests/fixtures/presentation-video/`; depends on: T001, T002 + +### Slice S2: Voicing, manifest, timeline, captions + +- [x] T004 [US-005] [AC-010] [IC-008] Tests then implementation of credential loading and the credential check (no key in output, configurable location) — files: `T/tools/voice_azure.py`; catches: key echoed in an error +- [x] T005 [US-002] [US-004] [FR-007] [FR-009] [AC-003] [AC-004] [AC-005] Tests then implementation of per-line voicing against a fake provider: content-addressed names, reuse, stale removal, retry budget, character count — files: `T/tools/voice_azure.py`; depends on: T002, T004 +- [x] T006 [FR-010] Manifest with measured length — files: `T/tools/voice_azure.py`, `T/tools/audio.py`; depends on: T005 +- [x] T007 [AF-002] [FR-030] Tests then implementation of silence measurement per file — files: `T/tools/audio.py`; catches: pause differing by provider; depends on: T006 +- [x] T008 [AF-001] [FR-010] [FR-012] [FR-018] [FR-030] [IC-004] Tests then implementation of the timeline tool and caption file: silence accounting, minimum slot, scene pauses, chapter marks, stale-manifest refusal — files: `T/tools/timeline.py`; catches: caption drift, hand timing; depends on: T007 + +### Slice S3: Measurement, probing, read-back, take splitting + +- [x] T009 [FR-035] [AC-018] Tests then implementation of pace per scene — files: `T/tools/measure.py`; depends on: T008 +- [x] T010 [FR-016] [AC-007] [IC-006] Tests then implementation of file measurement (resolution, frame rate, duration, loudness, peak) against the profile — files: `T/tools/measure.py`; catches: trusting the render exit status +- [x] T011 [AF-002] [AC-013] Tests then implementation of pause measurement in rendered sound against the timeline — files: `T/tools/measure.py`; depends on: T008 +- [x] T012 [AF-003] [FR-035] [AC-018] Tests then implementation of the setting probe (two runs with, two without, effect beyond spread) — files: `T/tools/probe_settings.py`; depends on: T005 +- [x] T013 [FR-034] [AC-015] Tests then implementation of read-back verification from word-timing files and key-term sample sentences — files: `T/tools/verify_speech.py`; depends on: T002 +- [x] T014 [FR-008] [FR-032] Tests then implementation of take splitting with match threshold — files: `T/tools/split_takes.py`; catches: skipped or repeated sentence accepted; depends on: T007 + +### Slice S4: Template composition and sample video + +- [x] T015 [FR-011] [FR-012] [FR-013] Composition reading `timeline.json`: root, scene frame with chapters, caption, audio track, sample scenes, legibility constants — files: `T/src/`, `T/package.json`, `T/tsconfig.json`; depends on: T008 +- [x] T016 [FR-014] [AC-006] [IC-005] Check-still and single-frame tools with inspection list — files: `T/tools/stills.mjs`, `T/tools/frame.mjs`; depends on: T015 +- [x] T017 [FR-015] [FR-016] [FR-017] Render locally and on a render machine, loudness normalization, opening sequence without re-encoding — files: `T/tools/render.sh`, `T/tools/render-remote.sh`, `T/tools/prepend.sh`; depends on: T015 +- [x] T018 [US-003] [AF-005] [SC-003] Seeded-defect fixture and its automatic findings — files: `tests/fixtures/presentation-video/defects/`; depends on: T003, T010 + +### Slice S5: Skills, guides, routing, credentials + +- [x] T019 [FR-003] [FR-031] [FR-032] [FR-033] Shared guides — files: `shared/media/speech-synthesis.md`, `shared/media/spoken-style.md` +- [x] T020 [FR-027] [FR-020–FR-023] Three skills — files: `skills/tutorial-video-scripting/SKILL.md`, `skills/presentation-video-production/SKILL.md`, `skills/presentation-video-review/SKILL.md`; depends on: T019 +- [x] T021 [AF-004] [US-005] [FR-024] [FR-025] [AC-009] [SC-005] Credential examples, ignore rule, test that examples hold no values and real files are ignored — files: `env/`, `.gitignore` +- [x] T022 [FR-027] [FR-028] [AC-012] [SC-006] Method registration, handoff graph, inventory test, trigger matrix and evaluation cases — files: `method.json`, `shared/policies/lifecycle-handoffs.md`, `shared/policies/lifecycle-selection.md`, `tests/test_writing_contracts.py`, `tests/fixtures/trigger-cases/matrix.json`, `evals/`; depends on: T020 +- [x] T023 [FR-029] Test that no organization-specific fact appears in skills, guides, templates and examples; depends on: T020 + +### Slice S6: Documentation, review record, playbook + +- [x] T024 [FR-019] [IC-009] Review record and project record templates — files: `T/review.md`, `T/README.md` +- [x] T025 [PB-WM-VIDEO-001] Author the playbook template — files: `T/playbook.md`; depends on: T017, T020 +- [x] T026 [AF-005] Document the still inspection for missing characters and the fonts' coverage — files: `skills/presentation-video-review/SKILL.md`, `T/README.md` +- [x] T027 README, usage, version 0.2.0 in six places, plugin keywords — files: `README.md`, `docs/usage.md`, `method.json`, `pyproject.toml`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json` + +## Dependencies + +| Work ID | Depends on | Reason | Parallel-safe with | +|---|---|---|---| +| S2 | S1 | Tools read the profile and pronunciation list | none | +| S3 | S2 | Measures the manifest and timeline | none | +| S4 | S2 | Composition reads the timeline | S3 | +| S5 | S1–S4 | Skills describe the finished tools | none | +| S6 | S5 | Documents the whole | none | + +## Final integration + +### Slice S7: Rehearsal and integrated verification + +- [x] T028 [SC-001] [SC-002] [PB-WM-VIDEO-001] Rehearse the playbook end to end on the sample video: voice with Azure, timeline, stills, render, measurements, captions; change one line and confirm one request — evidence: `docs/specs/WM-001-presentation-video/rehearsal.md` +- [x] T029 [FR-032] [AC-014] Voice one scene through the ElevenLabs connector, split it, verify — evidence: same file +- [x] T030 [AF-006] Fix the hard-coded issue label in Method Core and sync the fix — evidence: https://github.com/phense/method-core/pull/1 +- [x] T031 Run one existing project with the new tools using configuration changes only; compare the measured pause before and after — evidence: same file +- [x] T032a Independent review of the change; twelve findings corrected with tests (plan AF-007) +- [ ] T032b Review the playbook template with `playbook-review` at the committed revision + +### Slice S8: Convergence and final verification handoff + +- [ ] T033 Run convergence, resolve every actionable finding, capture fresh verification, update `FEATURES.md` and the issue. + +## Playbook obligations + +`PB-WM-VIDEO-001` (system): authored in T025, rehearsed in T028 and T029, +reviewed in T032. Its readiness blocks reporting the feature complete. diff --git a/docs/usage.md b/docs/usage.md index e4cc9bb..ca45f91 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,7 +1,7 @@ --- type: reference audience: Authors using Writing Method -version: 0.1.0 +version: 0.2.0 --- # Writing examples @@ -62,3 +62,38 @@ From that directory, run `quarto render --to em-manual-typst`. The default result is `_book/manual.pdf`. Inspect the actual contents links, page totals and page layout before delivery. This export preserves Markdown sources and does not publish remotely or re-run Humanizer over the finished text. + +## Make a narrated tutorial video + +> Make a tutorial video that shows new staff how to request leave in the HR +> portal. Source: docs/leave.md. We have no video profile yet. + +`tutorial-video-scripting` asks for what it cannot infer (language, speech +provider, voice and the languages it speaks) and writes `video-profile.json`. +It copies the project template, then writes the script: one audience, one +message per scene, narration in short spoken sentences, a source for every +claim, and the key terms in the pronunciation list. `python3 +tools/check_script.py` must report no errors, and you accept the script, +before anything is voiced. + +`presentation-video-production` first voices one line per key term so you can +hear them, then the rest. `python3 tools/timeline.py` derives every start time +from the measured sound, `node tools/stills.mjs stills` renders one still per +line, and `tools/render.sh` produces the file. `python3 tools/measure.py all +out/.mp4` ends with `PASSED` when resolution, loudness and the pause +between sentences match the profile. + +`presentation-video-review` looks at every still, listens to the video once +and records its findings in `review.md`. The video is ready when no Critical +or Important finding is open. + +## Correct one sentence in a finished video + +> In the leave video, the sentence about the approval step names the wrong +> button. Fix it. + +`presentation-video-production` edits the line in `src/narration.json` and +runs voicing again: one request is sent, because every other line is reused. +The timeline moves all later lines by itself. The stills of that scene, the +render and the measurement are repeated, and the review checks the changed +scene. diff --git a/engineering_method/issues.py b/engineering_method/issues.py index 4282d01..c3c9096 100644 --- a/engineering_method/issues.py +++ b/engineering_method/issues.py @@ -31,17 +31,44 @@ r"" ) ISSUE_PREFIX = " {_stamp(end)}", line["text"].replace("`", ""), ""] + return "\n".join(cues) + + +def chapter_list(timeline: dict, offset: float = 0.0) -> str: + return "\n".join(f"{int((c['start'] + offset) // 60)}:{int((c['start'] + offset) % 60):02d} {c['title']}" + for c in timeline["chapters"]) + + +def current_hashes(narration: dict, entries: list[dict], prof: dict) -> dict: + return {scene: [speech.text_hash(l, entries, prof) for l in lines] for scene, lines in narration.items()} + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + parser.add_argument("--caption-offset", type=float, default=0.0, + help="seconds of opening sequence placed in front of the video") + parser.add_argument("--draft", action="store_true", + help="estimate lengths without sound, to design the picture before voicing") + args = parser.parse_args(argv) + root = project.ROOT + try: + prof = profile_mod.load(root) + entries = speech.load_pronunciation(*speech.pronunciation_paths(root, prof)) + narration = project.load_narration(root) + if args.draft: + timeline = build(project.load_scenes(root), narration, draft_manifest(narration, prof), prof) + timeline["draft"] = True + else: + timeline = build(project.load_scenes(root), narration, project.load_manifest(root), prof, + current_hashes(narration, entries, prof)) + except (StaleManifest, profile_mod.ProfileError, speech.PronunciationError, KeyError) as error: + print(f"timeline: {error}", file=sys.stderr) + return 1 + project.write_json(root / "src" / "timeline.json", timeline) + (root / "out").mkdir(exist_ok=True) + (root / "out" / "captions.vtt").write_text(captions(timeline, args.caption_offset)) + (root / "out" / "chapters.txt").write_text(chapter_list(timeline, args.caption_offset) + "\n") + if args.draft: + print("DRAFT timeline: lengths are estimates and there is no sound; voice the script before rendering") + print(f"{len(timeline['scenes'])} scenes, {timeline['total'] / 60:.1f} min, {len(timeline['chapters'])} chapters") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/templates/presentation-video/tools/verify_speech.py b/templates/presentation-video/tools/verify_speech.py new file mode 100755 index 0000000..20eb137 --- /dev/null +++ b/templates/presentation-video/tools/verify_speech.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Read the voiced lines back with speech recognition and compare. + +A multilingual voice can read a plain word of the target language as foreign. +This tool catches that: every key term of the pronunciation list must be +recognized where the caption contains it, and each line must match its text. + + --samples print the lines to voice first: one per key term (scene:index), + to be voiced with `voice_azure.py --only ...` and verified + before the whole video is voiced + (default) verify all voiced lines; needs