CLIPPER-YOUTUBE / app.py
Akhmad123's picture
Update app.py
4de27de verified
Raw
History Blame Contribute Delete
12 kB
import spaces
import os
import re
import json
import requests
import gradio as gr
import yt_dlp
from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip
from google import genai
from google.genai import types
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
@spaces.GPU
# ==========================================
# 1. KREDENSIAL DARI SECRETS
# ==========================================
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
YT_CLIENT_ID = os.getenv("YT_CLIENT_ID")
YT_CLIENT_SECRET = os.getenv("YT_CLIENT_SECRET")
YT_REFRESH_TOKEN = os.getenv("YT_REFRESH_TOKEN")
gemini_client = genai.Client(api_key=GEMINI_API_KEY) if GEMINI_API_KEY else None
def extract_youtube_id(url):
pattern = r'(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})'
match = re.search(pattern, url)
return match.group(1) if match else None
# ==========================================
# 2. DOWNLOADER DENGAN PIPED API + YT-DLP
# ==========================================
def download_youtube_video(youtube_url, output_filename="downloaded_temp.mp4"):
"""Mengunduh video menggunakan Piped API untuk menembus IP datacenter."""
video_id = extract_youtube_id(youtube_url)
# Instans Piped API
piped_instances = [
"https://pipedapi.kavin.rocks",
"https://api.piped.privacydev.net",
"https://pipedapi.tokhmi.xyz"
]
if video_id:
for instance in piped_instances:
try:
print(f"Mencoba mengunduh via Piped API: {instance}")
resp = requests.get(f"{instance}/streams/{video_id}", timeout=10)
if resp.status_code == 200:
data = resp.json()
video_streams = data.get("videoStreams", [])
# Cari stream video mp4
stream_url = None
for stream in video_streams:
if stream.get("container") == "mp4" and not stream.get("videoOnly"):
stream_url = stream.get("url")
break
if not stream_url and video_streams:
stream_url = video_streams[0].get("url")
if stream_url:
video_bytes = requests.get(stream_url, stream=True, timeout=30)
with open(output_filename, "wb") as f:
for chunk in video_bytes.iter_content(chunk_size=1024*1024):
if chunk:
f.write(chunk)
print("✅ Berhasil mengunduh via Piped API!")
return output_filename
except Exception as e:
print(f"Gagal via Piped {instance}: {e}")
print("Mencoba fallback via yt-dlp...")
ydl_opts = {
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
'outtmpl': output_filename,
'overwrites': True,
'quiet': True,
'extractor_args': {
'youtube': {
'player_client': ['mweb', 'android', 'ios']
}
}
}
if os.path.exists("cookies.txt"):
ydl_opts['cookiefile'] = 'cookies.txt'
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([youtube_url])
return output_filename
def get_youtube_metadata(youtube_url):
"""Mengambil judul dan deskripsi video."""
video_id = extract_youtube_id(youtube_url)
if video_id:
try:
resp = requests.get(f"https://pipedapi.kavin.rocks/streams/{video_id}", timeout=5)
if resp.status_code == 200:
data = resp.json()
return data.get("title", "Video YouTube"), data.get("description", "")
except Exception:
pass
return "Konten Video Viral", "Deskripsi Konten Video"
# ==========================================
# 3. ANALISIS GEMINI AI (PERBAIKAN PARSING JSON)
# ==========================================
def analyze_with_gemini(video_title, video_description):
if not gemini_client:
return "🔥 SAKSIKAN MOMEN INI!", f"{video_title[:50]} #Shorts", "#shorts #viral #trending"
# Bersihkan input dari karakter kontrol
clean_title = video_title.replace('"', "'").replace('\n', ' ')
clean_desc = video_description[:500].replace('"', "'").replace('\n', ' ')
prompt = f"""
Anda adalah pakar pembuat konten YouTube Shorts viral.
Analisis data video berikut:
- Judul: {clean_title}
- Deskripsi: {clean_desc}
Buatkan:
1. HOOK: 1 kalimat teks pendek Bahasa Indonesia untuk ditempel di atas video (maksimal 6 kata, HURUF KAPITAL SEMUA, TANPA TANDA KUTIP).
2. TITLE: Judul Shorts viral (maksimal 60 karakter, TANPA TANDA KUTIP).
3. TAGS: 5 hashtag populer dipisahkan spasi.
Respon HANYA dalam format JSON valid berikut:
{{\n "hook": "TEKS HOOK KAPITAL",\n "title": "Judul Shorts Viral",\n "tags": "#shorts #tag2 #tag3 #tag4 #tag5"\n}}
"""
try:
response = gemini_client.models.generate_content(
model='gemini-3.5-flash-lite',
contents=prompt,
config=types.GenerateContentConfig(response_mime_type="application/json")
)
# Penanganan Parsing JSON Aman
raw_text = response.text.strip()
# Ambil hanya blok JSON jika ada karakter liar di luar {}
json_match = re.search(r'\{.*\}', raw_text, re.DOTALL)
if json_match:
raw_text = json_match.group(0)
data = json.loads(raw_text)
return (
data.get("hook", "JANGAN LEWATKAN INI!"),
data.get("title", f"{clean_title[:50]} #Shorts"),
data.get("tags", "#shorts #viral #trending")
)
except Exception as e:
print(f"Error Gemini API: {e}")
return "🔥 SAKSIKAN MOMEN INI!", f"{clean_title[:50]} #Shorts", "#shorts #viral #trending"
# ==========================================
# 4. EDITING & CLIPPING (MOVIEPY)
# ==========================================
def process_and_clip_video(youtube_url, start_time, end_time, hook_text):
output_input_file = "downloaded_temp.mp4"
output_clipped_file = "output_short_916.mp4"
download_youtube_video(youtube_url, output_input_file)
clip = VideoFileClip(output_input_file).subclip(start_time, end_time)
# Convert ke 9:16 (Shorts)
w, h = clip.size
target_aspect = 9 / 16
current_aspect = w / h
if current_aspect > target_aspect:
new_w = int(h * target_aspect)
clip_cropped = clip.crop(x1=int((w - new_w) / 2), width=new_w, height=h)
else:
new_h = int(w / target_aspect)
clip_cropped = clip.crop(y1=int((h - new_h) / 2), width=w, height=new_h)
# Teks Overlay Hook
txt_clip = TextClip(
hook_text,
fontsize=40,
color='yellow',
font='Arial-Bold',
method='caption',
align='center',
size=(int(clip_cropped.w * 0.85), None)
)
txt_clip = txt_clip.set_pos(('center', int(clip_cropped.h * 0.12))).set_duration(clip_cropped.duration)
final_video = CompositeVideoClip([clip_cropped, txt_clip])
final_video.write_videofile(
output_clipped_file,
codec='libx264',
audio_codec='aac',
fps=30,
preset='ultrafast'
)
clip.close()
final_video.close()
if os.path.exists(output_input_file):
os.remove(output_input_file)
return output_clipped_file
# ==========================================
# 5. UPLOAD KE YOUTUBE
# ==========================================
def upload_to_youtube(video_path, title, description, tags_str, privacy_status="private"):
if not all([YT_CLIENT_ID, YT_CLIENT_SECRET, YT_REFRESH_TOKEN]):
return "❌ Gagal Upload: Secrets YouTube belum dikonfigurasi!"
try:
creds = Credentials(
token=None,
refresh_token=YT_REFRESH_TOKEN,
token_uri="https://oauth2.googleapis.com/token",
client_id=YT_CLIENT_ID,
client_secret=YT_CLIENT_SECRET
)
youtube = build('youtube', 'v3', credentials=creds)
tags_list = [t.strip().replace("#", "") for t in tags_str.split() if t.strip()]
body = {
'snippet': {
'title': title[:100],
'description': f"{description}\n\n{tags_str}",
'tags': tags_list,
'categoryId': '22'
},
'status': {
'privacyStatus': privacy_status,
'selfDeclaredMadeForKids': False
}
}
media = MediaFileUpload(video_path, chunksize=-1, resumable=True, mimetype='video/mp4')
request = youtube.videos().insert(part=','.join(body.keys()), body=body, media_body=media)
response = None
while response is None:
_, response = request.next_chunk()
video_id = response.get("id")
return f"✅ Berhasil Diunggah! ID Video: {video_id}\nURL: https://youtu.be/{video_id}"
except Exception as e:
return f"❌ Error Upload: {str(e)}"
# ==========================================
# 6. WORKFLOW & UI GRADIO
# ==========================================
def full_workflow(youtube_url, start_sec, end_sec, privacy_choice, do_upload):
if not youtube_url:
return None, "⚠️ Masukkan URL YouTube terlebih dahulu!"
try:
print("1/4 Mengambil Metadata Video...")
orig_title, orig_desc = get_youtube_metadata(youtube_url)
print("2/4 Analisis Gemini AI...")
hook_text, gen_title, gen_tags = analyze_with_gemini(orig_title, orig_desc)
print("3/4 Clipping & Editing Video...")
clipped_path = process_and_clip_video(youtube_url, start_sec, end_sec, hook_text)
upload_status = "Status Upload: Dibatalkan (Checkbox tidak diaktifkan)."
if do_upload:
print("4/4 Mengunggah ke YouTube...")
upload_status = upload_to_youtube(
clipped_path, gen_title, f"Klip dari {youtube_url}", gen_tags, privacy_choice
)
summary = f"""
### 🤖 Hasil Analisis Gemini AI
- **Judul Asli:** {orig_title}
- **Hook Teks (Overlay):** `{hook_text}`
- **Judul Shorts AI:** `{gen_title}`
- **Hashtag:** `{gen_tags}`
---
### 📤 Status Publikasi
{upload_status}
"""
return clipped_path, summary
except Exception as e:
return None, f"❌ Terjadi kesalahan sistem: {str(e)}"
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🎬 Auto YouTube Clipper with Gemini AI")
gr.Markdown("Cukup tempelkan **URL YouTube**, Gemini AI akan menganalisis isi video, membuat teks hook, judul viral, dan hashtag secara otomatis.")
with gr.Row():
with gr.Column():
url_input = gr.Textbox(label="URL Video YouTube", placeholder="https://www.youtube.com/watch?v=...")
with gr.Row():
start_input = gr.Number(label="Detik Mulai", value=10)
end_input = gr.Number(label="Detik Selesai", value=40)
privacy_dropdown = gr.Dropdown(choices=["private", "unlisted", "public"], value="private", label="Privasi Upload")
upload_checkbox = gr.Checkbox(label="Unggah Otomatis ke YouTube Saya", value=False)
btn_process = gr.Button("⚡ Proses & Buat Shorts Otomatis", variant="primary")
with gr.Column():
video_output = gr.Video(label="Preview Shorts 9:16")
info_output = gr.Markdown(label="Laporan Analisis AI")
btn_process.click(
fn=full_workflow,
inputs=[url_input, start_input, end_input, privacy_dropdown, upload_checkbox],
outputs=[video_output, info_output]
)
if __name__ == "__main__":
demo.launch()