@thierrynakoa/fire-flow 12.2.1 → 13.0.1

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 (500) hide show
  1. package/CREDITS.md +25 -0
  2. package/DOMINION-FLOW-OVERVIEW.md +182 -38
  3. package/README.md +399 -455
  4. package/TROUBLESHOOTING.md +264 -264
  5. package/agents/fire-debugger.md +54 -0
  6. package/agents/fire-executor.md +1610 -1033
  7. package/agents/fire-fact-checker.md +1 -1
  8. package/agents/fire-planner.md +85 -17
  9. package/agents/fire-project-researcher.md +1 -1
  10. package/agents/fire-researcher.md +4 -22
  11. package/agents/{fire-phoenix-analyst.md → fire-resurrection-analyst.md} +394 -394
  12. package/agents/fire-reviewer.md +552 -499
  13. package/agents/fire-verifier.md +114 -19
  14. package/bin/cli.js +18 -101
  15. package/commands/fire-0-orient.md +2 -2
  16. package/commands/fire-1a-new.md +50 -15
  17. package/commands/fire-1c-setup.md +33 -5
  18. package/commands/fire-1d-discuss.md +87 -1
  19. package/commands/fire-2-plan.md +556 -527
  20. package/commands/fire-3-execute.md +2046 -1356
  21. package/commands/fire-4-verify.md +975 -906
  22. package/commands/fire-5-handoff.md +46 -5
  23. package/commands/fire-6-resume.md +2 -31
  24. package/commands/fire-add-new-skill.md +138 -19
  25. package/commands/fire-autonomous.md +14 -2
  26. package/commands/fire-complete-milestone.md +1 -1
  27. package/commands/fire-cost.md +179 -183
  28. package/commands/fire-debug.md +1 -6
  29. package/commands/fire-loop-resume.md +2 -2
  30. package/commands/fire-loop-stop.md +1 -1
  31. package/commands/fire-loop.md +2 -15
  32. package/commands/fire-map-codebase.md +1 -1
  33. package/commands/fire-migrate-database.md +548 -0
  34. package/commands/fire-new-milestone.md +1 -1
  35. package/commands/fire-reflect.md +1 -2
  36. package/commands/fire-research.md +142 -21
  37. package/commands/{fire-phoenix.md → fire-resurrect.md} +859 -603
  38. package/commands/fire-scaffold.md +297 -0
  39. package/commands/fire-search.md +1 -2
  40. package/commands/fire-security-scan.md +483 -484
  41. package/commands/fire-setup.md +359 -0
  42. package/commands/fire-skill.md +770 -0
  43. package/commands/fire-skills-diff.md +506 -506
  44. package/commands/fire-skills-history.md +388 -388
  45. package/commands/fire-skills-rollback.md +7 -7
  46. package/commands/fire-skills-sync.md +470 -470
  47. package/commands/fire-test.md +5 -5
  48. package/commands/fire-todos.md +1 -1
  49. package/commands/fire-update.md +5 -5
  50. package/commands/fire-validate-skills.md +282 -0
  51. package/commands/fire-vuln-scan.md +492 -493
  52. package/hooks/run-hook.sh +8 -8
  53. package/hooks/run-session-end.sh +7 -7
  54. package/hooks/session-end.sh +90 -90
  55. package/hooks/session-start.sh +1 -1
  56. package/package.json +4 -24
  57. package/plugin.json +7 -7
  58. package/references/autonomy-levels.md +235 -0
  59. package/references/behavioral-directives.md +95 -3
  60. package/references/blocker-tracking.md +1 -1
  61. package/references/circuit-breaker.md +93 -2
  62. package/references/context-engineering.md +227 -9
  63. package/references/honesty-protocols.md +70 -1
  64. package/references/issue-to-pr-pipeline.md +149 -150
  65. package/references/metrics-and-trends.md +1 -2
  66. package/references/research-improvements.md +4 -108
  67. package/references/sdlc-mapping.md +73 -0
  68. package/references/state-machine.md +151 -0
  69. package/skills-library/AVAILABLE_TOOLS_REFERENCE.md +333 -0
  70. package/skills-library/SKILLS-INDEX.md +57 -558
  71. package/skills-library/SKILLS_LIBRARY_INDEX.md +532 -0
  72. package/skills-library/_general/api-patterns/api-field-name-mismatch.md +107 -0
  73. package/skills-library/_general/api-patterns/streaming-command-timeout.md +122 -0
  74. package/skills-library/_general/api-patterns/streaming-proxy-cors-bypass.md +102 -0
  75. package/skills-library/_general/automation/settings-gui-generator.md +172 -0
  76. package/skills-library/_general/database-solutions/data-type-mapping-reference.md +181 -0
  77. package/skills-library/_general/database-solutions/mysql-limit-offset-string-coercion.md +102 -0
  78. package/skills-library/_general/database-solutions/mysql-to-pg-migration.md +195 -0
  79. package/skills-library/_general/database-solutions/orm-schema-portability.md +193 -0
  80. package/skills-library/_general/database-solutions/persistent-analysis-storage.md +207 -0
  81. package/skills-library/_general/database-solutions/pg-to-mysql-schema-migration-methodology.md +190 -0
  82. package/skills-library/_general/database-solutions/sql-dialect-compatibility-matrix.md +306 -0
  83. package/skills-library/_general/database-solutions/sqlite-to-pg-migration.md +219 -0
  84. package/skills-library/_general/frontend/canvas-bubble-animation-grouping.md +270 -0
  85. package/skills-library/_general/frontend/color-token-migration.md +112 -0
  86. package/skills-library/_general/frontend/framer-motion-layoutid-grouping.md +150 -0
  87. package/skills-library/_general/frontend/pyqt6-settings-dialog.md +191 -0
  88. package/skills-library/_general/frontend/react-flow-animated-layout-switching.md +101 -0
  89. package/skills-library/_general/frontend/react-hooks-order-debugging.md +141 -0
  90. package/skills-library/_general/frontend/redux-localstorage-auth-desync.md +126 -0
  91. package/skills-library/_general/frontend/safari-csp-theme-color-debugging.md +124 -0
  92. package/skills-library/_general/frontend/safari-sw-cache-poisoning.md +138 -0
  93. package/skills-library/_general/frontend/svg-sparkline-no-charting-library.md +131 -0
  94. package/skills-library/_general/growth-marketing/oss-daily-growth-intelligence.md +224 -0
  95. package/skills-library/_general/integrations/claude-code-local-mcp-integration.md +250 -0
  96. package/skills-library/_general/integrations/mcp-composite-tool-orchestration.md +200 -0
  97. package/skills-library/_general/methodology/AGENT_SDK_STANDALONE_TOOLING.md +181 -0
  98. package/skills-library/_general/methodology/AGENT_TEAMS_GUIDE.md +169 -0
  99. package/skills-library/_general/methodology/ALAS_STATEFUL_EXECUTION.md +207 -0
  100. package/skills-library/_general/methodology/AUTO_REVIEWER_SUBAGENT.md +211 -0
  101. package/skills-library/_general/methodology/CONSISTENCY_CHECK_AMBIGUITY_GATE.md +96 -0
  102. package/skills-library/_general/methodology/DEAD_ENDS_SHELF.md +4 -4
  103. package/skills-library/_general/methodology/DISTILL_NOT_DUMP.md +108 -0
  104. package/skills-library/_general/methodology/EXECUTION_PROGRESS_MONITOR.md +157 -0
  105. package/skills-library/_general/methodology/HIERARCHICAL_REVIEW_MARS.md +122 -0
  106. package/skills-library/_general/methodology/MCP_INTER_AGENT_BRIDGE.md +207 -0
  107. package/skills-library/_general/methodology/MERMAID_WIZARD_DIAGRAMS.md +77 -0
  108. package/skills-library/_general/methodology/MISSING_DIMENSION_DETECTOR.md +89 -0
  109. package/skills-library/_general/methodology/MULTI_AGENT_COORDINATION.md +397 -0
  110. package/skills-library/_general/methodology/OBSERVATION_MASKING.md +100 -0
  111. package/skills-library/_general/methodology/PHOENIX_REBUILD_METHODOLOGY.md +82 -11
  112. package/skills-library/_general/methodology/REVIEW_BACKTRACK_PANEL.md +140 -0
  113. package/skills-library/_general/methodology/REVIEW_FIX_LOOP.md +117 -0
  114. package/skills-library/_general/methodology/VOTING_VERDICT_ARBITRATION.md +155 -0
  115. package/skills-library/_general/methodology/ZERO_FRICTION_CLI_SETUP.md +2 -2
  116. package/skills-library/_general/methodology/dead-code-activation.md +123 -0
  117. package/skills-library/_general/methodology/debug-swarm-researcher-escape-hatch.md +240 -240
  118. package/skills-library/_general/methodology/shell-autonomous-loop-fixplan.md +1 -1
  119. package/skills-library/_general/patterns-standards/GOF_DESIGN_PATTERNS_FOR_AI_AGENTS.md +5 -5
  120. package/skills-library/_general/patterns-standards/cascading-failure-diagnosis.md +119 -0
  121. package/skills-library/_general/patterns-standards/domain-specific-layout-algorithms.md +209 -0
  122. package/skills-library/_general/patterns-standards/python-desktop-app-architecture.md +399 -0
  123. package/skills-library/_general/patterns-standards/realtime-monitoring-dashboard.md +457 -0
  124. package/skills-library/_general/patterns-standards/togglable-processing-pipeline.md +169 -0
  125. package/skills-library/_general/performance/liveclock-extraction.md +112 -0
  126. package/skills-library/_general/performance/ref-based-canvas-animation.md +117 -0
  127. package/skills-library/_general/performance/use-visible-interval.md +131 -0
  128. package/skills-library/_general/testing/playwright-firefox-withcredentials-auth-issue.md +104 -0
  129. package/skills-library/_quarantine/README.md +30 -0
  130. package/skills-library/api-patterns/BROADCAST_SCHEDULER_SHARED_EXECUTE_FUNCTION.md +150 -0
  131. package/skills-library/api-patterns/ERROR_RESPONSE_STANDARDS.md +145 -0
  132. package/skills-library/api-patterns/EXPRESS_ROUTE_ORDERING_MIDDLEWARE_INTERCEPTION.md +326 -0
  133. package/skills-library/api-patterns/PAGINATION_PATTERNS.md +137 -0
  134. package/skills-library/api-patterns/PODCAST_PROGRESS_TRACKING_THREE_ROOT_CAUSES.md +277 -0
  135. package/skills-library/api-patterns/RATE_LIMITING_TOGGLE.md +155 -0
  136. package/skills-library/api-patterns/graphql-content-queries.md +708 -0
  137. package/skills-library/appointment-scheduler-design.md +423 -0
  138. package/skills-library/automation/AUTO_POPULATE_COMPLETE_GUIDE.md +631 -0
  139. package/skills-library/automation/CC_WORKFLOW_STUDIO.md +83 -0
  140. package/skills-library/automation/CLAUDE_CODE_SWARM_MODE.md +95 -0
  141. package/skills-library/automation/DAEMON_TRIGGER_FILE_IPC.md +195 -0
  142. package/skills-library/automation/scheduled-content-publishing.md +608 -0
  143. package/skills-library/awesome-workflows/Blogging-Platform-Instructions/view_commands.md +25 -0
  144. package/skills-library/awesome-workflows/CREDENTIAL-SECURITY-WORKFLOW.md +109 -0
  145. package/skills-library/awesome-workflows/DEBUGGING-WORKFLOW.md +124 -0
  146. package/skills-library/awesome-workflows/Design-Review-Workflow/README.md +31 -0
  147. package/skills-library/awesome-workflows/Design-Review-Workflow/design-principles-example.md +129 -0
  148. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-agent.md +107 -0
  149. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-claude-md-snippet.md +24 -0
  150. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-slash-command.md +38 -0
  151. package/skills-library/awesome-workflows/PARALLEL-RESEARCH-WORKFLOW.md +89 -0
  152. package/skills-library/awesome-workflows/PHASE-EXECUTION-WORKFLOW.md +97 -0
  153. package/skills-library/awesome-workflows/SESSION-HANDOFF-WORKFLOW.md +116 -0
  154. package/skills-library/cms-patterns/content-branch-preview.md +515 -0
  155. package/skills-library/cms-patterns/inline-visual-editing.md +666 -0
  156. package/skills-library/cms-patterns/mdx-component-content.md +649 -0
  157. package/skills-library/cms-patterns/media-manager-abstraction.md +827 -0
  158. package/skills-library/cms-patterns/schema-driven-form-generator.md +838 -0
  159. package/skills-library/complexity-metrics/complexity-divider.md +707 -0
  160. package/skills-library/complexity-metrics/work-with-complexity.md +193 -0
  161. package/skills-library/creative-multimedia/animation-stack-guide.md +577 -0
  162. package/skills-library/creative-multimedia/audio-enhancement-pipeline.md +625 -0
  163. package/skills-library/creative-multimedia/content-repurposing-pipeline.md +1146 -0
  164. package/skills-library/creative-multimedia/data-visualization-generator.md +862 -0
  165. package/skills-library/creative-multimedia/doc-to-podcast-pipeline.md +2184 -0
  166. package/skills-library/creative-multimedia/ffmpeg-command-generator.md +405 -0
  167. package/skills-library/creative-multimedia/image-optimization-pipeline.md +605 -0
  168. package/skills-library/creative-multimedia/multi-format-content-generator.md +1759 -0
  169. package/skills-library/creative-multimedia/og-image-generator.md +635 -0
  170. package/skills-library/creative-multimedia/podcast-audio-composition.md +1355 -0
  171. package/skills-library/creative-multimedia/podcast-quality-evaluation.md +1452 -0
  172. package/skills-library/creative-multimedia/podcast-script-generation.md +1841 -0
  173. package/skills-library/creative-multimedia/svg-generation.md +750 -0
  174. package/skills-library/creative-multimedia/text-to-speech-provider-selector.md +1414 -0
  175. package/skills-library/creative-multimedia/transcription-pipeline-selector.md +677 -0
  176. package/skills-library/creative-multimedia/video-streaming-setup.md +559 -0
  177. package/skills-library/database-solutions/AI_RESPONSE_DATABASE_CACHING.md +520 -0
  178. package/skills-library/database-solutions/CONDITIONAL_SQL_MIGRATION_PATTERN.md +119 -0
  179. package/skills-library/database-solutions/DATABASE_COLUMN_NAME_MISMATCH.md +393 -0
  180. package/skills-library/database-solutions/DATABASE_SCHEMA.md +394 -0
  181. package/skills-library/database-solutions/DATABASE_SCHEMA_VERIFICATION_GUIDE.md +348 -0
  182. package/skills-library/database-solutions/DATABASE_STRATEGY.md +71 -0
  183. package/skills-library/database-solutions/ES_MODULE_SEED_SCRIPT_PATTERN.md +52 -0
  184. package/skills-library/database-solutions/MIGRATION_GUIDE.md +3 -0
  185. package/skills-library/database-solutions/PLPGSQL_VARIABLE_CONFLICT_FIX.md +208 -0
  186. package/skills-library/database-solutions/POSTGRESQL_JSONB_DOUBLE_STRINGIFY_FIX.md +245 -0
  187. package/skills-library/database-solutions/POSTGRESQL_LICENSE_TABLE_DESIGN.md +393 -0
  188. package/skills-library/database-solutions/POSTGRESQL_UUID_DOCUMENT_RAG_DUAL_SCOPE.md +732 -0
  189. package/skills-library/database-solutions/POSTGRES_SQL_TEMPLATE_BINDING_ERROR.md +240 -0
  190. package/skills-library/database-solutions/PRISMA_DB_PUSH_DATA_LOSS_PREVENTION.md +141 -0
  191. package/skills-library/database-solutions/PRODUCTION_QUERY_OPTIMIZATION_RESTART_FIX.md +389 -0
  192. package/skills-library/database-solutions/RLS_SECURITY_GUIDE.md +107 -0
  193. package/skills-library/database-solutions/SCHEMA_ENHANCEMENTS_GUIDE.md +373 -0
  194. package/skills-library/database-solutions/SCHEMA_MIGRATION_GUIDE.md +368 -0
  195. package/skills-library/database-solutions/SCHEMA_VERIFICATION_QUICK_REFERENCE.md +104 -0
  196. package/skills-library/database-solutions/ai-erd-generator.md +1213 -0
  197. package/skills-library/database-solutions/content-publishing-states.md +631 -0
  198. package/skills-library/database-solutions/database-schema-designer.md +522 -0
  199. package/skills-library/database-solutions/er-diagram-components.md +569 -0
  200. package/skills-library/database-solutions/er-to-ddl-mapping.md +1405 -0
  201. package/skills-library/database-solutions/erd-creator-textbook-research.md +433 -0
  202. package/skills-library/database-solutions/erd-react-flow-architecture.md +1965 -0
  203. package/skills-library/database-solutions/mariadb-aggregate-function-replacement.md +145 -0
  204. package/skills-library/database-solutions/normalization-validator.md +778 -0
  205. package/skills-library/database-solutions/postgres-full-text-search-content.md +494 -0
  206. package/skills-library/database-solutions/postgresql-to-mysql-runtime-translation.md +286 -0
  207. package/skills-library/database-solutions/regex-alternation-ordering-sql-types.md +92 -0
  208. package/skills-library/database-solutions/reserved-word-context-aware-quoting.md +142 -0
  209. package/skills-library/database-solutions/sql-ddl-generator.md +756 -0
  210. package/skills-library/database-solutions/supabase-connection-pooler-fix.md +102 -0
  211. package/skills-library/deployment-security/CPANEL_NODE_DEPLOYMENT.md +166 -0
  212. package/skills-library/deployment-security/DEPLOYMENT.md +275 -0
  213. package/skills-library/deployment-security/DEPLOYMENT_CHECKLIST.md +363 -0
  214. package/skills-library/deployment-security/DEPLOYMENT_PLAN.md +669 -0
  215. package/skills-library/deployment-security/KNEX_DATABASE_ABSTRACTION.md +444 -0
  216. package/skills-library/deployment-security/LICENSE_KEY_SYSTEM.md +206 -0
  217. package/skills-library/deployment-security/NODE18_DEPENDENCY_COMPATIBILITY.md +284 -0
  218. package/skills-library/deployment-security/PHP_INSTALLER_WIZARD_GUIDE.md +315 -0
  219. package/skills-library/deployment-security/PM2_ENVIRONMENT_VARIABLE_CACHING.md +256 -0
  220. package/skills-library/deployment-security/PM2_MEMORY_EXHAUSTION_FIX.md +370 -0
  221. package/skills-library/deployment-security/PRODUCTION_DEPLOYMENT_GUIDE.md +592 -0
  222. package/skills-library/deployment-security/PRODUCTION_HARDENING_DOCUMENTATION.md +307 -0
  223. package/skills-library/deployment-security/PRODUCTION_RECOVERY_CHERRY_PICK_PATTERN.md +202 -0
  224. package/skills-library/deployment-security/PYINSTALLER_CUDA_WHISPER_BUNDLING.md +236 -0
  225. package/skills-library/deployment-security/SECURITY.md +41 -0
  226. package/skills-library/deployment-security/SMTP_SSL_HOSTNAME_MISMATCH_SHARED_HOSTING.md +220 -0
  227. package/skills-library/deployment-security/SPA_SEO_OPTIMIZATION_CPANEL.md +200 -0
  228. package/skills-library/deployment-security/SUPABASE_EDGE_FUNCTIONS.md +338 -0
  229. package/skills-library/deployment-security/VERCEL_GITHUB_DEPLOYMENT_GUIDE.md +858 -0
  230. package/skills-library/deployment-security/VPS_DEPLOYMENT_READINESS.md +356 -0
  231. package/skills-library/deployment-security/deployment-changes-not-applying.md +241 -0
  232. package/skills-library/deployment-security/env-file-management-production-local.md +203 -0
  233. package/skills-library/deployment-security/express-secure-file-downloads.md +413 -0
  234. package/skills-library/deployment-security/react-production-deployment-desktop-guide.md +2011 -0
  235. package/skills-library/deployment-security/self-hosted-supabase-coolify-guide.md +1684 -0
  236. package/skills-library/deployment-security/unique-features-ai-strategy-plaid-security.md +1613 -0
  237. package/skills-library/deployment-security/vps-deployment.md +135 -0
  238. package/skills-library/document-processing/WORD_EXPORT_MARKDOWN_FORMATTING.md +482 -0
  239. package/skills-library/document-processing/document-ai-landingai-integration.md +677 -0
  240. package/skills-library/document-processing/express-secure-file-downloads-mern.md +413 -0
  241. package/skills-library/document-processing/express-secure-file-downloads.md +413 -0
  242. package/skills-library/document-processing/md-to-word-converter.md +318 -0
  243. package/skills-library/document-processing/pdf-forms-integration/README.md +101 -0
  244. package/skills-library/document-processing/pdf-forms-integration/SKILL.md +662 -0
  245. package/skills-library/ecommerce/ADMIN_PRODUCTS_GUIDE.md +428 -0
  246. package/skills-library/ecommerce/ECOMMERCE_API_REFERENCE.md +776 -0
  247. package/skills-library/ecommerce/ECOMMERCE_COMPLETION_SUMMARY.md +673 -0
  248. package/skills-library/ecommerce/ECOMMERCE_IMPLEMENTATION_GUIDE.md +729 -0
  249. package/skills-library/ecommerce/ECOMMERCE_QUICK_REFERENCE.md +521 -0
  250. package/skills-library/ecommerce/ECOMMERCE_TESTING_CHECKLIST.md +565 -0
  251. package/skills-library/ecommerce/ECOMMERCE_WORKFLOW_GUIDE.md +1059 -0
  252. package/skills-library/ecommerce/PRODUCT_CREATION_EXPANDED.md +522 -0
  253. package/skills-library/ecommerce/agentic-commerce-protocol.md +203 -0
  254. package/skills-library/ecommerce/cart-abandonment-recovery.md +236 -0
  255. package/skills-library/ecommerce/cart-architecture-patterns.md +300 -0
  256. package/skills-library/ecommerce/cart-item-count-indicator.md +264 -0
  257. package/skills-library/ecommerce/checkout-ux-conversion.md +227 -0
  258. package/skills-library/ecommerce/composable-commerce-selection.md +166 -0
  259. package/skills-library/ecommerce/ecommerce-analytics-patterns.md +167 -0
  260. package/skills-library/ecommerce/fraud-detection-patterns.md +179 -0
  261. package/skills-library/ecommerce/inventory-stock-management.md +270 -0
  262. package/skills-library/ecommerce/order-saga-state-machine.md +336 -0
  263. package/skills-library/ecommerce/payment-provider-abstraction.md +245 -0
  264. package/skills-library/ecommerce/pci-compliance-checklist.md +192 -0
  265. package/skills-library/ecommerce/refund-chargeback-handling.md +177 -0
  266. package/skills-library/ecommerce/shipping-carrier-integration.md +218 -0
  267. package/skills-library/ecommerce/webhook-idempotency-patterns.md +253 -0
  268. package/skills-library/excalidraw-diagrams/.github/workflows/ci.yml +558 -0
  269. package/skills-library/excalidraw-diagrams/.github/workflows/prompt-gallery.yml +448 -0
  270. package/skills-library/excalidraw-diagrams/.github/workflows/release.yml +42 -0
  271. package/skills-library/excalidraw-diagrams/.github/workflows/test-reusable-ci.yml +25 -0
  272. package/skills-library/excalidraw-diagrams/CLAUDE.md +57 -0
  273. package/skills-library/excalidraw-diagrams/LICENSE +21 -0
  274. package/skills-library/excalidraw-diagrams/README.md +178 -0
  275. package/skills-library/excalidraw-diagrams/SKILL.md +715 -0
  276. package/skills-library/form-solutions/BUTTON_TYPE_FORM_SUBMISSION.md +336 -0
  277. package/skills-library/form-solutions/FILLABLE_PDF_IMPLEMENTATION.md +226 -0
  278. package/skills-library/form-solutions/SURVEYJS_QUESTIONNAIRE_SYSTEM.md +367 -0
  279. package/skills-library/form-solutions/tiptap-minimal-setup.md +690 -0
  280. package/skills-library/frontend/scholarly-classification-bubble-map.md +149 -0
  281. package/skills-library/infrastructure/ci-cd-pipeline-builder.md +517 -0
  282. package/skills-library/infrastructure/observability-designer.md +264 -0
  283. package/skills-library/infrastructure/performance-profiler.md +621 -0
  284. package/skills-library/installer-wizard-patterns.md +249 -0
  285. package/skills-library/integrations/CLAUDE_CODE_TOKEN_ANALYTICS.md +160 -0
  286. package/skills-library/integrations/CONFIGURABLE_AI_PROVIDER_SELECTION.md +728 -0
  287. package/skills-library/integrations/SOCKET_IO_BROADCAST_ALL_VS_ROOM.md +141 -0
  288. package/skills-library/integrations/VIRTUAL_MEETINGS_IMPLEMENTATION.md +374 -0
  289. package/skills-library/integrations/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
  290. package/skills-library/integrations/YOUTUBE_API_SETUP.md +141 -0
  291. package/skills-library/integrations/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
  292. package/skills-library/integrations/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
  293. package/skills-library/integrations/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
  294. package/skills-library/integrations/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
  295. package/skills-library/integrations/ai-ml/GEMINI_AI_RAG_PIPELINE_COMPLETE_GUIDE.md +195 -0
  296. package/skills-library/integrations/ai-ml/GEMINI_IMAGE_GENERATION_SETUP.md +64 -0
  297. package/skills-library/integrations/cloudflare/cloudflare-turnstile-debugging.md +202 -0
  298. package/skills-library/integrations/cloudflare/cloudflare-turnstile-implementation.md +476 -0
  299. package/skills-library/integrations/cloudflare-turnstile-debugging.md +202 -0
  300. package/skills-library/integrations/cloudflare-turnstile-implementation.md +476 -0
  301. package/skills-library/integrations/ghost-creator-monetization-pattern.md +454 -0
  302. package/skills-library/integrations/headless-cms-architecture.md +484 -0
  303. package/skills-library/integrations/headless-cms-stack-selection.md +183 -0
  304. package/skills-library/integrations/payload-cms-patterns.md +674 -0
  305. package/skills-library/integrations/realtimestt-openwakeword-cuda-windows.md +229 -0
  306. package/skills-library/integrations/rss-podcast-integration.md +300 -0
  307. package/skills-library/integrations/wordpress/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
  308. package/skills-library/integrations/youtube/YOUTUBE_API_SETUP.md +141 -0
  309. package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
  310. package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
  311. package/skills-library/integrations/youtube/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
  312. package/skills-library/integrations/youtube/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
  313. package/skills-library/marketing/campaign-analytics.md +97 -0
  314. package/skills-library/marketing/content-creator.md +105 -0
  315. package/skills-library/marketing/marketing-strategy-pmm.md +94 -0
  316. package/skills-library/marketing/social-media-analyzer.md +81 -0
  317. package/skills-library/methodology/ADVANCED_ORCHESTRATION_PATTERNS.md +401 -0
  318. package/skills-library/methodology/AGENT_SELF_IMPROVEMENT_LOOP.md +179 -0
  319. package/skills-library/methodology/BREATH_BASED_PARALLEL_EXECUTION.md +1 -1
  320. package/skills-library/methodology/CLEANSING_CYCLE.md +358 -0
  321. package/skills-library/methodology/CONFIDENCE_ANNOTATION_PATTERN.md +143 -0
  322. package/skills-library/methodology/CRITICAL_PATTERNS_DOCUMENTATION_COMPLETE.md +204 -0
  323. package/skills-library/methodology/DELIVERABLES_SUMMARY.md +341 -0
  324. package/skills-library/methodology/DIFFICULTY_AWARE_AGENT_ROUTING.md +252 -0
  325. package/skills-library/methodology/EVOLUTIONARY_SKILL_SYNTHESIS.md +219 -0
  326. package/skills-library/methodology/GLOMERULUS_DECISION_GATE.md +223 -0
  327. package/skills-library/methodology/HIBERNATION_SYSTEM.md +231 -0
  328. package/skills-library/methodology/INSTRUMENTATION_OVER_RESTRICTION.md +192 -0
  329. package/skills-library/methodology/MASTER_COMPLETION_SUMMARY.md +444 -0
  330. package/skills-library/methodology/MASTER_SESSION_COMPLETION.md +743 -0
  331. package/skills-library/methodology/MERN_QUICK_REFERENCE.md +358 -0
  332. package/skills-library/methodology/ORGAN_AGENT_MAPPING.md +177 -0
  333. package/skills-library/methodology/PARALLEL_WAVE_BASED_REFACTORING.md +440 -0
  334. package/skills-library/methodology/QUICK_REFERENCE.md +358 -0
  335. package/skills-library/methodology/SDFT_ONPOLICY_SELF_DISTILLATION.md +186 -0
  336. package/skills-library/methodology/SELF_QUESTIONING_TASK_GENERATION.md +270 -0
  337. package/skills-library/methodology/SESSION_COMPLETION_SUMMARY.md +304 -0
  338. package/skills-library/methodology/SESSION_SUMMARY.md +432 -0
  339. package/skills-library/methodology/WARRIOR_WORKFLOW_DEBUGGING_PROTOCOL.md +252 -0
  340. package/skills-library/methodology/tech-debt-tracker.md +570 -0
  341. package/skills-library/parallel-debug/SKILL.md +60 -0
  342. package/skills-library/patterns-standards/API_PATTERN_FIX_SUMMARY.md +236 -0
  343. package/skills-library/patterns-standards/BATCH_OPERATIONS_WITH_PROGRESS_MODAL.md +362 -0
  344. package/skills-library/patterns-standards/CRITICAL_CODING_PATTERNS.md +639 -0
  345. package/skills-library/patterns-standards/DARK_MODE_MODAL_VISIBILITY.md +258 -0
  346. package/skills-library/patterns-standards/ERROR_RESILIENCE_IMPLEMENTATION.md +375 -0
  347. package/skills-library/patterns-standards/ES_MODULE_IMPORT_HOISTING_DOTENV.md +298 -0
  348. package/skills-library/patterns-standards/NESTED_BACKDROP_FILTER_CSS_ARTIFACT_FIX.md +76 -0
  349. package/skills-library/patterns-standards/ORDERED_DETECTOR_PIPELINE_GRACEFUL_FALLBACK.md +333 -0
  350. package/skills-library/patterns-standards/PHASE_IMPORT_ERROR_DEBUGGING.md +271 -0
  351. package/skills-library/patterns-standards/PYNPUT_GLOBAL_HOTKEY_VK_MATCHING.md +252 -0
  352. package/skills-library/patterns-standards/REACT_USEEFFECT_CASCADE_RESET_FIX.md +132 -0
  353. package/skills-library/patterns-standards/SUBMENU_HOVER_DROPDOWN_PATTERN.md +225 -0
  354. package/skills-library/patterns-standards/TAILWIND_TEXT_VISIBILITY_OVERRIDE.md +322 -0
  355. package/skills-library/patterns-standards/THEME_AWARE_CSS_VARIABLES_PATTERN.md +209 -0
  356. package/skills-library/patterns-standards/THEME_USER_OBJECT_PROPERTY_NAMING.md +194 -0
  357. package/skills-library/patterns-standards/TOOLTIP_BLOCKING_CLICKS_FIX.md +267 -0
  358. package/skills-library/patterns-standards/claude-code-plugin-structure.md +235 -0
  359. package/skills-library/patterns-standards/react-i18next-setup.md +429 -0
  360. package/skills-library/patterns-standards/thesys-c1-generative-ui-integration.md +967 -0
  361. package/skills-library/plugin-development/CLAUDE_CODE_COMMAND_REGISTRATION_SILENT_FAILURE.md +315 -0
  362. package/skills-library/plugin-development/plugin-command-namespace-vs-global.md +390 -0
  363. package/skills-library/plugin-development/plugin-doc-auto-generation.md +172 -0
  364. package/skills-library/security/GITHUB_REPO_SECURITY_AUDIT.md +115 -0
  365. package/skills-library/security/admin-deletion-safety.md +396 -0
  366. package/skills-library/security/application-vuln-patterns.md +477 -0
  367. package/skills-library/security/env-secrets-manager.md +686 -0
  368. package/skills-library/security/secure-ai-application-templates.md +347 -0
  369. package/skills-library/security/sql-injection-prevention-postgresjs.md +151 -0
  370. package/skills-library/supabase-connection-pooler-fix.md +102 -0
  371. package/skills-library/system-context/POWERSHELL_BASH_INTEROP.md +82 -0
  372. package/skills-library/system-context/SERVICE_LIFECYCLE_MANAGEMENT.md +119 -0
  373. package/skills-library/system-context/SKILL.md +40 -0
  374. package/skills-library/system-context/WINDOWS_DEV_ENVIRONMENT.md +73 -0
  375. package/skills-library/testing/E2E_PLAYWRIGHT_PATTERNS.md +99 -0
  376. package/skills-library/testing/INTEGRATION_TEST_STRATEGY.md +82 -0
  377. package/skills-library/testing/RED_GREEN_BUGFIX_GATE.md +203 -0
  378. package/skills-library/testing/TEST_DATA_MANAGEMENT.md +69 -0
  379. package/skills-library/testing/VITEST_UNIT_TEST_PATTERNS.md +75 -0
  380. package/skills-library/testing/playwright-api-security-tests.md +202 -0
  381. package/skills-library/toolbox/SKILL.md +84 -0
  382. package/skills-library/toolbox/code-graph-and-web-scraping-mcps.md +237 -0
  383. package/skills-library/ui-ux-pro-max/ACCESSIBILITY_ESSENTIALS.md +115 -0
  384. package/skills-library/ui-ux-pro-max/DESIGN_SYSTEM_SCAFFOLDING.md +133 -0
  385. package/skills-library/ui-ux-pro-max/RESPONSIVE_LAYOUT_PATTERNS.md +119 -0
  386. package/skills-library/ui-ux-pro-max/SKILL.md +386 -0
  387. package/skills-library/ui-ux-pro-max/data/charts.csv +26 -0
  388. package/skills-library/ui-ux-pro-max/data/colors.csv +97 -0
  389. package/skills-library/ui-ux-pro-max/data/icons.csv +101 -0
  390. package/skills-library/ui-ux-pro-max/data/landing.csv +31 -0
  391. package/skills-library/ui-ux-pro-max/data/products.csv +97 -0
  392. package/skills-library/ui-ux-pro-max/data/react-performance.csv +45 -0
  393. package/skills-library/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  394. package/skills-library/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  395. package/skills-library/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  396. package/skills-library/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  397. package/skills-library/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  398. package/skills-library/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  399. package/skills-library/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  400. package/skills-library/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  401. package/skills-library/ui-ux-pro-max/data/stacks/react.csv +54 -0
  402. package/skills-library/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  403. package/skills-library/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  404. package/skills-library/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  405. package/skills-library/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  406. package/skills-library/ui-ux-pro-max/data/styles.csv +68 -0
  407. package/skills-library/ui-ux-pro-max/data/typography.csv +58 -0
  408. package/skills-library/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  409. package/skills-library/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  410. package/skills-library/ui-ux-pro-max/data/web-interface.csv +31 -0
  411. package/skills-library/wordpress-style-theme-components.md +1526 -0
  412. package/templates/ASSUMPTIONS.md +1 -1
  413. package/templates/DECISION_LOG.md +0 -1
  414. package/templates/phase-prompt.md +1 -1
  415. package/templates/phoenix-comparison.md +6 -6
  416. package/templates/skill-api-integration.md +106 -0
  417. package/templates/skill-architecture-pattern.md +92 -0
  418. package/templates/skill-debug-pattern.md +98 -0
  419. package/templates/skill-devops-recipe.md +107 -0
  420. package/templates/skill-general.md +65 -0
  421. package/templates/skill-ui-component.md +113 -0
  422. package/tools/uat-runner.py +179 -0
  423. package/version.json +7 -3
  424. package/workflows/handoff-session.md +2 -2
  425. package/workflows/new-project.md +2 -2
  426. package/workflows/plan-phase.md +1 -1
  427. package/.claude-plugin/plugin.json +0 -64
  428. package/skills-library/_general/methodology/LIVE_BREADCRUMB_PROTOCOL.md +0 -242
  429. package/skills-library/_general/methodology/llm-judge-memory-crud.md +0 -241
  430. package/skills-library/methodology/REFLEXION_MEMORY_PATTERN.md +0 -183
  431. package/skills-library/methodology/RESEARCH_BACKED_WORKFLOW_UPGRADE.md +0 -263
  432. package/skills-library/methodology/SABBATH_REST_PATTERN.md +0 -267
  433. package/skills-library/methodology/STONE_AND_SCAFFOLD.md +0 -220
  434. package/skills-library/specialists/api-architecture/api-designer.md +0 -49
  435. package/skills-library/specialists/api-architecture/graphql-architect.md +0 -49
  436. package/skills-library/specialists/api-architecture/mcp-developer.md +0 -51
  437. package/skills-library/specialists/api-architecture/microservices-architect.md +0 -50
  438. package/skills-library/specialists/api-architecture/websocket-engineer.md +0 -48
  439. package/skills-library/specialists/backend/django-expert.md +0 -52
  440. package/skills-library/specialists/backend/fastapi-expert.md +0 -52
  441. package/skills-library/specialists/backend/laravel-specialist.md +0 -52
  442. package/skills-library/specialists/backend/nestjs-expert.md +0 -51
  443. package/skills-library/specialists/backend/rails-expert.md +0 -53
  444. package/skills-library/specialists/backend/spring-boot-engineer.md +0 -56
  445. package/skills-library/specialists/data-ml/fine-tuning-expert.md +0 -48
  446. package/skills-library/specialists/data-ml/ml-pipeline.md +0 -47
  447. package/skills-library/specialists/data-ml/pandas-pro.md +0 -47
  448. package/skills-library/specialists/data-ml/rag-architect.md +0 -51
  449. package/skills-library/specialists/data-ml/spark-engineer.md +0 -47
  450. package/skills-library/specialists/frontend/angular-architect.md +0 -52
  451. package/skills-library/specialists/frontend/flutter-expert.md +0 -51
  452. package/skills-library/specialists/frontend/nextjs-developer.md +0 -54
  453. package/skills-library/specialists/frontend/react-native-expert.md +0 -50
  454. package/skills-library/specialists/frontend/vue-expert.md +0 -51
  455. package/skills-library/specialists/infrastructure/chaos-engineer.md +0 -74
  456. package/skills-library/specialists/infrastructure/cloud-architect.md +0 -70
  457. package/skills-library/specialists/infrastructure/database-optimizer.md +0 -64
  458. package/skills-library/specialists/infrastructure/devops-engineer.md +0 -70
  459. package/skills-library/specialists/infrastructure/kubernetes-specialist.md +0 -52
  460. package/skills-library/specialists/infrastructure/monitoring-expert.md +0 -70
  461. package/skills-library/specialists/infrastructure/sre-engineer.md +0 -70
  462. package/skills-library/specialists/infrastructure/terraform-engineer.md +0 -51
  463. package/skills-library/specialists/languages/cpp-pro.md +0 -74
  464. package/skills-library/specialists/languages/csharp-developer.md +0 -69
  465. package/skills-library/specialists/languages/dotnet-core-expert.md +0 -54
  466. package/skills-library/specialists/languages/golang-pro.md +0 -51
  467. package/skills-library/specialists/languages/java-architect.md +0 -49
  468. package/skills-library/specialists/languages/javascript-pro.md +0 -68
  469. package/skills-library/specialists/languages/kotlin-specialist.md +0 -68
  470. package/skills-library/specialists/languages/php-pro.md +0 -49
  471. package/skills-library/specialists/languages/python-pro.md +0 -52
  472. package/skills-library/specialists/languages/react-expert.md +0 -51
  473. package/skills-library/specialists/languages/rust-engineer.md +0 -50
  474. package/skills-library/specialists/languages/sql-pro.md +0 -56
  475. package/skills-library/specialists/languages/swift-expert.md +0 -69
  476. package/skills-library/specialists/languages/typescript-pro.md +0 -51
  477. package/skills-library/specialists/platform/atlassian-mcp.md +0 -52
  478. package/skills-library/specialists/platform/embedded-systems.md +0 -53
  479. package/skills-library/specialists/platform/game-developer.md +0 -53
  480. package/skills-library/specialists/platform/salesforce-developer.md +0 -53
  481. package/skills-library/specialists/platform/shopify-expert.md +0 -49
  482. package/skills-library/specialists/platform/wordpress-pro.md +0 -49
  483. package/skills-library/specialists/quality/code-documenter.md +0 -51
  484. package/skills-library/specialists/quality/code-reviewer.md +0 -67
  485. package/skills-library/specialists/quality/debugging-wizard.md +0 -51
  486. package/skills-library/specialists/quality/fullstack-guardian.md +0 -51
  487. package/skills-library/specialists/quality/legacy-modernizer.md +0 -50
  488. package/skills-library/specialists/quality/playwright-expert.md +0 -65
  489. package/skills-library/specialists/quality/spec-miner.md +0 -56
  490. package/skills-library/specialists/quality/test-master.md +0 -65
  491. package/skills-library/specialists/security/secure-code-guardian.md +0 -55
  492. package/skills-library/specialists/security/security-reviewer.md +0 -53
  493. package/skills-library/specialists/workflow/architecture-designer.md +0 -53
  494. package/skills-library/specialists/workflow/cli-developer.md +0 -70
  495. package/skills-library/specialists/workflow/feature-forge.md +0 -65
  496. package/skills-library/specialists/workflow/prompt-engineer.md +0 -54
  497. package/skills-library/specialists/workflow/the-fool.md +0 -62
  498. /package/skills-library/{performance → _general/performance}/cache-augmented-generation.md +0 -0
  499. /package/skills-library/{debugging → parallel-debug}/FAILURE_TAXONOMY_CLASSIFICATION.md +0 -0
  500. /package/skills-library/{debugging → parallel-debug}/THREE_AGENT_HYPOTHESIS_DEBUGGING.md +0 -0
@@ -0,0 +1,1355 @@
1
+ ---
2
+ name: podcast-audio-composition
3
+ category: creative-multimedia
4
+ version: 1.0.0
5
+ contributed: 2026-03-10
6
+ contributor: dominion-flow-research
7
+ last_updated: 2026-03-10
8
+ tags: [podcast, audio, ffmpeg, mixing, composition, multi-track, intro-outro, background-music]
9
+ difficulty: medium
10
+ ---
11
+
12
+ # Podcast Audio Composition
13
+ **Related skills:** [ffmpeg-command-generator.md](ffmpeg-command-generator.md), [audio-enhancement-pipeline.md](audio-enhancement-pipeline.md)
14
+
15
+ ## Description
16
+
17
+ Compose multi-track podcast audio from individual speech segments, background music, intro/outro jingles, and sound effects — all using FFmpeg and Node.js. This skill covers the full production workflow: assembling TTS speech segments in script order, mixing music beds underneath dialogue, adding intro/outro with crossfades, applying per-speaker EQ and spatial separation, and exporting with proper loudness normalization and metadata.
18
+
19
+ ## When to Use
20
+
21
+ - Building AI-generated podcasts from TTS segments (Google Cloud TTS, Anthropic Claude voice, Gemini TTS)
22
+ - Composing multi-speaker audio from individual voice recordings
23
+ - Adding background music, intro jingles, and sound effects to speech
24
+ - Automating podcast post-production in a Node.js pipeline
25
+ - Creating educational narration with chapter breaks and ambient music
26
+ - Producing sermon recap audio with multiple speakers
27
+
28
+ ---
29
+
30
+ ## 1. Multi-Speaker Audio Assembly
31
+
32
+ ### Concept
33
+
34
+ Each speaker's lines are separate audio files (from TTS or recordings). A script JSON defines the assembly order. The composer concatenates them with natural pauses and optional crossfades.
35
+
36
+ ### Script JSON Format
37
+
38
+ ```json
39
+ {
40
+ "title": "Episode 42: Faith and Technology",
41
+ "speakers": {
42
+ "host": { "name": "Pastor James", "voice": "en-US-Neural2-D", "role": "host" },
43
+ "guest": { "name": "Dr. Sarah Chen", "voice": "en-US-Neural2-F", "role": "guest" }
44
+ },
45
+ "segments": [
46
+ { "speaker": "host", "file": "segments/001_host_intro.wav", "text": "Welcome to..." },
47
+ { "speaker": "guest", "file": "segments/002_guest_response.wav", "text": "Thanks for having me..." },
48
+ { "speaker": "host", "file": "segments/003_host_question.wav", "text": "So tell us about..." },
49
+ { "speaker": "guest", "file": "segments/004_guest_answer.wav", "text": "Well, the key insight is..." },
50
+ { "speaker": "host", "file": "segments/005_host_followup.wav", "text": "That's fascinating..." },
51
+ { "speaker": "guest", "file": "segments/006_guest_elaboration.wav", "text": "Exactly, and when you consider..." },
52
+ { "speaker": "host", "file": "segments/007_host_closing.wav", "text": "Thank you so much..." }
53
+ ],
54
+ "music": {
55
+ "intro": "assets/intro_jingle.mp3",
56
+ "outro": "assets/outro_jingle.mp3",
57
+ "bed": "assets/ambient_music_bed.mp3"
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Pause Strategy
63
+
64
+ Natural conversation has variable pauses. Robotic podcasts use identical gaps.
65
+
66
+ | Transition | Pause Duration | Rationale |
67
+ |-----------|---------------|-----------|
68
+ | Same speaker continues | 200ms | Brief breath pause |
69
+ | Speaker change (agreement) | 300ms | Natural handoff |
70
+ | Speaker change (new topic) | 500ms | Topic transition |
71
+ | After a question | 400ms | Thinking pause |
72
+ | Chapter break | 1000ms | Clear section separation |
73
+ | Before closing remarks | 800ms | Signals wind-down |
74
+
75
+ ---
76
+
77
+ ## 2. FFmpeg Filter Graphs for Podcast Production
78
+
79
+ ### a) Generate Silence Padding
80
+
81
+ ```bash
82
+ # Create silence files at 44100 Hz mono (match your TTS sample rate)
83
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 0.2 silence_200ms.wav
84
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 0.3 silence_300ms.wav
85
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 0.5 silence_500ms.wav
86
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 1.0 silence_1000ms.wav
87
+ ```
88
+
89
+ ### b) Concatenate Speech Segments with Silence Gaps
90
+
91
+ Create a `segments.txt` file listing each segment and silence in order:
92
+
93
+ ```
94
+ file 'segments/001_host_intro.wav'
95
+ file 'silence_300ms.wav'
96
+ file 'segments/002_guest_response.wav'
97
+ file 'silence_300ms.wav'
98
+ file 'segments/003_host_question.wav'
99
+ file 'silence_400ms.wav'
100
+ file 'segments/004_guest_answer.wav'
101
+ file 'silence_300ms.wav'
102
+ file 'segments/005_host_followup.wav'
103
+ file 'silence_300ms.wav'
104
+ file 'segments/006_guest_elaboration.wav'
105
+ file 'silence_500ms.wav'
106
+ file 'segments/007_host_closing.wav'
107
+ ```
108
+
109
+ ```bash
110
+ # Concatenate all segments + silences into one continuous track
111
+ ffmpeg -f concat -safe 0 -i segments.txt -c copy concatenated.wav
112
+ ```
113
+
114
+ **Important:** All input files must have the same sample rate, channel count, and codec. If they differ, re-encode first:
115
+
116
+ ```bash
117
+ # Normalize all segments to 44100 Hz mono PCM before concatenation
118
+ for f in segments/*.wav; do
119
+ ffmpeg -i "$f" -ar 44100 -ac 1 -c:a pcm_s16le "normalized_$(basename $f)"
120
+ done
121
+ ```
122
+
123
+ ### c) Crossfade Between Segments (Smoother Transitions)
124
+
125
+ For a more polished sound, crossfade instead of hard-cut with silence:
126
+
127
+ ```bash
128
+ # Crossfade two segments (0.3s overlap, triangular curve)
129
+ ffmpeg -i segment_01.wav -i segment_02.wav \
130
+ -filter_complex "[0:a][1:a]acrossfade=d=0.3:c1=tri:c2=tri" \
131
+ crossfaded.wav
132
+ ```
133
+
134
+ For chaining multiple crossfades (3+ segments):
135
+
136
+ ```bash
137
+ # Chain 4 segments with 0.3s crossfades
138
+ ffmpeg -i seg1.wav -i seg2.wav -i seg3.wav -i seg4.wav \
139
+ -filter_complex \
140
+ "[0:a][1:a]acrossfade=d=0.3:c1=tri:c2=tri[ab]; \
141
+ [ab][2:a]acrossfade=d=0.3:c1=tri:c2=tri[abc]; \
142
+ [abc][3:a]acrossfade=d=0.3:c1=tri:c2=tri" \
143
+ chained.wav
144
+ ```
145
+
146
+ **Crossfade curve options:**
147
+ - `tri` — triangular (linear fade, natural for speech)
148
+ - `qsin` — quarter sine (smooth, slightly musical)
149
+ - `esin` — exponential sine (very smooth, best for music transitions)
150
+ - `log` — logarithmic (quick fade, punchy)
151
+
152
+ ### d) Mix Background Music Under Speech
153
+
154
+ ```bash
155
+ # Simple mix: music at 15% volume underneath speech
156
+ ffmpeg -i speech.wav -i music.wav \
157
+ -filter_complex "[1:a]volume=0.15[music];[0:a][music]amix=inputs=2:duration=first" \
158
+ mixed.mp3
159
+
160
+ # Music fades in over 3s, plays at 12% volume, fades out over 3s
161
+ ffmpeg -i speech.wav -i music.wav \
162
+ -filter_complex \
163
+ "[1:a]volume=0.12,afade=t=in:d=3,afade=t=out:st=SPEECH_DURATION_MINUS_3:d=3[music]; \
164
+ [0:a][music]amix=inputs=2:duration=first" \
165
+ mixed_with_fades.mp3
166
+ ```
167
+
168
+ Replace `SPEECH_DURATION_MINUS_3` with the actual speech duration minus 3 seconds. Use `ffprobe` to measure:
169
+
170
+ ```bash
171
+ # Get audio duration in seconds
172
+ ffprobe -i speech.wav -show_entries format=duration -v quiet -of csv="p=0"
173
+ ```
174
+
175
+ ### e) Sidechain Compression (Duck Music Under Speech)
176
+
177
+ More professional than static volume — music automatically ducks when speech is present:
178
+
179
+ ```bash
180
+ # Music ducks under speech using sidechaincompress
181
+ ffmpeg -i speech.wav -i music.wav \
182
+ -filter_complex \
183
+ "[1:a]volume=0.25[music]; \
184
+ [music][0:a]sidechaincompress=threshold=0.03:ratio=5:attack=200:release=1000[ducked]; \
185
+ [0:a][ducked]amix=inputs=2:duration=first" \
186
+ ducked_output.mp3
187
+ ```
188
+
189
+ **Parameters:**
190
+ - `threshold=0.03` — duck when speech exceeds this level (low = sensitive)
191
+ - `ratio=5` — how much to reduce music (5:1 compression)
192
+ - `attack=200` — how fast music ducks (ms)
193
+ - `release=1000` — how fast music returns after speech stops (ms)
194
+
195
+ ### f) Add Intro/Outro with Crossfade
196
+
197
+ ```bash
198
+ # Crossfade intro (5s) into main content, then main into outro (5s)
199
+ ffmpeg -i intro.mp3 -i main_content.mp3 -i outro.mp3 \
200
+ -filter_complex \
201
+ "[0:a][1:a]acrossfade=d=2:c1=tri:c2=tri[mid]; \
202
+ [mid][2:a]acrossfade=d=2:c1=tri:c2=tri" \
203
+ final_episode.mp3
204
+ ```
205
+
206
+ For a hard-cut intro with fade-in on main content:
207
+
208
+ ```bash
209
+ # Intro plays fully, main content fades in, outro fades in at end
210
+ ffmpeg -i intro.mp3 -i main.mp3 -i outro.mp3 \
211
+ -filter_complex \
212
+ "[1:a]afade=t=in:d=1[main_faded]; \
213
+ [0:a][main_faded]concat=n=2:v=0:a=1[with_intro]; \
214
+ [2:a]afade=t=in:d=1[outro_faded]; \
215
+ [with_intro][outro_faded]concat=n=2:v=0:a=1" \
216
+ final.mp3
217
+ ```
218
+
219
+ ### g) Loudness Normalization (EBU R128 — Podcast Standard)
220
+
221
+ Two-pass normalization for broadcast-quality consistency:
222
+
223
+ ```bash
224
+ # Pass 1: Measure current loudness (capture JSON output)
225
+ ffmpeg -i input.wav -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json -f null NUL 2>&1
226
+
227
+ # Pass 2: Apply measured values for precise normalization
228
+ # Replace the measured_* values with output from Pass 1
229
+ ffmpeg -i input.wav \
230
+ -af loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=-23.5:measured_TP=-4.2:measured_LRA=14.1:measured_thresh=-34.8:linear=true \
231
+ -c:a libmp3lame -b:a 192k -ar 44100 \
232
+ normalized.mp3
233
+ ```
234
+
235
+ | Parameter | Value | Purpose |
236
+ |-----------|-------|---------|
237
+ | `I=-16` | -16 LUFS | Integrated loudness target (podcast standard) |
238
+ | `TP=-1.5` | -1.5 dBTP | True peak ceiling (prevents clipping on decode) |
239
+ | `LRA=11` | 11 LU | Loudness range (dynamic range window) |
240
+ | `linear=true` | — | Use linear normalization (preserves dynamics better) |
241
+
242
+ Single-pass shortcut (less precise but simpler):
243
+
244
+ ```bash
245
+ ffmpeg -i input.wav -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:a libmp3lame -b:a 192k normalized.mp3
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 3. Node.js fluent-ffmpeg Wrapper
251
+
252
+ ### Type Definitions
253
+
254
+ ```typescript
255
+ import ffmpeg from 'fluent-ffmpeg';
256
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
257
+ import { join, basename, dirname } from 'path';
258
+ import { execFileSync } from 'child_process';
259
+
260
+ // --- Type Definitions ---
261
+
262
+ interface AudioSegment {
263
+ speaker: string;
264
+ file: string;
265
+ text?: string;
266
+ pauseAfterMs?: number; // Override default pause (200-500ms)
267
+ }
268
+
269
+ interface SpeakerProfile {
270
+ name: string;
271
+ voice: string;
272
+ role: 'host' | 'guest' | 'narrator';
273
+ eqPreset?: 'deep' | 'bright' | 'neutral';
274
+ pan?: number; // -1.0 (full left) to 1.0 (full right)
275
+ volumeAdjust?: number; // dB adjustment (-3 to +3)
276
+ }
277
+
278
+ interface PodcastMetadata {
279
+ title: string;
280
+ artist: string;
281
+ album?: string;
282
+ date?: string;
283
+ genre?: string;
284
+ comment?: string;
285
+ trackNumber?: number;
286
+ coverArt?: string; // Path to cover image
287
+ }
288
+
289
+ interface CompositionConfig {
290
+ segments: AudioSegment[];
291
+ speakers: Record<string, SpeakerProfile>;
292
+ music?: {
293
+ intro?: string;
294
+ outro?: string;
295
+ bed?: string;
296
+ bedVolume?: number; // 0.0 - 1.0, default 0.12
297
+ };
298
+ crossfadeDuration?: number; // seconds, default 2
299
+ defaultPauseMs?: number; // default 300
300
+ outputFormat?: 'mp3' | 'wav' | 'ogg';
301
+ outputPath: string;
302
+ metadata?: PodcastMetadata;
303
+ tempDir?: string;
304
+ }
305
+
306
+ // --- Helper Functions ---
307
+
308
+ /**
309
+ * Get audio duration using ffprobe.
310
+ * Uses execFileSync (no shell) to avoid command injection.
311
+ */
312
+ function getAudioDuration(filePath: string): number {
313
+ const output = execFileSync('ffprobe', [
314
+ '-i', filePath,
315
+ '-show_entries', 'format=duration',
316
+ '-v', 'quiet',
317
+ '-of', 'csv=p=0'
318
+ ], { encoding: 'utf-8' });
319
+ return parseFloat(output.trim());
320
+ }
321
+
322
+ /**
323
+ * Run an FFmpeg command safely using execFileSync (no shell).
324
+ * All arguments are passed as an array to prevent injection.
325
+ */
326
+ function runFfmpegSync(args: string[]): string {
327
+ return execFileSync('ffmpeg', args, { encoding: 'utf-8', stdio: 'pipe' });
328
+ }
329
+ ```
330
+
331
+ ### PodcastComposer Class
332
+
333
+ ```typescript
334
+ class PodcastComposer {
335
+ private tempDir: string;
336
+ private tempFiles: string[] = [];
337
+
338
+ constructor(tempDir: string = './temp_podcast') {
339
+ this.tempDir = tempDir;
340
+ mkdirSync(this.tempDir, { recursive: true });
341
+ }
342
+
343
+ /**
344
+ * Generate a silence file of specified duration.
345
+ */
346
+ private createSilence(durationMs: number): string {
347
+ const outPath = join(this.tempDir, `silence_${durationMs}ms.wav`);
348
+ if (!existsSync(outPath)) {
349
+ runFfmpegSync([
350
+ '-y', '-f', 'lavfi',
351
+ '-i', `anullsrc=r=44100:cl=mono`,
352
+ '-t', String(durationMs / 1000),
353
+ outPath
354
+ ]);
355
+ this.tempFiles.push(outPath);
356
+ }
357
+ return outPath;
358
+ }
359
+
360
+ /**
361
+ * Normalize a segment to consistent sample rate and channels.
362
+ */
363
+ private normalizeSegment(inputPath: string, sampleRate: number = 44100): string {
364
+ const outPath = join(this.tempDir, `norm_${basename(inputPath)}`);
365
+ runFfmpegSync([
366
+ '-y', '-i', inputPath,
367
+ '-ar', String(sampleRate),
368
+ '-ac', '1',
369
+ '-c:a', 'pcm_s16le',
370
+ outPath
371
+ ]);
372
+ this.tempFiles.push(outPath);
373
+ return outPath;
374
+ }
375
+
376
+ /**
377
+ * Apply speaker-specific EQ profile to a segment.
378
+ */
379
+ private applySpeakerEQ(inputPath: string, profile: SpeakerProfile): string {
380
+ const outPath = join(this.tempDir, `eq_${basename(inputPath)}`);
381
+ const filters: string[] = [];
382
+
383
+ // EQ presets for voice differentiation
384
+ switch (profile.eqPreset) {
385
+ case 'deep':
386
+ // Host: warmer, fuller low-mid presence
387
+ filters.push('equalizer=f=200:t=q:w=1.0:g=2');
388
+ filters.push('equalizer=f=3000:t=q:w=1.5:g=1');
389
+ filters.push('highpass=f=60');
390
+ break;
391
+ case 'bright':
392
+ // Guest: clearer, more articulate high-mid
393
+ filters.push('equalizer=f=3500:t=q:w=1.5:g=3');
394
+ filters.push('equalizer=f=6000:t=q:w=2.0:g=1.5');
395
+ filters.push('highpass=f=100');
396
+ break;
397
+ case 'neutral':
398
+ default:
399
+ // Clean pass — minimal processing
400
+ filters.push('highpass=f=80');
401
+ break;
402
+ }
403
+
404
+ // Volume adjustment
405
+ if (profile.volumeAdjust) {
406
+ filters.push(`volume=${profile.volumeAdjust}dB`);
407
+ }
408
+
409
+ // Stereo panning (convert mono to stereo with pan position)
410
+ if (profile.pan !== undefined && profile.pan !== 0) {
411
+ const leftGain = Math.cos((profile.pan + 1) * Math.PI / 4);
412
+ const rightGain = Math.sin((profile.pan + 1) * Math.PI / 4);
413
+ filters.push(`pan=stereo|c0=${leftGain.toFixed(3)}*c0|c1=${rightGain.toFixed(3)}*c0`);
414
+ }
415
+
416
+ if (filters.length === 0) return inputPath;
417
+
418
+ const filterChain = filters.join(',');
419
+ runFfmpegSync(['-y', '-i', inputPath, '-af', filterChain, outPath]);
420
+ this.tempFiles.push(outPath);
421
+ return outPath;
422
+ }
423
+
424
+ /**
425
+ * Assemble speech segments with silence gaps between them.
426
+ * Normalizes all segments, applies speaker EQ, then concatenates.
427
+ */
428
+ async assembleSpeechSegments(
429
+ segments: AudioSegment[],
430
+ speakers: Record<string, SpeakerProfile> = {},
431
+ defaultPauseMs: number = 300
432
+ ): Promise<string> {
433
+ const outputPath = join(this.tempDir, 'assembled_speech.wav');
434
+ const concatListPath = join(this.tempDir, 'concat_list.txt');
435
+ const lines: string[] = [];
436
+
437
+ for (let i = 0; i < segments.length; i++) {
438
+ const seg = segments[i];
439
+
440
+ if (!existsSync(seg.file)) {
441
+ throw new Error(`Segment file not found: ${seg.file}`);
442
+ }
443
+
444
+ // Normalize to consistent format
445
+ let processed = this.normalizeSegment(seg.file);
446
+
447
+ // Apply speaker-specific EQ if profile exists
448
+ const profile = speakers[seg.speaker];
449
+ if (profile) {
450
+ processed = this.applySpeakerEQ(processed, profile);
451
+ }
452
+
453
+ lines.push(`file '${processed.replace(/\\/g, '/')}'`);
454
+
455
+ // Add silence gap after each segment (except the last)
456
+ if (i < segments.length - 1) {
457
+ const pauseMs = seg.pauseAfterMs || defaultPauseMs;
458
+ const silencePath = this.createSilence(pauseMs);
459
+ lines.push(`file '${silencePath.replace(/\\/g, '/')}'`);
460
+ }
461
+ }
462
+
463
+ writeFileSync(concatListPath, lines.join('\n'), 'utf-8');
464
+
465
+ runFfmpegSync([
466
+ '-y', '-f', 'concat', '-safe', '0',
467
+ '-i', concatListPath,
468
+ '-c:a', 'pcm_s16le',
469
+ outputPath
470
+ ]);
471
+
472
+ this.tempFiles.push(outputPath, concatListPath);
473
+ console.log(`[podcast-composer] Assembled ${segments.length} segments`);
474
+ return outputPath;
475
+ }
476
+
477
+ /**
478
+ * Mix background music underneath speech audio.
479
+ * Music volume is reduced and fades in/out gracefully.
480
+ */
481
+ async addBackgroundMusic(
482
+ speechPath: string,
483
+ musicPath: string,
484
+ musicVolume: number = 0.12
485
+ ): Promise<string> {
486
+ const outputPath = join(this.tempDir, 'speech_with_music.wav');
487
+ const speechDuration = getAudioDuration(speechPath);
488
+ const fadeOutStart = Math.max(0, speechDuration - 3);
489
+
490
+ const filterGraph =
491
+ `[1:a]volume=${musicVolume},afade=t=in:d=3,` +
492
+ `afade=t=out:st=${fadeOutStart.toFixed(2)}:d=3[music];` +
493
+ `[0:a][music]amix=inputs=2:duration=first`;
494
+
495
+ runFfmpegSync([
496
+ '-y', '-i', speechPath, '-i', musicPath,
497
+ '-filter_complex', filterGraph,
498
+ outputPath
499
+ ]);
500
+
501
+ this.tempFiles.push(outputPath);
502
+ console.log(`[podcast-composer] Added background music (vol=${musicVolume})`);
503
+ return outputPath;
504
+ }
505
+
506
+ /**
507
+ * Add intro and/or outro with crossfade transitions.
508
+ */
509
+ async addIntroOutro(
510
+ mainPath: string,
511
+ intro?: string,
512
+ outro?: string,
513
+ crossfadeSec: number = 2
514
+ ): Promise<string> {
515
+ let currentPath = mainPath;
516
+
517
+ // Add intro with crossfade
518
+ if (intro && existsSync(intro)) {
519
+ const withIntro = join(this.tempDir, 'with_intro.wav');
520
+ runFfmpegSync([
521
+ '-y', '-i', intro, '-i', currentPath,
522
+ '-filter_complex', `[0:a][1:a]acrossfade=d=${crossfadeSec}:c1=tri:c2=tri`,
523
+ withIntro
524
+ ]);
525
+ this.tempFiles.push(withIntro);
526
+ currentPath = withIntro;
527
+ }
528
+
529
+ // Add outro with crossfade
530
+ if (outro && existsSync(outro)) {
531
+ const withOutro = join(this.tempDir, 'with_intro_outro.wav');
532
+ runFfmpegSync([
533
+ '-y', '-i', currentPath, '-i', outro,
534
+ '-filter_complex', `[0:a][1:a]acrossfade=d=${crossfadeSec}:c1=tri:c2=tri`,
535
+ withOutro
536
+ ]);
537
+ this.tempFiles.push(withOutro);
538
+ currentPath = withOutro;
539
+ }
540
+
541
+ console.log(`[podcast-composer] Added intro/outro`);
542
+ return currentPath;
543
+ }
544
+
545
+ /**
546
+ * Two-pass EBU R128 loudness normalization.
547
+ * Targets -16 LUFS (podcast standard).
548
+ */
549
+ async normalizeAudio(inputPath: string): Promise<string> {
550
+ const outputPath = join(this.tempDir, 'normalized.wav');
551
+
552
+ // Pass 1: Measure current loudness
553
+ // Note: FFmpeg writes loudnorm JSON to stderr, so we capture it
554
+ let measureOutput: string;
555
+ try {
556
+ execFileSync('ffmpeg', [
557
+ '-i', inputPath,
558
+ '-af', 'loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json',
559
+ '-f', 'null', 'NUL'
560
+ ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
561
+ measureOutput = '';
562
+ } catch (err: any) {
563
+ // FFmpeg exits non-zero for -f null but stderr has our data
564
+ measureOutput = err.stderr || '';
565
+ }
566
+
567
+ // Parse measured values from JSON output
568
+ const jsonMatch = measureOutput.match(/\{[\s\S]*?"input_i"[\s\S]*?\}/);
569
+ if (!jsonMatch) {
570
+ throw new Error('Failed to parse loudness measurement from FFmpeg output');
571
+ }
572
+
573
+ const measured = JSON.parse(jsonMatch[0]);
574
+ const { input_i, input_tp, input_lra, input_thresh } = measured;
575
+
576
+ // Pass 2: Apply precise normalization with measured values
577
+ runFfmpegSync([
578
+ '-y', '-i', inputPath,
579
+ '-af', `loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=${input_i}:measured_TP=${input_tp}:measured_LRA=${input_lra}:measured_thresh=${input_thresh}:linear=true`,
580
+ outputPath
581
+ ]);
582
+
583
+ this.tempFiles.push(outputPath);
584
+ console.log(`[podcast-composer] Normalized to -16 LUFS (was ${input_i} LUFS)`);
585
+ return outputPath;
586
+ }
587
+
588
+ /**
589
+ * Export final podcast with format conversion and metadata tagging.
590
+ */
591
+ async exportFinal(
592
+ inputPath: string,
593
+ format: 'mp3' | 'wav' | 'ogg' = 'mp3',
594
+ metadata?: PodcastMetadata
595
+ ): Promise<string> {
596
+ const ext = format === 'ogg' ? 'ogg' : format;
597
+ const outputPath = join(dirname(this.tempDir), `final_podcast.${ext}`);
598
+
599
+ const codecMap: Record<string, { codec: string; bitrate: string }> = {
600
+ mp3: { codec: 'libmp3lame', bitrate: '192k' },
601
+ wav: { codec: 'pcm_s16le', bitrate: '' },
602
+ ogg: { codec: 'libvorbis', bitrate: '192k' },
603
+ };
604
+
605
+ const { codec, bitrate } = codecMap[format];
606
+
607
+ // Build FFmpeg args array
608
+ const args: string[] = ['-y', '-i', inputPath, '-c:a', codec];
609
+
610
+ if (bitrate) {
611
+ args.push('-b:a', bitrate);
612
+ }
613
+ args.push('-ar', '44100');
614
+
615
+ // Add metadata flags
616
+ if (metadata) {
617
+ if (metadata.title) args.push('-metadata', `title=${metadata.title}`);
618
+ if (metadata.artist) args.push('-metadata', `artist=${metadata.artist}`);
619
+ if (metadata.album) args.push('-metadata', `album=${metadata.album}`);
620
+ if (metadata.date) args.push('-metadata', `date=${metadata.date}`);
621
+ if (metadata.genre) args.push('-metadata', `genre=${metadata.genre}`);
622
+ if (metadata.comment) args.push('-metadata', `comment=${metadata.comment}`);
623
+ if (metadata.trackNumber) args.push('-metadata', `track=${metadata.trackNumber}`);
624
+ }
625
+
626
+ args.push(outputPath);
627
+ runFfmpegSync(args);
628
+
629
+ // Embed cover art for MP3 (if provided)
630
+ if (format === 'mp3' && metadata?.coverArt && existsSync(metadata.coverArt)) {
631
+ const withCover = outputPath.replace('.mp3', '_cover.mp3');
632
+ runFfmpegSync([
633
+ '-y', '-i', outputPath, '-i', metadata.coverArt,
634
+ '-map', '0:a', '-map', '1:0',
635
+ '-c', 'copy', '-id3v2_version', '3',
636
+ '-metadata:s:v', 'title=Album cover',
637
+ '-metadata:s:v', 'comment=Cover (front)',
638
+ withCover
639
+ ]);
640
+ unlinkSync(outputPath);
641
+ require('fs').renameSync(withCover, outputPath);
642
+ }
643
+
644
+ console.log(`[podcast-composer] Exported final podcast`);
645
+ return outputPath;
646
+ }
647
+
648
+ /**
649
+ * Full composition pipeline: assemble -> music -> intro/outro -> normalize -> export.
650
+ * This is the main entry point for end-to-end podcast production.
651
+ */
652
+ async compose(config: CompositionConfig): Promise<string> {
653
+ console.log(`[podcast-composer] Starting composition: ${config.metadata?.title || 'Untitled'}`);
654
+ const startTime = Date.now();
655
+
656
+ // Step 1: Assemble speech segments with pauses
657
+ let audioPath = await this.assembleSpeechSegments(
658
+ config.segments,
659
+ config.speakers,
660
+ config.defaultPauseMs
661
+ );
662
+
663
+ // Step 2: Mix background music (if provided)
664
+ if (config.music?.bed && existsSync(config.music.bed)) {
665
+ audioPath = await this.addBackgroundMusic(
666
+ audioPath,
667
+ config.music.bed,
668
+ config.music.bedVolume || 0.12
669
+ );
670
+ }
671
+
672
+ // Step 3: Add intro/outro (if provided)
673
+ if (config.music?.intro || config.music?.outro) {
674
+ audioPath = await this.addIntroOutro(
675
+ audioPath,
676
+ config.music.intro,
677
+ config.music.outro,
678
+ config.crossfadeDuration || 2
679
+ );
680
+ }
681
+
682
+ // Step 4: Loudness normalization (EBU R128, -16 LUFS)
683
+ audioPath = await this.normalizeAudio(audioPath);
684
+
685
+ // Step 5: Export to final format with metadata
686
+ const finalPath = await this.exportFinal(
687
+ audioPath,
688
+ config.outputFormat || 'mp3',
689
+ config.metadata
690
+ );
691
+
692
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
693
+ console.log(`[podcast-composer] Composition complete in ${elapsed}s`);
694
+
695
+ return finalPath;
696
+ }
697
+
698
+ /**
699
+ * Clean up temporary files created during composition.
700
+ */
701
+ cleanup(): void {
702
+ for (const file of this.tempFiles) {
703
+ try {
704
+ if (existsSync(file)) unlinkSync(file);
705
+ } catch { /* ignore cleanup errors */ }
706
+ }
707
+ this.tempFiles = [];
708
+ console.log('[podcast-composer] Temp files cleaned up');
709
+ }
710
+ }
711
+ ```
712
+
713
+ ### Usage Example
714
+
715
+ ```typescript
716
+ const composer = new PodcastComposer('./temp_podcast');
717
+
718
+ try {
719
+ const finalPath = await composer.compose({
720
+ segments: [
721
+ { speaker: 'host', file: 'segments/001_intro.wav', pauseAfterMs: 500 },
722
+ { speaker: 'guest', file: 'segments/002_response.wav', pauseAfterMs: 300 },
723
+ { speaker: 'host', file: 'segments/003_question.wav', pauseAfterMs: 400 },
724
+ { speaker: 'guest', file: 'segments/004_answer.wav', pauseAfterMs: 300 },
725
+ { speaker: 'host', file: 'segments/005_closing.wav' },
726
+ ],
727
+ speakers: {
728
+ host: { name: 'Pastor James', voice: 'en-US-Neural2-D', role: 'host', eqPreset: 'deep', pan: -0.15 },
729
+ guest: { name: 'Dr. Chen', voice: 'en-US-Neural2-F', role: 'guest', eqPreset: 'bright', pan: 0.15 },
730
+ },
731
+ music: {
732
+ intro: 'assets/intro_jingle.mp3',
733
+ outro: 'assets/outro_jingle.mp3',
734
+ bed: 'assets/ambient_bed.mp3',
735
+ bedVolume: 0.12,
736
+ },
737
+ crossfadeDuration: 2,
738
+ defaultPauseMs: 300,
739
+ outputFormat: 'mp3',
740
+ outputPath: './output/episode_042.mp3',
741
+ metadata: {
742
+ title: 'Episode 42: Faith and Technology',
743
+ artist: 'Ministry Podcast',
744
+ album: 'Ministry Podcast Season 3',
745
+ date: '2026',
746
+ genre: 'Podcast',
747
+ comment: 'AI-generated from sermon transcript — produced with Anthropic Claude + Google Gemini TTS',
748
+ },
749
+ });
750
+
751
+ console.log('Final podcast:', finalPath);
752
+ } finally {
753
+ composer.cleanup();
754
+ }
755
+ ```
756
+
757
+ ---
758
+
759
+ ## 4. Speaker Voice Differentiation
760
+
761
+ ### EQ Profiles by Role
762
+
763
+ Different EQ treatments create audible distinction between speakers, even when using similar TTS voices.
764
+
765
+ ```
766
+ Host (deep):
767
+ +2dB at 200Hz (warmth)
768
+ +1dB at 3kHz (presence)
769
+ Highpass at 60Hz
770
+ Result: Warm, authoritative
771
+
772
+ Guest (bright):
773
+ +3dB at 3.5kHz (clarity)
774
+ +1.5dB at 6kHz (air)
775
+ Highpass at 100Hz
776
+ Result: Clear, articulate
777
+
778
+ Narrator (neutral):
779
+ Highpass at 80Hz
780
+ No boost/cut
781
+ Result: Clean, transparent
782
+ ```
783
+
784
+ ### FFmpeg EQ Commands
785
+
786
+ ```bash
787
+ # Host voice — warm and authoritative
788
+ ffmpeg -i host_raw.wav \
789
+ -af "highpass=f=60,equalizer=f=200:t=q:w=1.0:g=2,equalizer=f=3000:t=q:w=1.5:g=1" \
790
+ host_eq.wav
791
+
792
+ # Guest voice — clear and articulate
793
+ ffmpeg -i guest_raw.wav \
794
+ -af "highpass=f=100,equalizer=f=3500:t=q:w=1.5:g=3,equalizer=f=6000:t=q:w=2.0:g=1.5" \
795
+ guest_eq.wav
796
+
797
+ # Narrator — clean pass
798
+ ffmpeg -i narrator_raw.wav \
799
+ -af "highpass=f=80" \
800
+ narrator_eq.wav
801
+ ```
802
+
803
+ ### Spatial Separation (Stereo Panning)
804
+
805
+ Subtle panning creates a "two people in a room" feel without being distracting:
806
+
807
+ ```bash
808
+ # Pan host slightly left (10-15% — subtle, not jarring)
809
+ ffmpeg -i host.wav -af "pan=stereo|c0=1.0*c0|c1=0.7*c0" host_panned.wav
810
+
811
+ # Pan guest slightly right
812
+ ffmpeg -i guest.wav -af "pan=stereo|c0=0.7*c0|c1=1.0*c0" guest_panned.wav
813
+
814
+ # Narrator stays centered
815
+ # (no panning needed — default center)
816
+ ```
817
+
818
+ **Guidelines:**
819
+ - Keep panning subtle: 10-20% off-center maximum
820
+ - Listener should feel spatial presence, not notice panning
821
+ - Mono compatibility: verify the mix sounds good summed to mono
822
+ - Skip panning for single-speaker podcasts or narration
823
+
824
+ ### Volume Leveling Across Speakers
825
+
826
+ ```bash
827
+ # Measure each speaker's average loudness
828
+ ffmpeg -i host_segments.wav -af ebur128=peak=true -f null NUL 2>&1
829
+ ffmpeg -i guest_segments.wav -af ebur128=peak=true -f null NUL 2>&1
830
+
831
+ # Apply gain adjustment to match target (-16 LUFS for both)
832
+ ffmpeg -i guest_segments.wav -af "volume=2.5dB" guest_leveled.wav
833
+ ```
834
+
835
+ ---
836
+
837
+ ## 5. Production Templates
838
+
839
+ ### Template A: Simple Two-Speaker Podcast
840
+
841
+ ```
842
+ Timeline:
843
+ | 2s intro | Host intro | Discussion... | Host outro | 2s outro |
844
+ | jingle | (10s) | (variable) | (10s) | jingle |
845
+ ^ ^ ^
846
+ crossfade music bed crossfade
847
+ (1.5s) at 10% vol (1.5s)
848
+ ```
849
+
850
+ ```typescript
851
+ const simpleConfig: CompositionConfig = {
852
+ segments: [
853
+ { speaker: 'host', file: 'host_intro.wav', pauseAfterMs: 500 },
854
+ // ... discussion segments ...
855
+ { speaker: 'host', file: 'host_outro.wav' },
856
+ ],
857
+ speakers: {
858
+ host: { name: 'Host', voice: 'en-US-Neural2-D', role: 'host', eqPreset: 'deep' },
859
+ guest: { name: 'Guest', voice: 'en-US-Neural2-F', role: 'guest', eqPreset: 'bright' },
860
+ },
861
+ music: {
862
+ intro: 'assets/jingle_2s.mp3',
863
+ outro: 'assets/jingle_2s.mp3',
864
+ bed: 'assets/soft_ambient.mp3',
865
+ bedVolume: 0.10,
866
+ },
867
+ crossfadeDuration: 1.5,
868
+ defaultPauseMs: 300,
869
+ outputFormat: 'mp3',
870
+ outputPath: './output/simple_episode.mp3',
871
+ metadata: { title: 'Episode Title', artist: 'Podcast Name', genre: 'Podcast', date: '2026' },
872
+ };
873
+ ```
874
+
875
+ ### Template B: Professional Production
876
+
877
+ ```
878
+ Timeline:
879
+ | 5s music | Host | Sponsor | Main | Break | Part 2 | Sponsor | Outro | 5s music |
880
+ | intro | welcome | slot | discussion | music | | slot | w/ CTA | outro |
881
+ ^ ^ ^
882
+ crossfade 3s music crossfade
883
+ (2.5s) transition (2.5s)
884
+ ```
885
+
886
+ ```typescript
887
+ const professionalConfig: CompositionConfig = {
888
+ segments: [
889
+ // Act 1: Welcome + Sponsor
890
+ { speaker: 'host', file: 'seg/welcome.wav', pauseAfterMs: 300 },
891
+ { speaker: 'host', file: 'seg/sponsor_read.wav', pauseAfterMs: 800 },
892
+ // Act 2: Main discussion
893
+ { speaker: 'host', file: 'seg/topic_intro.wav', pauseAfterMs: 400 },
894
+ { speaker: 'guest', file: 'seg/guest_point_1.wav', pauseAfterMs: 300 },
895
+ { speaker: 'host', file: 'seg/host_response_1.wav', pauseAfterMs: 300 },
896
+ { speaker: 'guest', file: 'seg/guest_point_2.wav', pauseAfterMs: 300 },
897
+ // Break marker — handled by pauseAfterMs
898
+ { speaker: 'host', file: 'seg/break_transition.wav', pauseAfterMs: 1000 },
899
+ // Act 3: Part 2 + Outro
900
+ { speaker: 'host', file: 'seg/part2_intro.wav', pauseAfterMs: 400 },
901
+ { speaker: 'guest', file: 'seg/guest_point_3.wav', pauseAfterMs: 300 },
902
+ { speaker: 'host', file: 'seg/sponsor_read_2.wav', pauseAfterMs: 500 },
903
+ { speaker: 'host', file: 'seg/closing_cta.wav' },
904
+ ],
905
+ speakers: {
906
+ host: { name: 'Host', voice: 'en-US-Neural2-D', role: 'host', eqPreset: 'deep', pan: -0.12 },
907
+ guest: { name: 'Guest', voice: 'en-US-Neural2-F', role: 'guest', eqPreset: 'bright', pan: 0.12 },
908
+ },
909
+ music: {
910
+ intro: 'assets/theme_5s.mp3',
911
+ outro: 'assets/theme_5s.mp3',
912
+ bed: 'assets/minimal_beat.mp3',
913
+ bedVolume: 0.08,
914
+ },
915
+ crossfadeDuration: 2.5,
916
+ defaultPauseMs: 300,
917
+ outputFormat: 'mp3',
918
+ outputPath: './output/professional_episode.mp3',
919
+ metadata: {
920
+ title: 'Episode 42: Faith and Technology',
921
+ artist: 'Ministry Podcast',
922
+ album: 'Ministry Podcast Season 3',
923
+ date: '2026',
924
+ genre: 'Podcast',
925
+ comment: 'Produced with Anthropic Claude + Google Gemini TTS',
926
+ },
927
+ };
928
+ ```
929
+
930
+ ### Template C: Educational Narration
931
+
932
+ ```
933
+ Timeline:
934
+ | Ambient | Chapter 1 | chime | Chapter 2 | chime | Summary | Music |
935
+ | fade in | narration | break | narration | break | | fade out |
936
+ ^ ^
937
+ 3s fade in 5s fade out
938
+ ambient music bed at 10% volume throughout
939
+ ```
940
+
941
+ ```typescript
942
+ const educationalConfig: CompositionConfig = {
943
+ segments: [
944
+ { speaker: 'narrator', file: 'seg/chapter1_intro.wav', pauseAfterMs: 200 },
945
+ { speaker: 'narrator', file: 'seg/chapter1_body.wav', pauseAfterMs: 1000 },
946
+ // Chapter break — long pause signals section change
947
+ { speaker: 'narrator', file: 'seg/chapter2_intro.wav', pauseAfterMs: 200 },
948
+ { speaker: 'narrator', file: 'seg/chapter2_body.wav', pauseAfterMs: 1000 },
949
+ { speaker: 'narrator', file: 'seg/summary.wav' },
950
+ ],
951
+ speakers: {
952
+ narrator: { name: 'Narrator', voice: 'en-US-Neural2-D', role: 'narrator', eqPreset: 'neutral' },
953
+ },
954
+ music: {
955
+ bed: 'assets/ambient_piano.mp3',
956
+ bedVolume: 0.10,
957
+ },
958
+ crossfadeDuration: 2,
959
+ defaultPauseMs: 200,
960
+ outputFormat: 'mp3',
961
+ outputPath: './output/educational_narration.mp3',
962
+ metadata: {
963
+ title: 'Understanding Grace: Chapter 1-2',
964
+ artist: 'Ministry Teaching Series',
965
+ genre: 'Podcast',
966
+ date: '2026',
967
+ comment: 'AI narration from source document — Anthropic Claude',
968
+ },
969
+ };
970
+ ```
971
+
972
+ ---
973
+
974
+ ## 6. Sound Design Elements
975
+
976
+ ### Sourcing Royalty-Free Audio
977
+
978
+ | Element | Source | License | Notes |
979
+ |---------|--------|---------|-------|
980
+ | Intro/outro music | [Pixabay Audio](https://pixabay.com/music/) | Pixabay License (free, no attribution) | Search "podcast intro", filter by <10s |
981
+ | Transition sounds | [Freesound.org](https://freesound.org) | CC0 / CC-BY | Search "whoosh", "chime", "transition" |
982
+ | Background ambient | [Free Music Archive](https://freemusicarchive.org) | CC-BY / CC0 | Search "ambient", "lo-fi", filter instrumental |
983
+ | Sound effects | [Mixkit](https://mixkit.co/free-sound-effects/) | Free license | Clean UI, categorized well |
984
+ | Chapter chimes | [Zapsplat](https://www.zapsplat.com) | Free with attribution | Large SFX library |
985
+
986
+ ### Recommended Sound Levels
987
+
988
+ ```
989
+ Element Volume Level Notes
990
+ ------- ------------ -----
991
+ Speech (primary) 0 dB (ref) Normalized to -16 LUFS
992
+ Background music bed -18 to -24 dB 10-15% of speech level
993
+ Intro/outro jingle -6 to -3 dB Prominent but not jarring
994
+ Transition whoosh/chime -12 to -9 dB Noticeable but brief
995
+ Ambient room tone -30 dB Barely perceptible
996
+ ```
997
+
998
+ ### FFmpeg Commands for Sound Design
999
+
1000
+ ```bash
1001
+ # Add a short transition chime between sections
1002
+ ffmpeg -i before.wav -i chime.wav -i after.wav \
1003
+ -filter_complex \
1004
+ "[1:a]volume=0.3,adelay=0|0[chime]; \
1005
+ [0:a][chime]amix=inputs=2:duration=first[with_chime]; \
1006
+ [with_chime][2:a]concat=n=2:v=0:a=1" \
1007
+ with_transition.wav
1008
+
1009
+ # Fade music from full volume to bed volume at speech start
1010
+ ffmpeg -i music.mp3 \
1011
+ -af "volume=1.0,afade=t=out:st=3:d=2,volume=0.12" \
1012
+ music_ducked.wav
1013
+
1014
+ # Create a "room tone" ambient layer (pink noise, very quiet)
1015
+ ffmpeg -f lavfi -i "anoisesrc=d=300:c=pink:r=44100:a=0.005" \
1016
+ -c:a pcm_s16le room_tone_5min.wav
1017
+ ```
1018
+
1019
+ ---
1020
+
1021
+ ## 7. Metadata Tagging
1022
+
1023
+ ### Full ID3 Tag Set for Podcast Distribution
1024
+
1025
+ ```bash
1026
+ # Complete metadata for podcast episode
1027
+ ffmpeg -i podcast.mp3 \
1028
+ -metadata title="Episode 42: Faith and Technology" \
1029
+ -metadata artist="Ministry Podcast" \
1030
+ -metadata album="Ministry Podcast Season 3" \
1031
+ -metadata album_artist="Ministry Podcast" \
1032
+ -metadata date="2026" \
1033
+ -metadata genre="Podcast" \
1034
+ -metadata track="42" \
1035
+ -metadata comment="AI-generated from sermon transcript — Anthropic Claude + Google Gemini TTS" \
1036
+ -metadata publisher="Ministry Name" \
1037
+ -metadata copyright="2026 Ministry Name" \
1038
+ -metadata language="eng" \
1039
+ -c copy \
1040
+ tagged_podcast.mp3
1041
+ ```
1042
+
1043
+ ### Embed Cover Art
1044
+
1045
+ ```bash
1046
+ # Add cover art to MP3 (ID3v2 embedded image)
1047
+ ffmpeg -i podcast.mp3 -i cover_art.jpg \
1048
+ -map 0:a -map 1:0 \
1049
+ -c copy -id3v2_version 3 \
1050
+ -metadata:s:v title="Album cover" \
1051
+ -metadata:s:v comment="Cover (front)" \
1052
+ podcast_with_cover.mp3
1053
+
1054
+ # Add cover art to M4A/AAC
1055
+ ffmpeg -i podcast.m4a -i cover_art.jpg \
1056
+ -map 0:a -map 1:0 \
1057
+ -c:a copy -c:v mjpeg \
1058
+ -disposition:v attached_pic \
1059
+ podcast_with_cover.m4a
1060
+ ```
1061
+
1062
+ ### Cover Art Specifications
1063
+
1064
+ | Platform | Minimum | Recommended | Max | Format |
1065
+ |----------|---------|-------------|-----|--------|
1066
+ | Apple Podcasts | 1400x1400 | 3000x3000 | 3000x3000 | JPEG/PNG |
1067
+ | Spotify | 640x640 | 3000x3000 | 3000x3000 | JPEG |
1068
+ | Google Podcasts | 600x600 | 1200x1200 | — | JPEG/PNG |
1069
+ | ID3 embed | — | 500x500 | 1000x1000 | JPEG (smaller file size) |
1070
+
1071
+ ### Validate Metadata
1072
+
1073
+ ```bash
1074
+ # Read back all metadata from the file
1075
+ ffprobe -i podcast.mp3 -show_format -show_streams -v quiet -print_format json
1076
+
1077
+ # Quick check — just metadata tags
1078
+ ffprobe -i podcast.mp3 -show_entries format_tags -v quiet -of default=noprint_wrappers=1
1079
+ ```
1080
+
1081
+ ---
1082
+
1083
+ ## 8. Quality Verification Checklist
1084
+
1085
+ After composition, verify the output meets podcast distribution standards:
1086
+
1087
+ ```bash
1088
+ # 1. Measure loudness (target: -16 LUFS, peak below -1.5 dBTP)
1089
+ ffmpeg -i final_podcast.mp3 -af ebur128=peak=true -f null NUL 2>&1
1090
+
1091
+ # 2. Check duration matches expected length
1092
+ ffprobe -i final_podcast.mp3 -show_entries format=duration -v quiet -of csv="p=0"
1093
+
1094
+ # 3. Verify sample rate and bitrate
1095
+ ffprobe -i final_podcast.mp3 -show_entries stream=sample_rate,bit_rate -v quiet
1096
+
1097
+ # 4. Generate waveform for visual inspection
1098
+ ffmpeg -i final_podcast.mp3 -lavfi showwavespic=s=1920x200:colors=0x2563EB waveform.png
1099
+
1100
+ # 5. Generate spectrogram to check for artifacts
1101
+ ffmpeg -i final_podcast.mp3 -lavfi showspectrumpic=s=1920x1080:mode=combined:scale=log spectrogram.png
1102
+ ```
1103
+
1104
+ ### Automated Quality Gate (Node.js)
1105
+
1106
+ ```typescript
1107
+ interface QualityResult {
1108
+ lufs: number;
1109
+ truePeak: number;
1110
+ lra: number;
1111
+ duration: number;
1112
+ sampleRate: number;
1113
+ pass: boolean;
1114
+ issues: string[];
1115
+ }
1116
+
1117
+ async function verifyPodcastQuality(filePath: string): Promise<QualityResult> {
1118
+ const issues: string[] = [];
1119
+
1120
+ // Measure loudness (capture stderr where FFmpeg writes the JSON)
1121
+ let measureOutput: string;
1122
+ try {
1123
+ execFileSync('ffmpeg', [
1124
+ '-i', filePath,
1125
+ '-af', 'loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json',
1126
+ '-f', 'null', 'NUL'
1127
+ ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
1128
+ measureOutput = '';
1129
+ } catch (err: any) {
1130
+ measureOutput = err.stderr || '';
1131
+ }
1132
+
1133
+ const jsonMatch = measureOutput.match(/\{[\s\S]*?"input_i"[\s\S]*?\}/);
1134
+ const measured = jsonMatch ? JSON.parse(jsonMatch[0]) : null;
1135
+
1136
+ if (!measured) {
1137
+ return {
1138
+ lufs: 0, truePeak: 0, lra: 0, duration: 0, sampleRate: 0,
1139
+ pass: false, issues: ['Could not measure loudness']
1140
+ };
1141
+ }
1142
+
1143
+ const lufs = parseFloat(measured.input_i);
1144
+ const truePeak = parseFloat(measured.input_tp);
1145
+ const lra = parseFloat(measured.input_lra);
1146
+
1147
+ // Get duration and sample rate
1148
+ const probeOutput = execFileSync('ffprobe', [
1149
+ '-i', filePath,
1150
+ '-show_entries', 'format=duration:stream=sample_rate',
1151
+ '-v', 'quiet', '-of', 'json'
1152
+ ], { encoding: 'utf-8' });
1153
+
1154
+ const probe = JSON.parse(probeOutput);
1155
+ const duration = parseFloat(probe.format?.duration || '0');
1156
+ const sampleRate = parseInt(probe.streams?.[0]?.sample_rate || '0', 10);
1157
+
1158
+ // Quality checks
1159
+ if (lufs < -18 || lufs > -14) {
1160
+ issues.push(`Loudness out of range: ${lufs} LUFS (target: -16 +/- 2)`);
1161
+ }
1162
+ if (truePeak > -1.0) {
1163
+ issues.push(`True peak too high: ${truePeak} dBTP (max: -1.0)`);
1164
+ }
1165
+ if (lra > 15) {
1166
+ issues.push(`Dynamic range too wide: ${lra} LU (max: 15)`);
1167
+ }
1168
+ if (lra < 3) {
1169
+ issues.push(`Dynamic range too narrow: ${lra} LU (min: 3)`);
1170
+ }
1171
+ if (sampleRate < 44100) {
1172
+ issues.push(`Sample rate too low: ${sampleRate} Hz (min: 44100)`);
1173
+ }
1174
+ if (duration < 10) {
1175
+ issues.push(`Duration suspiciously short: ${duration}s`);
1176
+ }
1177
+
1178
+ const pass = issues.length === 0;
1179
+
1180
+ console.log(
1181
+ `[quality-check] ${pass ? 'PASS' : 'FAIL'}` +
1182
+ ` — ${lufs.toFixed(1)} LUFS, ${truePeak.toFixed(1)} dBTP, ${lra.toFixed(1)} LU`
1183
+ );
1184
+ if (!pass) issues.forEach(i => console.warn(` - ${i}`));
1185
+
1186
+ return { lufs, truePeak, lra, duration, sampleRate, pass, issues };
1187
+ }
1188
+ ```
1189
+
1190
+ ---
1191
+
1192
+ ## 9. Advanced Techniques
1193
+
1194
+ ### Batch Episode Production
1195
+
1196
+ Generate multiple episodes from a script directory:
1197
+
1198
+ ```typescript
1199
+ import { readdir, readFile } from 'fs/promises';
1200
+
1201
+ async function batchProduceEpisodes(
1202
+ scriptDir: string,
1203
+ outputDir: string
1204
+ ): Promise<void> {
1205
+ const files = await readdir(scriptDir);
1206
+ const scripts = files.filter(f => f.endsWith('.json'));
1207
+
1208
+ console.log(`[batch] Found ${scripts.length} episode scripts`);
1209
+
1210
+ for (const scriptFile of scripts) {
1211
+ const scriptPath = join(scriptDir, scriptFile);
1212
+ const raw = await readFile(scriptPath, 'utf-8');
1213
+ const config: CompositionConfig = JSON.parse(raw);
1214
+
1215
+ config.outputPath = join(outputDir, scriptFile.replace('.json', '.mp3'));
1216
+
1217
+ const composer = new PodcastComposer(join('./temp', scriptFile));
1218
+ try {
1219
+ const result = await composer.compose(config);
1220
+ const quality = await verifyPodcastQuality(result);
1221
+ console.log(`[batch] ${scriptFile}: ${quality.pass ? 'PASS' : 'FAIL'}`);
1222
+ } finally {
1223
+ composer.cleanup();
1224
+ }
1225
+ }
1226
+ }
1227
+ ```
1228
+
1229
+ ### Dynamic Music Ducking with Silence Detection
1230
+
1231
+ Instead of static volume, detect speech segments and duck music automatically:
1232
+
1233
+ ```bash
1234
+ # Detect silent regions in speech track (pauses where music can be louder)
1235
+ ffmpeg -i speech.wav -af silencedetect=noise=-35dB:d=0.5 -f null NUL 2>&1
1236
+
1237
+ # Use sidechaincompress for real-time ducking
1238
+ # Music plays at full volume during silence, ducks under speech
1239
+ ffmpeg -i speech.wav -i music.wav \
1240
+ -filter_complex \
1241
+ "[1:a]volume=0.25[music]; \
1242
+ [music][0:a]sidechaincompress=threshold=0.02:ratio=8:attack=100:release=800:knee=3[ducked]; \
1243
+ [0:a][ducked]amix=inputs=2:duration=first:weights=1 0.8" \
1244
+ auto_ducked.wav
1245
+ ```
1246
+
1247
+ ### Chapter Markers for Podcast Players
1248
+
1249
+ Some podcast players support chapter markers (MP4/M4A format):
1250
+
1251
+ ```bash
1252
+ # Create chapter metadata file (chapters.txt)
1253
+ # Format: ;FFMETADATA1
1254
+ cat > chapters.txt << 'CHAPTEREOF'
1255
+ ;FFMETADATA1
1256
+ [CHAPTER]
1257
+ TIMEBASE=1/1000
1258
+ START=0
1259
+ END=5000
1260
+ title=Intro
1261
+
1262
+ [CHAPTER]
1263
+ TIMEBASE=1/1000
1264
+ START=5000
1265
+ END=120000
1266
+ title=Welcome & Overview
1267
+
1268
+ [CHAPTER]
1269
+ TIMEBASE=1/1000
1270
+ START=120000
1271
+ END=600000
1272
+ title=Main Discussion
1273
+
1274
+ [CHAPTER]
1275
+ TIMEBASE=1/1000
1276
+ START=600000
1277
+ END=660000
1278
+ title=Closing Thoughts
1279
+ CHAPTEREOF
1280
+
1281
+ # Apply chapters to M4A file
1282
+ ffmpeg -i podcast.m4a -i chapters.txt -map_metadata 1 -c copy podcast_with_chapters.m4a
1283
+ ```
1284
+
1285
+ ### Podcast RSS Feed Integration
1286
+
1287
+ After producing the audio, generate the RSS enclosure entry:
1288
+
1289
+ ```typescript
1290
+ import { statSync } from 'fs';
1291
+
1292
+ function generateRSSEnclosure(filePath: string, baseUrl: string): string {
1293
+ const stats = statSync(filePath);
1294
+ const filename = basename(filePath);
1295
+ const duration = getAudioDuration(filePath);
1296
+ const minutes = Math.floor(duration / 60);
1297
+ const seconds = Math.floor(duration % 60);
1298
+
1299
+ return `
1300
+ <item>
1301
+ <title>${filename.replace(/\.[^.]+$/, '').replace(/_/g, ' ')}</title>
1302
+ <enclosure url="${baseUrl}/${filename}" length="${stats.size}" type="audio/mpeg" />
1303
+ <itunes:duration>${minutes}:${seconds.toString().padStart(2, '0')}</itunes:duration>
1304
+ <pubDate>${new Date().toUTCString()}</pubDate>
1305
+ </item>
1306
+ `.trim();
1307
+ }
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ ## 10. Troubleshooting
1313
+
1314
+ ### Common Issues
1315
+
1316
+ | Problem | Cause | Fix |
1317
+ |---------|-------|-----|
1318
+ | "Discarding samples" warning | Sample rate mismatch between segments | Normalize all inputs to same sample rate first |
1319
+ | Clicks between concatenated segments | Hard cut at non-zero crossing | Add 5ms crossfade or 10ms fade-out/fade-in at boundaries |
1320
+ | Music too loud / drowns speech | `amix` normalizes by default | Use `weights` parameter or set music `volume` lower |
1321
+ | Output louder than expected | `amix` gain normalization | Add `normalize=0` to `amix` filter |
1322
+ | Crossfade produces silence gap | Duration > segment length | Crossfade duration must be shorter than shortest segment |
1323
+ | Mono/stereo mismatch | Mixing mono speech with stereo music | Convert all to same channel layout before mixing |
1324
+
1325
+ ### Fix Clicks at Boundaries
1326
+
1327
+ ```bash
1328
+ # Add 10ms fade-out to end of each segment before concatenation
1329
+ ffmpeg -i segment.wav -af "afade=t=out:st=DURATION_MINUS_0.01:d=0.01" segment_clean.wav
1330
+
1331
+ # Or add 5ms crossfade between every pair during concatenation
1332
+ # (handled by the PodcastComposer class automatically when crossfade > 0)
1333
+ ```
1334
+
1335
+ ### Fix amix Volume Normalization
1336
+
1337
+ ```bash
1338
+ # Default amix divides volume by number of inputs — speech gets quieter
1339
+ # Fix: use weights to keep speech at full volume
1340
+ ffmpeg -i speech.wav -i music.wav \
1341
+ -filter_complex "[1:a]volume=0.12[music];[0:a][music]amix=inputs=2:duration=first:weights=1 1:normalize=0" \
1342
+ output.mp3
1343
+ ```
1344
+
1345
+ ---
1346
+
1347
+ ## References
1348
+
1349
+ - FFmpeg filter documentation: https://ffmpeg.org/ffmpeg-filters.html
1350
+ - EBU R128 loudness standard: https://tech.ebu.ch/docs/r/r128.pdf
1351
+ - Apple Podcasts requirements: https://podcasters.apple.com/support/823
1352
+ - Spotify podcast specs: https://podcasters.spotify.com/resources
1353
+ - FireRedTTS-2: arXiv 2509.02020 (Sep 2025)
1354
+ - DialoSpeech: arXiv 2510.08373 (Oct 2025)
1355
+ - Related skills: [ffmpeg-command-generator.md](ffmpeg-command-generator.md), [audio-enhancement-pipeline.md](audio-enhancement-pipeline.md), [transcription-pipeline-selector.md](transcription-pipeline-selector.md), [content-repurposing-pipeline.md](content-repurposing-pipeline.md)