Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import subprocess | |
| import os | |
| import uuid | |
| import re | |
| # Fungsi untuk mengekstrak ID Video (misal: I0RkuBBi0a8) dari URL lengkap | |
| def extract_video_id(url): | |
| pattern = r"(?:v=|\/)([0-9A-Za-z_-]{11}).*" | |
| match = re.search(pattern, url) | |
| return match.group(1) if match else None | |
| def get_direct_url(youtube_url): | |
| video_id = extract_video_id(youtube_url) | |
| if not video_id: | |
| return None, "Format URL YouTube tidak valid." | |
| # Menggunakan beberapa instance Piped API sebagai Proxy Load Balancer | |
| api_urls = [ | |
| f"https://pipedapi.kavin.rocks/streams/{video_id}", | |
| f"https://pipedapi.adminforge.de/streams/{video_id}", | |
| f"https://api.piped.projectsegfau.lt/streams/{video_id}" | |
| ] | |
| for api_url in api_urls: | |
| try: | |
| response = requests.get(api_url, timeout=15) | |
| if response.status_code == 200: | |
| data = response.json() | |
| streams = data.get("videoStreams", []) | |
| # Cari stream yang berisi gabungan video+audio (progressive) | |
| # Piped menandai stream tanpa audio sebagai videoOnly: true | |
| progressive_streams = [s for s in streams if not s.get("videoOnly", True)] | |
| if progressive_streams: | |
| # Ambil kualitas yang tersedia (biasanya sudah dibungkus proxy Piped) | |
| return progressive_streams[0]["url"], f"Sukses via {api_url.split('/')[2]}" | |
| except Exception: | |
| # Jika server Piped ini mati/timeout, abaikan dan coba server berikutnya | |
| continue | |
| return None, "Semua server proxy Piped sedang sibuk atau gagal merespons." | |
| def clip_youtube(url, start_time, end_time): | |
| # 1. Dapatkan Direct Link dari Piped | |
| direct_url, extract_status = get_direct_url(url) | |
| if not direct_url: | |
| return None, f"Gagal membobol link. Detail: {extract_status}" | |
| # 2. Persiapan File | |
| output_filename = f"clip_{uuid.uuid4().hex[:8]}.mp4" | |
| start_sec = parse_time(start_time) | |
| end_sec = parse_time(end_time) | |
| duration = end_sec - start_sec | |
| if duration <= 0: | |
| return None, "Waktu selesai harus lebih besar dari waktu mulai!" | |
| # 3. Eksekusi FFmpeg langsung dari URL Streaming Proxy | |
| cmd = [ | |
| "ffmpeg", | |
| "-ss", str(start_sec), | |
| "-i", direct_url, | |
| "-t", str(duration), | |
| "-c:v", "libx264", | |
| "-preset", "veryfast", | |
| "-c:a", "aac", | |
| "-y", output_filename | |
| ] | |
| try: | |
| subprocess.run(cmd, check=True, capture_output=True) | |
| if os.path.exists(output_filename): | |
| return output_filename, "Berhasil! Klip siap diputar dan diunduh." | |
| else: | |
| return None, "Terjadi kesalahan saat memotong video di server." | |
| except subprocess.CalledProcessError as e: | |
| return None, f"Error FFmpeg: {e.stderr.decode('utf-8')}" | |
| # Helper function | |
| def parse_time(time_str): | |
| try: | |
| parts = time_str.split(':') | |
| if len(parts) == 2: | |
| return int(parts[0]) * 60 + int(parts[1]) | |
| elif len(parts) == 3: | |
| return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2]) | |
| return int(time_str) | |
| except: | |
| return 0 | |
| # UI Gradio | |
| with gr.Blocks(title="YouTube Piped Clipper") as app: | |
| gr.Markdown("## ✂️ YouTube Clipper (Proxy Version)") | |
| gr.Markdown("Mengekstrak dan memotong video menggunakan jaringan proxy *open-source* (Piped) untuk menghindari blokir infrastruktur YouTube.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| url_input = gr.Textbox(label="URL YouTube", placeholder="https://www.youtube.com/watch?v=...") | |
| start_input = gr.Textbox(label="Waktu Mulai (MM:SS)", placeholder="01:30") | |
| end_input = gr.Textbox(label="Waktu Selesai (MM:SS)", placeholder="02:00") | |
| submit_btn = gr.Button("✂️ Potong Video", variant="primary") | |
| with gr.Column(): | |
| video_output = gr.Video(label="Hasil Klip") | |
| status_output = gr.Textbox(label="Status Sistem", interactive=False) | |
| submit_btn.click( | |
| fn=clip_youtube, | |
| inputs=[url_input, start_input, end_input], | |
| outputs=[video_output, status_output] | |
| ) | |
| app.queue(max_size=5).launch() |