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,278 @@
1
+ """
2
+ Timeline optimization for video editing.
3
+
4
+ Handles:
5
+ - Merging overlapping segments
6
+ - Snapping to word boundaries
7
+ - Applying padding/handles
8
+ - Enforcing minimum segment lengths
9
+ - Target duration constraints
10
+ """
11
+
12
+ from typing import Any, Dict, List, Tuple
13
+
14
+ from .models import EditPlan, Segment, SegmentCategory, TranscriptResult, Word
15
+
16
+
17
+ def optimize_timeline(
18
+ edit_plan: EditPlan,
19
+ transcript: TranscriptResult,
20
+ config: Dict[str, Any]
21
+ ) -> EditPlan:
22
+ """
23
+ Optimize edit plan timeline.
24
+
25
+ Args:
26
+ edit_plan: Initial edit plan
27
+ transcript: Transcript with word timestamps
28
+ config: Configuration with padding, min_segment_length, etc.
29
+
30
+ Returns:
31
+ Optimized EditPlan
32
+ """
33
+ padding_ms = config.get("padding_ms", 120)
34
+ padding_s = padding_ms / 1000.0
35
+ min_segment_length = config.get("min_segment_length", 1.2)
36
+ target_length = config.get("target_length")
37
+
38
+ segments_to_keep = [
39
+ _snap_to_word_boundaries(seg, transcript.words)
40
+ for seg in edit_plan.segments_to_keep
41
+ ]
42
+
43
+ segments_to_remove = [
44
+ _snap_to_word_boundaries(seg, transcript.words)
45
+ for seg in edit_plan.segments_to_remove
46
+ ]
47
+
48
+ segments_to_keep = _merge_overlapping(segments_to_keep)
49
+
50
+ segments_to_keep = _apply_padding(
51
+ segments_to_keep,
52
+ padding_s,
53
+ 0.0,
54
+ transcript.duration
55
+ )
56
+
57
+ segments_to_keep = [
58
+ seg for seg in segments_to_keep
59
+ if seg.duration >= min_segment_length
60
+ ]
61
+
62
+ segments_to_remove = _calculate_remove_segments(
63
+ segments_to_keep,
64
+ edit_plan.segments_to_remove,
65
+ transcript.duration
66
+ )
67
+
68
+ if target_length:
69
+ segments_to_keep = _apply_target_length(
70
+ segments_to_keep,
71
+ segments_to_remove,
72
+ target_length
73
+ )
74
+
75
+ return EditPlan(
76
+ segments_to_keep=segments_to_keep,
77
+ segments_to_remove=segments_to_remove,
78
+ chapters=edit_plan.chapters,
79
+ summary=edit_plan.summary,
80
+ topics=edit_plan.topics
81
+ )
82
+
83
+
84
+ def _snap_to_word_boundaries(segment: Segment, words: List[Word]) -> Segment:
85
+ """Snap segment boundaries to nearest word boundaries."""
86
+ if not words:
87
+ return segment
88
+
89
+ new_start = segment.start
90
+ for word in words:
91
+ if word.start >= segment.start:
92
+ new_start = word.start
93
+ break
94
+ if word.end >= segment.start:
95
+ new_start = word.start
96
+ break
97
+
98
+ new_end = segment.end
99
+ for word in reversed(words):
100
+ if word.end <= segment.end:
101
+ new_end = word.end
102
+ break
103
+ if word.start <= segment.end:
104
+ new_end = word.end
105
+ break
106
+
107
+ if new_end <= new_start:
108
+ return segment
109
+
110
+ return Segment(
111
+ start=new_start,
112
+ end=new_end,
113
+ category=segment.category,
114
+ reason=segment.reason,
115
+ confidence=segment.confidence,
116
+ text=segment.text,
117
+ words=segment.words
118
+ )
119
+
120
+
121
+ def _merge_overlapping(segments: List[Segment]) -> List[Segment]:
122
+ """Merge overlapping or adjacent segments."""
123
+ if not segments:
124
+ return []
125
+
126
+ sorted_segs = sorted(segments, key=lambda s: s.start)
127
+ merged = [sorted_segs[0]]
128
+
129
+ for seg in sorted_segs[1:]:
130
+ last = merged[-1]
131
+
132
+ if seg.start <= last.end + 0.1:
133
+ merged[-1] = Segment(
134
+ start=last.start,
135
+ end=max(last.end, seg.end),
136
+ category=last.category,
137
+ reason=f"{last.reason}; {seg.reason}" if seg.reason else last.reason,
138
+ text=f"{last.text} {seg.text}".strip()
139
+ )
140
+ else:
141
+ merged.append(seg)
142
+
143
+ return merged
144
+
145
+
146
+ def _apply_padding(
147
+ segments: List[Segment],
148
+ padding_s: float,
149
+ min_time: float,
150
+ max_time: float
151
+ ) -> List[Segment]:
152
+ """Apply padding/handles to segment boundaries."""
153
+ padded = []
154
+
155
+ for seg in segments:
156
+ new_start = max(min_time, seg.start - padding_s)
157
+ new_end = min(max_time, seg.end + padding_s)
158
+
159
+ padded.append(Segment(
160
+ start=new_start,
161
+ end=new_end,
162
+ category=seg.category,
163
+ reason=seg.reason,
164
+ text=seg.text
165
+ ))
166
+
167
+ return _merge_overlapping(padded)
168
+
169
+
170
+ def _calculate_remove_segments(
171
+ keep_segments: List[Segment],
172
+ original_remove: List[Segment],
173
+ total_duration: float
174
+ ) -> List[Segment]:
175
+ """Calculate remove segments as gaps between keep segments."""
176
+ if not keep_segments:
177
+ return original_remove
178
+
179
+ remove_segments = []
180
+ sorted_keep = sorted(keep_segments, key=lambda s: s.start)
181
+
182
+ if sorted_keep[0].start > 0.01:
183
+ reason = _find_remove_reason(0, sorted_keep[0].start, original_remove)
184
+ category = _find_remove_category(0, sorted_keep[0].start, original_remove)
185
+ remove_segments.append(Segment(
186
+ start=0,
187
+ end=sorted_keep[0].start,
188
+ category=category,
189
+ reason=reason
190
+ ))
191
+
192
+ for i in range(len(sorted_keep) - 1):
193
+ gap_start = sorted_keep[i].end
194
+ gap_end = sorted_keep[i + 1].start
195
+
196
+ if gap_end - gap_start > 0.01:
197
+ reason = _find_remove_reason(gap_start, gap_end, original_remove)
198
+ category = _find_remove_category(gap_start, gap_end, original_remove)
199
+ remove_segments.append(Segment(
200
+ start=gap_start,
201
+ end=gap_end,
202
+ category=category,
203
+ reason=reason
204
+ ))
205
+
206
+ if sorted_keep[-1].end < total_duration - 0.01:
207
+ reason = _find_remove_reason(sorted_keep[-1].end, total_duration, original_remove)
208
+ category = _find_remove_category(sorted_keep[-1].end, total_duration, original_remove)
209
+ remove_segments.append(Segment(
210
+ start=sorted_keep[-1].end,
211
+ end=total_duration,
212
+ category=category,
213
+ reason=reason
214
+ ))
215
+
216
+ return remove_segments
217
+
218
+
219
+ def _find_remove_reason(start: float, end: float, original_remove: List[Segment]) -> str:
220
+ """Find reason for removal from original segments."""
221
+ for seg in original_remove:
222
+ if seg.start < end and seg.end > start:
223
+ return seg.reason
224
+ return "Removed"
225
+
226
+
227
+ def _find_remove_category(start: float, end: float, original_remove: List[Segment]) -> SegmentCategory:
228
+ """Find category for removal from original segments."""
229
+ for seg in original_remove:
230
+ if seg.start < end and seg.end > start:
231
+ return seg.category
232
+ return SegmentCategory.FILLER
233
+
234
+
235
+ def _apply_target_length(
236
+ keep_segments: List[Segment],
237
+ remove_segments: List[Segment],
238
+ target_length: float
239
+ ) -> List[Segment]:
240
+ """Adjust segments to meet target length."""
241
+ current_duration = sum(s.duration for s in keep_segments)
242
+
243
+ if current_duration <= target_length:
244
+ return keep_segments
245
+
246
+ excess = current_duration - target_length
247
+ sorted_segs = sorted(keep_segments, key=lambda s: s.confidence)
248
+
249
+ result = []
250
+ removed_duration = 0.0
251
+
252
+ for seg in sorted_segs:
253
+ if removed_duration < excess and seg.duration < excess - removed_duration:
254
+ removed_duration += seg.duration
255
+ else:
256
+ result.append(seg)
257
+
258
+ return sorted(result, key=lambda s: s.start)
259
+
260
+
261
+ def get_keep_intervals(edit_plan: EditPlan) -> List[Tuple[float, float]]:
262
+ """Get list of (start, end) tuples for segments to keep."""
263
+ intervals = [(seg.start, seg.end) for seg in edit_plan.segments_to_keep]
264
+ return sorted(intervals, key=lambda x: x[0])
265
+
266
+
267
+ def get_remove_intervals(edit_plan: EditPlan) -> List[Tuple[float, float, str]]:
268
+ """Get list of (start, end, reason) tuples for segments to remove."""
269
+ intervals = [
270
+ (seg.start, seg.end, seg.reason)
271
+ for seg in edit_plan.segments_to_remove
272
+ ]
273
+ return sorted(intervals, key=lambda x: x[0])
274
+
275
+
276
+ def calculate_final_duration(edit_plan: EditPlan) -> float:
277
+ """Calculate final video duration after edits."""
278
+ return sum(seg.duration for seg in edit_plan.segments_to_keep)
@@ -0,0 +1,233 @@
1
+ """
2
+ Audio transcription with word-level timestamps.
3
+
4
+ Supports:
5
+ - OpenAI Whisper API (default, requires OPENAI_API_KEY)
6
+ - Local faster-whisper (optional fallback)
7
+ """
8
+
9
+ import os
10
+ from pathlib import Path
11
+ from typing import List, Literal
12
+
13
+ from .models import TranscriptResult, Word
14
+ from .utils import extract_audio, create_workdir
15
+
16
+
17
+ def transcript(
18
+ input_path: str,
19
+ provider: Literal["openai", "local", "auto"] = "auto",
20
+ language: str = "en",
21
+ workdir: str = None
22
+ ) -> TranscriptResult:
23
+ """
24
+ Transcribe audio/video with word-level timestamps.
25
+
26
+ Args:
27
+ input_path: Path to audio or video file
28
+ provider: Transcription provider (openai, local, auto)
29
+ language: Language code
30
+ workdir: Working directory for temp files
31
+
32
+ Returns:
33
+ TranscriptResult with text and word timestamps
34
+ """
35
+ if not os.path.exists(input_path):
36
+ raise FileNotFoundError(f"Input file not found: {input_path}")
37
+
38
+ if provider == "auto":
39
+ if os.environ.get("OPENAI_API_KEY"):
40
+ provider = "openai"
41
+ else:
42
+ provider = "local"
43
+
44
+ audio_path = input_path
45
+ input_ext = Path(input_path).suffix.lower()
46
+
47
+ if input_ext in [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"]:
48
+ if workdir is None:
49
+ work_path = create_workdir(input_path)
50
+ else:
51
+ work_path = Path(workdir)
52
+ work_path.mkdir(parents=True, exist_ok=True)
53
+
54
+ audio_path = str(work_path / "audio" / "extracted.wav")
55
+ Path(audio_path).parent.mkdir(parents=True, exist_ok=True)
56
+ extract_audio(input_path, audio_path)
57
+
58
+ if provider == "openai":
59
+ return _transcribe_openai(audio_path, language)
60
+ elif provider == "local":
61
+ return _transcribe_local(audio_path, language)
62
+ else:
63
+ raise ValueError(f"Unknown provider: {provider}")
64
+
65
+
66
+ def _transcribe_openai(audio_path: str, language: str) -> TranscriptResult:
67
+ """Transcribe using OpenAI Whisper API."""
68
+ try:
69
+ from openai import OpenAI
70
+ except ImportError:
71
+ raise ImportError(
72
+ "OpenAI package required for transcription. "
73
+ "Install with: pip install openai"
74
+ )
75
+
76
+ api_key = os.environ.get("OPENAI_API_KEY")
77
+ if not api_key:
78
+ raise ValueError(
79
+ "OPENAI_API_KEY environment variable required for OpenAI transcription"
80
+ )
81
+
82
+ client = OpenAI(api_key=api_key)
83
+
84
+ file_size = os.path.getsize(audio_path)
85
+ if file_size > 25 * 1024 * 1024:
86
+ return _transcribe_openai_chunked(client, audio_path, language)
87
+
88
+ with open(audio_path, "rb") as f:
89
+ response = client.audio.transcriptions.create(
90
+ model="whisper-1",
91
+ file=f,
92
+ language=language,
93
+ response_format="verbose_json",
94
+ timestamp_granularities=["word"]
95
+ )
96
+
97
+ words = []
98
+ if hasattr(response, "words") and response.words:
99
+ for w in response.words:
100
+ words.append(Word(
101
+ text=w.word if hasattr(w, "word") else w.get("word", ""),
102
+ start=w.start if hasattr(w, "start") else w.get("start", 0),
103
+ end=w.end if hasattr(w, "end") else w.get("end", 0),
104
+ confidence=1.0
105
+ ))
106
+
107
+ duration = words[-1].end if words else 0.0
108
+
109
+ return TranscriptResult(
110
+ text=response.text if hasattr(response, "text") else str(response),
111
+ words=words,
112
+ language=language,
113
+ duration=duration,
114
+ provider="openai"
115
+ )
116
+
117
+
118
+ def _transcribe_openai_chunked(
119
+ client,
120
+ audio_path: str,
121
+ language: str,
122
+ chunk_duration: int = 600
123
+ ) -> TranscriptResult:
124
+ """Transcribe long audio by chunking."""
125
+ import subprocess
126
+ import tempfile
127
+
128
+ result = subprocess.run(
129
+ ["ffprobe", "-v", "quiet", "-show_entries", "format=duration",
130
+ "-of", "default=noprint_wrappers=1:nokey=1", audio_path],
131
+ capture_output=True, text=True
132
+ )
133
+ total_duration = float(result.stdout.strip())
134
+
135
+ all_words: List[Word] = []
136
+ all_text_parts: List[str] = []
137
+
138
+ offset = 0.0
139
+
140
+ while offset < total_duration:
141
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
142
+ chunk_path = tmp.name
143
+
144
+ subprocess.run([
145
+ "ffmpeg", "-y",
146
+ "-ss", str(offset),
147
+ "-i", audio_path,
148
+ "-t", str(chunk_duration),
149
+ "-acodec", "pcm_s16le",
150
+ "-ar", "16000",
151
+ "-ac", "1",
152
+ chunk_path
153
+ ], capture_output=True)
154
+
155
+ with open(chunk_path, "rb") as f:
156
+ response = client.audio.transcriptions.create(
157
+ model="whisper-1",
158
+ file=f,
159
+ language=language,
160
+ response_format="verbose_json",
161
+ timestamp_granularities=["word"]
162
+ )
163
+
164
+ if hasattr(response, "words") and response.words:
165
+ for w in response.words:
166
+ word_start = (w.start if hasattr(w, "start") else w.get("start", 0)) + offset
167
+ word_end = (w.end if hasattr(w, "end") else w.get("end", 0)) + offset
168
+ all_words.append(Word(
169
+ text=w.word if hasattr(w, "word") else w.get("word", ""),
170
+ start=word_start,
171
+ end=word_end,
172
+ confidence=1.0
173
+ ))
174
+
175
+ all_text_parts.append(response.text if hasattr(response, "text") else str(response))
176
+
177
+ os.unlink(chunk_path)
178
+ offset += chunk_duration
179
+
180
+ return TranscriptResult(
181
+ text=" ".join(all_text_parts),
182
+ words=all_words,
183
+ language=language,
184
+ duration=total_duration,
185
+ provider="openai"
186
+ )
187
+
188
+
189
+ def _transcribe_local(audio_path: str, language: str) -> TranscriptResult:
190
+ """Transcribe using local faster-whisper."""
191
+ try:
192
+ from faster_whisper import WhisperModel
193
+ except ImportError:
194
+ raise ImportError(
195
+ "faster-whisper package required for local transcription. "
196
+ "Install with: pip install faster-whisper"
197
+ )
198
+
199
+ model_size = os.environ.get("WHISPER_MODEL", "small")
200
+ device = os.environ.get("WHISPER_DEVICE", "auto")
201
+ compute_type = os.environ.get("WHISPER_COMPUTE_TYPE", "auto")
202
+
203
+ model = WhisperModel(model_size, device=device, compute_type=compute_type)
204
+
205
+ segments, info = model.transcribe(
206
+ audio_path,
207
+ language=language,
208
+ word_timestamps=True
209
+ )
210
+
211
+ words: List[Word] = []
212
+ text_parts: List[str] = []
213
+
214
+ for segment in segments:
215
+ text_parts.append(segment.text)
216
+ if segment.words:
217
+ for w in segment.words:
218
+ words.append(Word(
219
+ text=w.word,
220
+ start=w.start,
221
+ end=w.end,
222
+ confidence=w.probability if hasattr(w, "probability") else 1.0
223
+ ))
224
+
225
+ duration = words[-1].end if words else 0.0
226
+
227
+ return TranscriptResult(
228
+ text=" ".join(text_parts),
229
+ words=words,
230
+ language=info.language if hasattr(info, "language") else language,
231
+ duration=duration,
232
+ provider="local"
233
+ )