| import gradio as gr |
| import subprocess |
| import os |
| import json |
| import re |
| import shutil |
| import zipfile |
| from pathlib import Path |
| from urllib.parse import urlparse, unquote |
|
|
| try: |
| import requests |
| except ImportError: |
| print("Error: Falta instalar 'requests'") |
|
|
| |
| subprocess.run(["git", "config", "--global", "user.email", "bot@codeberg.org"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| subprocess.run(["git", "config", "--global", "user.name", "Hugging Face Bot"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
|
|
| |
|
|
| def clean_for_folder(name): |
| return re.sub(r'[<>:"/\\|?*]', '_', name).strip()[:200] |
|
|
| def clean_for_repo(name): |
| name = re.sub(r'[^a-zA-Z0-9_-]', '-', name) |
| return re.sub(r'-+', '-', name).strip('-')[:100] |
|
|
| def get_filename_from_url(url): |
| try: |
| parsed = urlparse(url) |
| path = unquote(parsed.path) |
| basename = os.path.basename(path) |
| if basename and '.' in basename: |
| return Path(basename).stem |
| except: |
| pass |
| return "video" |
|
|
| def log_generator(message, log_list): |
| log_list.append(message) |
| return "\n".join(log_list), log_list |
|
|
| def detect_audio_streams(source_val): |
| cmd_probe = [ |
| 'ffprobe', '-v', 'error', |
| '-select_streams', 'a', |
| '-show_entries', 'stream=index,codec_name:stream_tags=language,title', |
| '-of', 'json', |
| '-i', source_val |
| ] |
| try: |
| res = subprocess.run(cmd_probe, capture_output=True, text=True, timeout=60) |
| data = json.loads(res.stdout) |
| streams = data.get('streams', []) |
| audio_tracks = [] |
| for stream in streams: |
| index = stream.get('index', 0) |
| tags = stream.get('tags', {}) |
| language = tags.get('language', 'und') |
| title = tags.get('title', f'Audio {index}') |
| codec = stream.get('codec_name', 'unknown') |
| audio_tracks.append({'index': index, 'language': language.lower(), 'title': title, 'codec': codec}) |
| return audio_tracks |
| except Exception as e: |
| return [] |
|
|
| def prioritize_audio_tracks(tracks): |
| priority_langs = ['spa', 'es', 'spanish', 'español', 'latino', 'lat', 'es-mx', 'es-419'] |
| def get_priority(track): |
| lang = track['language'].lower() |
| title = track['title'].lower() |
| for term in priority_langs: |
| if term in lang or term in title: return 0 |
| if lang == 'und': return 1 |
| return 2 |
| return sorted(tracks, key=get_priority) |
|
|
| def create_master_m3u8(output_dir, video_streams, audio_playlists): |
| master_content = "#EXTM3U\n#EXT-X-VERSION:7\n\n" |
| for i, audio_info in enumerate(audio_playlists): |
| default = "YES" if audio_info['is_default'] else "NO" |
| autoselect = "YES" if audio_info['is_default'] else "NO" |
| master_content += f'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="{audio_info["title"]}",LANGUAGE="{audio_info["language"]}",DEFAULT={default},AUTOSELECT={autoselect},URI="{audio_info["file"]}"\n' |
| master_content += "\n" |
| for vid in video_streams: |
| master_content += f'#EXT-X-STREAM-INF:BANDWIDTH={vid["bandwidth"]},RESOLUTION={vid["resolution"]},CODECS="{vid["codecs"]}",AUDIO="audio"\n{vid["file"]}\n' |
| with open(output_dir / "master.m3u8", "w") as f: |
| f.write(master_content) |
|
|
| |
|
|
| def upload_to_codeberg(output_dir, repo_name, codeberg_token, username, batch_size, stream_format, logs): |
| try: |
| msg, logs = log_generator("📦 Creando repositorio en Codeberg...", logs) |
| yield msg, logs, None |
| |
| url_api = f"https://codeberg.org/api/v1/user/repos" |
| headers = {"Authorization": f"token {codeberg_token}", "Content-Type": "application/json"} |
| data = {"name": repo_name, "private": True, "auto_init": False} |
| |
| try: |
| r = requests.post(url_api, headers=headers, json=data, timeout=30) |
| except: pass |
| |
| repo_url = f"https://codeberg.org/{username}/{repo_name}" |
| git_dir = str(output_dir) |
| git_auth_url = repo_url.replace('https://', f'https://{username}:{codeberg_token}@') |
| |
| subprocess.run(['git', 'init'], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| subprocess.run(['git', 'remote', 'add', 'origin', git_auth_url], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| subprocess.run(['git', 'config', 'http.postBuffer', '524288000'], cwd=git_dir, check=True) |
| |
| files = os.listdir(git_dir) |
| if stream_format == "DASH (MPD)": |
| seg_files = [f for f in files if f.endswith('.m4s')] |
| manifest_files = [f for f in files if f.endswith('.mpd')] |
| manifest_name = "manifest.mpd" |
| else: |
| seg_files = [f for f in files if f.endswith('.ts')] |
| manifest_files = [f for f in files if f.endswith('.m3u8')] |
| manifest_name = "master.m3u8" |
| |
| if manifest_files: |
| subprocess.run(['git', 'add'] + manifest_files, cwd=git_dir, check=True) |
| subprocess.run(['git', 'commit', '-m', 'Add manifests'], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| try: |
| subprocess.run(['git', 'push', '-f', 'origin', 'HEAD:main'], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=600) |
| except: pass |
| |
| msg, logs = log_generator(f"⬆️ Subiendo {len(seg_files)} segmentos...", logs) |
| yield msg, logs, None |
| |
| for i in range(0, len(seg_files), batch_size): |
| batch = seg_files[i:i+batch_size] |
| subprocess.run(['git', 'add'] + batch, cwd=git_dir, check=True) |
| subprocess.run(['git', 'commit', '-m', f'Batch {i}'], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| try: |
| subprocess.run(['git', 'push', '-f', 'origin', 'HEAD:main'], cwd=git_dir, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=600) |
| except: pass |
| |
| final_url = f"{repo_url}/raw/branch/main/{manifest_name}" |
| msg, logs = log_generator(f"✅ Codeberg: {final_url}", logs) |
| yield msg, logs, final_url |
| |
| except Exception as e: |
| msg, logs = log_generator(f"❌ Error Codeberg: {str(e)}", logs) |
| yield msg, logs, None |
|
|
| def upload_to_cloudflare_pages(output_dir, project_name, cf_token, cf_account_id, stream_format, logs): |
| try: |
| msg, logs = log_generator("☁️ Iniciando subida a Cloudflare Pages...", logs) |
| yield msg, logs, None |
| |
| headers = { |
| "Authorization": f"Bearer {cf_token}", |
| "Content-Type": "application/json" |
| } |
| |
| |
| msg, logs = log_generator("📦 Verificando proyecto en Cloudflare...", logs) |
| yield msg, logs, None |
| |
| project_url = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/pages/projects" |
| project_exists = False |
| |
| try: |
| r = requests.get(project_url, headers=headers, timeout=30) |
| if r.status_code == 200: |
| projects = r.json().get('result', []) |
| project_exists = any(p['name'] == project_name for p in projects) |
| |
| if not project_exists: |
| msg, logs = log_generator(f"📦 Creando proyecto: {project_name}", logs) |
| yield msg, logs, None |
| |
| project_data = { |
| "name": project_name, |
| "production_branch": "main" |
| } |
| r = requests.post(project_url, headers=headers, json=project_data, timeout=30) |
| |
| if r.status_code in [200, 201]: |
| msg, logs = log_generator(f"✅ Proyecto creado exitosamente", logs) |
| yield msg, logs, None |
| project_exists = True |
| else: |
| error_msg = r.json().get('errors', [{}])[0].get('message', 'Error desconocido') |
| raise Exception(f"Error creando proyecto: {error_msg}") |
| else: |
| msg, logs = log_generator(f"✅ Proyecto ya existe", logs) |
| yield msg, logs, None |
| else: |
| raise Exception(f"Error verificando proyectos: {r.status_code}") |
| except Exception as e: |
| msg, logs = log_generator(f"❌ Error en paso 1: {str(e)}", logs) |
| yield msg, logs, None |
| raise |
| |
| if not project_exists: |
| raise Exception("No se pudo crear o verificar el proyecto") |
| |
| |
| msg, logs = log_generator("📦 Preparando deployment...", logs) |
| yield msg, logs, None |
| |
| |
| files_to_upload = [] |
| for file in output_dir.iterdir(): |
| if file.is_file(): |
| files_to_upload.append(file) |
| |
| msg, logs = log_generator(f"📁 {len(files_to_upload)} archivos para subir", logs) |
| yield msg, logs, None |
| |
| |
| import hashlib |
| manifest = {} |
| |
| for file in files_to_upload: |
| with open(file, 'rb') as f: |
| file_hash = hashlib.sha256(f.read()).hexdigest() |
| manifest[f"/{file.name}"] = file_hash |
| |
| |
| deployment_url = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/pages/projects/{project_name}/deployments" |
| |
| deployment_data = { |
| "branch": "main", |
| "manifest": manifest |
| } |
| |
| msg, logs = log_generator("🚀 Iniciando deployment...", logs) |
| yield msg, logs, None |
| |
| r = requests.post(deployment_url, headers=headers, json=deployment_data, timeout=60) |
| |
| if r.status_code not in [200, 201]: |
| error_detail = r.json() if r.content else "Sin detalles" |
| raise Exception(f"Error iniciando deployment ({r.status_code}): {error_detail}") |
| |
| deployment = r.json().get('result', {}) |
| deployment_id = deployment.get('id') |
| |
| if not deployment_id: |
| raise Exception("No se recibió deployment_id") |
| |
| msg, logs = log_generator(f"✅ Deployment iniciado: {deployment_id[:8]}...", logs) |
| yield msg, logs, None |
| |
| |
| msg, logs = log_generator(f"⬆️ Subiendo archivos...", logs) |
| yield msg, logs, None |
| |
| uploaded_count = 0 |
| for idx, file in enumerate(files_to_upload): |
| try: |
| |
| file_upload_url = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/pages/projects/{project_name}/deployments/{deployment_id}/files/{file.name}" |
| |
| with open(file, 'rb') as f: |
| file_content = f.read() |
| |
| upload_headers = { |
| "Authorization": f"Bearer {cf_token}", |
| "Content-Type": "application/octet-stream" |
| } |
| |
| r = requests.put(file_upload_url, headers=upload_headers, data=file_content, timeout=120) |
| |
| if r.status_code in [200, 201]: |
| uploaded_count += 1 |
| if (idx + 1) % 10 == 0: |
| msg, logs = log_generator(f" ⬆️ {uploaded_count}/{len(files_to_upload)} archivos subidos", logs) |
| yield msg, logs, None |
| else: |
| msg, logs = log_generator(f" ⚠️ Error subiendo {file.name}: {r.status_code}", logs) |
| yield msg, logs, None |
| |
| except Exception as e: |
| msg, logs = log_generator(f" ⚠️ Error con {file.name}: {str(e)}", logs) |
| yield msg, logs, None |
| continue |
| |
| msg, logs = log_generator(f"✅ {uploaded_count}/{len(files_to_upload)} archivos subidos", logs) |
| yield msg, logs, None |
| |
| |
| msg, logs = log_generator("🏁 Finalizando deployment...", logs) |
| yield msg, logs, None |
| |
| finalize_url = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/pages/projects/{project_name}/deployments/{deployment_id}/finalize" |
| |
| r = requests.post(finalize_url, headers=headers, timeout=60) |
| |
| if r.status_code in [200, 201]: |
| result = r.json().get('result', {}) |
| cf_url = result.get('url', f"https://{project_name}.pages.dev") |
| manifest_name = "manifest.mpd" if stream_format == "DASH (MPD)" else "master.m3u8" |
| final_url = f"{cf_url}/{manifest_name}" |
| |
| msg, logs = log_generator(f"✅ Cloudflare Pages: {final_url}", logs) |
| yield msg, logs, final_url |
| else: |
| |
| manifest_name = "manifest.mpd" if stream_format == "DASH (MPD)" else "master.m3u8" |
| final_url = f"https://{project_name}.pages.dev/{manifest_name}" |
| msg, logs = log_generator(f"⚠️ Deployment puede estar procesando", logs) |
| yield msg, logs, None |
| msg, logs = log_generator(f"📋 URL esperada: {final_url}", logs) |
| yield msg, logs, final_url |
| |
| except Exception as e: |
| msg, logs = log_generator(f"❌ Error Cloudflare Pages: {str(e)}", logs) |
| yield msg, logs, None |
| |
| |
| try: |
| manifest_name = "manifest.mpd" if stream_format == "DASH (MPD)" else "master.m3u8" |
| final_url = f"https://{project_name}.pages.dev/{manifest_name}" |
| msg, logs = log_generator(f"📋 Si el proyecto se creó, la URL será: {final_url}", logs) |
| yield msg, logs, None |
| except: |
| pass |
|
|
| |
|
|
| def process_video(file_input, url_input, codeberg_token, cf_token, cf_account_id, |
| conversion_mode, stream_format, upload_codeberg_flag, upload_cloudflare_flag, |
| batch_size, delete_local, progress=gr.Progress()): |
| |
| logs = ["🚀 Iniciando conversión..."] |
| |
| try: |
| |
| source = None |
| is_url = False |
| |
| if file_input: |
| source = file_input.name |
| elif url_input: |
| source = url_input |
| is_url = True |
| else: |
| return "\n".join(logs + ["❌ Selecciona archivo o URL"]), "Error" |
| |
| |
| if not upload_codeberg_flag and not upload_cloudflare_flag: |
| return "\n".join(logs + ["❌ Selecciona al menos un destino"]), "Error" |
| |
| if upload_codeberg_flag and not codeberg_token: |
| return "\n".join(logs + ["❌ Se requiere Token de Codeberg"]), "Error" |
| |
| if upload_cloudflare_flag and (not cf_token or not cf_account_id): |
| return "\n".join(logs + ["❌ Se requiere Token y Account ID de Cloudflare"]), "Error" |
| |
| |
| username = None |
| if upload_codeberg_flag: |
| headers_cb = {"Authorization": f"token {codeberg_token}"} |
| try: |
| user_resp = requests.get("https://codeberg.org/api/v1/user", headers=headers_cb, timeout=10) |
| if user_resp.status_code != 200: |
| raise Exception("Token de Codeberg inválido") |
| username = user_resp.json().get('login') |
| logs.append(f"👤 Usuario: {username}") |
| except Exception as e: |
| return "\n".join(logs + [f"❌ {str(e)}"]), "Error" |
| |
| |
| base_name = Path(source).stem if not is_url else get_filename_from_url(source) |
| repo_name = clean_for_repo(base_name) |
| folder_name = clean_for_folder(base_name) + f"_{stream_format.lower().replace(' ', '_')}" |
| output_dir = Path.cwd() / folder_name |
| |
| if output_dir.exists(): |
| shutil.rmtree(output_dir) |
| output_dir.mkdir(exist_ok=True) |
| |
| logs.append(f"📹 Procesando: {base_name}") |
| yield "\n".join(logs), "Procesando" |
| |
| |
| audio_tracks = detect_audio_streams(source) |
| if not audio_tracks: |
| audio_tracks = [{'index': 0, 'language': 'und', 'title': 'Audio', 'codec': 'unknown'}] |
| audio_tracks = prioritize_audio_tracks(audio_tracks) |
| |
| logs.append(f"🎵 {len(audio_tracks)} streams de audio detectados") |
| yield "\n".join(logs), "Procesando" |
| |
| |
| if stream_format == "HLS (M3U8)": |
| |
| audio_playlists = [] |
| for i, track in enumerate(audio_tracks): |
| audio_file = output_dir / f"audio_{i}.m3u8" |
| seg_audio = output_dir / f"audio_{i}_%03d.ts" |
| |
| if conversion_mode == "Copy Video + Copy Audio": |
| audio_params = ['-c:a', 'copy'] |
| else: |
| audio_params = ['-c:a', 'libmp3lame', '-b:a', '192k', '-ar', '48000'] |
| |
| cmd_audio = ['ffmpeg', '-i', source, '-map', f"0:{track['index']}"] + audio_params + [ |
| '-hls_time', '10', '-hls_list_size', '0', '-hls_segment_filename', str(seg_audio), str(audio_file), '-y', '-loglevel', 'warning' |
| ] |
| subprocess.run(cmd_audio, capture_output=True, timeout=3600) |
| audio_playlists.append({'file': f"audio_{i}.m3u8", 'language': track['language'], 'title': track['title'], 'is_default': i == 0}) |
| |
| |
| video_streams = [] |
| if conversion_mode in ["Copy Video + Copy Audio", "Copy Video + MP3 Audio"]: |
| video_file = output_dir / "video_1080p.m3u8" |
| seg_video = output_dir / "video_1080p_%03d.ts" |
| cmd_video = ['ffmpeg', '-i', source, '-map', '0:v', '-c:v', 'copy', '-an', '-hls_time', '10', '-hls_list_size', '0', |
| '-hls_segment_filename', str(seg_video), str(video_file), '-y', '-loglevel', 'warning'] |
| subprocess.run(cmd_video, capture_output=True, timeout=7200) |
| video_streams.append({'file': 'video_1080p.m3u8', 'resolution': '1920x1080', 'bandwidth': 5000000, 'codecs': 'avc1.640028'}) |
| |
| elif conversion_mode == "Multi-Res (1080p + 720p)": |
| for res in [{'label': '1080p', 'scale': 'scale=-2:1080', 'br': '5000k', 'res': '1920x1080'}, |
| {'label': '720p', 'scale': 'scale=-2:720', 'br': '2800k', 'res': '1280x720'}]: |
| video_file = output_dir / f"video_{res['label']}.m3u8" |
| seg_video = output_dir / f"video_{res['label']}_%03d.ts" |
| cmd_video = ['ffmpeg', '-i', source, '-map', '0:v', '-an', '-c:v', 'libx264', '-preset', 'medium', '-crf', '20', |
| '-vf', res['scale'], '-b:v', res['br'], '-maxrate', res['br'], '-bufsize', str(int(res['br'].replace('k',''))*2) + 'k', |
| '-pix_fmt', 'yuv420p', '-hls_time', '10', '-hls_list_size', '0', '-hls_segment_filename', str(seg_video), |
| str(video_file), '-y', '-loglevel', 'warning'] |
| subprocess.run(cmd_video, capture_output=True, timeout=7200) |
| video_streams.append({'file': f"video_{res['label']}.m3u8", 'resolution': res['res'], |
| 'bandwidth': int(res['br'].replace('k', '000')) + 192000, 'codecs': 'avc1.640028'}) |
| |
| create_master_m3u8(output_dir, video_streams, audio_playlists) |
| |
| elif stream_format == "DASH (MPD)": |
| cmd = ['ffmpeg', '-i', source] |
| is_copy = (conversion_mode == "Copy Video + Copy Audio") |
| |
| if is_copy: |
| cmd.extend(['-map', '0:v:0', '-c:v:0', 'copy']) |
| else: |
| if conversion_mode == "Multi-Res (1080p + 720p)": |
| for idx, r in enumerate([{'scale': 'scale=-2:1080', 'br': '5000k'}, {'scale': 'scale=-2:720', 'br': '2800k'}]): |
| cmd.extend(['-map', '0:v:0', f'-c:v:{idx}', 'libx264', '-preset', 'medium', '-crf', '20', |
| f'-vf:v:{idx}', r['scale'], f'-b:v:{idx}', r['br'], '-pix_fmt', 'yuv420p']) |
| |
| for i, track in enumerate(audio_tracks): |
| cmd.extend(['-map', f"0:{track['index']}", f'-c:a:{i}', 'aac' if not is_copy else 'copy', '-b:a:192k', '-ar', '48000']) |
| |
| mpd_output = output_dir / "manifest.mpd" |
| cmd.extend(['-f', 'dash', '-seg_duration', '10', '-use_template', '1', '-use_timeline', '0', |
| '-init_seg_name', 'init-$RepresentationID$.m4s', '-media_seg_name', 'chunk-$RepresentationID$-$Number%05d$.m4s', |
| str(mpd_output), '-y', '-loglevel', 'warning']) |
| subprocess.run(cmd, capture_output=True, timeout=7200) |
| |
| logs.append("✅ Conversión completada") |
| yield "\n".join(logs), "Subiendo" |
| |
| |
| result_links = [] |
| |
| if upload_codeberg_flag: |
| for update in upload_to_codeberg(output_dir, repo_name, codeberg_token, username, int(batch_size), stream_format, logs): |
| yield update[0], "Subiendo" |
| if update[2]: |
| result_links.append(f"📂 Codeberg: {update[2]}") |
| |
| if upload_cloudflare_flag: |
| for update in upload_to_cloudflare_pages(output_dir, repo_name, cf_token, cf_account_id, stream_format, logs): |
| yield update[0], "Subiendo" |
| if update[2]: |
| result_links.append(f"☁️ Cloudflare: {update[2]}") |
| |
| if delete_local: |
| shutil.rmtree(output_dir) |
| logs.append("🗑️ Archivos locales eliminados") |
| |
| final_output = "\n".join(logs + ["", "🔗 Enlaces:"] + result_links) |
| return final_output, "¡Listo!" |
| |
| except Exception as e: |
| import traceback |
| traceback.print_exc() |
| return "\n".join(logs + [f"❌ ERROR: {str(e)}"]), "Fallo" |
|
|
| |
|
|
| with gr.Blocks(title="Video Streaming Converter", theme=gr.themes.Soft()) as demo: |
| |
| gr.Markdown("# 🎬 Video Streaming Converter") |
| gr.Markdown("Convierte videos a HLS/DASH y súbelos a Codeberg o Cloudflare Pages") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| codeberg_token = gr.Textbox( |
| label="Codeberg Token", |
| value="92427ac14a228f0762ec303d478b9f093be4f608", |
| type="password", |
| placeholder="Token con permisos 'repo'" |
| ) |
| cf_token = gr.Textbox( |
| label="Cloudflare Token", |
| value="mOvchd-yxYyQ6Zj3xMb_38Rkf-HwROchlsx-Ud9H", |
| type="password", |
| placeholder="Token de Cloudflare Pages" |
| ) |
| cf_account_id = gr.Textbox( |
| label="Cloudflare Account ID", |
| value="bd06ac4017668e45b656db342029929d", |
| placeholder="ID de tu cuenta" |
| ) |
| |
| |
| conversion_mode = gr.Radio( |
| choices=["Copy Video + Copy Audio", "Copy Video + MP3 Audio", "Multi-Res (1080p + 720p)"], |
| value="Multi-Res (1080p + 720p)", |
| label="Modo" |
| ) |
| |
| stream_format = gr.Radio( |
| choices=["HLS (M3U8)", "DASH (MPD)"], |
| value="HLS (M3U8)", |
| label="Formato" |
| ) |
| |
| |
| upload_codeberg = gr.Checkbox(label="Subir a Codeberg", value=True) |
| upload_cloudflare = gr.Checkbox(label="Subir a Cloudflare Pages", value=False) |
| |
| |
| with gr.Accordion("Opciones Avanzadas", open=False): |
| batch_size = gr.Number(value=20, label="Batch Size", precision=0) |
| delete_local = gr.Checkbox(value=True, label="Borrar archivos locales") |
| |
| with gr.Column(scale=2): |
| |
| with gr.Tab("Archivo"): |
| file_input = gr.File(label="Subir Video", file_types=["video"]) |
| with gr.Tab("URL"): |
| url_input = gr.Textbox(label="URL del Video", placeholder="https://ejemplo.com/video.mp4") |
| |
| |
| btn_process = gr.Button("🚀 PROCESAR Y SUBIR", variant="primary", size="lg") |
| |
| |
| log_output = gr.Textbox(label="Log", lines=15, interactive=False) |
| status_output = gr.Textbox(label="Estado", interactive=False) |
| |
| gr.Markdown("---") |
| gr.Markdown("💡 **Tip:** Cloudflare Pages es rápido pero público. Codeberg es privado pero más lento.") |
| |
| |
| btn_process.click( |
| fn=process_video, |
| inputs=[ |
| file_input, url_input, codeberg_token, cf_token, cf_account_id, |
| conversion_mode, stream_format, upload_codeberg, upload_cloudflare, |
| batch_size, delete_local |
| ], |
| outputs=[log_output, status_output] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |