Who: Content creators, video editors, accessibility teams Why: Manual captioning is time-consuming and expensive. Automated captions improve accessibility and SEO.
# TEMPLATE.yamlname: video-caption-generatorversion: "1.0.0"description: "Generate captions from video files with language detection"author: "PraisonAI"license: "MIT"tags: - video - captions - accessibility - transcriptionrequires: env: - OPENAI_API_KEY packages: - praisonaiagents optional_env: - ANTHROPIC_API_KEY external: - ffmpeginputs: video_path: type: string description: "Path to the video file to caption" required: true language: type: string description: "Language code (e.g., 'en', 'es', 'fr'). Auto-detect if omitted." required: false default: "auto" output_format: type: string description: "Caption output format" required: false default: "srt" enum: - srt - vttoutputs: captions_file: type: string description: "Path to the generated caption file" summary: type: string description: "Brief summary of the video content" ok: type: boolean description: "Whether the operation succeeded"cli: command: "praison recipes run video-caption-generator" examples: - 'praison recipes run video-caption-generator --input ''{"video_path": "video.mp4"}''' - 'praison recipes run video-caption-generator --input ''{"video_path": "video.mp4", "language": "en", "output_format": "vtt"}'''safety: dry_run_default: false requires_consent: false overwrites_files: true network_access: true pii_handling: false
3
Create recipe.py
Implement the main recipe logic:
# recipe.pyimport osimport subprocessimport tempfilefrom pathlib import Pathfrom praisonaiagents import Agent, Task, AgentTeamdef run(input_data: dict, config: dict = None) -> dict: """ Generate captions from a video file. Args: input_data: Contains video_path, language, output_format config: Optional configuration overrides Returns: Dict with captions_file, summary, and ok status """ # Validate required inputs video_path = input_data.get("video_path") if not video_path: return { "ok": False, "error": {"code": "MISSING_INPUT", "message": "video_path is required"}, "captions_file": None, "summary": None, } if not os.path.exists(video_path): return { "ok": False, "error": {"code": "FILE_NOT_FOUND", "message": f"Video file not found: {video_path}"}, "captions_file": None, "summary": None, } language = input_data.get("language", "auto") output_format = input_data.get("output_format", "srt") try: # Extract audio from video audio_path = extract_audio(video_path) # Create transcription agent transcriber = Agent( name="Transcription Specialist", role="Audio Transcription Expert", goal="Accurately transcribe audio content with timestamps", instructions=""" You are an expert transcriptionist. - Transcribe audio accurately with proper punctuation - Include timestamps for each segment - Identify speaker changes when possible - Handle multiple languages if detected """, ) # Create caption formatting agent formatter = Agent( name="Caption Formatter", role="Caption File Specialist", goal=f"Format transcription into {output_format.upper()} format", instructions=f""" You are a caption formatting expert. - Convert transcriptions to {output_format.upper()} format - Ensure proper timestamp formatting - Keep caption lines under 42 characters - Split long sentences appropriately """, ) # Create summarizer agent summarizer = Agent( name="Content Summarizer", role="Video Content Analyst", goal="Provide a brief summary of the video content", instructions=""" You are a content analyst. - Summarize the main topics discussed - Keep summary under 100 words - Highlight key points """, ) # Define tasks transcribe_task = Task( name="transcribe_audio", description=f""" Transcribe the audio from: {audio_path} Language: {language if language != 'auto' else 'auto-detect'} Provide timestamped transcription segments. """, expected_output="Timestamped transcription with segments", agent=transcriber, ) format_task = Task( name="format_captions", description=f""" Format the transcription into {output_format.upper()} caption format. For SRT format: 1 00:00:00,000 --> 00:00:02,500 Caption text here For VTT format: WEBVTT 00:00:00.000 --> 00:00:02.500 Caption text here """, expected_output=f"Properly formatted {output_format.upper()} captions", agent=formatter, context=[transcribe_task], ) summarize_task = Task( name="summarize_content", description="Summarize the video content based on the transcription.", expected_output="Brief summary of video content", agent=summarizer, context=[transcribe_task], ) # Execute agents agents = AgentTeam( agents=[transcriber, formatter, summarizer], tasks=[transcribe_task, format_task, summarize_task], ) result = agents.start() # Save captions to file video_name = Path(video_path).stem captions_file = f"{video_name}.{output_format}" with open(captions_file, "w", encoding="utf-8") as f: f.write(result.get("format_captions", "")) # Cleanup temp audio file if audio_path and os.path.exists(audio_path): os.remove(audio_path) return { "ok": True, "captions_file": captions_file, "summary": result.get("summarize_content", ""), "artifacts": [ {"path": captions_file, "type": "text", "size_bytes": os.path.getsize(captions_file)} ], "warnings": [], } except Exception as e: return { "ok": False, "error": {"code": "PROCESSING_ERROR", "message": str(e)}, "captions_file": None, "summary": None, }def extract_audio(video_path: str) -> str: """Extract audio from video file using ffmpeg.""" try: # Create temp file for audio temp_audio = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) temp_audio.close() # Extract audio using ffmpeg cmd = [ "ffmpeg", "-i", video_path, "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-y", temp_audio.name ] subprocess.run(cmd, check=True, capture_output=True) return temp_audio.name except FileNotFoundError: # ffmpeg not installed - return video path for direct processing return video_path except subprocess.CalledProcessError as e: raise RuntimeError(f"Audio extraction failed: {e.stderr.decode()}")
4
Create test_recipe.py
Write tests for the recipe:
# test_recipe.pyimport pytestimport osimport tempfilefrom recipe import rundef test_missing_video_path(): """Test error handling for missing video_path.""" result = run({}) assert result["ok"] is False assert result["error"]["code"] == "MISSING_INPUT"def test_file_not_found(): """Test error handling for non-existent file.""" result = run({"video_path": "/nonexistent/video.mp4"}) assert result["ok"] is False assert result["error"]["code"] == "FILE_NOT_FOUND"def test_output_format_default(): """Test that default output format is SRT.""" # This test would need a mock video file passdef test_valid_output_formats(): """Test that both SRT and VTT formats are accepted.""" # Validation only - actual processing requires video file valid_formats = ["srt", "vtt"] for fmt in valid_formats: input_data = { "video_path": "test.mp4", "output_format": fmt } # Would need mock file for full test assert fmt in valid_formats@pytest.mark.integrationdef test_end_to_end(): """Full integration test with real video file.""" # Skip if no test video available test_video = os.environ.get("TEST_VIDEO_PATH") if not test_video or not os.path.exists(test_video): pytest.skip("No test video available") result = run({ "video_path": test_video, "language": "en", "output_format": "srt" }) assert result["ok"] is True assert result["captions_file"] is not None assert os.path.exists(result["captions_file"]) # Cleanup if os.path.exists(result["captions_file"]): os.remove(result["captions_file"])@pytest.mark.integrationdef test_vtt_format(): """Test VTT output format.""" test_video = os.environ.get("TEST_VIDEO_PATH") if not test_video: pytest.skip("No test video available") result = run({ "video_path": test_video, "output_format": "vtt" }) if result["ok"]: assert result["captions_file"].endswith(".vtt")
5
Create README.md
Document the recipe:
# Video Caption GeneratorGenerate captions from video files with automatic language detection.## Quick Start```bashpraison recipes run video-caption-generator --input '{"video_path": "my-video.mp4"}'
# Basic runpraison recipes run video-caption-generator \ --input '{"video_path": "my-video.mp4"}'# With all optionspraison recipes run video-caption-generator \ --input '{"video_path": "video.mp4", "language": "en", "output_format": "vtt"}'# Dry run (see what would happen)praison recipes run video-caption-generator \ --input '{"video_path": "video.mp4"}' \ --dry-run