     1|"""Render preview MP4 videos from AutoShortsBot renderer manifests."""
     2|
     3|from __future__ import annotations
     4|
     5|import argparse
     6|import json
     7|import sys
     8|from pathlib import Path
     9|
    10|PROJECT_ROOT = Path(__file__).resolve().parents[1]
    11|if str(PROJECT_ROOT) not in sys.path:
    12|    sys.path.insert(0, str(PROJECT_ROOT))
    13|
    14|from autoshorts.preview_renderer import render_preview_video
    15|
    16|
    17|def _default_output_for_manifest(manifest_path: Path, output_dir: Path) -> Path:
    18|    return output_dir / f"{manifest_path.stem}.mp4"
    19|
    20|
    21|def main() -> None:
    22|    parser = argparse.ArgumentParser(description="Render manifest-driven preview MP4 files.")
    23|    parser.add_argument("manifest", help="Path to a render manifest JSON file.")
    24|    parser.add_argument("--out-dir", default="data/previews/testbatch_002_top3")
    25|    parser.add_argument("--width", type=int, default=540)
    26|    parser.add_argument("--height", type=int, default=960)
    27|    parser.add_argument("--fps", type=int, default=24)
    28|    args = parser.parse_args()
    29|
    30|    manifest_path = Path(args.manifest)
    31|    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    32|    output_path = _default_output_for_manifest(manifest_path, Path(args.out_dir))
    33|    result = render_preview_video(
    34|        manifest,
    35|        output_path,
    36|        width=args.width,
    37|        height=args.height,
    38|        fps=args.fps,
    39|    )
    40|    print(f"Rendered preview: {result['output_path']}")
    41|    print(f"Frames: {result['frame_count']}")
    42|    print(f"FPS: {result['fps']}")
    43|
    44|
    45|if __name__ == "__main__":
    46|    main()
    47|


     1|"""Generate renderer-ready JSON manifests for selected AutoShortsBot scripts."""
     2|
     3|from __future__ import annotations
     4|
     5|import argparse
     6|import sys
     7|from pathlib import Path
     8|
     9|PROJECT_ROOT = Path(__file__).resolve().parents[1]
    10|if str(PROJECT_ROOT) not in sys.path:
    11|    sys.path.insert(0, str(PROJECT_ROOT))
    12|
    13|from autoshorts.growth_plan import load_testbatch
    14|from autoshorts.render_manifest import write_render_manifests
    15|from autoshorts.shot_planner import build_shot_plan
    16|from autoshorts.voiceover_finalizer import finalize_voiceover
    17|
    18|DEFAULT_SELECTED_IDS = ["ai-004", "ai-005", "attention-002"]
    19|
    20|
    21|def main() -> None:
    22|    parser = argparse.ArgumentParser(description="Generate renderer manifest JSON files.")
    23|    parser.add_argument("--batch", default="data/drafts/testbatch_002.json")
    24|    parser.add_argument("--out", default="data/render_manifests/testbatch_002_top3")
    25|    parser.add_argument("--ids", nargs="*", default=DEFAULT_SELECTED_IDS)
    26|    args = parser.parse_args()
    27|
    28|    batch = load_testbatch(Path(args.batch))
    29|    selected = set(args.ids)
    30|    plans = [
    31|        build_shot_plan(finalize_voiceover(idea))
    32|        for idea in batch.get("ideas", [])
    33|        if idea["id"] in selected
    34|    ]
    35|    paths = write_render_manifests(plans, Path(args.out))
    36|
    37|    print(f"Generated {len(paths)} render manifests in {args.out}")
    38|    for path in paths:
    39|        print(path)
    40|
    41|
    42|if __name__ == "__main__":
    43|    main()
    44|


     1|import json
     2|from pathlib import Path
     3|
     4|from autoshorts.growth_plan import load_testbatch
     5|from autoshorts.render_manifest import build_render_manifest, write_render_manifests
     6|from autoshorts.shot_planner import build_shot_plan
     7|from autoshorts.voiceover_finalizer import finalize_voiceover
     8|
     9|
    10|def _shot_plan(script_id: str):
    11|    batch = load_testbatch(Path("data/drafts/testbatch_002.json"))
    12|    idea = next(item for item in batch["ideas"] if item["id"] == script_id)
    13|    return build_shot_plan(finalize_voiceover(idea))
    14|
    15|
    16|def test_build_render_manifest_maps_shot_plan_to_renderer_contract():
    17|    plan = _shot_plan("ai-004")
    18|
    19|    manifest = build_render_manifest(plan)
    20|
    21|    assert manifest["schema_version"] == "render_manifest.v1"
    22|    assert manifest["video"]["id"] == "ai-004"
    23|    assert manifest["video"]["aspect_ratio"] == "9:16"
    24|    assert manifest["video"]["resolution"] == {"width": 1080, "height": 1920}
    25|    assert manifest["timeline"][0]["track"] == "visual"
    26|    assert manifest["timeline"][0]["start_ms"] == 0
    27|    assert manifest["timeline"][0]["duration_ms"] > 0
    28|    assert manifest["timeline"][0]["type"] == "kinetic_text"
    29|    assert manifest["timeline"][0]["text"] == plan["scenes"][0]["on_screen_text"]
    30|    assert manifest["timeline"][0]["style"]["safe_area_pct"] == 8
    31|    assert any(item["track"] == "captions" for item in manifest["timeline"])
    32|    assert manifest["render_constraints"]["avoid_static_slideshow"] is True
    33|
    34|
    35|def test_build_render_manifest_uses_asset_placeholders_by_type():
    36|    plan = _shot_plan("attention-002")
    37|
    38|    manifest = build_render_manifest(plan)
    39|
    40|    visual_items = [item for item in manifest["timeline"] if item["track"] == "visual"]
    41|    assert any(item["type"] == "stock_broll" for item in visual_items)
    42|    assert any(item["type"] == "card_animation" for item in visual_items)
    43|    assert all(item["asset_ref"].startswith("placeholder://") for item in visual_items)
    44|    assert manifest["asset_requirements"]["stock_broll"]
    45|    assert manifest["asset_requirements"]["screen_recording"]
    46|
    47|
    48|def test_write_render_manifests_outputs_valid_json_files(tmp_path):
    49|    plans = [_shot_plan("ai-004"), _shot_plan("ai-005"), _shot_plan("attention-002")]
    50|
    51|    paths = write_render_manifests(plans, tmp_path)
    52|
    53|    assert len(paths) == 3
    54|    assert all(path.suffix == ".json" for path in paths)
    55|    payload = json.loads(paths[0].read_text(encoding="utf-8"))
    56|    assert payload["schema_version"] == "render_manifest.v1"
    57|    assert "timeline" in payload
    58|    assert len(payload["timeline"]) >= 10
    59|


Explanation:
**Implementing growth plan**

I need to import from the growth plan and review the package requirements. It looks like I need to work with a draft by ID, a manifest, and a preview path. I’m thinking I could output markdown with the MEDIA path for each item in the Telegram package. Also, I should include commands. It sounds like testing will be crucial to check everything works smoothly. Let me keep this structured!

"""Telegram review package generation for AutoShortsBot preview candidates."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

APPROVAL_COMMANDS = [
    "Freigabe alle",
    "Freigabe YouTube",
    "Freigabe TikTok",
    "Freigabe Instagram",
    "Ablehnen",
    "Ändern: <requested change>",
]

PLATFORM_LABELS = {
    "youtube_shorts": "YouTube Shorts",
    "tiktok": "TikTok",
    "instagram_reels": "Instagram Reels",
    "facebook_reels": "Facebook Reels",
}

def _idea_index(batch: dict[str, Any]) -> dict[str, dict[str, Any]]:
    return {idea["id"]: idea for idea in batch.get("ideas", [])}

def _format_platforms(platforms: list[str]) -> str:
    return ", ".join(PLATFORM_LABELS.get(platform, platform) for platform in platforms)

def _manifest_summary(manifest: dict[str, Any]) -> dict[str, Any]:
    visual_items = [item for item in manifest["timeline"] if item["track"] == "visual"]
    caption_items = [item for item in manifest["timeline"] if item["track"] == "captions"]
    visual_types = sorted({item["type"] for item in visual_items})
    return {
        "duration_seconds": round(manifest["video"]["duration_ms"] / 1000),
        "visual_scene_count": len(visual_items),
        "caption_beat_count": len(caption_items),
        "visual_types": visual_types,
    }

def build_review_package(
    idea: dict[str, Any],
    manifest: dict[str, Any],
    preview_path: Path | None = None,
) -> dict[str, Any]:
    """Build structured Telegram review metadata for one rendered candidate."""
    summary = _manifest_summary(manifest)
    package = {
        "id": idea["id"],
        "title": idea["title"],
        "pillar": idea["pillar"],
        "platforms": idea["platform_target"],
        "duration_seconds": summary["duration_seconds"],
        "hook": idea["hook"],
        "caption": idea["caption"],
        "cta": idea["cta"],
        "quality_score": idea["quality_score"],
        "risk_notes": idea["risk_notes"],
        "visual_scene_count": summary["visual_scene_count"],
        "caption_beat_count": summary["caption_beat_count"],
        "visual_types": summary["visual_types"],
        "approval_commands": APPROVAL_COMMANDS,
    }
    if preview_path is not None:
        package["preview_path"] = str(preview_path)
    return package

def format_review_package(package: dict[str, Any]) -> str:
    """Format a Telegram-safe review message for one candidate."""
    preview_line = ""
    if package.get("preview_path"):
        preview_line = f"\nMEDIA:{package['preview_path']}\n"

    commands = "\n".join(f"- `{command}`" for command in package["approval_commands"])
    visual_types = ", ".join(item.replace("_", " ") for item in package["visual_types"])
    platforms = _format_platforms(package["platforms"])

    return f"""## AutoShortsBot Review — {package['id']}
{preview_line}
**Titel:** {package['title']}
**Pillar:** `{package['pillar']}`
**Zielplattformen:** {platforms}
**Dauer:** {package['duration_seconds']}s
**Quality Score:** {package['quality_score']}/10

**Hook:** {package['hook']}

**Caption:** {package['caption']}
**CTA:** {package['cta']}

**Render-Check:** {package['visual_scene_count']} visual scenes, {package['caption_beat_count']} caption beats; types: {visual_types}.
**Risk Notes:** {package['risk_notes']}

**Freigabe-Befehle:**
{commands}
""".strip() + "\n"

def write_review_packages(
    batch: dict[str, Any],
    manifest_dir: Path,
    preview_dir: Path,
    output_dir: Path,
) -> list[Path]:
    """Write one Telegram review package Markdown file per render manifest."""
    output_dir.mkdir(parents=True, exist_ok=True)
    ideas = _idea_index(batch)
    paths: list[Path] = []

    for manifest_path in sorted(manifest_dir.glob("*.json")):
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
        video_id = manifest["video"]["id"]
        if video_id not in ideas:
            raise KeyError(f"No idea found for manifest video id: {video_id}")
        preview_path = preview_dir / f"{manifest_path.stem}.mp4"
        package = build_review_package(ideas[video_id], manifest, preview_path)
        output_path = output_dir / f"{manifest_path.stem}.md"
        output_path.write_text(format_review_package(package), encoding="utf-8")
        paths.append(output_path)

    return paths


"""Generate Telegram-ready review packages for rendered AutoShortsBot previews."""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from autoshorts.growth_plan import load_testbatch
from autoshorts.review_package import write_review_packages

def main() -> None:
    parser = argparse.ArgumentParser(description="Generate Telegram review Markdown packages.")
    parser.add_argument("--batch", default="data/drafts/testbatch_002.json")
    parser.add_argument("--manifest-dir", default="data/render_manifests/testbatch_002_top3")
    parser.add_argument("--preview-dir", default="data/previews/testbatch_002_top3")
    parser.add_argument("--out", default="data/telegram_reviews/testbatch_002_top3")
    args = parser.parse_args()

    batch = load_testbatch(Path(args.batch))
    paths = write_review_packages(
        batch=batch,
        manifest_dir=Path(args.manifest_dir),
        preview_dir=Path(args.preview_dir),
        output_dir=Path(args.out),
    )

    print(f"Generated {len(paths)} Telegram review packages in {args.out}")
    for path in paths:
        print(path)

if __name__ == "__main__":
    main()


import json
from pathlib import Path

from autoshorts.growth_plan import load_testbatch
from autoshorts.review_package import build_review_package, format_review_package, write_review_packages

def _batch() -> dict:
    return load_testbatch(Path("data/drafts/testbatch_002.json"))

def _idea(script_id: str) -> dict:
    return next(item for item in _batch()["ideas"] if item["id"] == script_id)

def _manifest(script_id: str) -> dict:
    manifest_path = next(Path("data/render_manifests/testbatch_002_top3").glob(f"{script_id}-*.json"))
    return json.loads(manifest_path.read_text(encoding="utf-8"))

def test_build_review_package_collects_approval_metadata():
    package = build_review_package(
        _idea("ai-004"),
        _manifest("ai-004"),
        Path("data/previews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.mp4"),
    )

    assert package["id"] == "ai-004"
    assert package["pillar"] == "ai_life_systems"
    assert package["duration_seconds"] == 43
    assert package["quality_score"] == 10
    assert package["visual_scene_count"] == 5
    assert "screen_recording" in package["visual_types"]
    assert "Freigabe alle" in package["approval_commands"]
    assert package["preview_path"].endswith(".mp4")

def test_format_review_package_is_telegram_safe_and_actionable():
    package = build_review_package(
        _idea("attention-002"),
        _manifest("attention-002"),
        Path("/tmp/attention-002.mp4"),
    )

    message = format_review_package(package)

    assert message.startswith("## AutoShortsBot Review — attention-002")
    assert "MEDIA:/tmp/attention-002.mp4" in message
    assert "**Hook:** The algorithm does not need you happy" in message
    assert "`Freigabe YouTube`" in message
    assert "**Risk Notes:**" in message
    assert "|" not in message

def test_write_review_packages_outputs_one_markdown_per_manifest(tmp_path):
    paths = write_review_packages(
        batch=_batch(),
        manifest_dir=Path("data/render_manifests/testbatch_002_top3"),
        preview_dir=Path("data/previews/testbatch_002_top3"),
        output_dir=tmp_path,
    )

    assert len(paths) == 3
    assert all(path.suffix == ".md" for path in paths)
    text = paths[0].read_text(encoding="utf-8")
    assert "AutoShortsBot Review" in text
    assert "Freigabe-Befehle" in text


terminal

Output:
............................                                             [100%]
28 passed in 0.94s


Output:
Generated 3 Telegram review packages in data/telegram_reviews/testbatch_002_top3
data/telegram_reviews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md
data/telegram_reviews/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md
data/telegram_reviews/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md


Explanation:
**Updating documentation**

I think I need to update the README and other documentation. Specifically, I want to ensure the current phase is clearly marked and includes a review of the package generated. It might be good to inspect the package itself too, just to ensure everything is functioning as expected. Keeping documentation updated will help everyone have the most accurate and useful information! I want to make sure I'm thorough.


     1|## AutoShortsBot Review — ai-004
     2|
     3|MEDIA:data/previews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.mp4
     4|
     5|**Titel:** Stop asking AI to think for you
     6|**Pillar:** `ai_life_systems`
     7|**Zielplattformen:** YouTube Shorts, TikTok, Instagram Reels
     8|**Dauer:** 43s
     9|**Quality Score:** 10/10
    10|
    11|**Hook:** Stop asking AI to think for you. Ask it to argue with you.
    12|
    13|**Caption:** AI is more useful as a critic than as a guru.
    14|**CTA:** Comment CRITIC if you want the prompt.
    15|
    16|**Render-Check:** 5 visual scenes, 13 caption beats; types: card animation, kinetic text, screen recording.
    17|**Risk Notes:** Keep human decision authority explicit.
    18|
    19|**Freigabe-Befehle:**
    20|- `Freigabe alle`
    21|- `Freigabe YouTube`
    22|- `Freigabe TikTok`
    23|- `Freigabe Instagram`
    24|- `Ablehnen`
    25|- `Ändern: <requested change>`
    26|


Explanation:
**Adjusting media paths**

It looks like I need to handle MEDIA paths properly in the generated output, especially for Telegram. The previews currently use relative paths, but since Telegram requires absolute paths, I might want to adjust that in the script. The tool suggests using "MEDIA:/absolute/path," so I'll need to refactor the build_review_package to ensure the preview path is absolute. I'll check my tests as well, ensuring they adapt to these changes while maintaining the correct format.

patch

    if preview_path is not None:
        package["preview_path"] = str(preview_path)
    return package

    if preview_path is not None:
        package["preview_path"] = str(preview_path.resolve())
    return package


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/review_package.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/review_package.py
@@ -67,7 +67,7 @@
         "approval_commands": APPROVAL_COMMANDS,
     }
     if preview_path is not None:
-        package["preview_path"] = str(preview_path)
+        package["preview_path"] = str(preview_path.resolve())
     return package


terminal

pytest -q && python scripts/generate_telegram_review_packages.py


Output:
............................                                             [100%]
28 passed in 0.99s
Generated 3 Telegram review packages in data/telegram_reviews/testbatch_002_top3
data/telegram_reviews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md
data/telegram_reviews/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md
data/telegram_reviews/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md


     1|## AutoShortsBot Review — ai-004
     2|
     3|MEDIA:/home/agent/jarvis_runtime/AutoShortsBot/data/previews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.mp4
     4|
     5|**Titel:** Stop asking AI to think for you
     6|**Pillar:** `ai_life_systems`
     7|**Zielplattformen:** YouTube Shorts, TikTok, Instagram Reels
     8|**Dauer:** 43s
     9|**Quality Score:** 10/10
    10|
    11|**Hook:** Stop asking AI to think for you. Ask it to argue with you.
    12|
    13|**Caption:** AI is more useful as a critic than as a guru.
    14|**CTA:** Comment CRITIC if you want the prompt.
    15|
    16|**Render-Check:** 5 visual scenes, 13 caption beats; types: card animation, kinetic text, screen recording.
    17|**Risk Notes:** Keep human decision authority explicit.
    18|
    19|**Freigabe-Befehle:**
    20|- `Freigabe alle`
    21|- `Freigabe YouTube`
    22|- `Freigabe TikTok`
    23|- `Freigabe Instagram`
    24|- `Ablehnen`
    25|- `Ändern: <requested change>`
    26|


Explanation:
**Considering automation enhancements**

I’m thinking about how to improve shortform growth automation. It might be useful to include a note saying, “When using Telegram, ensure you use absolute paths for local files.” This could help with skill maintenance, making the process smoother. I want to ensure that the information I provide is practical and user-friendly so that anyone using this will have a better experience. It’s all about making workflows more efficient!

shortform-growth-automation

   - Send drafts/previews to Telegram with: video or script, title, caption, hashtags, duration, target platforms, quality score, and risk notes.
   - Do not post publicly without explicit approval commands such as `Freigabe alle`, `Freigabe YouTube`, `Freigabe TikTok`, `Ablehnen`, or `Ändern: ...`.

   - Send drafts/previews to Telegram with: video or script, title, caption, hashtags, duration, target platforms, quality score, and risk notes.
   - When attaching local preview files through Telegram/Hermes, use `MEDIA:/absolute/path/to/file.mp4`; relative `MEDIA:` paths are not reliable for native delivery.
   - Do not post publicly without explicit approval commands such as `Freigabe alle`, `Freigabe YouTube`, `Freigabe TikTok`, `Ablehnen`, or `Ändern: ...`.


Patched SKILL.md in skill 'shortform-growth-automation' (1 replacement).


Erledigt, Sir. Nächster Baustein der Content-Maschine sitzt: **Telegram Review Layer** für die ersten drei gerenderten Kandidaten.

Gebaut/erweitert:

- `autoshorts/review_package.py`
  - erzeugt strukturierte Review-Pakete pro Preview
  - enthält Hook, Pillar, Plattformen, Dauer, Caption, CTA, Quality Score, Risk Notes
  - enthält explizite Freigabe-Befehle
  - nutzt jetzt korrekt absolute `MEDIA:`-Pfade für Telegram

- `scripts/generate_telegram_review_packages.py`
  - generiert Telegram-ready Markdown Review Cards

- `tests/test_review_package.py`
  - TDD-Abdeckung für Review-Pakete, Telegram-Format und Datei-Output

- Generierte Review-Pakete:
  - `data/telegram_reviews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md`
  - `data/telegram_reviews/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md`
  - `data/telegram_reviews/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md`

Verifikation:

```text
28 passed in 0.99s
```

Hier sind die drei Preview-Kandidaten für Review:

MEDIA:/home/agent/jarvis_runtime/AutoShortsBot/data/previews/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.mp4

MEDIA:/home/agent/jarvis_runtime/AutoShortsBot/data/previews/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.mp4

MEDIA:/home/agent/jarvis_runtime/AutoShortsBot/data/previews/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.mp4

Freigabe bleibt strikt manuell — keine Publikation ohne klares Kommando.
Gültige Befehle wären z.B.:

- `Freigabe alle`
- `Freigabe YouTube`
- `Freigabe TikTok`
- `Freigabe Instagram`
- `Ablehnen`
- `Ändern: <Wunsch>`

Explanation:
**Finalizing media details**

I need to ensure I include some media in the final output—I wonder if it might be too much, but let's go for it anyway. I should mention that some absolute references are essential, and maybe I’ll include about three lines regarding MEDIA. It’s also important to mention any relevant files related to the content. I’ll make sure everything is clear and connected!