Erledigt, Sir.

## Neu eingebaut: FFmpeg MP4 Preview Renderer

Neue Dateien:

```text
autoshorts/rendering/ffmpeg_renderer.py
tests/test_ffmpeg_renderer.py
```

## Neue API

```python
render_ffmpeg_preview_video(plan: FFmpegConcatPlan) -> FFmpegRenderResult
```

Der Flow ist jetzt:

```text
RenderManifest
→ LocalPreviewPlan
→ PNG scene cards
→ FFmpegConcatPlan
→ MP4 timing preview
```

Wir haben damit den ersten echten lokalen Video-Artefakt-Schritt.

## FFmpegRenderResult

```python
FFmpegRenderResult(
    script_id=...,
    output_path=...,
    concat_file_path=...,
    returncode=0,
    stdout=...,
    stderr=...,
    side_effects=("write_concat", "call_ffmpeg", "write_mp4"),
    external_calls=("ffmpeg",),
)
```

Also diesmal klar: **ja**, hier wird geschrieben und **ja**, hier wird FFmpeg aufgerufen. Kein stilles Herumgeschmuggel in einem “Formatter”. Vorbildlich langweilig.

## Was der Renderer macht

`render_ffmpeg_preview_video()`:

1. validiert `FFmpegConcatPlan`
2. schreibt `.concat.txt`
3. ruft `ffmpeg`
4. erzeugt `.mp4`
5. nutzt:
   - concat demuxer
   - `fps=<plan.fps>`
   - `format=yuv420p`
   - `+faststart`

## Tests

Neue Tests prüfen:

- concat file wird geschrieben
- MP4 wird erzeugt
- MP4 ist non-empty
- Result metadata korrekt
- Side Effects korrekt:
  ```text
  ("write_concat", "call_ffmpeg", "write_mp4")
  ```
- External Calls korrekt:
  ```text
  ("ffmpeg",)
  ```
- invalid plan wird rejected
- `ffprobe` Smoke Test, falls verfügbar:
  - width `540`
  - height `960`
  - fps `24`
  - duration ungefähr `35s`

Tests skippen sauber, falls `ffmpeg` / `ffprobe` nicht installiert sind.

## TDD eingehalten

RED:

```text
ModuleNotFoundError: No module named 'autoshorts.rendering.ffmpeg_renderer'
```

GREEN:

```text
3 passed
```

Full suite:

```text
109 passed in 5.96s
```

## Manuell verifiziert

```text
ai_life_systems-ai-weekly-review-routine
preview.mp4 True True
preview.concat.txt True
('write_concat', 'call_ffmpeg', 'write_mp4') ('ffmpeg',) 0
```

Bedeutung:

- Script korrekt
- MP4 existiert
- MP4 ist non-empty
- concat file existiert
- Side Effects korrekt
- External Call korrekt
- FFmpeg returncode `0`

## README aktualisiert

Dokumentiert:

- `autoshorts.rendering.ffmpeg_renderer`
- explizite FFmpeg Boundary
- `.concat.txt` + `.mp4`
- `yuv420p`
- `+faststart`
- FFmpeg als externes System-Prerequisite

## Commit

```text
89a0ebe feat: render local ffmpeg preview videos
```

Arbeitsverzeichnis ist sauber.

## Aktueller Stand der Pipeline

```text
ContentBrief
→ ScriptDraft
→ VideoCandidate
→ ReviewBatch
→ Telegram ReviewDelivery
→ ShotPlan
→ RenderManifest
→ HTML Preview
→ SocialMediaDraft
→ Social Draft Review
→ SocialDraftDelivery
→ LocalPreviewPlan
→ PNG scene cards
→ FFmpegConcatPlan
→ MP4 timing preview
```

## Nächster sinnvoller Schritt

Jetzt brauchen wir einen Demo-CLI für den kompletten lokalen Preview-Render:

```text
autoshorts/cli/demo_local_preview_video.py
tests/test_demo_local_preview_video_cli.py
```

Command:

```bash
python -m autoshorts.cli.demo_local_preview_video --out-dir data/previews/demo
```

Ziel:

- deterministic Demo Content bauen
- PNG Frames schreiben
- FFmpegConcatPlan bauen
- MP4 rendern
- Metadaten ausgeben:
  - output path
  - concat file path
  - frame count
  - side effects
  - external calls
- keine Telegram Calls
- keine Plattform Calls
- keine AI Calls
- keine Secrets

Dann haben wir einen echten lokalen Command, der einen reviewbaren MP4-Timing-Preview erzeugt. Noch nicht hübsch wie finaler Content — aber als Timing-/Approval-Dummy endlich konkret sichtbar.


Explanation:
**Implementing CLI and Next Steps**

I need to take action by loading the relevant skills. After that, I should implement the command-line interface (CLI) as requested. It’s essential to outline the next steps clearly. I’ll make sure to use the appropriate tools to complete this task effectively. Alright, let’s get going and figure this out together!


test-driven-development

---
name: test-driven-development
description: "TDD: enforce RED-GREEN-REFACTOR, tests before code."
version: 1.1.0
author: Hermes Agent (adapted from obra/superpowers)
license: MIT
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [testing, tdd, development, quality, red-green-refactor]
    related_skills: [systematic-debugging, writing-plans, subagent-driven-development]
---

# Test-Driven Development (TDD)

## Overview

Write the test first. Watch it fail. Write minimal code to pass.

**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.

**Violating the letter of the rules is violating the spirit of the rules.**

## When to Use

**Always:**
- New features
- Bug fixes
- Refactoring
- Behavior changes

**Exceptions (ask the user first):**
- Throwaway prototypes
- Generated code
- Configuration files

Thinking "skip TDD just this once"? Stop. That's rationalization.

## The Iron Law

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Write code before the test? Delete it. Start over.

**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete

Implement fresh from tests. Period.

## Red-Green-Refactor Cycle

### RED — Write Failing Test

Write one minimal test showing what should happen.

**Good test:**
```python
def test_retries_failed_operations_3_times():
    attempts = 0
    def operation():
        nonlocal attempts
        attempts += 1
        if attempts < 3:
            raise Exception('fail')
        return 'success'

    result = retry_operation(operation)

    assert result == 'success'
    assert attempts == 3
```
Clear name, tests real behavior, one thing.

**Bad test:**
```python
def test_retry_works():
    mock = MagicMock()
    mock.side_effect = [Exception(), Exception(), 'success']
    result = retry_operation(mock)
    assert result == 'success'  # What about retry count? Timing?
```
Vague name, tests mock not real code.

**Requirements:**
- One behavior per test
- Clear descriptive name ("and" in name? Split it)
- Real code, not mocks (unless truly unavoidable)
- Name describes behavior, not implementation

### Verify RED — Watch It Fail

**MANDATORY. Never skip.**

```bash
# Use terminal tool to run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
```

Confirm:
- Test fails (not errors from typos)
- Failure message is expected
- Fails because the feature is missing

**Test passes immediately?** You're testing existing behavior. Fix the test.

**Test errors?** Fix the error, re-run until it fails correctly.

### GREEN — Minimal Code

Write the simplest code to pass the test. Nothing more.

**Good:**
```python
def add(a, b):
    return a + b  # Nothing extra
```

**Bad:**
```python
def add(a, b):
    result = a + b
    logging.info(f"Adding {a} + {b} = {result}")  # Extra!
    return result
```

Don't add features, refactor other code, or "improve" beyond the test.

**Cheating is OK in GREEN:**
- Hardcode return values
- Copy-paste
- Duplicate code
- Skip edge cases

We'll fix it in REFACTOR.

### Verify GREEN — Watch It Pass

**MANDATORY.**

```bash
# Run the specific test
pytest tests/test_feature.py::test_specific_behavior -v

# Then run ALL tests to check for regressions
pytest tests/ -q
```

Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)

**Test fails?** Fix the code, not the test.

**Other tests fail?** Fix regressions now.

### REFACTOR — Clean Up

After green only:
- Remove duplication
- Improve names
- Extract helpers
- Simplify expressions

Keep tests green throughout. Don't add behavior.

**If tests fail during refactor:** Undo immediately. Take smaller steps.

### Repeat

Next failing test for next behavior. One cycle at a time.

## Why Order Matters

**"I'll write tests after to verify it works"**

Tests written after code pass immediately. Passing immediately proves nothing:
- Might test the wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug

Test-first forces you to see the test fail, proving it actually tests something.

**"I already manually tested all the edge cases"**

Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive

Automated tests are systematic. They run the same way every time.

**"Deleting X hours of work is wasteful"**

Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (high confidence)
- Keep it and add tests after (low confidence, likely bugs)

The "waste" is keeping code you can't trust.

**"TDD is dogmatic, being pragmatic means adapting"**

TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)

"Pragmatic" shortcuts = debugging in production = slower.

**"Tests after achieve the same goals — it's spirit not ritual"**

No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"

Tests-after are biased by your implementation. You test what you built, not what's required. Tests-first force edge case discovery before implementing.

## Common Rationalizations

| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for the code you touch. |

## Red Flags — STOP and Start Over

If you catch yourself doing any of these, delete the code and restart with TDD:

- Code before test
- Test after implementation
- Test passes immediately on first run
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."

**All of these mean: Delete code. Start over with TDD.**

## Verification Checklist

Before marking work complete:

- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason (feature missing, not typo)
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output pristine (no errors, warnings)
- [ ] Tests use real code (mocks only if unavoidable)
- [ ] Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

## When Stuck

| Problem | Solution |
|---------|----------|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify the design. |

## Hermes Agent Integration

### Running Tests

Use the `terminal` tool to run tests at each step:

```python
# RED — verify failure
terminal("pytest tests/test_feature.py::test_name -v")

# GREEN — verify pass
terminal("pytest tests/test_feature.py::test_name -v")

# Full suite — verify no regressions
terminal("pytest tests/ -q")
```

### CLI Entry Points

For new `python -m package.cli.command` style entry points, keep the TDD loop boring and side-effect free:

1. Write a test that imports `main` from the missing CLI module and calls `main([])`.
2. Capture stdout/stderr with the project test framework (`capsys` in pytest) or inject a `stdout` file-like object if supported.
3. RED should usually be an import failure for the missing CLI module/package, not a vague end-to-end shell failure.
4. Implement `main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int` so tests can call it without spawning a subprocess.
5. Add the `if __name__ == "__main__": raise SystemExit(main())` guard only after the callable path is green.
6. Verify both the unit test and the real module invocation, e.g. `python -m package.cli.command`, before committing.

This catches command wiring while avoiding accidental network calls, publishing actions, or other theatrical nonsense masquerading as a CLI test.

### With delegate_task

When dispatching subagents for implementation, enforce TDD in the goal:

```python
delegate_task(
    goal="Implement [feature] using strict TDD",
    context="""
    Follow test-driven-development skill:
    1. Write failing test FIRST
    2. Run test to verify it fails
    3. Write minimal code to pass
    4. Run test to verify it passes
    5. Refactor if needed
    6. Commit

    Project test command: pytest tests/ -q
    Project structure: [describe relevant files]
    """,
    toolsets=['terminal', 'file']
)
```

### With systematic-debugging

Bug found? Write failing test reproducing it. Follow TDD cycle. The test proves the fix and prevents regression.

Never fix bugs without a test.

## Demo / Fixture Features

When adding a deterministic demo fixture for later integration wiring, still use RED-GREEN-REFACTOR. Write the wished-for import/API in the test first, verify the missing module/API failure, then implement the smallest fixture that proves the real workflow path. Assert stable IDs/order, bucket counts/classifications, and safety invariants such as review text not implying publication. See `references/deterministic-demo-fixtures.md` for the concise pattern.

## Side-Effect-Free Integration Boundaries

When building toward an external integration (Telegram, email, publishing APIs, render farms, payment flows, etc.), add a tested preparation boundary before any real I/O:

1. Write the wished-for pure function in the test first, e.g. `prepare_review_delivery(text) -> DeliveryPlan`.
2. RED should be a missing module/API or missing validation failure, not a real network/send failure.
3. The GREEN implementation should return a typed/dataclass plan object with explicit metadata: destination/topic, delivery type, human-approval requirement, and `side_effects=()`.
4. Validate safety constraints at the boundary: non-empty content, platform size limits, no forbidden action language, no implicit approval, no credentials or generated media unless explicitly expected.
5. Add a demo CLI only after the pure boundary is green. The CLI should print metadata first, then payload, and still perform no external I/O.
6. Verify both the unit tests and a real `python -m package.cli.demo_*` invocation before committing.

This pattern lets later sending/rendering/publishing adapters consume a boring, audited object instead of smuggling side effects into formatting code. Boring is how we avoid building a content catapult with a moustache.

### Media / Rendering Pipeline Boundaries

When building toward image/video generation or renderer integration, add deterministic planning artifacts before any GPU/API work:

1. Write tests for the wished-for pure API first, e.g. `create_shot_plan(script)`, `build_render_manifest(plan)`, `build_local_preview_plan(manifest)`, or `build_ffmpeg_concat_plan(plan, frames, output_path)`.
2. RED should be a missing module/API or missing validation behavior — never a failed ComfyUI/Wan/Qwen/API invocation.
3. Keep each stage inspectable and side-effect-free until the explicit adapter layer: `ScriptDraft -> ShotPlan -> RenderManifest -> PreviewPlan/PreviewArtifact -> renderer adapter`.
4. Assert stable IDs/order, scene counts, timing continuity, vertical settings (`1080x1920`, `30fps` when building Shorts/Reels/TikTok), reduced preview settings (`540x960`, `24fps` when building fast local timing previews), and placeholder/runtime paths before generating media.
5. Add guardrail tests that reject static slideshows, baked-in/generated text inside assets, non-renderer-owned captions/labels, unsafe render settings, missing fields, bad timings, non-PNG frame paths, non-MP4 output paths, and mismatched frame counts.
6. Split artifact-writing/rendering into explicit boundaries after the pure plans are green: frame writer returns `side_effects=("write_png",)`, concat planning remains side-effect-free, and the FFmpeg renderer returns `side_effects=("write_concat", "call_ffmpeg", "write_mp4")` plus `external_calls=("ffmpeg",)`.
7. Only after the manifest/preview boundary is green should a later adapter call local GPU tools or external services. Planning first; GPU roulette later.

This prevents wasting render time on unclear direction and keeps expensive media generation behind a small, audited boundary.

### Approval-First Media/Social Pipelines

When a feature crosses from content planning toward media/social delivery, prefer many small RED-GREEN-README-COMMIT slices instead of one large integration jump:

1. typed artifact/model (`SocialMediaDraft`, `RenderManifest`, etc.)
2. renderer/formatter for human review text
3. side-effect-free delivery/preparation boundary
4. demo CLI that prints metadata first, then payload

For each slice, write the missing import/API test first, verify the expected import/API failure, implement only that slice, run the focused test, run the full suite, update docs, and commit. Keep invariant tests explicit: `requires_human_approval=True`, `side_effects=()`, no external calls, no credential access, no file writes unless that adapter is the explicit feature, and no accidental action language such as `publish`/`post` in review/delivery text.

This staged approach keeps approval semantics auditable and prevents a later renderer, Telegram sender, or platform adapter from being smuggled into formatting code.

## Testing Anti-Patterns

- **Testing mock behavior instead of real behavior** — mocks should verify interactions, not replace the system under test
- **Testing implementation details** — test behavior/results, not internal method calls
- **Happy path only** — always test edge cases, errors, and boundaries
- **Brittle tests** — tests should verify behavior, not structure; refactoring shouldn't break them
- **Substring sentinels that collide with generated IDs** — when asserting that amounts or fixture values are excluded from a result, assert the full formatted token (e.g. `999.00`) or a typed field, not a short substring like `999` that can appear inside UUIDs/hash-like IDs.
- **Domain classes named `Test*` trigger pytest collection warnings** — if a production dataclass/model must be named like `TestBatch`, set `__test__ = False` on the class or choose a non-`Test` prefix. Keep the fix in production code when the name is part of the domain language, and verify the warning is gone before committing.
- **Defaulting with `x or default` hides invalid explicit inputs** — when a factory/test helper accepts optional overrides, use `x if x is not None else default` so tests can pass intentionally blank strings (`""`) or zero values and watch validation reject them. Otherwise invalid-input tests may silently receive defaults and give false confidence.

## Final Rule

```
Production code → test exists and failed first
Otherwise → not TDD
```

No exceptions without the user's explicit permission.

To view linked files, call skill_view(name, file_path) where file_path is e.g. 'references/api.md' or 'assets/config.yaml'


social-media-shorts-automation

# Manifest-driven preview renderer for Shorts

Use this when a shorts automation project already has render manifests and needs the first video output without slipping into generic AI slideshow rendering.

## Durable pattern

Keep the renderer deterministic and manifest-driven:

```text
Render manifest JSON -> scene/card frames -> FFmpeg MP4 -> preview/approval
```

The LLM should not render frames. It may help create scripts, shot plans, and manifests, but the renderer should consume structured timeline fields and produce reproducible preview media.

## Renderer v1 architecture

A pragmatic first renderer can use:

- Pillow/PIL to draw vertical scene frames/cards.
- FFmpeg concat demuxer to turn frame images into an MP4.
- Render manifest fields for timing, text, asset type, visual direction, retention goal, resolution, aspect ratio, and fps.

Recommended module shape:

- `build_preview_frames(manifest, width, height)` or `build_local_preview_plan(manifest)`
  - side-effect-free plan/artifact list, one representative visual item per manifest timeline item
  - uses the manifest text, asset type, timing, and retention goal
  - records reduced preview settings such as `540x960`, `24fps` for fast timing review
- `write_frame_images(frames, output_dir)` or `write_local_preview_frames(plan, output_dir)`
  - first explicit artifact-writing boundary
  - writes numbered PNGs for inspection/debugging
  - returns metadata with `side_effects=("write_png",)` and `external_calls=()`
- `build_ffmpeg_concat_plan(preview_plan, frame_write_result, output_path)`
  - side-effect-free FFmpeg plan
  - validates frame count, `.png` paths, positive durations, `.mp4` output, `.concat.txt` path
  - creates concat-demuxer text but does not write it or call FFmpeg
- `render_preview_video(concat_plan)` / `render_ffmpeg_preview_video(plan)`
  - explicit FFmpeg boundary only after the concat plan is green
  - writes the `.concat.txt`, calls FFmpeg, and writes MP4
  - exports `yuv420p` MP4 with `+faststart`
  - returns metadata with `side_effects=("write_concat", "call_ffmpeg", "write_mp4")` and `external_calls=("ffmpeg",)`

This is a preview/timing renderer, not the final beauty renderer. It validates scene flow, text weight, timing, and approval review before spending time on polished assets. Keep the side-effect-free plan stages separate from artifact-writing/rendering stages so tests and review can catch bad timing before any heavy media step.

## TDD checks worth preserving

Write tests before implementation that assert:

- side-effect-free preview plans have no external calls and no file writes
- preview frames equal the number of visual scene items
- first frame starts at `0ms`
- frame durations are positive and match manifest timing
- image dimensions match requested vertical preview size
- first frame text matches the first visual timeline item
- PNG frame files are written and valid at the frame-writer boundary
- generated media paths stay under runtime/temp preview directories and are gitignored
- concat plans are side-effect-free, reference only `.png` frames, produce `.mp4` output paths, and contain positive durations
- renderer results explicitly report `write_concat`, `call_ffmpeg`, and `write_mp4` side effects
- MP4 output exists and is non-empty
- `ffprobe` verifies width, height, frame rate, and duration for real render smoke tests

## CLI pattern

Provide a small script such as `scripts/render_preview.py`:

```bash
python3 scripts/render_preview.py \
  data/render_manifests/<batch>/<video>.json \
  --out-dir data/previews/<batch> \
  --width 540 \
  --height 960 \
  --fps 24
```

Use reduced preview resolution like `540x960` for fast local review. Keep final `1080x1920` as a later renderer-quality target.

## Repo hygiene

- Keep generated MP4/PNG media out of Git via `.gitignore`.
- Commit renderer code, tests, scripts, manifests, and lightweight planning artifacts.
- Do not commit rendered previews unless explicitly needed as small fixtures.
- Add runtime dependencies explicitly, e.g. `Pillow>=10.0`; treat FFmpeg as an external system prerequisite checked at runtime.

## Pitfalls

- Do not let the renderer infer timing or visuals from prose when the manifest already encodes them.
- Do not start with AI video generation as the renderer; use it later only as an asset supplier.
- Do not over-polish the first renderer. Its job is approval/timing validation.
- Do not publish from this step. Preview rendering still requires human approval before posting.
- Watch typography consistency in early previews: fixed-size fonts can make some scenes look huge and others tiny. Add tests/metadata for fitted font sizes, max line counts, and minimum retention-goal readability before scaling to more videos.


Explanation:
**Planning CLI Implementation**

I need to implement a demo CLI and start by writing a test. It's essential to ensure that the output directory uses a temporary location. The main function should accept arguments like `--out-dir`, and I can test it by invoking `main(["--out-dir", str(tmp_path)])`. I’m considering if the test should skip if ffmpeg isn't available. Once that’s set, I might also send media for user approval through a content preview. After creating the CLI, I could generate actual previews in `/tmp`, ensuring generated media is ignored in Git.

import shutil
from pathlib import Path

import pytest

from autoshorts.cli.demo_local_preview_video import main

pytestmark = pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg is required")

def test_demo_local_preview_video_cli_renders_mp4_and_prints_metadata(tmp_path, capsys):
    exit_code = main(["--out-dir", str(tmp_path)])

    captured = capsys.readouterr()
    output_path = tmp_path / "preview.mp4"
    concat_path = tmp_path / "preview.concat.txt"

    assert exit_code == 0
    assert output_path.exists()
    assert output_path.stat().st_size > 0
    assert concat_path.exists()
    assert "Preview video: " in captured.out
    assert str(output_path) in captured.out
    assert "Concat file: " in captured.out
    assert str(concat_path) in captured.out
    assert "Frame count: 5" in captured.out
    assert "Side effects: write_concat, call_ffmpeg, write_mp4" in captured.out
    assert "External calls: ffmpeg" in captured.out
    assert "Script: ai_life_systems-ai-weekly-review-routine" in captured.out
    assert captured.err == ""
    assert "publish" not in captured.out.casefold()
    assert "post" not in captured.out.casefold()


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_demo_local_preview_video_cli.py">
ERROR [6:6] Import "autoshorts.cli.demo_local_preview_video" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


terminal

python -m pytest tests/test_demo_local_preview_video_cli.py -q


Output:
==================================== ERRORS ====================================
_________ ERROR collecting tests/test_demo_local_preview_video_cli.py __________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_demo_local_preview_video_cli.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../../.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_demo_local_preview_video_cli.py:6: in <module>
    from autoshorts.cli.demo_local_preview_video import main
E   ModuleNotFoundError: No module named 'autoshorts.cli.demo_local_preview_video'
=========================== short test summary info ============================
ERROR tests/test_demo_local_preview_video_cli.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.14s


Explanation:
**Planning CLI Implementation**

I'm thinking I need to implement a Command Line Interface (CLI) for my project. I'll use argparse to handle the command line arguments effectively. This will help ensure I build a robust pipeline that can efficiently process commands. I'm already imagining how I'll structure the commands and options to make it user-friendly. It seems like a solid step forward for enhancing accessibility in the project. I’m excited to see how it all comes together!

"""Render the deterministic local MP4 timing preview demo."""

from __future__ import annotations

import argparse
import sys
from collections.abc import Sequence
from pathlib import Path
from typing import TextIO

from autoshorts.content.brief import create_content_brief
from autoshorts.content.script_draft import create_script_draft
from autoshorts.rendering.ffmpeg_plan import build_ffmpeg_concat_plan
from autoshorts.rendering.ffmpeg_renderer import render_ffmpeg_preview_video
from autoshorts.rendering.local_frame_writer import write_local_preview_frames
from autoshorts.rendering.local_preview import build_local_preview_plan
from autoshorts.rendering.manifest import build_render_manifest
from autoshorts.rendering.shot_plan import create_shot_plan

def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Render deterministic AutoShorts local preview MP4.")
    parser.add_argument(
        "--out-dir",
        default="data/previews/demo-local-preview",
        help="Directory for PNG frames, concat file, and preview MP4.",
    )
    return parser

def _build_preview_video(output_dir: Path):
    brief = create_content_brief(
        pillar_id="ai_life_systems",
        topic="AI weekly review routine",
        audience_problem="I use AI tools but still lose control of priorities.",
        payoff="a 10-minute Sunday review that keeps AI as assistant, not boss",
    )
    script = create_script_draft(brief)
    shot_plan = create_shot_plan(script)
    manifest = build_render_manifest(shot_plan)
    preview_plan = build_local_preview_plan(manifest)
    frame_result = write_local_preview_frames(preview_plan, output_dir=output_dir / "frames")
    concat_plan = build_ffmpeg_concat_plan(preview_plan, frame_result, output_path=output_dir / "preview.mp4")
    render_result = render_ffmpeg_preview_video(concat_plan)
    return preview_plan, frame_result, render_result

def main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int:
    """Render demo PNG frames and MP4 timing preview, then print metadata."""

    args = _parser().parse_args(argv)
    output = stdout or sys.stdout
    output_dir = Path(args.out_dir)
    preview_plan, frame_result, render_result = _build_preview_video(output_dir)

    print(f"Script: {render_result.script_id}", file=output)
    print(f"Preview video: {render_result.output_path}", file=output)
    print(f"Concat file: {render_result.concat_file_path}", file=output)
    print(f"Frame count: {len(frame_result.frame_paths)}", file=output)
    print(f"Preview format: {preview_plan.width}x{preview_plan.height} @ {preview_plan.fps}fps", file=output)
    print(f"Frame side effects: {', '.join(frame_result.side_effects)}", file=output)
    print(f"Side effects: {', '.join(render_result.side_effects)}", file=output)
    print(f"External calls: {', '.join(render_result.external_calls)}", file=output)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


Output:
.                                                                        [100%]
1 passed in 2.64s


Output:
........................................................................ [ 65%]
......................................                                   [100%]
110 passed in 8.70s


Explanation:
**Planning README patch**

I see that I need to make a README patch and there's a manual run needed to the /tmp directory. I'll make sure to commit those changes afterward. It seems straightforward, but I want to ensure that everything is correct and clear. Taking these steps will help maintain clarity and proper documentation as I go along. Let's proceed carefully to avoid any mistakes!

patch

- `autoshorts.cli.demo_local_preview_video` — local CLI entry point that renders deterministic PNG frames and an MP4 timing preview.
- `autoshorts.cli.demo_render_preview` — local CLI entry point that prints side-effect-free render-preview metadata plus HTML.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -8,6 +8,7 @@

 Implemented foundation:

+- `autoshorts.cli.demo_local_preview_video` — local CLI entry point that renders deterministic PNG frames and an MP4 timing preview.
 - `autoshorts.cli.demo_render_preview` — local CLI entry point that prints side-effect-free render-preview metadata plus HTML.
 - `autoshorts.cli.demo_review_batch` — local CLI entry point that prints the deterministic review demo.
 - `autoshorts.cli.demo_social_draft` — local CLI entry point that prints a side-effect-free social media draft review delivery.

No linter for .md files


The renderer exports `yuv420p` MP4 with `+faststart`. Tests skip if `ffmpeg`/`ffprobe` are unavailable and otherwise smoke-probe width, height, frame rate, and approximate duration.

Run the deterministic local MP4 timing preview with:

```bash
python -m autoshorts.cli.demo_local_preview_video --out-dir data/previews/demo-local-preview
```

The command creates PNG scene cards under `frames/`, writes `preview.concat.txt`, renders `preview.mp4`, and prints script/output metadata. It performs no Telegram calls, no platform calls, and no AI/API calls.

## Social media draft

The renderer exports `yuv420p` MP4 with `+faststart`. Tests skip if `ffmpeg`/`ffprobe` are unavailable and otherwise smoke-probe width, height, frame rate, and approximate duration.

## Social media draft


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -131,6 +131,14 @@

 The renderer exports `yuv420p` MP4 with `+faststart`. Tests skip if `ffmpeg`/`ffprobe` are unavailable and otherwise smoke-probe width, height, frame rate, and approximate duration.

+Run the deterministic local MP4 timing preview with:
+
+```bash
+python -m autoshorts.cli.demo_local_preview_video --out-dir data/previews/demo-local-preview
+```
+
+The command creates PNG scene cards under `frames/`, writes `preview.concat.txt`, renders `preview.mp4`, and prints script/output metadata. It performs no Telegram calls, no platform calls, and no AI/API calls.
+
 ## Social media draft

 `autoshorts.social.draft.build_social_media_draft()` turns a validated `VideoCandidate` plus `RenderPreview` into an approval-first `SocialMediaDraft` containing:

No linter for .md files


terminal

rm -rf /tmp/autoshorts-demo-local-preview && python -m autoshorts.cli.demo_local_preview_video --out-dir /tmp/autoshorts-demo-local-preview && python - <<'PY'
from pathlib import Path
p = Path('/tmp/autoshorts-demo-local-preview/preview.mp4')
print('exists', p.exists())
print('size', p.stat().st_size if p.exists() else 0)
print('frames', len(list((p.parent/'frames').glob('*.png'))))
PY
python -m pytest tests -q


Output:
Script: ai_life_systems-ai-weekly-review-routine
Preview video: /tmp/autoshorts-demo-local-preview/preview.mp4
Concat file: /tmp/autoshorts-demo-local-preview/preview.concat.txt
Frame count: 5
Preview format: 540x960 @ 24fps
Frame side effects: write_png
Side effects: write_concat, call_ffmpeg, write_mp4
External calls: ffmpeg
exists True
size 174359
frames 5
........................................................................ [ 65%]
......................................                                   [100%]
110 passed in 8.46s


git status --short && git add README.md autoshorts/cli/demo_local_preview_video.py tests/test_demo_local_preview_video_cli.py && git commit -m "feat: add demo local preview video cli" && git log --oneline -n 8 && git status --short


Output:
M README.md
?? autoshorts/cli/demo_local_preview_video.py
?? tests/test_demo_local_preview_video_cli.py
[feat/core-approval-quality-gates 496b0ac] feat: add demo local preview video cli
 3 files changed, 109 insertions(+)
 create mode 100644 autoshorts/cli/demo_local_preview_video.py
 create mode 100644 tests/test_demo_local_preview_video_cli.py
496b0ac feat: add demo local preview video cli
89a0ebe feat: render local ffmpeg preview videos
1128b7c feat: add ffmpeg concat planning
7e881b3 feat: write local preview frame cards
02ecf59 feat: add local preview frame planning
3b387f4 feat: add demo social draft cli
515c549 feat: add social draft delivery preparation
a7d10fa feat: render social media draft reviews


Erledigt, Sir.

## Neu eingebaut: Demo Local Preview Video CLI

Neue Dateien:

```text
autoshorts/cli/demo_local_preview_video.py
tests/test_demo_local_preview_video_cli.py
```

## Neuer Command

```bash
python -m autoshorts.cli.demo_local_preview_video --out-dir /tmp/autoshorts-demo-local-preview
```

Der läuft jetzt komplett durch:

```text
ContentBrief
→ ScriptDraft
→ ShotPlan
→ RenderManifest
→ LocalPreviewPlan
→ PNG scene cards
→ FFmpegConcatPlan
→ MP4 timing preview
```

## Verifiziert

Output:

```text
Script: ai_life_systems-ai-weekly-review-routine
Preview video: /tmp/autoshorts-demo-local-preview/preview.mp4
Concat file: /tmp/autoshorts-demo-local-preview/preview.concat.txt
Frame count: 5
Preview format: 540x960 @ 24fps
Frame side effects: write_png
Side effects: write_concat, call_ffmpeg, write_mp4
External calls: ffmpeg
exists True
size 174359
frames 5
```

Tests:

```text
110 passed in 8.46s
```

Commit:

```text
496b0ac feat: add demo local preview video cli
```

Arbeitsverzeichnis sauber.

Hier ist der erzeugte aktuelle Timing-Preview:

MEDIA:/tmp/autoshorts-demo-local-preview/preview.mp4

---

## Was das jetzt ist

Das ist ein **technischer Timing-/Structure-Preview**, noch kein finaler Content-Preview.

Er zeigt:

- 5 Szenen
- Timing
- Hook/Caption Text
- Scene Labels
- Asset Mode
- Motion Instructions
- grobe vertikale Cards

Er zeigt noch **nicht**:

- echte Motion Graphics
- Voiceover
- Musik/SFX
- AI-/Qwen-/Wan-Visuals
- final polish
- echte captions synced to audio

Also: Es ist der Rohbau. Noch nicht die Villa. Aber immerhin steht kein Zirkuszelt mehr auf der Baustelle.

---

# Nächste Schritte bis zum effektiven Content-Preview zur Freigabe

## Phase 1 — Approval Package mit MP4 + Social Draft

Nächster sinnvoller Schritt:

```text
MP4 timing preview
+ SocialMediaDraft Review
→ ContentPreviewApprovalPackage
```

Neue Module:

```text
autoshorts/preview/package.py
tests/test_content_preview_package.py
```

Zielobjekt:

```python
ContentPreviewApprovalPackage(
    candidate_id=...,
    preview_video_path=...,
    social_review_text=...,
    approval_commands=(...),
    requires_human_approval=True,
    side_effects=(),
)
```

Damit haben wir ein einheitliches Ding, das sagt:

> “Hier ist das Video-Preview + Caption/Titel/Hashtags + Freigabe-Kommandos.”

## Phase 2 — Telegram-safe Preview Delivery

Danach:

```text
ContentPreviewApprovalPackage
→ TelegramPreviewDelivery
```

Neue Module:

```text
autoshorts/preview/delivery.py
tests/test_content_preview_delivery.py
```

Ziel:

- Text <= Telegram Limit
- Video path vorhanden
- Approval mandatory
- kein Upload zu Plattformen
- kein Auto-Senden ohne explizite Adapter-Grenze
- keine `publish/post`-Sprache

Dann können wir lokal erzeugen:

```text
MEDIA:/.../preview.mp4
+
Review Text
+
Freigabe-Kommandos
```

## Phase 3 — Demo CLI für echten Freigabeentwurf

Dann:

```text
autoshorts/cli/demo_content_preview.py
tests/test_demo_content_preview_cli.py
```

Command:

```bash
python -m autoshorts.cli.demo_content_preview --out-dir data/previews/demo-content-preview
```

Output:

```text
Preview video: data/previews/demo-content-preview/preview.mp4
Review text length: ...
Requires human approval: yes
Side effects: write_png, write_concat, call_ffmpeg, write_mp4
External calls: ffmpeg
```

Und der Text enthält:

```text
## Content Preview Review

Video:
<preview path>

Candidate:
...

Platforms:
YouTube Shorts, TikTok, Instagram Reels

Hook:
...

Caption:
...

Hashtags:
...

Approval commands:
Freigabe alle
Freigabe YouTube
Freigabe TikTok
Freigabe Instagram
Ablehnen
Ändern: <...>
```

**Das ist der erste echte Freigabe-Preview.**

## Phase 4 — Renderer v2: weniger statisch

Aktuell sind es Scene Cards. Für “Content Preview, das nicht beleidigend statisch wirkt” brauchen wir danach:

```text
autoshorts/rendering/motion_preview.py
```

Ziel:

- simple Ken Burns / pan / zoom
- kinetic text intro
- scene transitions
- progress bar
- captions renderer-owned
- sichtbare Bewegung alle 1–2 Sekunden

Noch lokal, noch ohne AI-Video.

## Phase 5 — TTS / Voiceover

Dann:

```text
ScriptDraft
→ VoiceoverScript
→ TTS audio
→ caption timing
```

Für dich relevant:

- Default Voice laut Profil: `de-DE-KillianNeural`
- Wenn Content englisch bleibt, müssten wir eine englische Voice wählen.
- Output:
  - `.mp3` oder `.wav`
  - später `.srt` / caption timings

Danach:

```text
MP4 visual preview
+ TTS audio
→ audio-synced preview
```

Das wäre schon deutlich näher an einem echten Content-Preview.

## Phase 6 — Qwen Image / Wan / ComfyUI Assets

Erst danach würde ich Qwen/Wan anbinden:

```text
RenderManifest Scene
→ Keyframe Prompt
→ Qwen Image Keyframe
→ Wan I2V Clip
→ imported/local asset
→ deterministic final assembly
```

Wichtig bleibt:

- keine baked-in text in AI assets
- captions/labels/arrows renderer-owned
- AI-Video nur für Textur/Bewegung
- Review jedes Clips vor finaler Assembly

## Meine empfohlene Reihenfolge ab jetzt

Kurz und praktisch:

```text
1. ContentPreviewApprovalPackage
2. TelegramPreviewDelivery / local delivery object
3. demo_content_preview CLI
4. Motion Preview Renderer v2
5. TTS + synced captions
6. Qwen/Wan asset layer
7. Final review package
8. Erst nach expliziter Freigabe: Plattformadapter
```

## Direkt nächster Task

Ich würde jetzt bauen:

```text
autoshorts/preview/package.py
tests/test_content_preview_package.py
```

Damit verbinden wir den vorhandenen MP4-Preview mit dem Social-Media-Freigabetext zu einem echten Content-Preview-Paket. Danach kann ich daraus den Telegram-fähigen Freigabeentwurf machen.

Explanation:
**Planning content preview steps**

I need to summarize the steps towards an effective content preview. First, I’ll include the media file location so it’s accessible. I’ll confirm that I’ve completed the command line interface tasks, tests, and commit. The roadmap involves combining the MP4 and social draft into an approval package for delivery, potentially through Telegram. Then, I'll focus on enhancing preview quality with motion, captions, TTS, and audio. The current MP4 is a timing preview, not the final one. Steps include creating the demo content preview package, attaching the MP4, and final approval.