import os import uuid import subprocess import whisper from flask import Flask, request, send_file, render_template_string from werkzeug.utils import secure_filename app = Flask(__name__) # Configuration UPLOAD_FOLDER = 'uploads' OUTPUT_FOLDER = 'outputs' os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(OUTPUT_FOLDER, exist_ok=True) # Load Whisper model (small for speed, or 'base', 'medium') print("🔄 Loading Whisper model...") model = whisper.load_model("small") # small is good balance of speed/accuracy print("✅ Model loaded!") # HTML Template HTML = '''
Upload video, get word-by-word subtitles burned in!
''' def extract_audio(video_path, audio_path): """Extract audio from video using FFmpeg""" subprocess.run([ 'ffmpeg', '-i', video_path, '-ac', '1', '-ar', '16000', audio_path, '-y' ], capture_output=True, check=True) def get_word_timestamps(audio_path): """Get word-by-word timestamps using Whisper""" result = model.transcribe(audio_path, word_timestamps=True) words = [] for segment in result['segments']: for word in segment.get('words', []): words.append({ 'word': word['word'].strip(), 'start': word['start'], 'end': word['end'] }) return words def format_time(seconds): """Convert seconds to SRT time format (HH:MM:SS,mmm)""" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = seconds % 60 millis = int((secs % 1) * 1000) return f"{hours:02d}:{minutes:02d}:{int(secs):02d},{millis:03d}" def generate_srt(words, output_path): """Generate SRT file with word-by-word timing""" with open(output_path, 'w', encoding='utf-8') as f: for i, word in enumerate(words, 1): start = format_time(word['start']) end = format_time(word['end']) f.write(f"{i}\n{start} --> {end}\n{word['word']}\n\n") return output_path def add_subtitles(video_path, srt_path, output_path): """Burn subtitles into video using FFmpeg""" subprocess.run([ 'ffmpeg', '-i', video_path, '-vf', f"subtitles={srt_path}:force_style='FontSize=24,OutlineColour=&H00000000,BorderStyle=1'", '-c:a', 'copy', '-preset', 'fast', output_path, '-y' ], capture_output=True, check=True) return output_path @app.route('/') def home(): return render_template_string(HTML) @app.route('/process', methods=['POST']) def process_video(): """Main processing endpoint""" if 'video' not in request.files: return "No video file", 400 file = request.files['video'] if file.filename == '': return "No file selected", 400 # Generate unique IDs video_id = uuid.uuid4().hex[:8] filename = secure_filename(file.filename) # File paths input_path = os.path.join(UPLOAD_FOLDER, f"{video_id}_{filename}") audio_path = os.path.join(UPLOAD_FOLDER, f"{video_id}.wav") srt_path = os.path.join(OUTPUT_FOLDER, f"{video_id}.srt") output_path = os.path.join(OUTPUT_FOLDER, f"{video_id}_subtitled.mp4") try: # Step 1: Save video file.save(input_path) print(f"📁 Video saved: {input_path}") # Step 2: Extract audio print("🎵 Extracting audio...") extract_audio(input_path, audio_path) # Step 3: Get word timestamps print("🎙️ Transcribing with Whisper...") words = get_word_timestamps(audio_path) print(f"✅ Found {len(words)} words") # Step 4: Generate SRT print("📝 Generating subtitles...") generate_srt(words, srt_path) # Step 5: Burn subtitles into video print("🔥 Adding subtitles to video...") add_subtitles(input_path, srt_path, output_path) print(f"✅ Done! Output: {output_path}") # Send file for download return send_file( output_path, as_attachment=True, download_name="subtitled_video.mp4" ) except Exception as e: print(f"❌ Error: {e}") return f"Error: {str(e)}", 500 finally: # Cleanup for f in [input_path, audio_path, srt_path]: if os.path.exists(f): try: os.remove(f) except: pass @app.route('/health') def health(): return {"status": "ok"} if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, debug=False)