| import os |
| import uuid |
| import subprocess |
| import whisper |
| from flask import Flask, request, send_file, render_template_string |
| from werkzeug.utils import secure_filename |
| import re |
| import platform |
|
|
| 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!") |
|
|
| |
|
|
| def get_system_font(): |
| """Get appropriate font path based on operating system""" |
| system = platform.system() |
| |
| if system == "Linux": |
| |
| font_paths = [ |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", |
| "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf", |
| "/usr/share/fonts/truetype/ubuntu/Ubuntu-Regular.ttf" |
| ] |
| elif system == "Windows": |
| |
| font_paths = [ |
| "C:\\Windows\\Fonts\\arial.ttf", |
| "C:\\Windows\\Fonts\\consola.ttf", |
| "C:\\Windows\\Fonts\\segoeui.ttf" |
| ] |
| elif system == "Darwin": |
| font_paths = [ |
| "/System/Library/Fonts/Arial.ttf", |
| "/System/Library/Fonts/Helvetica.ttc" |
| ] |
| else: |
| font_paths = ["Arial"] |
| |
| for font in font_paths: |
| if os.path.exists(font): |
| print(f"✅ Found font: {font}") |
| return font |
| |
| print("⚠️ No specific font found, using 'Arial' as fallback") |
| return "Arial" |
|
|
| FONT_PATH = get_system_font() |
|
|
| |
|
|
| def is_urdu_script(text): |
| """Check if text contains Urdu/Arabic script characters""" |
| urdu_range = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]') |
| return bool(urdu_range.search(text)) |
|
|
| def urdu_to_roman_simple(urdu_text): |
| """Simple Urdu to Roman conversion""" |
| urdu_to_roman = { |
| 'ا': 'a', 'ب': 'b', 'پ': 'p', 'ت': 't', 'ٹ': 't', 'ث': 's', |
| 'ج': 'j', 'چ': 'ch', 'ح': 'h', 'خ': 'kh', 'د': 'd', 'ڈ': 'd', |
| 'ذ': 'z', 'ر': 'r', 'ڑ': 'r', 'ز': 'z', 'ژ': 'zh', 'س': 's', |
| 'ش': 'sh', 'ص': 's', 'ض': 'z', 'ط': 't', 'ظ': 'z', 'ع': 'a', |
| 'غ': 'gh', 'ف': 'f', 'ق': 'q', 'ک': 'k', 'گ': 'g', 'ل': 'l', |
| 'م': 'm', 'ن': 'n', 'و': 'o', 'ہ': 'h', 'ھ': 'h', 'ء': 'a', |
| 'ی': 'y', 'ے': 'e', ' ': ' ', '?': '?', '!': '!', '،': ',', '۔': '.', |
| 'آ': 'a', 'ؤ': 'o', 'ئ': 'i', 'ۓ': 'e' |
| } |
| |
| roman_chars = [] |
| for char in urdu_text: |
| if char in urdu_to_roman: |
| roman_chars.append(urdu_to_roman[char]) |
| else: |
| roman_chars.append(char) |
| |
| roman_text = ''.join(roman_chars) |
| roman_text = re.sub(r'aa', 'a', roman_text) |
| roman_text = re.sub(r'ee', 'i', roman_text) |
| roman_text = re.sub(r'oo', 'o', roman_text) |
| |
| return roman_text |
|
|
| def convert_to_roman(text): |
| """Convert Urdu script to Roman Urdu""" |
| if is_urdu_script(text): |
| return urdu_to_roman_simple(text) |
| return text |
|
|
| |
| 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; } |
| </style> |
| </head> |
| <body> |
| <h1>🎬 Word-by-Word Subtitle Generator</h1> |
| <p>Upload video (Urdu → Roman Urdu, English as is)</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> |
| </body> |
| </html> |
| ''' |
|
|
| def extract_audio(video_path, audio_path): |
| """Extract audio from video""" |
| 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""" |
| result = model.transcribe(audio_path, word_timestamps=True) |
| |
| words = [] |
| for segment in result['segments']: |
| for word in segment.get('words', []): |
| original = word['word'].strip() |
| display = convert_to_roman(original) |
| |
| words.append({ |
| 'word': display, |
| 'start': word['start'], |
| 'end': word['end'] |
| }) |
| |
| return words |
|
|
| def format_time(seconds): |
| """Convert seconds to SRT time format""" |
| 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 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 using FFmpeg with proper font""" |
| |
| font_style = f"FontName={FONT_PATH},FontSize=24,OutlineColour=&H00000000,BorderStyle=1" |
| |
| cmd = [ |
| 'ffmpeg', '-i', video_path, |
| '-vf', f"subtitles={srt_path}:force_style='{font_style}'", |
| '-c:a', 'copy', |
| '-preset', 'fast', |
| output_path, '-y' |
| ] |
| |
| print(f"🎨 Using font: {FONT_PATH}") |
| subprocess.run(cmd, 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(): |
| 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") |
| |
| print("🎵 Extracting audio...") |
| extract_audio(input_path, audio_path) |
| |
| print("🎙️ Transcribing...") |
| words = get_word_timestamps(audio_path) |
| print(f"✅ Found {len(words)} words") |
| |
| print("📝 Generating subtitles...") |
| generate_srt(words, srt_path) |
| |
| print("🔥 Burning subtitles...") |
| add_subtitles(input_path, srt_path, 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) |