| 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__) |
|
|
| |
| UPLOAD_FOLDER = 'uploads' |
| OUTPUT_FOLDER = 'outputs' |
| os.makedirs(UPLOAD_FOLDER, exist_ok=True) |
| os.makedirs(OUTPUT_FOLDER, exist_ok=True) |
|
|
| |
| print("π Loading Whisper model...") |
| model = whisper.load_model("small") |
| print("β
Model loaded!") |
|
|
| |
| HTML = ''' |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Word-by-Word Subtitle Generator</title> |
| <style> |
| body { font-family: Arial; max-width: 600px; margin: 50px auto; padding: 20px; } |
| input, button { padding: 10px; margin: 10px 0; width: 100%; } |
| button { background: #007bff; color: white; border: none; cursor: pointer; } |
| button:hover { background: #0056b3; } |
| .status { margin: 20px 0; padding: 10px; border-radius: 5px; } |
| .success { background: #d4edda; color: #155724; } |
| .error { background: #f8d7da; color: #721c24; } |
| </style> |
| </head> |
| <body> |
| <h1>π¬ Word-by-Word Subtitle Generator</h1> |
| <p>Upload video, get word-by-word subtitles burned in!</p> |
| |
| <form action="/process" method="post" enctype="multipart/form-data"> |
| <input type="file" name="video" accept="video/*" required> |
| <button type="submit">π¬ Generate Subtitles</button> |
| </form> |
| <div id="status" class="status" style="display:none"></div> |
| </body> |
| </html> |
| ''' |
|
|
| 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 |
| |
| |
| video_id = uuid.uuid4().hex[:8] |
| filename = secure_filename(file.filename) |
| |
| |
| 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: |
| |
| file.save(input_path) |
| print(f"π Video saved: {input_path}") |
| |
| |
| print("π΅ Extracting audio...") |
| extract_audio(input_path, audio_path) |
| |
| |
| print("ποΈ Transcribing with Whisper...") |
| words = get_word_timestamps(audio_path) |
| print(f"β
Found {len(words)} words") |
| |
| |
| print("π Generating subtitles...") |
| generate_srt(words, srt_path) |
| |
| |
| print("π₯ Adding subtitles to video...") |
| add_subtitles(input_path, srt_path, output_path) |
| |
| print(f"β
Done! Output: {output_path}") |
| |
| |
| 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: |
| |
| 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) |