Nick088/Fast-Subtitle-Maker
64
1import os2import subprocess3import json4from datetime import timedelta5import tempfile6import re7import gradio as gr8import groq9from groq import Groq10 11 12# setup groq 13 14client = Groq(api_key=os.environ.get("Groq_Api_Key"))15 16def handle_groq_error(e, model_name):17 error_data = e.args[0]18 19 if isinstance(error_data, str):20 # Use regex to extract the JSON part of the string21 json_match = re.search(r'(\{.*\})', error_data)22 if json_match:23 json_str = json_match.group(1)24 # Ensure the JSON string is well-formed25 json_str = json_str.replace("'", '"') # Replace single quotes with double quotes26 error_data = json.loads(json_str)27 28 if isinstance(e, groq.AuthenticationError):29 if isinstance(error_data, dict) and 'error' in error_data and 'message' in error_data['error']:30 error_message = error_data['error']['message']31 raise gr.Error(error_message)32 elif isinstance(e, groq.RateLimitError):33 if isinstance(error_data, dict) and 'error' in error_data and 'message' in error_data['error']:34 error_message = error_data['error']['message']35 error_message = re.sub(r'org_[a-zA-Z0-9]+', 'org_(censored)', error_message) # censor org36 raise gr.Error(error_message)37 else:38 raise gr.Error(f"Error during Groq API call: {e}")39 40 41# language codes for subtitle maker42 43LANGUAGE_CODES = {44 "English": "en",45 "Chinese": "zh",46 "German": "de",47 "Spanish": "es",48 "Russian": "ru",49 "Korean": "ko",50 "French": "fr",51 "Japanese": "ja",52 "Portuguese": "pt",53 "Turkish": "tr",54 "Polish": "pl",55 "Catalan": "ca",56 "Dutch": "nl",57 "Arabic": "ar",58 "Swedish": "sv",59 "Italian": "it",60 "Indonesian": "id",61 "Hindi": "hi",62 "Finnish": "fi",63 "Vietnamese": "vi",64 "Hebrew": "he",65 "Ukrainian": "uk",66 "Greek": "el",67 "Malay": "ms",68 "Czech": "cs",69 "Romanian": "ro",70 "Danish": "da",71 "Hungarian": "hu",72 "Tamil": "ta",73 "Norwegian": "no",74 "Thai": "th",75 "Urdu": "ur",76 "Croatian": "hr",77 "Bulgarian": "bg",78 "Lithuanian": "lt",79 "Latin": "la",80 "Māori": "mi",81 "Malayalam": "ml",82 "Welsh": "cy",83 "Slovak": "sk",84 "Telugu": "te",85 "Persian": "fa",86 "Latvian": "lv",87 "Bengali": "bn",88 "Serbian": "sr",89 "Azerbaijani": "az",90 "Slovenian": "sl",91 "Kannada": "kn",92 "Estonian": "et",93 "Macedonian": "mk",94 "Breton": "br",95 "Basque": "eu",96 "Icelandic": "is",97 "Armenian": "hy",98 "Nepali": "ne",99 "Mongolian": "mn",100 "Bosnian": "bs",101 "Kazakh": "kk",102 "Albanian": "sq",103 "Swahili": "sw",104 "Galician": "gl",105 "Marathi": "mr",106 "Panjabi": "pa",107 "Sinhala": "si",108 "Khmer": "km",109 "Shona": "sn",110 "Yoruba": "yo",111 "Somali": "so",112 "Afrikaans": "af",113 "Occitan": "oc",114 "Georgian": "ka",115 "Belarusian": "be",116 "Tajik": "tg",117 "Sindhi": "sd",118 "Gujarati": "gu",119 "Amharic": "am",120 "Yiddish": "yi",121 "Lao": "lo",122 "Uzbek": "uz",123 "Faroese": "fo",124 "Haitian": "ht",125 "Pashto": "ps",126 "Turkmen": "tk",127 "Norwegian Nynorsk": "nn",128 "Maltese": "mt",129 "Sanskrit": "sa",130 "Luxembourgish": "lb",131 "Burmese": "my",132 "Tibetan": "bo",133 "Tagalog": "tl",134 "Malagasy": "mg",135 "Assamese": "as",136 "Tatar": "tt",137 "Hawaiian": "haw",138 "Lingala": "ln",139 "Hausa": "ha",140 "Bashkir": "ba",141 "jw": "jw",142 "Sundanese": "su",143}144 145 146# helper functions147 148def split_audio(input_file_path, chunk_size_mb):149 chunk_size = chunk_size_mb * 1024 * 1024 # Convert MB to bytes150 file_number = 1151 chunks = []152 with open(input_file_path, 'rb') as f:153 chunk = f.read(chunk_size)154 while chunk:155 chunk_name = f"{os.path.splitext(input_file_path)[0]}_part{file_number:03}.mp3" # Pad file number for correct ordering156 with open(chunk_name, 'wb') as chunk_file:157 chunk_file.write(chunk)158 chunks.append(chunk_name)159 file_number += 1160 chunk = f.read(chunk_size)161 return chunks162 163def merge_audio(chunks, output_file_path):164 with open("temp_list.txt", "w") as f:165 for file in chunks:166 f.write(f"file '{file}'\n")167 try:168 subprocess.run(169 [170 "ffmpeg",171 "-f",172 "concat",173 "-safe", "0",174 "-i",175 "temp_list.txt",176 "-c",177 "copy",178 "-y",179 output_file_path180 ],181 check=True182 )183 os.remove("temp_list.txt")184 for chunk in chunks:185 os.remove(chunk)186 except subprocess.CalledProcessError as e:187 raise gr.Error(f"Error during audio merging: {e}")188 189 190# Checks file extension, size, and downsamples or splits if needed.191 192ALLOWED_FILE_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"]193MAX_FILE_SIZE_MB = 25194CHUNK_SIZE_MB = 25195 196def check_file(input_file_path):197 if not input_file_path:198 raise gr.Error("Please upload an audio/video file.")199 200 file_size_mb = os.path.getsize(input_file_path) / (1024 * 1024)201 file_extension = input_file_path.split(".")[-1].lower()202 203 if file_extension not in ALLOWED_FILE_EXTENSIONS:204 raise gr.Error(f"Invalid file type (.{file_extension}). Allowed types: {', '.join(ALLOWED_FILE_EXTENSIONS)}")205 206 if file_size_mb > MAX_FILE_SIZE_MB:207 gr.Warning(208 f"File size too large ({file_size_mb:.2f} MB). Attempting to downsample to 16kHz MP3 128kbps. Maximum size allowed: {MAX_FILE_SIZE_MB} MB"209 )210 211 output_file_path = os.path.splitext(input_file_path)[0] + "_downsampled.mp3"212 try:213 subprocess.run(214 [215 "ffmpeg",216 "-i",217 input_file_path,218 "-ar",219 "16000",220 "-ab",221 "128k",222 "-ac",223 "1",224 "-f",225 "mp3",226 "-y",227 output_file_path,228 ],229 check=True230 )231 232 # Check size after downsampling233 downsampled_size_mb = os.path.getsize(output_file_path) / (1024 * 1024)234 if downsampled_size_mb > MAX_FILE_SIZE_MB:235 gr.Warning(f"File still too large after downsampling ({downsampled_size_mb:.2f} MB). Splitting into {CHUNK_SIZE_MB} MB chunks.")236 return split_audio(output_file_path, CHUNK_SIZE_MB), "split"237 238 return output_file_path, None239 except subprocess.CalledProcessError as e:240 raise gr.Error(f"Error during downsampling: {e}")241 return input_file_path, None242 243 244# subtitle maker245 246def format_time(seconds_float):247 # Calculate total whole seconds and milliseconds248 total_seconds = int(seconds_float)249 milliseconds = int((seconds_float - total_seconds) * 1000)250 251 # Calculate hours, minutes, and remaining seconds252 hours = total_seconds // 3600253 minutes = (total_seconds % 3600) // 60254 seconds = total_seconds % 60255 256 return f"{hours:02}:{minutes:02}:{seconds:02},{milliseconds:03}"257 258def json_to_srt(transcription_json):259 srt_lines = []260 261 for segment in transcription_json:262 start_time = format_time(segment['start'])263 end_time = format_time(segment['end'])264 text = segment['text']265 266 srt_line = f"{segment['id']+1}\n{start_time} --> {end_time}\n{text}\n"267 srt_lines.append(srt_line)268 269 return '\n'.join(srt_lines)270 271 272def words_json_to_srt(words_data, starting_id=0):273 srt_lines = []274 previous_end_time = 0.0 # Keep track of the end time of the previous word275 276 for i, word_entry in enumerate(words_data):277 # Get original start and end times278 start_seconds = word_entry['start']279 end_seconds = word_entry['end']280 281 # --- Overlap Prevention Logic ---282 # Ensure the start time is not before the previous word ended283 start_seconds = max(start_seconds, previous_end_time)284 285 # Ensure the end time is not before the start time (can happen with adjustments)286 # And add a tiny minimum duration (e.g., 50ms) if start and end are identical,287 # otherwise the subtitle might flash too quickly or be ignored by players.288 min_duration = 0.050 # 50 milliseconds289 if end_seconds <= start_seconds:290 end_seconds = start_seconds + min_duration291 # --- End of Overlap Prevention ---292 293 # Format the potentially adjusted times294 start_time_fmt = format_time(start_seconds)295 end_time_fmt = format_time(end_seconds)296 text = word_entry['word']297 srt_id = starting_id + i + 1298 299 srt_line = f"{srt_id}\n{start_time_fmt} --> {end_time_fmt}\n{text}\n"300 srt_lines.append(srt_line)301 302 # Update previous_end_time for the next iteration using the *adjusted* end time303 previous_end_time = end_seconds 304 305 return '\n'.join(srt_lines)306 307def generate_subtitles(input_file, prompt, timestamp_granularities_str, language, auto_detect_language, model, include_video, font_selection, font_file, font_color, font_size, outline_thickness, outline_color):308 309 input_file_path = input_file310 311 processed_path, split_status = check_file(input_file_path)312 full_srt_content = "" # Used for accumulating SRT content string for split files313 srt_chunks_paths = [] # Used to store paths of individual SRT chunk files for merging314 video_chunks = [] # Used to store paths of video chunks with embedded subs315 total_duration = 0 # Cumulative duration for timestamp adjustment in split files316 srt_entry_offset = 0 # Cumulative SRT entry count (words or segments) for ID adjustment317 318 # transforms the gradio dropdown choice str to a python list needed for the groq api319 timestamp_granularities_list = [gran.strip() for gran in timestamp_granularities_str.split(',') if gran.strip()]320 321 # Determine primary granularity for logic (prefer word if both specified, else segment)322 primary_granularity = "word" if "word" in timestamp_granularities_list else "segment"323 324 # handling splitted files or single ones325 if split_status == "split":326 for i, chunk_path in enumerate(processed_path):327 chunk_srt_content = "" # SRT content for the current chunk328 temp_srt_path = f"{os.path.splitext(chunk_path)[0]}.srt" # Path for this chunk's SRT file329 330 try:331 gr.Info(f"Processing chunk {i+1}/{len(processed_path)}...")332 with open(chunk_path, "rb") as file:333 transcription_json_response = client.audio.transcriptions.create(334 file=(os.path.basename(chunk_path), file.read()),335 model=model,336 prompt=prompt,337 response_format="verbose_json",338 timestamp_granularities=timestamp_granularities_list,339 language=None if auto_detect_language else language,340 temperature=0.0,341 )342 343 if primary_granularity == "word":344 word_data = transcription_json_response.words345 if word_data:346 # Adjust timestamps BEFORE generating SRT347 adjusted_word_data = []348 for entry in word_data:349 adjusted_entry = entry.copy()350 adjusted_entry['start'] += total_duration351 adjusted_entry['end'] += total_duration352 adjusted_word_data.append(adjusted_entry)353 354 # Generate SRT using adjusted data and current offset355 chunk_srt_content = words_json_to_srt(adjusted_word_data, srt_entry_offset)356 357 # Update offsets for the *next* chunk358 total_duration = adjusted_word_data[-1]['end'] # Use adjusted end time359 srt_entry_offset += len(word_data) # Increment by number of words in this chunk360 else:361 gr.Warning(f"API returned no word timestamps for chunk {i+1}.")362 363 elif primary_granularity == "segment":364 segment_data = transcription_json_response.segments365 if segment_data:366 # Adjust timestamps and IDs BEFORE generating SRT367 adjusted_segment_data = []368 max_original_id = -1369 for entry in segment_data:370 adjusted_entry = entry.copy()371 adjusted_entry['start'] += total_duration372 adjusted_entry['end'] += total_duration373 max_original_id = max(max_original_id, adjusted_entry['id']) # Track max original ID for offset calc374 adjusted_entry['id'] += srt_entry_offset # Adjust ID for SRT generation375 adjusted_segment_data.append(adjusted_entry)376 377 # Generate SRT using adjusted data378 chunk_srt_content = json_to_srt(adjusted_segment_data) # json_to_srt uses the 'id' field directly379 380 # Update offsets for the *next* chunk381 total_duration = adjusted_segment_data[-1]['end'] # Use adjusted end time382 srt_entry_offset += (max_original_id + 1) # Increment by number of segments in this chunk (based on original IDs)383 else:384 gr.Warning(f"API returned no segment timestamps for chunk {i+1}.")385 else:386 # This case should ideally not be reached due to dropdown default/logic387 gr.Warning(f"Invalid timestamp granularity for chunk {i+1}. Skipping SRT generation for this chunk.")388 389 # Write and store path for this chunk's SRT file if content exists390 if chunk_srt_content:391 with open(temp_srt_path, "w", encoding="utf-8") as temp_srt_file:392 temp_srt_file.write(chunk_srt_content)393 srt_chunks_paths.append(temp_srt_path)394 full_srt_content += chunk_srt_content # Append to the full content string as well395 396 # Video embedding for the chunk397 if include_video and input_file_path.lower().endswith((".mp4", ".webm")):398 try:399 output_video_chunk_path = chunk_path.replace(os.path.splitext(chunk_path)[1], "_with_subs" + os.path.splitext(chunk_path)[1])400 # Handle font selection401 font_name = None402 font_dir = None403 if font_selection == "Custom Font File" and font_file:404 font_name = os.path.splitext(os.path.basename(font_file.name))[0]405 font_dir = os.path.dirname(font_file.name)406 elif font_selection == "Custom Font File" and not font_file:407 gr.Warning(f"Custom Font File selected but none uploaded. Using default font for chunk {i+1}.")408 409 # FFmpeg command for the chunk410 subprocess.run(411 [412 "ffmpeg", "-y", "-i", chunk_path,413 "-vf", f"subtitles={temp_srt_path}:fontsdir={font_dir}:force_style='FontName={font_name},Fontsize={int(font_size)},PrimaryColour=&H{font_color[1:]}&,OutlineColour=&H{outline_color[1:]}&,BorderStyle={int(outline_thickness)},Outline=1'",414 "-preset", "fast", output_video_chunk_path,415 ], check=True,416 )417 video_chunks.append(output_video_chunk_path)418 except subprocess.CalledProcessError as e:419 # Warn but continue processing other chunks420 gr.Warning(f"Error adding subtitles to video chunk {i+1}: {e}. Skipping video for this chunk.")421 except Exception as e: # Catch other potential errors during font handling etc.422 gr.Warning(f"Error preparing subtitle style for video chunk {i+1}: {e}. Skipping video for this chunk.")423 424 elif include_video and i == 0: # Show warning only once for non-video input425 gr.Warning(f"Include Video checked, but input isn't MP4/WebM. Only SRT will be generated.", duration=15)426 427 428 except groq.AuthenticationError as e:429 handle_groq_error(e, model) # This will raise gr.Error and stop execution430 except groq.RateLimitError as e:431 handle_groq_error(e, model) # This will raise gr.Error and stop execution432 except Exception as e:433 gr.Warning(f"Error processing chunk {i+1}: {e}. Skipping this chunk.")434 # Remove potentially incomplete SRT for this chunk if it exists435 if os.path.exists(temp_srt_path):436 try: os.remove(temp_srt_path)437 except: pass438 continue # Move to the next chunk439 440 # After processing all chunks441 final_srt_path = None442 final_video_path = None443 444 # Merge SRT chunks if any were created445 if srt_chunks_paths:446 final_srt_path = os.path.splitext(input_file_path)[0] + "_final.srt"447 gr.Info("Merging SRT chunks...")448 with open(final_srt_path, 'w', encoding="utf-8") as outfile:449 # Use the full_srt_content string which ensures correct order and content450 outfile.write(full_srt_content)451 # Clean up individual srt chunks paths452 for srt_chunk_file in srt_chunks_paths:453 try: os.remove(srt_chunk_file)454 except: pass455 # Clean up intermediate audio chunks used for transcription456 for chunk in processed_path:457 try: os.remove(chunk)458 except: pass459 else:460 gr.Warning("No SRT content was generated from any chunk.")461 462 463 # Merge video chunks if any were created464 if video_chunks:465 # Check if number of video chunks matches expected number based on successful SRT generation466 if len(video_chunks) != len(srt_chunks_paths):467 gr.Warning("Mismatch between successful SRT chunks and video chunks created. Video merge might be incomplete.")468 469 final_video_path = os.path.splitext(input_file_path)[0] + '_merged_video_with_subs.mp4' # More descriptive name470 gr.Info("Merging video chunks...")471 try:472 merge_audio(video_chunks, final_video_path) # Re-using merge_audio logic for video files473 # video_chunks are removed inside merge_audio if successful474 except Exception as e:475 gr.Error(f"Failed to merge video chunks: {e}")476 final_video_path = None # Indicate failure477 478 return final_srt_path, final_video_path479 480 else: # Single file processing (no splitting)481 final_srt_path = None482 final_video_path = None483 temp_srt_path = os.path.splitext(processed_path)[0] + ".srt" # Use processed_path for naming484 485 try:486 gr.Info("Processing file...")487 with open(processed_path, "rb") as file:488 transcription_json_response = client.audio.transcriptions.create(489 file=(os.path.basename(processed_path), file.read()),490 model=model,491 prompt=prompt,492 response_format="verbose_json",493 timestamp_granularities=timestamp_granularities_list,494 language=None if auto_detect_language else language,495 temperature=0.0,496 )497 498 srt_content = "" # Initialize499 500 if primary_granularity == "word":501 word_data = transcription_json_response.words502 if word_data:503 srt_content = words_json_to_srt(word_data, 0) # Start IDs from 0504 else:505 gr.Warning("API returned no word timestamps.")506 elif primary_granularity == "segment":507 segment_data = transcription_json_response.segments508 if segment_data:509 # No need to adjust IDs/timestamps for single file510 srt_content = json_to_srt(segment_data)511 else:512 gr.Warning("API returned no segment timestamps.")513 else:514 # Should not happen515 gr.Warning("Invalid timestamp granularity selected. Skipping SRT generation.")516 517 # Write SRT file if content exists518 if srt_content:519 with open(temp_srt_path, "w", encoding="utf-8") as temp_srt_file:520 temp_srt_file.write(srt_content)521 final_srt_path = temp_srt_path # Set the final path522 523 # Video embedding logic524 if include_video and input_file_path.lower().endswith((".mp4", ".webm")):525 try:526 output_video_path = processed_path.replace(527 os.path.splitext(processed_path)[1], "_with_subs" + os.path.splitext(processed_path)[1]528 )529 # Handle font selection530 font_name = None531 font_dir = None532 if font_selection == "Custom Font File" and font_file:533 font_name = os.path.splitext(os.path.basename(font_file.name))[0]534 font_dir = os.path.dirname(font_file.name)535 elif font_selection == "Custom Font File" and not font_file:536 gr.Warning(f"Custom Font File selected but none uploaded. Using default font.")537 538 # FFmpeg command539 gr.Info("Adding subtitles to video...")540 subprocess.run(541 [542 "ffmpeg", "-y", "-i", processed_path, # Use processed_path as input543 "-vf", f"subtitles={temp_srt_path}:fontsdir={font_dir}:force_style='FontName={font_name},Fontsize={int(font_size)},PrimaryColour=&H{font_color[1:]}&,OutlineColour=&H{outline_color[1:]}&,BorderStyle={int(outline_thickness)},Outline=1'",544 "-preset", "fast", output_video_path,545 ], check=True,546 )547 final_video_path = output_video_path548 except subprocess.CalledProcessError as e:549 gr.Error(f"Error during subtitle addition: {e}")550 # Keep SRT file, but no video output551 final_video_path = None552 except Exception as e:553 gr.Error(f"Error preparing subtitle style for video: {e}")554 final_video_path = None555 556 elif include_video:557 # Warning for non-video input shown once558 gr.Warning(f"Include Video checked, but input isn't MP4/WebM. Only SRT will be generated.", duration=15)559 560 # Clean up downsampled file if it was created and different from original input561 if processed_path != input_file_path and os.path.exists(processed_path):562 try: os.remove(processed_path)563 except: pass564 565 return final_srt_path, final_video_path # Return paths (video might be None)566 567 else: # No SRT content generated568 gr.Warning("No SRT content could be generated.")569 # Clean up downsampled file if created570 if processed_path != input_file_path and os.path.exists(processed_path):571 try: os.remove(processed_path)572 except: pass573 return None, None # Return None for both outputs574 575 except groq.AuthenticationError as e:576 handle_groq_error(e, model)577 except groq.RateLimitError as e:578 handle_groq_error(e, model)579 except Exception as e: # Catch any other error during single file processing580 # Clean up downsampled file if created581 if processed_path != input_file_path and os.path.exists(processed_path):582 try: os.remove(processed_path)583 except: pass584 # Clean up potentially created empty SRT585 if os.path.exists(temp_srt_path):586 try: os.remove(temp_srt_path)587 except: pass588 raise gr.Error(f"An unexpected error occurred: {e}")589 590 591theme = gr.themes.Soft(592 primary_hue="sky",593 secondary_hue="blue",594 neutral_hue="neutral"595).set(596 border_color_primary='*neutral_300',597 block_border_width='1px',598 block_border_width_dark='1px',599 block_title_border_color='*secondary_100',600 block_title_border_color_dark='*secondary_200',601 input_background_fill_focus='*secondary_300',602 input_border_color='*border_color_primary',603 input_border_color_focus='*secondary_500',604 input_border_width='1px',605 input_border_width_dark='1px',606 slider_color='*secondary_500',607 slider_color_dark='*secondary_600'608)609 610css = """611.gradio-container{max-width: 1400px !important}612h1{text-align:center}613.extra-option {614 display: none;615}616.extra-option.visible {617 display: block;618}619"""620 621 622 623with gr.Blocks(theme=theme, css=css) as interface:624 gr.Markdown(625 """626 # Fast Subtitle Maker627 Inference by Groq API 628 If you are having API Rate Limit issues, you can retry later based on the [rate limits](https://console.groq.com/docs/rate-limits) or <a href="https://huggingface.co/spaces/Nick088/Fast-Subtitle-Maker?duplicate=true" style="display: inline-block;margin-top: .5em;margin-right: .25em;" target="_blank"> <img style="margin-bottom: 0em;display: inline;margin-top: -.25em;" src="https://img.shields.io/badge/-Duplicate%20Space-blue?labelColor=white&style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAP5JREFUOE+lk7FqAkEURY+ltunEgFXS2sZGIbXfEPdLlnxJyDdYB62sbbUKpLbVNhyYFzbrrA74YJlh9r079973psed0cvUD4A+4HoCjsA85X0Dfn/RBLBgBDxnQPfAEJgBY+A9gALA4tcbamSzS4xq4FOQAJgCDwV2CPKV8tZAJcAjMMkUe1vX+U+SMhfAJEHasQIWmXNN3abzDwHUrgcRGmYcgKe0bxrblHEB4E/pndMazNpSZGcsZdBlYJcEL9Afo75molJyM2FxmPgmgPqlWNLGfwZGG6UiyEvLzHYDmoPkDDiNm9JR9uboiONcBXrpY1qmgs21x1QwyZcpvxt9NS09PlsPAAAAAElFTkSuQmCC&logoWidth=14" alt="Duplicate Space"></a> with <a href=https://console.groq.com/keys>your own API Key</a> </p>629 Hugging Face Space by [Nick088](https://linktr.ee/Nick088) 630 <br> <a href="https://discord.gg/AQsmBmgEPy"> <img src="https://img.shields.io/discord/1198701940511617164?color=%23738ADB&label=Discord&style=for-the-badge" alt="Discord"> </a> 631 """632 )633 634 with gr.Column():635 # Input components636 input_file = gr.File(label="Upload Audio/Video", file_types=[f".{ext}" for ext in ALLOWED_FILE_EXTENSIONS], visible=True)637 638 # Model and options639 model_choice_subtitles = gr.Dropdown(choices=["whisper-large-v3", "whisper-large-v3-turbo", "distil-whisper-large-v3-en"], value="whisper-large-v3-turbo", label="Audio Speech Recogition (ASR) Model", info="'whisper-large-v3' = Multilingual high quality, 'whisper-large-v3-turbo' = Multilingual fast with minimal impact on quality, good balance, 'distil-whisper-large-v3-en' = English only, fastest with also slight impact on quality")640 transcribe_prompt_subtitles = gr.Textbox(label="Prompt (Optional)", info="Specify any context or spelling corrections.")641 timestamp_granularities_str = gr.Dropdown(choices=["word", "segment"], value="word", label="Timestamp Granularities", info="The level of detail of time measurement in the timestamps.")642 with gr.Row():643 language_subtitles = gr.Dropdown(choices=[(lang, code) for lang, code in LANGUAGE_CODES.items()], value="en", label="Language")644 auto_detect_language_subtitles = gr.Checkbox(label="Auto Detect Language")645 646 # Generate button647 transcribe_button_subtitles = gr.Button("Generate Subtitles")648 649 # Output and settings650 include_video_option = gr.Checkbox(label="Include Video with Subtitles")651 gr.Markdown("The SubText Rip (SRT) File, contains the subtitles, you can upload this to any video editing app for adding the subs to your video and also modify/stilyze them")652 srt_output = gr.File(label="SRT Output File")653 show_subtitle_settings = gr.Checkbox(label="Show Subtitle Video Settings", visible=False)654 with gr.Row(visible=False) as subtitle_video_settings:655 with gr.Column():656 font_selection = gr.Radio(["Arial", "Custom Font File"], value="Arial", label="Font Selection", info="Select what font to use")657 font_file = gr.File(label="Upload Font File (TTF or OTF)", file_types=[".ttf", ".otf"], visible=False)658 font_color = gr.ColorPicker(label="Font Color", value="#FFFFFF")659 font_size = gr.Slider(label="Font Size (in pixels)", minimum=10, maximum=60, value=24, step=1)660 outline_thickness = gr.Slider(label="Outline Thickness", minimum=0, maximum=5, value=1, step=1)661 outline_color = gr.ColorPicker(label="Outline Color", value="#000000")662 663 664 video_output = gr.Video(label="Output Video with Subtitles", visible=False)665 666 667 # Event bindings668 669 # show video output670 include_video_option.change(lambda include_video: gr.update(visible=include_video), inputs=[include_video_option], outputs=[video_output])671 # show video output subs settings checkbox672 include_video_option.change(lambda include_video: gr.update(visible=include_video), inputs=[include_video_option], outputs=[show_subtitle_settings])673 # show video output subs settings674 show_subtitle_settings.change(lambda show: gr.update(visible=show), inputs=[show_subtitle_settings], outputs=[subtitle_video_settings])675 # uncheck show subtitle settings checkbox if include video is unchecked (to make the output subs settings not visible)676 show_subtitle_settings.change(lambda show, include_video: gr.update(visible=show and include_video), inputs=[show_subtitle_settings, include_video_option], outputs=[show_subtitle_settings])677 # show custom font file selection678 font_selection.change(lambda font_selection: gr.update(visible=font_selection == "Custom Font File"), inputs=[font_selection], outputs=[font_file])679 680 # Update language dropdown based on model selection681 def update_language_options(model):682 if model == "distil-whisper-large-v3-en":683 return gr.update(choices=[("English", "en")], value="en", interactive=False)684 else:685 return gr.update(choices=[(lang, code) for lang, code in LANGUAGE_CODES.items()], value="en", interactive=True)686 687 model_choice_subtitles.change(fn=update_language_options, inputs=[model_choice_subtitles], outputs=[language_subtitles])688 689 # Modified generate subtitles event690 transcribe_button_subtitles.click(691 fn=generate_subtitles,692 inputs=[693 input_file,694 transcribe_prompt_subtitles,695 timestamp_granularities_str,696 language_subtitles,697 auto_detect_language_subtitles,698 model_choice_subtitles,699 include_video_option,700 font_selection,701 font_file,702 font_color,703 font_size,704 outline_thickness,705 outline_color,706 ],707 outputs=[srt_output, video_output],708 )709 710interface.launch(share=True)