Created
May 20, 2025 22:52
-
-
Save ZanSara/4bab5db89376d595128e0688804d694c to your computer and use it in GitHub Desktop.
Video to SRT with Deepgram
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Video to SRT Converter using Deepgram API | |
| This script extracts audio from a video file and uses Deepgram's API to generate | |
| subtitles in SRT format. | |
| Requirements: | |
| deepgram-sdk>=3.11.0 | |
| ffmpeg-python>=0.2.0 | |
| httpx>=0.24.0 | |
| # Video to SRT Converter | |
| A Python script that extracts audio from video files and uses Deepgram's speech recognition API to generate accurate subtitles in SRT format. | |
| ## Features | |
| - Extracts audio from video files using FFmpeg | |
| - Transcribes audio using Deepgram's API | |
| - Generates properly formatted SRT subtitle files | |
| - Supports timestamp formatting for dialogue | |
| - Command-line interface for easy usage | |
| ## Requirements | |
| - Python 3.7 or higher | |
| - FFmpeg installed on your system | |
| - Deepgram API key | |
| ## Installation | |
| 1. Clone or download this repository | |
| 2. Install the required Python packages: | |
| ```bash | |
| pip install -r requirements.txt | |
| ``` | |
| 3. Make sure FFmpeg is installed on your system: | |
| - On Ubuntu/Debian: `sudo apt install ffmpeg` | |
| - On macOS (with Homebrew): `brew install ffmpeg` | |
| - On Windows: Download from [FFmpeg's official website](https://ffmpeg.org/download.html) | |
| 4. Get a Deepgram API key: | |
| - Sign up at [Deepgram](https://console.deepgram.com/signup) | |
| - Create a new API key in your dashboard | |
| ## Usage | |
| You can use the script from the command line: | |
| ```bash | |
| python video_to_srt.py your_video.mp4 --api-key YOUR_DEEPGRAM_API_KEY | |
| ``` | |
| Or set the API key as an environment variable: | |
| ```bash | |
| # Set the API key as an environment variable | |
| export DEEPGRAM_API_KEY=your_deepgram_api_key | |
| # Run the script | |
| python video_to_srt.py your_video.mp4 | |
| ``` | |
| ### Command-line options | |
| ``` | |
| usage: video_to_srt.py [-h] [-o OUTPUT] [-k API_KEY] [--keep-audio] video_path | |
| Convert video to SRT subtitles using Deepgram API | |
| positional arguments: | |
| video_path Path to the video file | |
| optional arguments: | |
| -h, --help show this help message and exit | |
| -o OUTPUT, --output OUTPUT | |
| Path to save the SRT file (default: same as video with .srt extension) | |
| -k API_KEY, --api-key API_KEY | |
| Deepgram API key (can also be set with DEEPGRAM_API_KEY environment variable) | |
| --keep-audio Keep the extracted audio file (default: False) | |
| ``` | |
| ## Example | |
| ```bash | |
| python video_to_srt.py interview.mp4 -o interview_subtitles.srt | |
| ``` | |
| This will: | |
| 1. Extract audio from `interview.mp4` | |
| 2. Transcribe the audio using Deepgram | |
| 3. Create `interview_subtitles.srt` with properly formatted subtitles | |
| ## Notes | |
| - The script uses Deepgram's Nova-2 model by default, which provides accurate transcription for English. | |
| - For non-English videos, you can modify the language parameter in the `transcribe_audio` function. | |
| - The audio extraction creates a temporary file that is deleted after processing unless you use the `--keep-audio` flag. | |
| ## License | |
| This project is licensed under the MIT License - see the LICENSE file for details. | |
| ## Acknowledgments | |
| - [Deepgram](https://deepgram.com/) for their speech recognition API | |
| - [FFmpeg](https://ffmpeg.org/) for audio/video processing capabilities | |
| """ | |
| import os | |
| import sys | |
| import argparse | |
| import datetime | |
| import tempfile | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Any | |
| import ffmpeg | |
| import httpx | |
| from deepgram import DeepgramClient, PrerecordedOptions, FileSource, DeepgramClientOptions | |
| def extract_audio(video_path: str, output_path: Optional[str] = None) -> str: | |
| """ | |
| Extract audio from a video file using ffmpeg. | |
| Args: | |
| video_path: Path to the video file | |
| output_path: Optional path for the output audio file | |
| Returns: | |
| Path to the extracted audio file | |
| """ | |
| output_path = (output_path or "audio") + ".wav" | |
| try: | |
| # Use ffmpeg to extract audio | |
| ( | |
| ffmpeg | |
| .input(video_path) | |
| .output(output_path, acodec='pcm_s16le', ar='16000', ac=1) | |
| .global_args('-y') # Overwrite output file if it exists | |
| .run(quiet=True, capture_stdout=True, capture_stderr=True) | |
| ) | |
| return output_path | |
| except ffmpeg.Error as e: | |
| print(f"Error extracting audio: {e.stderr.decode()}") | |
| if os.path.exists(output_path): | |
| os.remove(output_path) | |
| sys.exit(1) | |
| def transcribe_audio(audio_path: str, api_key: str) -> Any: | |
| """ | |
| Transcribe audio using Deepgram API. | |
| Args: | |
| audio_path: Path to the audio file | |
| api_key: Deepgram API key | |
| Returns: | |
| Deepgram response object | |
| """ | |
| try: | |
| # Initialize the Deepgram client | |
| deepgram = DeepgramClient(api_key) | |
| # Set up options for the transcription | |
| options = PrerecordedOptions( | |
| model="nova-3", | |
| smart_format=True, | |
| utterances=True, | |
| punctuate=True, | |
| diarize=True, | |
| language="multi", | |
| filler_words=True | |
| ) | |
| # Read the audio file | |
| with open(audio_path, "rb") as audio: | |
| buffer_data = audio.read() | |
| # Create the file source payload | |
| payload = { | |
| "buffer": buffer_data | |
| } | |
| # Send the audio to Deepgram for transcription | |
| response = deepgram.listen.rest.v("1").transcribe_file( | |
| payload, options, timeout=httpx.Timeout(300.0, connect=10.0) | |
| ) | |
| print(response) | |
| return response | |
| except Exception as e: | |
| print(f"Error during transcription: {str(e)}") | |
| sys.exit(1) | |
| def format_time(seconds: float) -> str: | |
| """ | |
| Format time in SRT format (HH:MM:SS,mmm). | |
| Args: | |
| seconds: Time in seconds | |
| Returns: | |
| Formatted time string | |
| """ | |
| # Convert seconds to a timedelta | |
| time_obj = datetime.timedelta(seconds=seconds) | |
| # Extract hours, minutes, seconds, and milliseconds | |
| hours = int(time_obj.total_seconds() // 3600) | |
| minutes = int((time_obj.total_seconds() % 3600) // 60) | |
| seconds = int(time_obj.total_seconds() % 60) | |
| milliseconds = int((time_obj.total_seconds() % 1) * 1000) | |
| # Format the time in SRT format | |
| return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}" | |
| def create_srt(response: Any, output_path: str) -> None: | |
| """ | |
| Create an SRT file from a Deepgram response. | |
| Args: | |
| response: Deepgram transcription response | |
| output_path: Path to save the SRT file | |
| """ | |
| try: | |
| if hasattr(response, 'to_dict'): | |
| response_dict = response.to_dict() | |
| else: | |
| response_dict = response | |
| # Check for utterances in the response | |
| if 'results' in response_dict and 'utterances' in response_dict['results']: | |
| utterances = response_dict['results']['utterances'] | |
| with open(output_path, 'w', encoding='utf-8') as srt_file: | |
| for i, utterance in enumerate(utterances): | |
| start_time = utterance['start'] | |
| end_time = utterance['end'] | |
| text = utterance['transcript'] | |
| # Write the subtitle entry to the SRT file | |
| srt_file.write(f"{i+1}\n") | |
| srt_file.write(f"{format_time(start_time)} --> {format_time(end_time)}\n") | |
| srt_file.write(f"{text}\n\n") | |
| print(f"SRT file saved to {output_path}") | |
| # Handle the alternative response structure | |
| elif 'results' in response_dict and 'channels' in response_dict['results'] and response_dict['results']['channels']: | |
| channel = response_dict['results']['channels'][0] | |
| if 'alternatives' in channel and channel['alternatives']: | |
| alternative = channel['alternatives'][0] | |
| # Check if words are available | |
| if 'words' in alternative and alternative['words']: | |
| words = alternative['words'] | |
| with open(output_path, 'w', encoding='utf-8') as srt_file: | |
| # Group words into sentences or chunks of reasonable length | |
| chunks = [] | |
| current_chunk = [] | |
| for word in words: | |
| current_chunk.append(word) | |
| # Start a new chunk after punctuation or when chunk gets too long | |
| if word['word'][-1] in '.!?' or len(current_chunk) >= 10: | |
| chunks.append(current_chunk) | |
| current_chunk = [] | |
| # Add any remaining words as a chunk | |
| if current_chunk: | |
| chunks.append(current_chunk) | |
| # Write each chunk as a subtitle entry | |
| for i, chunk in enumerate(chunks): | |
| if chunk: | |
| start_time = chunk[0]['start'] | |
| end_time = chunk[-1]['end'] | |
| text = ' '.join(word['word'] for word in chunk) | |
| # Write the subtitle entry to the SRT file | |
| srt_file.write(f"{i+1}\n") | |
| srt_file.write(f"{format_time(start_time)} --> {format_time(end_time)}\n") | |
| srt_file.write(f"{text}\n\n") | |
| print(f"SRT file saved to {output_path}") | |
| # Handle case where transcript is available but not word-level timestamps | |
| elif 'transcript' in alternative: | |
| transcript = alternative['transcript'] | |
| with open(output_path, 'w', encoding='utf-8') as srt_file: | |
| # Add entire transcript as a single subtitle | |
| srt_file.write("1\n") | |
| srt_file.write("00:00:00,000 --> 99:59:59,999\n") # Full duration | |
| srt_file.write(f"{transcript}\n\n") | |
| print(f"SRT file saved to {output_path} (full transcript only, no timestamps)") | |
| else: | |
| print("Error: No transcript or words found in the response") | |
| sys.exit(1) | |
| else: | |
| print("Error: No alternatives found in the response") | |
| sys.exit(1) | |
| else: | |
| print("Error: Unexpected response format from Deepgram API") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error creating SRT file: {str(e)}") | |
| sys.exit(1) | |
| def main(): | |
| # Set up command line argument parsing | |
| parser = argparse.ArgumentParser(description="Convert video to SRT subtitles using Deepgram API") | |
| parser.add_argument("video_path", help="Path to the video file") | |
| parser.add_argument( | |
| "-o", "--output", | |
| help="Path to save the SRT file (default: same as video with .srt extension)" | |
| ) | |
| parser.add_argument( | |
| "-k", "--api-key", | |
| help="Deepgram API key (can also be set with DEEPGRAM_API_KEY environment variable)" | |
| ) | |
| parser.add_argument( | |
| "-l", "--language", default="pt", | |
| help="Language code for transcription (default: pt for Portuguese)" | |
| ) | |
| parser.add_argument( | |
| "--keep-audio", action="store_true", | |
| help="Keep the extracted audio file (default: False)" | |
| ) | |
| parser.add_argument( | |
| "--verbose", action="store_true", | |
| help="Enable verbose logging (default: False)" | |
| ) | |
| args = parser.parse_args() | |
| # Check if the video file exists | |
| if not os.path.isfile(args.video_path): | |
| print(f"Error: Video file '{args.video_path}' not found") | |
| sys.exit(1) | |
| # Get the API key | |
| api_key = args.api_key or os.environ.get("DEEPGRAM_API_KEY") | |
| if not api_key: | |
| print("Error: Deepgram API key not provided") | |
| print("Please provide the API key using the -k/--api-key option or set the DEEPGRAM_API_KEY environment variable") | |
| sys.exit(1) | |
| # Set the output path for the SRT file | |
| video_path = Path(args.video_path) | |
| if args.output: | |
| srt_path = args.output | |
| else: | |
| srt_path = str(video_path.with_suffix('.srt')) | |
| # Extract audio from the video | |
| print(f"Extracting audio from {args.video_path}...") | |
| audio_path = extract_audio(args.video_path, args.video_path) | |
| try: | |
| # Transcribe the audio | |
| print("Transcribing audio using Deepgram API...") | |
| response = transcribe_audio(audio_path, api_key) | |
| # Create the SRT file | |
| print("Creating SRT file...") | |
| create_srt(response, srt_path) | |
| finally: | |
| # Clean up the temporary audio file if not keeping it | |
| if not args.keep_audio and audio_path: | |
| os.remove(audio_path) | |
| print(f"Temporary audio file removed") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment