CoolFace
Apppublic

Nick088/Fast-Subtitle-Maker

sourceHugging Faceotherupdated 1y agoView on Hugging Face
64likes
app.py710 linesDownload Raw Back to root
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)