agent-recipes 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. agent_recipes/__init__.py +27 -0
  2. agent_recipes/recipe_runtime/__init__.py +28 -0
  3. agent_recipes/recipe_runtime/core.py +385 -0
  4. agent_recipes/templates/ai-ab-hook-tester/recipe.yaml +45 -0
  5. agent_recipes/templates/ai-ab-hook-tester/tools.py +169 -0
  6. agent_recipes/templates/ai-angle-generator/recipe.yaml +49 -0
  7. agent_recipes/templates/ai-angle-generator/tools.py +182 -0
  8. agent_recipes/templates/ai-api-doc-generator/README.md +59 -0
  9. agent_recipes/templates/ai-api-doc-generator/TEMPLATE.yaml +29 -0
  10. agent_recipes/templates/ai-api-tester/README.md +60 -0
  11. agent_recipes/templates/ai-api-tester/TEMPLATE.yaml +29 -0
  12. agent_recipes/templates/ai-audio-enhancer/README.md +59 -0
  13. agent_recipes/templates/ai-audio-enhancer/TEMPLATE.yaml +28 -0
  14. agent_recipes/templates/ai-audio-normalizer/README.md +13 -0
  15. agent_recipes/templates/ai-audio-normalizer/TEMPLATE.yaml +44 -0
  16. agent_recipes/templates/ai-audio-splitter/README.md +14 -0
  17. agent_recipes/templates/ai-audio-splitter/TEMPLATE.yaml +47 -0
  18. agent_recipes/templates/ai-background-music-generator/README.md +59 -0
  19. agent_recipes/templates/ai-background-music-generator/TEMPLATE.yaml +28 -0
  20. agent_recipes/templates/ai-background-remover/README.md +60 -0
  21. agent_recipes/templates/ai-background-remover/TEMPLATE.yaml +27 -0
  22. agent_recipes/templates/ai-barcode-scanner/README.md +60 -0
  23. agent_recipes/templates/ai-barcode-scanner/TEMPLATE.yaml +26 -0
  24. agent_recipes/templates/ai-blog-generator/README.md +59 -0
  25. agent_recipes/templates/ai-blog-generator/TEMPLATE.yaml +28 -0
  26. agent_recipes/templates/ai-brief-generator/recipe.yaml +52 -0
  27. agent_recipes/templates/ai-brief-generator/tools.py +231 -0
  28. agent_recipes/templates/ai-broll-builder/recipe.yaml +47 -0
  29. agent_recipes/templates/ai-broll-builder/tools.py +204 -0
  30. agent_recipes/templates/ai-calendar-scheduler/README.md +60 -0
  31. agent_recipes/templates/ai-calendar-scheduler/TEMPLATE.yaml +29 -0
  32. agent_recipes/templates/ai-changelog-generator/README.md +14 -0
  33. agent_recipes/templates/ai-changelog-generator/TEMPLATE.yaml +46 -0
  34. agent_recipes/templates/ai-chart-generator/README.md +61 -0
  35. agent_recipes/templates/ai-chart-generator/TEMPLATE.yaml +32 -0
  36. agent_recipes/templates/ai-code-documenter/README.md +12 -0
  37. agent_recipes/templates/ai-code-documenter/TEMPLATE.yaml +37 -0
  38. agent_recipes/templates/ai-code-refactorer/README.md +59 -0
  39. agent_recipes/templates/ai-code-refactorer/TEMPLATE.yaml +28 -0
  40. agent_recipes/templates/ai-code-reviewer/README.md +59 -0
  41. agent_recipes/templates/ai-code-reviewer/TEMPLATE.yaml +31 -0
  42. agent_recipes/templates/ai-color-palette-extractor/README.md +60 -0
  43. agent_recipes/templates/ai-color-palette-extractor/TEMPLATE.yaml +27 -0
  44. agent_recipes/templates/ai-comment-miner/recipe.yaml +40 -0
  45. agent_recipes/templates/ai-comment-miner/tools.py +141 -0
  46. agent_recipes/templates/ai-commit-message-generator/README.md +59 -0
  47. agent_recipes/templates/ai-commit-message-generator/TEMPLATE.yaml +31 -0
  48. agent_recipes/templates/ai-content-calendar/recipe.yaml +43 -0
  49. agent_recipes/templates/ai-content-calendar/tools.py +170 -0
  50. agent_recipes/templates/ai-context-enricher/recipe.yaml +48 -0
  51. agent_recipes/templates/ai-context-enricher/tools.py +258 -0
  52. agent_recipes/templates/ai-contract-analyzer/README.md +60 -0
  53. agent_recipes/templates/ai-contract-analyzer/TEMPLATE.yaml +34 -0
  54. agent_recipes/templates/ai-csv-cleaner/README.md +13 -0
  55. agent_recipes/templates/ai-csv-cleaner/TEMPLATE.yaml +45 -0
  56. agent_recipes/templates/ai-cta-generator/recipe.yaml +54 -0
  57. agent_recipes/templates/ai-cta-generator/tools.py +174 -0
  58. agent_recipes/templates/ai-daily-news-show/recipe.yaml +103 -0
  59. agent_recipes/templates/ai-daily-news-show/tools.py +308 -0
  60. agent_recipes/templates/ai-data-anonymizer/README.md +60 -0
  61. agent_recipes/templates/ai-data-anonymizer/TEMPLATE.yaml +31 -0
  62. agent_recipes/templates/ai-data-profiler/README.md +14 -0
  63. agent_recipes/templates/ai-data-profiler/TEMPLATE.yaml +42 -0
  64. agent_recipes/templates/ai-dependency-auditor/README.md +12 -0
  65. agent_recipes/templates/ai-dependency-auditor/TEMPLATE.yaml +37 -0
  66. agent_recipes/templates/ai-doc-translator/README.md +12 -0
  67. agent_recipes/templates/ai-doc-translator/TEMPLATE.yaml +41 -0
  68. agent_recipes/templates/ai-duplicate-finder/README.md +59 -0
  69. agent_recipes/templates/ai-duplicate-finder/TEMPLATE.yaml +28 -0
  70. agent_recipes/templates/ai-ebook-converter/README.md +60 -0
  71. agent_recipes/templates/ai-ebook-converter/TEMPLATE.yaml +27 -0
  72. agent_recipes/templates/ai-email-parser/README.md +59 -0
  73. agent_recipes/templates/ai-email-parser/TEMPLATE.yaml +29 -0
  74. agent_recipes/templates/ai-etl-pipeline/README.md +60 -0
  75. agent_recipes/templates/ai-etl-pipeline/TEMPLATE.yaml +30 -0
  76. agent_recipes/templates/ai-excel-formula-generator/README.md +59 -0
  77. agent_recipes/templates/ai-excel-formula-generator/TEMPLATE.yaml +28 -0
  78. agent_recipes/templates/ai-face-blur/README.md +60 -0
  79. agent_recipes/templates/ai-face-blur/TEMPLATE.yaml +28 -0
  80. agent_recipes/templates/ai-fact-checker/recipe.yaml +52 -0
  81. agent_recipes/templates/ai-fact-checker/tools.py +279 -0
  82. agent_recipes/templates/ai-faq-generator/README.md +59 -0
  83. agent_recipes/templates/ai-faq-generator/TEMPLATE.yaml +28 -0
  84. agent_recipes/templates/ai-file-organizer/README.md +59 -0
  85. agent_recipes/templates/ai-file-organizer/TEMPLATE.yaml +29 -0
  86. agent_recipes/templates/ai-folder-packager/README.md +15 -0
  87. agent_recipes/templates/ai-folder-packager/TEMPLATE.yaml +48 -0
  88. agent_recipes/templates/ai-form-filler/README.md +60 -0
  89. agent_recipes/templates/ai-form-filler/TEMPLATE.yaml +30 -0
  90. agent_recipes/templates/ai-hashtag-optimizer/recipe.yaml +45 -0
  91. agent_recipes/templates/ai-hashtag-optimizer/tools.py +134 -0
  92. agent_recipes/templates/ai-hook-generator/recipe.yaml +50 -0
  93. agent_recipes/templates/ai-hook-generator/tools.py +177 -0
  94. agent_recipes/templates/ai-image-captioner/README.md +59 -0
  95. agent_recipes/templates/ai-image-captioner/TEMPLATE.yaml +28 -0
  96. agent_recipes/templates/ai-image-cataloger/README.md +13 -0
  97. agent_recipes/templates/ai-image-cataloger/TEMPLATE.yaml +39 -0
  98. agent_recipes/templates/ai-image-optimizer/README.md +13 -0
  99. agent_recipes/templates/ai-image-optimizer/TEMPLATE.yaml +43 -0
  100. agent_recipes/templates/ai-image-resizer/README.md +12 -0
  101. agent_recipes/templates/ai-image-resizer/TEMPLATE.yaml +39 -0
  102. agent_recipes/templates/ai-image-tagger/README.md +59 -0
  103. agent_recipes/templates/ai-image-tagger/TEMPLATE.yaml +28 -0
  104. agent_recipes/templates/ai-image-upscaler/README.md +60 -0
  105. agent_recipes/templates/ai-image-upscaler/TEMPLATE.yaml +27 -0
  106. agent_recipes/templates/ai-invoice-processor/README.md +60 -0
  107. agent_recipes/templates/ai-invoice-processor/TEMPLATE.yaml +34 -0
  108. agent_recipes/templates/ai-json-to-csv/README.md +12 -0
  109. agent_recipes/templates/ai-json-to-csv/TEMPLATE.yaml +36 -0
  110. agent_recipes/templates/ai-log-analyzer/README.md +59 -0
  111. agent_recipes/templates/ai-log-analyzer/TEMPLATE.yaml +28 -0
  112. agent_recipes/templates/ai-markdown-to-pdf/README.md +12 -0
  113. agent_recipes/templates/ai-markdown-to-pdf/TEMPLATE.yaml +40 -0
  114. agent_recipes/templates/ai-meeting-summarizer/README.md +59 -0
  115. agent_recipes/templates/ai-meeting-summarizer/TEMPLATE.yaml +32 -0
  116. agent_recipes/templates/ai-meta-tag-generator/README.md +59 -0
  117. agent_recipes/templates/ai-meta-tag-generator/TEMPLATE.yaml +28 -0
  118. agent_recipes/templates/ai-news-capture-pack/recipe.yaml +42 -0
  119. agent_recipes/templates/ai-news-capture-pack/tools.py +150 -0
  120. agent_recipes/templates/ai-news-crawler/recipe.yaml +99 -0
  121. agent_recipes/templates/ai-news-crawler/tools.py +417 -0
  122. agent_recipes/templates/ai-news-deduper/recipe.yaml +47 -0
  123. agent_recipes/templates/ai-news-deduper/tools.py +235 -0
  124. agent_recipes/templates/ai-newsletter-generator/README.md +59 -0
  125. agent_recipes/templates/ai-newsletter-generator/TEMPLATE.yaml +28 -0
  126. agent_recipes/templates/ai-note-summarizer/README.md +59 -0
  127. agent_recipes/templates/ai-note-summarizer/TEMPLATE.yaml +28 -0
  128. agent_recipes/templates/ai-pdf-summarizer/README.md +12 -0
  129. agent_recipes/templates/ai-pdf-summarizer/TEMPLATE.yaml +40 -0
  130. agent_recipes/templates/ai-pdf-to-markdown/README.md +19 -0
  131. agent_recipes/templates/ai-pdf-to-markdown/TEMPLATE.yaml +63 -0
  132. agent_recipes/templates/ai-performance-analyzer/recipe.yaml +45 -0
  133. agent_recipes/templates/ai-performance-analyzer/tools.py +159 -0
  134. agent_recipes/templates/ai-podcast-cleaner/README.md +117 -0
  135. agent_recipes/templates/ai-podcast-cleaner/TEMPLATE.yaml +117 -0
  136. agent_recipes/templates/ai-podcast-cleaner/agents.yaml +59 -0
  137. agent_recipes/templates/ai-podcast-cleaner/workflow.yaml +77 -0
  138. agent_recipes/templates/ai-podcast-transcriber/README.md +59 -0
  139. agent_recipes/templates/ai-podcast-transcriber/TEMPLATE.yaml +32 -0
  140. agent_recipes/templates/ai-post-copy-generator/recipe.yaml +41 -0
  141. agent_recipes/templates/ai-post-copy-generator/tools.py +105 -0
  142. agent_recipes/templates/ai-product-description-generator/README.md +59 -0
  143. agent_recipes/templates/ai-product-description-generator/TEMPLATE.yaml +28 -0
  144. agent_recipes/templates/ai-publisher-pack/recipe.yaml +44 -0
  145. agent_recipes/templates/ai-publisher-pack/tools.py +252 -0
  146. agent_recipes/templates/ai-qr-code-generator/README.md +60 -0
  147. agent_recipes/templates/ai-qr-code-generator/TEMPLATE.yaml +26 -0
  148. agent_recipes/templates/ai-regex-generator/README.md +59 -0
  149. agent_recipes/templates/ai-regex-generator/TEMPLATE.yaml +28 -0
  150. agent_recipes/templates/ai-repo-readme/README.md +13 -0
  151. agent_recipes/templates/ai-repo-readme/TEMPLATE.yaml +42 -0
  152. agent_recipes/templates/ai-report-generator/README.md +61 -0
  153. agent_recipes/templates/ai-report-generator/TEMPLATE.yaml +32 -0
  154. agent_recipes/templates/ai-resume-parser/README.md +60 -0
  155. agent_recipes/templates/ai-resume-parser/TEMPLATE.yaml +33 -0
  156. agent_recipes/templates/ai-rss-aggregator/README.md +60 -0
  157. agent_recipes/templates/ai-rss-aggregator/TEMPLATE.yaml +30 -0
  158. agent_recipes/templates/ai-schema-generator/README.md +12 -0
  159. agent_recipes/templates/ai-schema-generator/TEMPLATE.yaml +34 -0
  160. agent_recipes/templates/ai-screen-recorder/recipe.yaml +43 -0
  161. agent_recipes/templates/ai-screen-recorder/tools.py +184 -0
  162. agent_recipes/templates/ai-screenshot-capture/recipe.yaml +45 -0
  163. agent_recipes/templates/ai-screenshot-capture/tools.py +231 -0
  164. agent_recipes/templates/ai-screenshot-ocr/README.md +12 -0
  165. agent_recipes/templates/ai-screenshot-ocr/TEMPLATE.yaml +37 -0
  166. agent_recipes/templates/ai-script-writer/recipe.yaml +58 -0
  167. agent_recipes/templates/ai-script-writer/tools.py +297 -0
  168. agent_recipes/templates/ai-sentiment-analyzer/README.md +59 -0
  169. agent_recipes/templates/ai-sentiment-analyzer/TEMPLATE.yaml +28 -0
  170. agent_recipes/templates/ai-seo-optimizer/README.md +59 -0
  171. agent_recipes/templates/ai-seo-optimizer/TEMPLATE.yaml +28 -0
  172. agent_recipes/templates/ai-signal-ranker/recipe.yaml +54 -0
  173. agent_recipes/templates/ai-signal-ranker/tools.py +256 -0
  174. agent_recipes/templates/ai-sitemap-generator/README.md +59 -0
  175. agent_recipes/templates/ai-sitemap-generator/TEMPLATE.yaml +26 -0
  176. agent_recipes/templates/ai-sitemap-scraper/README.md +13 -0
  177. agent_recipes/templates/ai-sitemap-scraper/TEMPLATE.yaml +41 -0
  178. agent_recipes/templates/ai-slide-generator/README.md +60 -0
  179. agent_recipes/templates/ai-slide-generator/TEMPLATE.yaml +29 -0
  180. agent_recipes/templates/ai-slide-to-notes/README.md +12 -0
  181. agent_recipes/templates/ai-slide-to-notes/TEMPLATE.yaml +37 -0
  182. agent_recipes/templates/ai-social-media-generator/README.md +59 -0
  183. agent_recipes/templates/ai-social-media-generator/TEMPLATE.yaml +28 -0
  184. agent_recipes/templates/ai-sql-generator/README.md +59 -0
  185. agent_recipes/templates/ai-sql-generator/TEMPLATE.yaml +28 -0
  186. agent_recipes/templates/ai-subtitle-generator/README.md +59 -0
  187. agent_recipes/templates/ai-subtitle-generator/TEMPLATE.yaml +31 -0
  188. agent_recipes/templates/ai-test-generator/README.md +59 -0
  189. agent_recipes/templates/ai-test-generator/TEMPLATE.yaml +28 -0
  190. agent_recipes/templates/ai-translation-batch/README.md +59 -0
  191. agent_recipes/templates/ai-translation-batch/TEMPLATE.yaml +28 -0
  192. agent_recipes/templates/ai-url-to-markdown/README.md +14 -0
  193. agent_recipes/templates/ai-url-to-markdown/TEMPLATE.yaml +44 -0
  194. agent_recipes/templates/ai-video-chapter-generator/README.md +59 -0
  195. agent_recipes/templates/ai-video-chapter-generator/TEMPLATE.yaml +32 -0
  196. agent_recipes/templates/ai-video-compressor/README.md +59 -0
  197. agent_recipes/templates/ai-video-compressor/TEMPLATE.yaml +28 -0
  198. agent_recipes/templates/ai-video-editor/README.md +254 -0
  199. agent_recipes/templates/ai-video-editor/TEMPLATE.yaml +139 -0
  200. agent_recipes/templates/ai-video-editor/agents.yaml +36 -0
  201. agent_recipes/templates/ai-video-editor/requirements.txt +8 -0
  202. agent_recipes/templates/ai-video-editor/scripts/run.sh +10 -0
  203. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__init__.py +45 -0
  204. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__main__.py +8 -0
  205. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/__init__.cpython-312.pyc +0 -0
  206. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/cli.cpython-312.pyc +0 -0
  207. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/config.cpython-312.pyc +0 -0
  208. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/ffmpeg_probe.cpython-312.pyc +0 -0
  209. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/heuristics.cpython-312.pyc +0 -0
  210. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/llm_plan.cpython-312.pyc +0 -0
  211. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/models.cpython-312.pyc +0 -0
  212. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/pipeline.cpython-312.pyc +0 -0
  213. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/render.cpython-312.pyc +0 -0
  214. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/timeline.cpython-312.pyc +0 -0
  215. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/transcribe.cpython-312.pyc +0 -0
  216. agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/utils.cpython-312.pyc +0 -0
  217. agent_recipes/templates/ai-video-editor/src/ai_video_editor/cli.py +343 -0
  218. agent_recipes/templates/ai-video-editor/src/ai_video_editor/config.py +102 -0
  219. agent_recipes/templates/ai-video-editor/src/ai_video_editor/ffmpeg_probe.py +92 -0
  220. agent_recipes/templates/ai-video-editor/src/ai_video_editor/heuristics.py +119 -0
  221. agent_recipes/templates/ai-video-editor/src/ai_video_editor/llm_plan.py +277 -0
  222. agent_recipes/templates/ai-video-editor/src/ai_video_editor/models.py +343 -0
  223. agent_recipes/templates/ai-video-editor/src/ai_video_editor/pipeline.py +287 -0
  224. agent_recipes/templates/ai-video-editor/src/ai_video_editor/render.py +274 -0
  225. agent_recipes/templates/ai-video-editor/src/ai_video_editor/timeline.py +278 -0
  226. agent_recipes/templates/ai-video-editor/src/ai_video_editor/transcribe.py +233 -0
  227. agent_recipes/templates/ai-video-editor/src/ai_video_editor/utils.py +222 -0
  228. agent_recipes/templates/ai-video-editor/src/input.mov +0 -0
  229. agent_recipes/templates/ai-video-editor/src/out.mp4 +0 -0
  230. agent_recipes/templates/ai-video-editor/tests/test_heuristics.py +130 -0
  231. agent_recipes/templates/ai-video-editor/tests/test_models.py +152 -0
  232. agent_recipes/templates/ai-video-editor/tests/test_timeline.py +105 -0
  233. agent_recipes/templates/ai-video-editor/workflow.yaml +51 -0
  234. agent_recipes/templates/ai-video-highlight-extractor/README.md +60 -0
  235. agent_recipes/templates/ai-video-highlight-extractor/TEMPLATE.yaml +33 -0
  236. agent_recipes/templates/ai-video-merger/recipe.yaml +40 -0
  237. agent_recipes/templates/ai-video-merger/tools.py +172 -0
  238. agent_recipes/templates/ai-video-thumbnails/README.md +16 -0
  239. agent_recipes/templates/ai-video-thumbnails/TEMPLATE.yaml +53 -0
  240. agent_recipes/templates/ai-video-to-gif/README.md +14 -0
  241. agent_recipes/templates/ai-video-to-gif/TEMPLATE.yaml +64 -0
  242. agent_recipes/templates/ai-voice-cloner/README.md +59 -0
  243. agent_recipes/templates/ai-voice-cloner/TEMPLATE.yaml +31 -0
  244. agent_recipes/templates/ai-voiceover-generator/recipe.yaml +41 -0
  245. agent_recipes/templates/ai-voiceover-generator/tools.py +194 -0
  246. agent_recipes/templates/ai-watermark-adder/README.md +59 -0
  247. agent_recipes/templates/ai-watermark-adder/TEMPLATE.yaml +26 -0
  248. agent_recipes/templates/ai-watermark-remover/README.md +60 -0
  249. agent_recipes/templates/ai-watermark-remover/TEMPLATE.yaml +32 -0
  250. agent_recipes/templates/data-transformer/README.md +75 -0
  251. agent_recipes/templates/data-transformer/TEMPLATE.yaml +63 -0
  252. agent_recipes/templates/data-transformer/agents.yaml +70 -0
  253. agent_recipes/templates/data-transformer/workflow.yaml +92 -0
  254. agent_recipes/templates/shorts-generator/README.md +61 -0
  255. agent_recipes/templates/shorts-generator/TEMPLATE.yaml +65 -0
  256. agent_recipes/templates/shorts-generator/agents.yaml +66 -0
  257. agent_recipes/templates/shorts-generator/workflow.yaml +86 -0
  258. agent_recipes/templates/transcript-generator/README.md +103 -0
  259. agent_recipes/templates/transcript-generator/TEMPLATE.yaml +57 -0
  260. agent_recipes/templates/transcript-generator/agents.yaml +62 -0
  261. agent_recipes/templates/transcript-generator/workflow.yaml +82 -0
  262. agent_recipes/templates/video-editor/README.md +70 -0
  263. agent_recipes/templates/video-editor/TEMPLATE.yaml +55 -0
  264. agent_recipes/templates/video-editor/agents.yaml +68 -0
  265. agent_recipes/templates/video-editor/workflow.yaml +92 -0
  266. agent_recipes-0.0.5.dist-info/METADATA +145 -0
  267. agent_recipes-0.0.5.dist-info/RECORD +269 -0
  268. agent_recipes-0.0.5.dist-info/WHEEL +5 -0
  269. agent_recipes-0.0.5.dist-info/top_level.txt +1 -0
  270. /236/326/177nE/243/231/214/232/265/322m/201/253/353/022C/372/321/266/b/225^=/272/017t/262/3337/310@/315wb/341pB/277z/216/330/314/004/265B/213/375/236/203/026/373/307/354z41/347#/374q/262/22589/032/276 /277/244Vh/322/017/004/224/215/004/367/377/375/335/n +0 -0
@@ -0,0 +1,222 @@
1
+ """
2
+ Utility functions for AI Video Editor.
3
+
4
+ Provides:
5
+ - FFmpeg detection and command building
6
+ - File hashing for reproducibility
7
+ - Working directory management
8
+ - Time formatting utilities
9
+ """
10
+
11
+ import hashlib
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional, Tuple
17
+ import json
18
+
19
+
20
+ def check_ffmpeg() -> Tuple[bool, str]:
21
+ """Check if ffmpeg is available."""
22
+ try:
23
+ result = subprocess.run(
24
+ ["ffmpeg", "-version"],
25
+ capture_output=True,
26
+ text=True,
27
+ timeout=10
28
+ )
29
+ if result.returncode == 0:
30
+ version_line = result.stdout.split("\n")[0]
31
+ return True, version_line
32
+ return False, "ffmpeg returned non-zero exit code"
33
+ except FileNotFoundError:
34
+ return False, "ffmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"
35
+ except subprocess.TimeoutExpired:
36
+ return False, "ffmpeg check timed out"
37
+ except Exception as e:
38
+ return False, f"Error checking ffmpeg: {e}"
39
+
40
+
41
+ def check_ffprobe() -> Tuple[bool, str]:
42
+ """Check if ffprobe is available."""
43
+ try:
44
+ result = subprocess.run(
45
+ ["ffprobe", "-version"],
46
+ capture_output=True,
47
+ text=True,
48
+ timeout=10
49
+ )
50
+ if result.returncode == 0:
51
+ version_line = result.stdout.split("\n")[0]
52
+ return True, version_line
53
+ return False, "ffprobe returned non-zero exit code"
54
+ except FileNotFoundError:
55
+ return False, "ffprobe not found. Install ffmpeg which includes ffprobe."
56
+ except subprocess.TimeoutExpired:
57
+ return False, "ffprobe check timed out"
58
+ except Exception as e:
59
+ return False, f"Error checking ffprobe: {e}"
60
+
61
+
62
+ def ensure_ffmpeg():
63
+ """Raise error if ffmpeg is not available."""
64
+ available, msg = check_ffmpeg()
65
+ if not available:
66
+ raise RuntimeError(f"FFmpeg is required but not available: {msg}")
67
+
68
+
69
+ def file_hash(path: str, algorithm: str = "sha256") -> str:
70
+ """Compute hash of a file."""
71
+ h = hashlib.new(algorithm)
72
+ with open(path, "rb") as f:
73
+ for chunk in iter(lambda: f.read(8192), b""):
74
+ h.update(chunk)
75
+ return h.hexdigest()
76
+
77
+
78
+ def create_workdir(input_path: str, base_dir: Optional[str] = None) -> Path:
79
+ """Create a working directory for video processing."""
80
+ if base_dir is None:
81
+ base_dir = os.path.join(os.getcwd(), ".praison", "video")
82
+
83
+ input_hash = file_hash(input_path)[:12]
84
+ input_name = Path(input_path).stem
85
+ workdir = Path(base_dir) / f"{input_name}_{input_hash}"
86
+
87
+ workdir.mkdir(parents=True, exist_ok=True)
88
+ (workdir / "audio").mkdir(exist_ok=True)
89
+ (workdir / "segments").mkdir(exist_ok=True)
90
+ (workdir / "output").mkdir(exist_ok=True)
91
+
92
+ return workdir
93
+
94
+
95
+ def format_duration(seconds: float) -> str:
96
+ """Format duration in human-readable format."""
97
+ hours = int(seconds // 3600)
98
+ minutes = int((seconds % 3600) // 60)
99
+ secs = int(seconds % 60)
100
+
101
+ parts = []
102
+ if hours > 0:
103
+ parts.append(f"{hours}h")
104
+ if minutes > 0 or hours > 0:
105
+ parts.append(f"{minutes}m")
106
+ parts.append(f"{secs}s")
107
+
108
+ return " ".join(parts)
109
+
110
+
111
+ def parse_duration(duration_str: str) -> float:
112
+ """Parse duration string to seconds."""
113
+ import re
114
+
115
+ total = 0.0
116
+
117
+ h_match = re.search(r"(\d+)h", duration_str)
118
+ if h_match:
119
+ total += int(h_match.group(1)) * 3600
120
+
121
+ m_match = re.search(r"(\d+)m", duration_str)
122
+ if m_match:
123
+ total += int(m_match.group(1)) * 60
124
+
125
+ s_match = re.search(r"(\d+)s", duration_str)
126
+ if s_match:
127
+ total += int(s_match.group(1))
128
+
129
+ if total == 0:
130
+ try:
131
+ total = float(duration_str)
132
+ except ValueError:
133
+ pass
134
+
135
+ return total
136
+
137
+
138
+ def extract_audio(video_path: str, output_path: str, sample_rate: int = 16000, mono: bool = True) -> str:
139
+ """Extract audio from video file."""
140
+ ensure_ffmpeg()
141
+
142
+ cmd = [
143
+ "ffmpeg", "-y",
144
+ "-i", video_path,
145
+ "-vn",
146
+ "-acodec", "pcm_s16le",
147
+ "-ar", str(sample_rate),
148
+ ]
149
+
150
+ if mono:
151
+ cmd.extend(["-ac", "1"])
152
+
153
+ cmd.append(output_path)
154
+
155
+ result = subprocess.run(cmd, capture_output=True, text=True)
156
+ if result.returncode != 0:
157
+ raise RuntimeError(f"Failed to extract audio: {result.stderr}")
158
+
159
+ return output_path
160
+
161
+
162
+ def generate_edl(
163
+ segments_to_keep: List[Tuple[float, float]],
164
+ segments_to_remove: List[Tuple[float, float, str]],
165
+ output_path: str,
166
+ fps: float = 30.0
167
+ ) -> str:
168
+ """Generate Edit Decision List (EDL) file."""
169
+ lines = [
170
+ "TITLE: AI Video Editor EDL",
171
+ "FCM: NON-DROP FRAME",
172
+ ""
173
+ ]
174
+
175
+ for i, (start, end) in enumerate(segments_to_keep, 1):
176
+ start_tc = _seconds_to_timecode(start, fps)
177
+ end_tc = _seconds_to_timecode(end, fps)
178
+ lines.append(f"{i:03d} AX V C {start_tc} {end_tc} {start_tc} {end_tc}")
179
+
180
+ lines.append("")
181
+ lines.append("* REMOVED SEGMENTS:")
182
+
183
+ for start, end, reason in segments_to_remove:
184
+ start_tc = _seconds_to_timecode(start, fps)
185
+ end_tc = _seconds_to_timecode(end, fps)
186
+ lines.append(f"* {start_tc} - {end_tc}: {reason}")
187
+
188
+ content = "\n".join(lines)
189
+ Path(output_path).write_text(content)
190
+ return output_path
191
+
192
+
193
+ def _seconds_to_timecode(seconds: float, fps: float = 30.0) -> str:
194
+ """Convert seconds to SMPTE timecode."""
195
+ frames = int((seconds % 1) * fps)
196
+ total_secs = int(seconds)
197
+ hours = total_secs // 3600
198
+ minutes = (total_secs % 3600) // 60
199
+ secs = total_secs % 60
200
+ return f"{hours:02d}:{minutes:02d}:{secs:02d}:{frames:02d}"
201
+
202
+
203
+ def save_config_snapshot(config: Dict[str, Any], workdir: Path) -> str:
204
+ """Save configuration snapshot for reproducibility."""
205
+ config_path = workdir / "config_snapshot.json"
206
+ with open(config_path, "w") as f:
207
+ json.dump(config, f, indent=2, default=str)
208
+ return str(config_path)
209
+
210
+
211
+ def cleanup_workdir(workdir: Path, keep_output: bool = True):
212
+ """Clean up working directory."""
213
+ if not workdir.exists():
214
+ return
215
+
216
+ for item in workdir.iterdir():
217
+ if keep_output and item.name == "output":
218
+ continue
219
+ if item.is_dir():
220
+ shutil.rmtree(item)
221
+ else:
222
+ item.unlink()
@@ -0,0 +1,130 @@
1
+ """
2
+ Unit tests for heuristic-based content analysis.
3
+ """
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ # Add src to path for imports
9
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
10
+
11
+ import pytest
12
+
13
+
14
+ class TestFillerDetection:
15
+ """Test filler word detection."""
16
+
17
+ def test_detect_fillers(self):
18
+ from ai_video_editor.models import TranscriptResult, Word
19
+ from ai_video_editor.heuristics import detect_fillers
20
+
21
+ transcript = TranscriptResult(
22
+ text="So um I think uh this is like really important",
23
+ words=[
24
+ Word(text="So", start=0.0, end=0.2),
25
+ Word(text="um", start=0.3, end=0.5),
26
+ Word(text="I", start=0.6, end=0.7),
27
+ Word(text="think", start=0.7, end=1.0),
28
+ Word(text="uh", start=1.1, end=1.3),
29
+ Word(text="this", start=1.4, end=1.6),
30
+ Word(text="is", start=1.6, end=1.8),
31
+ Word(text="like", start=1.9, end=2.1),
32
+ Word(text="really", start=2.2, end=2.5),
33
+ Word(text="important", start=2.5, end=3.0),
34
+ ],
35
+ duration=3.0
36
+ )
37
+
38
+ filler_words = ["um", "uh", "like"]
39
+ fillers = detect_fillers(transcript, filler_words)
40
+
41
+ assert len(fillers) == 3
42
+ assert fillers[0].text == "um"
43
+ assert fillers[1].text == "uh"
44
+ assert fillers[2].text == "like"
45
+
46
+ def test_detect_fillers_case_insensitive(self):
47
+ from ai_video_editor.models import TranscriptResult, Word
48
+ from ai_video_editor.heuristics import detect_fillers
49
+
50
+ transcript = TranscriptResult(
51
+ text="UM this is UH important",
52
+ words=[
53
+ Word(text="UM", start=0.0, end=0.3),
54
+ Word(text="this", start=0.4, end=0.6),
55
+ Word(text="is", start=0.6, end=0.8),
56
+ Word(text="UH", start=0.9, end=1.1),
57
+ Word(text="important", start=1.2, end=1.6),
58
+ ],
59
+ duration=1.6
60
+ )
61
+
62
+ fillers = detect_fillers(transcript, ["um", "uh"])
63
+ assert len(fillers) == 2
64
+
65
+
66
+ class TestRepetitionDetection:
67
+ """Test repetition detection."""
68
+
69
+ def test_detect_repetitions(self):
70
+ from ai_video_editor.models import TranscriptResult, Word
71
+ from ai_video_editor.heuristics import detect_repetitions
72
+
73
+ transcript = TranscriptResult(
74
+ text="I think I think this is good",
75
+ words=[
76
+ Word(text="I", start=0.0, end=0.1),
77
+ Word(text="think", start=0.1, end=0.4),
78
+ Word(text="I", start=0.5, end=0.6),
79
+ Word(text="think", start=0.6, end=0.9),
80
+ Word(text="this", start=1.0, end=1.2),
81
+ Word(text="is", start=1.2, end=1.4),
82
+ Word(text="good", start=1.4, end=1.7),
83
+ ],
84
+ duration=1.7
85
+ )
86
+
87
+ repetitions = detect_repetitions(transcript, window_size=5, min_repeat_words=2)
88
+ assert len(repetitions) >= 1
89
+
90
+
91
+ class TestSilenceDetection:
92
+ """Test silence detection."""
93
+
94
+ def test_detect_silence(self):
95
+ from ai_video_editor.models import TranscriptResult, Word
96
+ from ai_video_editor.heuristics import detect_silence
97
+
98
+ transcript = TranscriptResult(
99
+ text="Hello world",
100
+ words=[
101
+ Word(text="Hello", start=0.0, end=0.5),
102
+ Word(text="world", start=2.0, end=2.5),
103
+ ],
104
+ duration=2.5
105
+ )
106
+
107
+ silences = detect_silence(transcript, threshold_ms=1000)
108
+ assert len(silences) == 1
109
+ assert silences[0].start == 0.5
110
+ assert silences[0].end == 2.0
111
+
112
+ def test_no_silence_below_threshold(self):
113
+ from ai_video_editor.models import TranscriptResult, Word
114
+ from ai_video_editor.heuristics import detect_silence
115
+
116
+ transcript = TranscriptResult(
117
+ text="Hello world",
118
+ words=[
119
+ Word(text="Hello", start=0.0, end=0.5),
120
+ Word(text="world", start=0.8, end=1.3),
121
+ ],
122
+ duration=1.3
123
+ )
124
+
125
+ silences = detect_silence(transcript, threshold_ms=700)
126
+ assert len(silences) == 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,152 @@
1
+ """
2
+ Unit tests for AI Video Editor models.
3
+ """
4
+
5
+ import sys
6
+ import os
7
+ from pathlib import Path
8
+
9
+ # Add src to path for imports
10
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
11
+
12
+ import pytest
13
+
14
+
15
+ class TestWord:
16
+ """Test Word model."""
17
+
18
+ def test_word_serialization(self):
19
+ from ai_video_editor.models import Word
20
+
21
+ word = Word(text="hello", start=1.0, end=1.5, confidence=0.95)
22
+ data = word.to_dict()
23
+
24
+ assert data["text"] == "hello"
25
+ assert data["start"] == 1.0
26
+ assert data["end"] == 1.5
27
+ assert data["confidence"] == 0.95
28
+
29
+ word2 = Word.from_dict(data)
30
+ assert word2.text == word.text
31
+ assert word2.start == word.start
32
+
33
+
34
+ class TestSegment:
35
+ """Test Segment model."""
36
+
37
+ def test_segment_duration(self):
38
+ from ai_video_editor.models import Segment, SegmentCategory
39
+
40
+ seg = Segment(start=10.0, end=15.5, category=SegmentCategory.KEEP)
41
+ assert seg.duration == 5.5
42
+
43
+ def test_segment_serialization(self):
44
+ from ai_video_editor.models import Segment, SegmentCategory
45
+
46
+ seg = Segment(
47
+ start=0.0,
48
+ end=10.0,
49
+ category=SegmentCategory.FILLER,
50
+ reason="um",
51
+ text="um"
52
+ )
53
+
54
+ data = seg.to_dict()
55
+ assert data["category"] == "filler"
56
+
57
+ seg2 = Segment.from_dict(data)
58
+ assert seg2.category == SegmentCategory.FILLER
59
+
60
+
61
+ class TestEditPlan:
62
+ """Test EditPlan model."""
63
+
64
+ def test_edit_plan_stats(self):
65
+ from ai_video_editor.models import EditPlan, Segment, SegmentCategory
66
+
67
+ plan = EditPlan(
68
+ segments_to_keep=[
69
+ Segment(start=0, end=10, category=SegmentCategory.KEEP),
70
+ Segment(start=15, end=25, category=SegmentCategory.KEEP),
71
+ ],
72
+ segments_to_remove=[
73
+ Segment(start=10, end=12, category=SegmentCategory.FILLER),
74
+ Segment(start=12, end=15, category=SegmentCategory.SILENCE),
75
+ ]
76
+ )
77
+
78
+ assert plan.total_keep_duration == 20.0
79
+ assert plan.total_remove_duration == 5.0
80
+
81
+ stats = plan.removal_stats
82
+ assert stats["filler"] == 2.0
83
+ assert stats["silence"] == 3.0
84
+
85
+ def test_edit_plan_json(self, tmp_path):
86
+ from ai_video_editor.models import EditPlan, Segment, SegmentCategory
87
+
88
+ plan = EditPlan(
89
+ segments_to_keep=[
90
+ Segment(start=0, end=10, category=SegmentCategory.KEEP, reason="intro"),
91
+ ],
92
+ summary="Test video",
93
+ topics=["testing"]
94
+ )
95
+
96
+ json_path = tmp_path / "plan.json"
97
+ plan.to_json(json_path)
98
+
99
+ plan2 = EditPlan.from_json(json_path)
100
+ assert plan2.summary == "Test video"
101
+ assert len(plan2.segments_to_keep) == 1
102
+ assert plan2.topics == ["testing"]
103
+
104
+
105
+ class TestTranscriptResult:
106
+ """Test TranscriptResult model."""
107
+
108
+ def test_transcript_to_srt(self, tmp_path):
109
+ from ai_video_editor.models import TranscriptResult, Word
110
+
111
+ transcript = TranscriptResult(
112
+ text="Hello world this is a test",
113
+ words=[
114
+ Word(text="Hello", start=0.0, end=0.5),
115
+ Word(text="world", start=0.5, end=1.0),
116
+ Word(text="this", start=1.0, end=1.3),
117
+ Word(text="is", start=1.3, end=1.5),
118
+ Word(text="a", start=1.5, end=1.6),
119
+ Word(text="test", start=1.6, end=2.0),
120
+ ],
121
+ duration=2.0
122
+ )
123
+
124
+ srt_path = tmp_path / "captions.srt"
125
+ srt_content = transcript.to_srt(srt_path)
126
+
127
+ assert srt_path.exists()
128
+ assert "Hello world this is a test" in srt_content
129
+ assert "-->" in srt_content
130
+
131
+
132
+ class TestPresets:
133
+ """Test preset configurations."""
134
+
135
+ def test_presets_exist(self):
136
+ from ai_video_editor.config import PRESETS
137
+
138
+ assert "podcast" in PRESETS
139
+ assert "meeting" in PRESETS
140
+ assert "course" in PRESETS
141
+ assert "clean" in PRESETS
142
+
143
+ def test_podcast_preset(self):
144
+ from ai_video_editor.config import PRESETS
145
+
146
+ podcast = PRESETS["podcast"]
147
+ assert podcast["remove_fillers"] is True
148
+ assert "um" in podcast["filler_words"]
149
+
150
+
151
+ if __name__ == "__main__":
152
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,105 @@
1
+ """
2
+ Unit tests for timeline optimization.
3
+ """
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ # Add src to path for imports
9
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
10
+
11
+ import pytest
12
+
13
+
14
+ class TestTimelineOptimization:
15
+ """Test timeline optimization functions."""
16
+
17
+ def test_merge_overlapping_segments(self):
18
+ from ai_video_editor.models import Segment, SegmentCategory
19
+ from ai_video_editor.timeline import _merge_overlapping
20
+
21
+ segments = [
22
+ Segment(start=0, end=10, category=SegmentCategory.KEEP),
23
+ Segment(start=8, end=15, category=SegmentCategory.KEEP),
24
+ Segment(start=20, end=25, category=SegmentCategory.KEEP),
25
+ ]
26
+
27
+ merged = _merge_overlapping(segments)
28
+
29
+ assert len(merged) == 2
30
+ assert merged[0].start == 0
31
+ assert merged[0].end == 15
32
+ assert merged[1].start == 20
33
+
34
+ def test_get_keep_intervals(self):
35
+ from ai_video_editor.models import EditPlan, Segment, SegmentCategory
36
+ from ai_video_editor.timeline import get_keep_intervals
37
+
38
+ plan = EditPlan(
39
+ segments_to_keep=[
40
+ Segment(start=10, end=20, category=SegmentCategory.KEEP),
41
+ Segment(start=0, end=5, category=SegmentCategory.KEEP),
42
+ ]
43
+ )
44
+
45
+ intervals = get_keep_intervals(plan)
46
+
47
+ assert intervals[0] == (0, 5)
48
+ assert intervals[1] == (10, 20)
49
+
50
+ def test_calculate_final_duration(self):
51
+ from ai_video_editor.models import EditPlan, Segment, SegmentCategory
52
+ from ai_video_editor.timeline import calculate_final_duration
53
+
54
+ plan = EditPlan(
55
+ segments_to_keep=[
56
+ Segment(start=0, end=10, category=SegmentCategory.KEEP),
57
+ Segment(start=20, end=35, category=SegmentCategory.KEEP),
58
+ ]
59
+ )
60
+
61
+ duration = calculate_final_duration(plan)
62
+ assert duration == 25.0
63
+
64
+
65
+ class TestUtils:
66
+ """Test utility functions."""
67
+
68
+ def test_format_duration(self):
69
+ from ai_video_editor.utils import format_duration
70
+
71
+ assert format_duration(30) == "30s"
72
+ assert format_duration(90) == "1m 30s"
73
+ assert format_duration(3661) == "1h 1m 1s"
74
+
75
+ def test_parse_duration(self):
76
+ from ai_video_editor.utils import parse_duration
77
+
78
+ assert parse_duration("30s") == 30.0
79
+ assert parse_duration("5m") == 300.0
80
+ assert parse_duration("1h30m") == 5400.0
81
+ assert parse_duration("1h30m45s") == 5445.0
82
+ assert parse_duration("90") == 90.0
83
+
84
+ def test_file_hash(self, tmp_path):
85
+ from ai_video_editor.utils import file_hash
86
+
87
+ test_file = tmp_path / "test.txt"
88
+ test_file.write_text("hello world")
89
+
90
+ hash1 = file_hash(str(test_file))
91
+ hash2 = file_hash(str(test_file))
92
+
93
+ assert hash1 == hash2
94
+ assert len(hash1) == 64
95
+
96
+ def test_check_ffmpeg(self):
97
+ from ai_video_editor.utils import check_ffmpeg
98
+
99
+ available, msg = check_ffmpeg()
100
+ assert isinstance(available, bool)
101
+ assert isinstance(msg, str)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,51 @@
1
+ name: AI Video Editor Workflow
2
+ description: |
3
+ End-to-end AI video editing pipeline.
4
+ Uses praisonai_tools.video CLI for video processing.
5
+
6
+ Tier 1: Shell orchestration via shell_tool
7
+ Tier 2/3: Native tools via praisonai_tools.video module
8
+
9
+ process: sequential
10
+
11
+ variables:
12
+ input: ""
13
+ output: ""
14
+ preset: "podcast"
15
+ verbose: "true"
16
+
17
+ agents:
18
+ video_editor:
19
+ name: VideoEditor
20
+ role: Video Editor
21
+ goal: Edit videos using AI analysis
22
+ instructions: |
23
+ You are an AI video editor. Use the praisonai_tools.video CLI to process videos.
24
+
25
+ Available commands:
26
+ - python -m praisonai_tools.video edit <input> --output <output> --preset <preset> -v
27
+ - python -m praisonai_tools.video probe <input>
28
+ - python -m praisonai_tools.video transcribe <input> --output transcript.srt
29
+
30
+ Presets: podcast, meeting, course, clean
31
+ tools: [shell_tool]
32
+
33
+ tasks:
34
+ - name: probe_video
35
+ agent: video_editor
36
+ description: |
37
+ Probe the input video to get metadata.
38
+ Run: python -m praisonai_tools.video probe "{input}"
39
+ expected_output: Video metadata (duration, resolution, fps, codec)
40
+
41
+ - name: edit_video
42
+ agent: video_editor
43
+ description: |
44
+ Edit the video using AI analysis.
45
+ Run: python -m praisonai_tools.video edit "{input}" --preset {preset} --output "{output}" -v
46
+ expected_output: |
47
+ Edited video with:
48
+ - Filler words removed
49
+ - Repetitions removed
50
+ - Silences trimmed
51
+ - Transcript and captions generated