Captions + AI covers + FFmpeg uniquify + AutoSocial queue handoff with three-state QA.
Asset factory → queue handoff.
#!/usr/bin/env python3
"""Content Factory Agent
Hybrids: simplified-cli · FFmpeg · Aaron/Corey caption skills · AutoSocial queue · three-state QA
"""
from __future__ import annotations
import json, os, subprocess
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Literal
Verdict = Literal["PASS", "FAIL", "INCONCLUSIVE"]
@dataclass
class R:
name: str
verdict: Verdict
detail: str
evidence: dict
def sh(cmd, timeout=180) -> R:
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
ok = p.returncode == 0
return R(cmd[0], "PASS" if ok else "FAIL", (p.stdout or p.stderr)[-400:], {"code": p.returncode})
except FileNotFoundError as e:
return R(cmd[0], "INCONCLUSIVE", str(e), {})
except subprocess.TimeoutExpired:
return R(cmd[0], "INCONCLUSIVE", "timeout", {})
def caption_batch(topic: str, n: int = 5) -> R:
# Marketing-skills style variants
variants = [f"{topic} — angle {i+1}: concrete outcome, soft CTA." for i in range(n)]
banned = ["guaranteed", "risk-free riches"]
for v in variants:
if any(b in v.lower() for b in banned):
return R("captions", "FAIL", "BLOCK claim", {"verdict": "BLOCK"})
path = Path(os.environ.get("OUT_DIR", ".")) / "captions.json"
path.write_text(json.dumps(variants, indent=2))
return R("captions", "PASS", f"{n} variants", {"verdict": "SHIP", "path": str(path)})
def main():
out = Path(os.environ.get("OUT_DIR", "./content_out"))
out.mkdir(parents=True, exist_ok=True)
topic = os.environ.get("TOPIC", "AI workflow for solo founders")
media = Path(os.environ.get("MEDIA_IN", "in.mp4"))
results = [caption_batch(topic)]
# simplified-cli image (optional)
if os.environ.get("SIMPLIFIED_API_KEY"):
results.append(sh(["simplified", "ai-image:generate", "--wait", "--prompt", topic, "--out", str(out / "cover.png")]))
else:
results.append(R("simplified-cli", "INCONCLUSIVE", "no API key", {}))
# FFmpeg uniquify if media exists
if media.exists():
dst = out / "unique.mp4"
results.append(sh([
"ffmpeg", "-y", "-i", str(media),
"-vf", "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920",
"-c:v", "libx264", "-crf", "20", "-c:a", "aac", "-ar", "48000", "-shortest", str(dst),
], 300))
q = {
"platform": "tiktok",
"media": str(dst),
"caption": json.loads((out / "captions.json").read_text())[0],
"own_content_only": True,
"source": "content-factory",
}
(out / "queue_item.json").write_text(json.dumps(q, indent=2))
results.append(R("autosocial-queue", "PASS", "queue_item.json", q))
else:
results.append(R("ffmpeg", "INCONCLUSIVE", f"missing {media}", {}))
fails = [r for r in results if r.verdict == "FAIL"]
inc = [r for r in results if r.verdict == "INCONCLUSIVE"]
status = "FAIL" if fails else ("INCONCLUSIVE" if inc else "PASS")
report = {"status": status, "all_pass": status == "PASS", "results": [asdict(r) for r in results]}
(out / "content_report.json").write_text(json.dumps(report, indent=2))
print(json.dumps(report, indent=2))
raise SystemExit(0 if report["all_pass"] else 2)
if __name__ == "__main__":
main()