DeeeeeeM
- Added changes to Playlist Tag Checker to handle Private, Unlisted, and Age restricted videos.
ba14140
| import os | |
| os.environ['KMP_DUPLICATE_LIB_OK']='True' | |
| import tempfile | |
| import mimetypes | |
| import gradio as gr | |
| import torch | |
| import stable_whisper | |
| from stable_whisper.text_output import result_to_any, sec2srt | |
| import time | |
| from yt_dlp import YoutubeDL | |
| import csv | |
| import os | |
| import subprocess | |
| import glob | |
| import shutil | |
| def process_media( | |
| model_size, source_lang, upload, model_type, | |
| max_chars, max_words, extend_in, extend_out, collapse_gaps, | |
| max_lines_per_segment, line_penalty, longest_line_char_penalty, | |
| initial_prompt=None, *args | |
| ): | |
| if not initial_prompt: | |
| initial_prompt = None | |
| start_time = time.time() | |
| if upload is None: | |
| return None, None, None, None | |
| temp_path = upload.name | |
| if model_type == "faster whisper": | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| model = stable_whisper.load_faster_whisper(model_size, device=device) | |
| result = model.transcribe( | |
| temp_path, | |
| language=source_lang, | |
| vad=True, | |
| regroup=False, | |
| #denoiser="demucs", | |
| #batch_size=16, | |
| initial_prompt=initial_prompt | |
| ) | |
| else: | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| model = stable_whisper.load_model(model_size, device=device) | |
| result = model.transcribe( | |
| temp_path, | |
| language=source_lang, | |
| vad=True, | |
| regroup=False, | |
| no_speech_threshold=0.9, | |
| denoiser="demucs", | |
| initial_prompt=initial_prompt | |
| ) | |
| # ADVANCED SETTINGS # | |
| if max_chars or max_words: | |
| result.split_by_length( | |
| max_chars=int(max_chars) if max_chars else None, | |
| max_words=int(max_words) if max_words else None | |
| ) | |
| # ----- Anti-flickering ----- # | |
| extend_start = float(extend_in) if extend_in else 0.0 | |
| extend_end = float(extend_out) if extend_out else 0.0 | |
| collapse_gaps_under = float(collapse_gaps) if collapse_gaps else 0.0 | |
| for i in range(len(result) - 1): | |
| cur = result[i] | |
| next = result[i+1] | |
| if next.start - cur.end < extend_start + extend_end: | |
| k = extend_end / (extend_start + extend_end) if (extend_start + extend_end) > 0 else 0 | |
| mid = cur.end * (1 - k) + next.start * k | |
| cur.end = next.start = mid | |
| else: | |
| cur.end += extend_end | |
| next.start -= extend_start | |
| if next.start - cur.end <= collapse_gaps_under: | |
| cur.end = next.start = (cur.end + next.start) / 2 | |
| if result: | |
| result[0].start = max(0, result[0].start - extend_start) | |
| result[-1].end += extend_end | |
| # --- Custom SRT block output --- # | |
| original_filename = os.path.splitext(os.path.basename(temp_path))[0] | |
| srt_dir = tempfile.gettempdir() | |
| subtitles_path = os.path.join(srt_dir, f"{original_filename}.srt") | |
| result_to_any( | |
| result=result, | |
| filepath=subtitles_path, | |
| filetype='srt', | |
| segments2blocks=lambda segments: segments2blocks( | |
| segments, | |
| int(max_lines_per_segment) if max_lines_per_segment else 3, | |
| float(line_penalty) if line_penalty else 22.01, | |
| float(longest_line_char_penalty) if longest_line_char_penalty else 1.0 | |
| ), | |
| word_level=False, | |
| ) | |
| srt_file_path = subtitles_path | |
| transcript_txt = result.to_txt() | |
| mime, _ = mimetypes.guess_type(temp_path) | |
| audio_out = temp_path if mime and mime.startswith("audio") else None | |
| video_out = temp_path if mime and mime.startswith("video") else None | |
| return audio_out, video_out, transcript_txt, srt_file_path | |
| def optimize_text(text, max_lines_per_segment, line_penalty, longest_line_char_penalty): | |
| text = text.strip() | |
| words = text.split() | |
| psum = [0] | |
| for w in words: | |
| psum += [psum[-1] + len(w) + 1] | |
| bestScore = 10 ** 30 | |
| bestSplit = None | |
| def backtrack(level, wordsUsed, maxLineLength, split): | |
| nonlocal bestScore, bestSplit | |
| if wordsUsed == len(words): | |
| score = level * line_penalty + maxLineLength * longest_line_char_penalty | |
| if score < bestScore: | |
| bestScore = score | |
| bestSplit = split | |
| return | |
| if level + 1 == max_lines_per_segment: | |
| backtrack( | |
| level + 1, len(words), | |
| max(maxLineLength, psum[len(words)] - psum[wordsUsed] - 1), | |
| split + [words[wordsUsed:]] | |
| ) | |
| return | |
| for levelWords in range(1, len(words) - wordsUsed + 1): | |
| backtrack( | |
| level + 1, wordsUsed + levelWords, | |
| max(maxLineLength, psum[wordsUsed + levelWords] - psum[wordsUsed] - 1), | |
| split + [words[wordsUsed:wordsUsed + levelWords]] | |
| ) | |
| backtrack(0, 0, 0, []) | |
| if not bestSplit: | |
| return text | |
| if len(bestSplit) > max_lines_per_segment or any(len(line) == 1 for line in bestSplit): | |
| return text | |
| optimized = '\n'.join(' '.join(words) for words in bestSplit) | |
| return optimized | |
| def segment2optimizedsrtblock(segment: dict, idx: int, max_lines_per_segment, line_penalty, longest_line_char_penalty, strip=True) -> str: | |
| return f'{idx}\n{sec2srt(segment["start"])} --> {sec2srt(segment["end"])}\n' \ | |
| f'{optimize_text(segment["text"], max_lines_per_segment, line_penalty, longest_line_char_penalty)}' | |
| def segments2blocks(segments, max_lines_per_segment, line_penalty, longest_line_char_penalty): | |
| return '\n\n'.join( | |
| segment2optimizedsrtblock(s, i, max_lines_per_segment, line_penalty, longest_line_char_penalty, strip=True) | |
| for i, s in enumerate(segments) | |
| ) | |
| def extract_playlist_to_csv(playlist_url, cookies_path=None): | |
| ydl_opts = { | |
| 'extract_flat': True, | |
| 'quiet': True, | |
| 'dump_single_json': True | |
| } | |
| try: | |
| cookies_path = _normalize_file_path(cookies_path) | |
| if cookies_path: | |
| ydl_opts['cookies'] = cookies_path | |
| with YoutubeDL(ydl_opts) as ydl: | |
| result = ydl.extract_info(playlist_url, download=False) | |
| entries = result.get('entries', []) | |
| fd, csv_path = tempfile.mkstemp(suffix=".csv", text=True) | |
| os.close(fd) | |
| with open(csv_path, 'w', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow(['Title', 'Video ID', 'URL']) | |
| for video in entries: | |
| title = video.get('title', 'N/A') | |
| video_id = video['id'] | |
| url = f'https://www.youtube.com/watch?v={video_id}' | |
| writer.writerow([title, video_id, url]) | |
| return csv_path | |
| except Exception as e: | |
| return None | |
| def download_srt(video_urls, cookies_path=None): | |
| try: | |
| if not video_urls: | |
| return None, "No URL provided" | |
| if isinstance(video_urls, (list, tuple)): | |
| urls = [u.strip() for u in video_urls if u and u.strip()] | |
| else: | |
| parts = [] | |
| for line in str(video_urls).splitlines(): | |
| for part in line.split(','): | |
| parts.append(part.strip()) | |
| urls = [p for p in parts if p] | |
| if not urls: | |
| return None, "No URL provided" | |
| downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads") | |
| output_template = os.path.join(downloads_dir, "%(id)s.%(ext)s") | |
| errors = [] | |
| cookies_path = _normalize_file_path(cookies_path) | |
| try: | |
| if shutil.which("yt-dlp"): | |
| for url in urls: | |
| if not url: | |
| continue | |
| cmd = [ | |
| "yt-dlp", | |
| "--write-subs", | |
| "--write-auto-subs", | |
| "--sub-lang", "en-US", | |
| "--skip-download", | |
| "--convert-subs", "srt", | |
| "-o", output_template, | |
| # pass cookies if provided | |
| url | |
| ] | |
| if cookies_path: | |
| cmd.extend(["--cookies", cookies_path]) | |
| try: | |
| result = subprocess.run(cmd, check=True, capture_output=True, text=True) | |
| print(result.stdout) | |
| print(result.stderr) | |
| except Exception as e: | |
| errors.append(f"{url}: {e}") | |
| else: | |
| ydl_opts = { | |
| 'writesubtitles': True, | |
| 'writeautomaticsub': True, | |
| 'subtitleslangs': ['en-US', 'en'], | |
| 'skip_download': True, | |
| 'outtmpl': output_template, | |
| 'quiet': True, | |
| 'subtitlesformat': 'srt' | |
| } | |
| if cookies_path: | |
| ydl_opts['cookies'] = cookies_path | |
| try: | |
| with YoutubeDL(ydl_opts) as ydl: | |
| ydl.download(urls) | |
| except Exception as e: | |
| errors.append(str(e)) | |
| except Exception as e: | |
| errors.append(str(e)) | |
| srt_files = glob.glob(os.path.join(downloads_dir, "*.srt")) | |
| vtt_files = glob.glob(os.path.join(downloads_dir, "*.vtt")) | |
| all_files = srt_files + vtt_files | |
| if not all_files: | |
| if any("HTTP Error 429" in e or "429" in e for e in errors): | |
| return None, "Error: HTTP 429 Too Many Requests from YouTube. Try again later." | |
| err_msg = "; ".join(errors) if errors else "No subtitle files found in Downloads." | |
| return None, f"SRT download error: {err_msg}" | |
| temp_dir = tempfile.mkdtemp(prefix="ssui_srt_") | |
| copied_paths = [] | |
| copy_errors = [] | |
| for fpath in all_files: | |
| try: | |
| dest = os.path.join(temp_dir, os.path.basename(fpath)) | |
| shutil.copy2(fpath, dest) | |
| copied_paths.append(dest) | |
| except Exception as e: | |
| copy_errors.append(f"{fpath}: {e}") | |
| if not copied_paths: | |
| msg = "; ".join(copy_errors) if copy_errors else "Failed to copy subtitle files." | |
| return None, f"SRT copy error: {msg}" | |
| if len(copied_paths) == 1: | |
| return copied_paths[0], f"Downloaded subtitle copied to {copied_paths[0]}" | |
| zip_base = os.path.join(temp_dir, "srt_files") | |
| zip_path = shutil.make_archive(zip_base, "zip", temp_dir) | |
| return zip_path, f"Multiple subtitle files archived to {zip_path}" | |
| except Exception as e: | |
| print("SRT download error:", e) | |
| return None, "Saved in Downloads" | |
| def _normalize_file_path(file_input): | |
| """Normalize a Gradio file return value (or path) to a string path for yt-dlp cookies. | |
| Supports strings, file-like objects, and Gradio dict-style file objects. | |
| """ | |
| if not file_input: | |
| return None | |
| # Direct string path | |
| if isinstance(file_input, str): | |
| return file_input | |
| # Gradio returns a dict sometimes with a 'name' or 'tmp_path' field | |
| if isinstance(file_input, dict): | |
| for k in ("name", "tmp_path", "tempfile", "file_path", "path"): | |
| if k in file_input and file_input[k]: | |
| return file_input[k] | |
| return None | |
| # File-like objects often have a .name attribute | |
| try: | |
| return getattr(file_input, "name", None) | |
| except Exception: | |
| return None | |
| def check_youtube_tag(video_url, tag_to_check, cookies_path=None): | |
| try: | |
| cookies_path = _normalize_file_path(cookies_path) | |
| ydl_opts = {"quiet": True} | |
| if cookies_path: | |
| ydl_opts["cookies"] = cookies_path | |
| # Use a browser-like User-Agent by default to reduce SABR/format issues | |
| ydl_opts.setdefault("http_headers", {}) | |
| ydl_opts["http_headers"].setdefault("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") | |
| with YoutubeDL(ydl_opts) as ydl: | |
| info = ydl.extract_info(video_url, download=False) | |
| tags = info.get('tags', []) | |
| tag_to_check_norm = tag_to_check.lower() | |
| tags_norm = [t.lower() for t in tags] | |
| # Exact match, case-insensitive, apostrophe style must match | |
| exists = any(tag_to_check_norm == t for t in tags_norm) | |
| if exists: | |
| return f"Tag/s '{tag_to_check}' EXISTS in video" | |
| else: | |
| return f"Tag/s '{tag_to_check}' DOES NOT EXIST in video.\n\nTags found: {tags if tags else 'None'}" | |
| except Exception as e: | |
| err = str(e) | |
| if 'Sign in to confirm your age' in err or ('Sign in' in err and 'age' in err): | |
| return f"Error checking {video_url}: This video is age-restricted and requires authentication (provide a cookies.txt file)." | |
| if 'HTTP Error 403' in err or '403' in err: | |
| return f"Error checking {video_url}: HTTP 403 Forbidden - try supplying a cookies file or updating yt-dlp with `yt-dlp -U`." | |
| return f"Error checking {video_url}: {err}" | |
| def check_playlist_tags(playlist_url, tag_to_check, cookies_path=None): | |
| import tempfile, csv | |
| try: | |
| cookies_path = _normalize_file_path(cookies_path) | |
| ydl_opts = { | |
| 'extract_flat': True, | |
| 'quiet': True, | |
| 'dump_single_json': True | |
| } | |
| if cookies_path: | |
| ydl_opts['cookies'] = cookies_path | |
| # Use browser user agent | |
| ydl_opts.setdefault("http_headers", {}) | |
| ydl_opts["http_headers"].setdefault("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") | |
| with YoutubeDL(ydl_opts) as ydl: | |
| result = ydl.extract_info(playlist_url, download=False) | |
| entries = result.get('entries', []) | |
| rows = [] | |
| tag_to_check_norm = tag_to_check.lower() | |
| for video in entries: | |
| video_id = video.get('id') | |
| if not video_id: | |
| title = video.get('title', 'N/A') | |
| rows.append([title, '', 'No video ID in playlist entry']) | |
| continue | |
| video_url = f'https://www.youtube.com/watch?v={video_id}' | |
| title = video.get('title', 'N/A') | |
| video_opts = {'quiet': True} | |
| if cookies_path: | |
| video_opts['cookies'] = cookies_path | |
| # Add a user agent | |
| video_opts.setdefault("http_headers", {}) | |
| video_opts["http_headers"].setdefault("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") | |
| try: | |
| with YoutubeDL(video_opts) as ydl_video: | |
| info = ydl_video.extract_info(video_url, download=False) | |
| # Detect unlisted flag if available | |
| is_unlisted = info.get('is_unlisted') if isinstance(info, dict) else False | |
| # Detect private, membership or age-limit fields if present | |
| is_private = info.get('is_private') if isinstance(info, dict) and 'is_private' in info else False | |
| age_limit = info.get('age_limit') if isinstance(info, dict) and 'age_limit' in info else 0 | |
| # Tags processing | |
| tags = info.get('tags', []) or [] | |
| tags_norm = [t.lower() for t in tags] | |
| exists = any(tag_to_check_norm == t for t in tags_norm) | |
| # Build note components | |
| parts = [] | |
| if is_unlisted: | |
| parts.append('Unlisted') | |
| if is_private: | |
| parts.append('Private') | |
| elif age_limit and int(age_limit) >= 18: | |
| parts.append('Age-restricted') | |
| if exists: | |
| parts.append(f"Tag/s '{tag_to_check}' exists in video") | |
| else: | |
| parts.append('Tag/s does not exist in video') | |
| note = '; '.join(parts) | |
| rows.append([title, video_url, note]) | |
| except Exception as e: | |
| err = str(e) | |
| err_lower = err.lower() | |
| if 'sign in to confirm your age' in err_lower or ('age' in err_lower and 'sign in' in err_lower): | |
| note = 'Age-restricted - cookies required or signed-in account needed' | |
| elif 'private' in err_lower and 'video' in err_lower: | |
| note = 'Private video - access denied' | |
| elif 'video unavailable' in err_lower or 'not available' in err_lower or 'removed' in err_lower: | |
| note = 'Video unavailable or removed' | |
| elif '403' in err_lower or 'forbidden' in err_lower: | |
| note = 'HTTP Error 403 Forbidden - cookies may be required or access denied' | |
| else: | |
| note = f"Could not check video: {err}" | |
| rows.append([title, video_url, note]) | |
| # Write to temp CSV | |
| fd, csv_path = tempfile.mkstemp(suffix=".csv", text=True) | |
| os.close(fd) | |
| with open(csv_path, 'w', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow(["Title", "URL", "Notes"]) | |
| writer.writerows(rows) | |
| return csv_path | |
| except Exception as e: | |
| # Write error to CSV | |
| fd, csv_path = tempfile.mkstemp(suffix=".csv", text=True) | |
| os.close(fd) | |
| with open(csv_path, 'w', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow(["Title", "URL", "Notes"]) | |
| writer.writerow(["Error", "", str(e)]) | |
| return csv_path | |
| WHISPER_LANGUAGES = [ | |
| ("Afrikaans", "af"), | |
| ("Albanian", "sq"), | |
| ("Amharic", "am"), | |
| ("Arabic", "ar"), | |
| ("Armenian", "hy"), | |
| ("Assamese", "as"), | |
| ("Azerbaijani", "az"), | |
| ("Bashkir", "ba"), | |
| ("Basque", "eu"), | |
| ("Belarusian", "be"), | |
| ("Bengali", "bn"), | |
| ("Bosnian", "bs"), | |
| ("Breton", "br"), | |
| ("Bulgarian", "bg"), | |
| ("Burmese", "my"), | |
| ("Catalan", "ca"), | |
| ("Chinese", "zh"), | |
| ("Croatian", "hr"), | |
| ("Czech", "cs"), | |
| ("Danish", "da"), | |
| ("Dutch", "nl"), | |
| ("English", "en"), | |
| ("Estonian", "et"), | |
| ("Faroese", "fo"), | |
| ("Finnish", "fi"), | |
| ("French", "fr"), | |
| ("Galician", "gl"), | |
| ("Georgian", "ka"), | |
| ("German", "de"), | |
| ("Greek", "el"), | |
| ("Gujarati", "gu"), | |
| ("Haitian Creole", "ht"), | |
| ("Hausa", "ha"), | |
| ("Hebrew", "he"), | |
| ("Hindi", "hi"), | |
| ("Hungarian", "hu"), | |
| ("Icelandic", "is"), | |
| ("Indonesian", "id"), | |
| ("Italian", "it"), | |
| ("Japanese", "ja"), | |
| ("Javanese", "jv"), | |
| ("Kannada", "kn"), | |
| ("Kazakh", "kk"), | |
| ("Khmer", "km"), | |
| ("Korean", "ko"), | |
| ("Lao", "lo"), | |
| ("Latin", "la"), | |
| ("Latvian", "lv"), | |
| ("Lingala", "ln"), | |
| ("Lithuanian", "lt"), | |
| ("Luxembourgish", "lb"), | |
| ("Macedonian", "mk"), | |
| ("Malagasy", "mg"), | |
| ("Malay", "ms"), | |
| ("Malayalam", "ml"), | |
| ("Maltese", "mt"), | |
| ("Maori", "mi"), | |
| ("Marathi", "mr"), | |
| ("Mongolian", "mn"), | |
| ("Nepali", "ne"), | |
| ("Norwegian", "no"), | |
| ("Nyanja", "ny"), | |
| ("Occitan", "oc"), | |
| ("Pashto", "ps"), | |
| ("Persian", "fa"), | |
| ("Polish", "pl"), | |
| ("Portuguese", "pt"), | |
| ("Punjabi", "pa"), | |
| ("Romanian", "ro"), | |
| ("Russian", "ru"), | |
| ("Sanskrit", "sa"), | |
| ("Serbian", "sr"), | |
| ("Shona", "sn"), | |
| ("Sindhi", "sd"), | |
| ("Sinhala", "si"), | |
| ("Slovak", "sk"), | |
| ("Slovenian", "sl"), | |
| ("Somali", "so"), | |
| ("Spanish", "es"), | |
| ("Sundanese", "su"), | |
| ("Swahili", "sw"), | |
| ("Swedish", "sv"), | |
| ("Tagalog", "tl"), | |
| ("Tajik", "tg"), | |
| ("Tamil", "ta"), | |
| ("Tatar", "tt"), | |
| ("Telugu", "te"), | |
| ("Thai", "th"), | |
| ("Turkish", "tr"), | |
| ("Turkmen", "tk"), | |
| ("Ukrainian", "uk"), | |
| ("Urdu", "ur"), | |
| ("Uzbek", "uz"), | |
| ("Vietnamese", "vi"), | |
| ("Welsh", "cy"), | |
| ("Yiddish", "yi"), | |
| ("Yoruba", "yo"), | |
| ] | |
| with gr.Blocks() as interface: | |
| gr.HTML( | |
| """ | |
| <style>.html-container.svelte-phx28p.padding { padding: 0 !important; }</style> | |
| <div class='custom-container'> | |
| <h1 style='text-align: left;'>Speech Solutions✨</h1> | |
| <p style='text-align: left;'> Hosted on 🤗 | |
| <a href="https://huggingface.co/spaces/DeeeeeM/ssui-app" target="_blank"> | |
| <b>Hugging Face Spaces</b> | |
| </a> | |
| </p> | |
| """ | |
| ) | |
| gr.Markdown( | |
| """ | |
| This is a Gradio UI app that combines AI-powered speech and language processing technologies. This app supports the following features: | |
| - Speech-to-text (WhisperAI) | |
| - Language translation (GPT-4) (In progress) | |
| - Improved transcription (GPT-4) (In progress) | |
| - Text to Speech (In progress) | |
| UPDATE: The app now includes Youtube metadata extraction features: (title / URL / ID, subtitles, tag checking) | |
| <i><b>NOTE: This app is currently in the process of applying other AI-solutions for other use cases.</b></i> | |
| """ | |
| ) | |
| with gr.Tabs(): | |
| with gr.TabItem("Speech to Text"): | |
| gr.HTML("<h2 style='text-align: left;'>OpenAI / Whisper + stable-ts</h2>") | |
| gr.Markdown( | |
| """ | |
| Open Ai's <b>Whisper</b> is a versatile speech recognition model trained on diverse audio for tasks like multilingual transcription, translation, and language ID. With the help of <b>stable-ts</b>, it provides accurate word-level timestamps in chronological order without extra processing. | |
| <i>Note: The default values are set for balanced and faster processing, | |
| you can choose: large, large v2, and large v3 <b>MODEL SIZE</b> for more accuracy, but they may take longer to process.</i> | |
| """ | |
| ) | |
| #General Settings | |
| with gr.Row(): | |
| #Media Input | |
| with gr.Column(scale=1): | |
| file_input = gr.File(label="Upload Audio or Video", file_types=["audio", "video"]) | |
| #Settings | |
| with gr.Column(scale=1): | |
| with gr.Group(): | |
| source_lang = gr.Dropdown( | |
| choices=WHISPER_LANGUAGES, | |
| label="Source Language", | |
| value="tl", | |
| interactive=True | |
| ) | |
| model_type = gr.Dropdown( | |
| choices=["faster whisper", "whisper"], | |
| label="Model Type", | |
| value="faster whisper", | |
| interactive=True | |
| ) | |
| model_size = gr.Dropdown( | |
| choices=[ | |
| "deepdml/faster-whisper-large-v3-turbo-ct2", | |
| "large-v3-turbo", | |
| "large-v3", | |
| "large-v2", | |
| "large", | |
| "medium", | |
| "small", | |
| "base", | |
| "tiny" | |
| ], | |
| label="Model Size", | |
| value="large-v2", | |
| interactive=True | |
| ) | |
| initial_prompt = gr.Textbox( | |
| label="Initial Prompt (optional)", | |
| lines=3, | |
| placeholder="Add context, names, or style for the model here", | |
| interactive=True | |
| ) | |
| #Advanced Settings | |
| with gr.Accordion("Advanced Settings", open=False): | |
| gr.Markdown( | |
| """ | |
| These settings allow you to customize the segmentation of the audio or video file. Adjust these parameters to control how the segments are created based on characters, words, and lines. | |
| <b><i>Note: The values currently set are the default values. You can adjust them to your needs, but be aware that changing these values may affect the segmentation of the audio or video file.</i></b> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| max_chars = gr.Number( | |
| label="Max Chars", | |
| info="Maximum characters allowed in segment", | |
| value=86, | |
| precision=0, | |
| interactive=True | |
| ) | |
| max_words = gr.Number( | |
| label="Max Words", | |
| info="Maximum words allowed in segment", | |
| value=30, | |
| precision=0, | |
| interactive=True | |
| ) | |
| max_lines_per_segment = gr.Number( | |
| label="Max Lines Per Segment", | |
| info="Max lines allowed per subtitle segment", | |
| value=3, | |
| precision=0, | |
| interactive=True | |
| ) | |
| with gr.Column(): | |
| extend_in = gr.Number( | |
| label="Extend In", | |
| info="Extend the start of all segments by this value (in seconds)", | |
| value=0, | |
| precision=2, | |
| ) | |
| extend_out = gr.Number( | |
| label="Extend Out", | |
| info="Extend the end of all segments by this value (in seconds)", | |
| value=0.5, | |
| precision=2, | |
| interactive=True | |
| ) | |
| collapse_gaps = gr.Number( | |
| label="Collapse Gaps", | |
| info="Collapse gaps between segments under a certain duration", | |
| value=0.3, | |
| precision=2, | |
| interactive=True | |
| ) | |
| with gr.Column(): | |
| line_penalty = gr.Number( | |
| label="Longest Line Character", | |
| info="Penalty for each additional line (used to decide when to split segment into several lines)", | |
| value=22.01, | |
| precision=2, | |
| interactive=True | |
| ) | |
| longest_line_char_penalty = gr.Number( | |
| label="Longest Line Character", | |
| info="Penalty for each character of the longest segment line (used to decide when to split segment into several lines)", | |
| value=1, | |
| precision=2, | |
| interactive=True | |
| ) | |
| submit_btn = gr.Button("- PROCESS -") | |
| with gr.Row(): | |
| with gr.Column(): | |
| transcript_output = gr.Textbox(label="Transcript", lines=8, interactive=False) | |
| srt_output = gr.File(label="Download SRT", interactive=False) | |
| with gr.Column(): | |
| video_output = gr.Video(label="Video Output") | |
| audio_output = gr.Audio(label="Audio Output") | |
| submit_btn.click( | |
| fn=process_media, | |
| inputs=[ | |
| model_size, source_lang, file_input, model_type, | |
| max_chars, max_words, extend_in, extend_out, collapse_gaps, | |
| max_lines_per_segment, line_penalty, longest_line_char_penalty | |
| ], | |
| outputs=[audio_output, video_output, transcript_output, srt_output] | |
| ) | |
| with gr.TabItem("Youtube playlist extractor"): | |
| gr.Markdown("### Extract YT Title, URL, and ID from a YouTube playlist and download as CSV.") | |
| playlist_url = gr.Textbox(label="YouTube Playlist URL", placeholder="Paste playlist URL here") | |
| cookie_file_extract = gr.File(label="YouTube Cookies File (optional)", file_types=None, interactive=True) | |
| process_btn = gr.Button("Process") | |
| csv_output = gr.File(label="Download CSV") | |
| process_btn.click( | |
| extract_playlist_to_csv, | |
| inputs=[playlist_url, cookie_file_extract], | |
| outputs=csv_output | |
| ) | |
| with gr.TabItem("SRT Downloader"): | |
| gr.Markdown("### Download English subtitles (.srt) from a YouTube video(s). <i>Separate each URL with a comma or Enter for multiple videos.</i>") | |
| srt_url = gr.Textbox(label="YouTube Video URL", placeholder="Paste video URL here") | |
| cookie_file_srt = gr.File(label="YouTube Cookies File (optional)", file_types=None, interactive=True) | |
| srt_btn = gr.Button("Process") | |
| srt_file = gr.File(label="Download SRT") | |
| srt_status = gr.Textbox(label="Status", interactive=False) | |
| srt_btn.click( | |
| download_srt, | |
| inputs=[srt_url, cookie_file_srt], | |
| outputs=[srt_file, srt_status] | |
| ) | |
| with gr.TabItem("Tag Checker"): | |
| gr.Markdown("### Check if a specific tag exists in a YouTube video's metadata.") | |
| gr.Markdown("*Tip: If a video is age-restricted or otherwise requires authentication, export cookies from your browser (cookies.txt) and upload it below.*") | |
| gr.Markdown("*How to export cookies: Install the 'Get cookies.txt' extension in your browser, sign into YouTube in the browser, then export using the extension and upload the cookies file here.*") | |
| tag_url = gr.Textbox(label="YouTube Video URL", placeholder="Paste video URL here") | |
| tag_input = gr.Textbox(label="Tag to Check", placeholder="Type the tag (e.g. series:my father's wife)") | |
| cookie_file_tag = gr.File(label="YouTube Cookies File (optional)", file_types=None, interactive=True) | |
| tag_btn = gr.Button("Process") | |
| tag_output = gr.Textbox(label="Tag Check Result", interactive=False) | |
| tag_btn.click( | |
| check_youtube_tag, | |
| inputs=[tag_url, tag_input, cookie_file_tag], | |
| outputs=tag_output | |
| ) | |
| with gr.TabItem("Playlist Tag Checker"): | |
| gr.Markdown( | |
| """ | |
| Check if a specific tag exists in all videos of a YouTube playlist. | |
| <b><i>Note: The process may take longer due to the number of videos being checked.</i></b> | |
| """ | |
| ) | |
| gr.Markdown("*Tip: If some videos are age-restricted, upload a cookies.txt file so the app can check them.*") | |
| gr.Markdown("*How to export cookies: Install the 'Get cookies.txt' extension in your browser, sign into YouTube in the browser, then export using the extension and upload the cookies file here.*") | |
| playlist_url_tags = gr.Textbox(label="YouTube Playlist URL", placeholder="Paste playlist URL here") | |
| tag_input_playlist = gr.Textbox(label="Tag to Check", placeholder="Type the tag (e.g. series:my father's wife)") | |
| cookie_file_playlist = gr.File(label="YouTube Cookies File (optional)", file_types=None, interactive=True) | |
| tag_btn_playlist = gr.Button("Process") | |
| tag_output_playlist = gr.File(label="Download Tag Check CSV", interactive=False) | |
| tag_btn_playlist.click( | |
| check_playlist_tags, | |
| inputs=[playlist_url_tags, tag_input_playlist, cookie_file_playlist], | |
| outputs=tag_output_playlist | |
| ) | |
| gr.HTML( | |
| """ | |
| <audio id="notify-audio" src="https://www.soundjay.com/buttons/sounds/button-3.mp3"></audio> | |
| <script> | |
| function playNotify() { | |
| var audio = document.getElementById('notify-audio'); | |
| if (audio) { audio.play(); } | |
| } | |
| let outputs = document.querySelectorAll("textarea, input[type='file'], video, audio"); | |
| outputs.forEach(function(output) { | |
| output.addEventListener("change", playNotify); | |
| }); | |
| }); | |
| </script> | |
| """ | |
| ) | |
| interface.launch(share=True) |