| import os |
| os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1" |
|
|
| import gradio as gr |
| import uuid, json, requests, subprocess, shutil, random, re |
| from pathlib import Path |
| from googleapiclient.discovery import build |
| from googleapiclient.http import MediaFileUpload |
| from google.oauth2.credentials import Credentials |
| from google.auth.transport.requests import Request |
|
|
| |
| |
| |
| PEXELS_API_KEY = os.getenv("PEXELS_API", "") |
| PIXABAY_API_KEY = os.getenv("PIXABAY_API", "") |
| DRIVE_SCOPES = ["https://www.googleapis.com/auth/drive.file"] |
| WORK_DIR = Path("work") |
| WORK_DIR.mkdir(exist_ok=True) |
|
|
| REDDIT_SUBS = { |
| "😂 Komedi": ["funny", "Whatcouldgowrong", "instant_regret", "contagious_laughter"], |
| "🔥 Viral": ["nextfuckinglevel", "interestingasfuck", "Damnthatsinteresting", "BeAmazed"], |
| "🐾 Hayvanlar": ["aww", "AnimalsBeingBros", "likeus", "rarepuppers"], |
| "🏆 Spor": ["sports", "MaddenGIFs", "hitmanimals", "holdmybeer"], |
| "🌍 Dünya": ["worldnews", "europe", "CrazyFuckingVideos", "PublicFreakout"], |
| "✨ Hepsi Karışık": ["funny", "nextfuckinglevel", "aww", "interestingasfuck", "BeAmazed"], |
| } |
|
|
| ARCHIVE_QUERIES = { |
| "😂 Komedi": ["comedy short film", "funny animation", "slapstick"], |
| "🔥 Viral": ["viral video", "amazing stunt", "incredible moment"], |
| "🐾 Hayvanlar": ["animals nature", "wildlife documentary", "cute animals"], |
| "🏆 Spor": ["sports highlights", "athletic amazing", "competition"], |
| "🌍 Dünya": ["world culture", "travel documentary", "street life"], |
| "✨ Hepsi Karışık": ["funny", "amazing", "nature", "documentary short"], |
| } |
|
|
| def uid(): |
| return uuid.uuid4().hex[:8] |
|
|
| def cleanup_work(): |
| for f in WORK_DIR.glob("*"): |
| try: f.unlink() |
| except: pass |
|
|
| |
| |
| |
| def fetch_reddit_videos(category, count=4): |
| subs = REDDIT_SUBS.get(category, REDDIT_SUBS["✨ Hepsi Karışık"]) |
| results = [] |
| headers = {"User-Agent": "VideoBot/1.0"} |
|
|
| for sub in random.sample(subs, min(2, len(subs))): |
| try: |
| url = f"https://www.reddit.com/r/{sub}/hot.json?limit=25" |
| r = requests.get(url, headers=headers, timeout=15) |
| posts = r.json()["data"]["children"] |
|
|
| for post in posts: |
| d = post["data"] |
| |
| if not d.get("is_video"): |
| continue |
| media = d.get("media", {}) |
| if not media: |
| continue |
| reddit_video = media.get("reddit_video", {}) |
| video_url = reddit_video.get("fallback_url", "") |
| if not video_url: |
| continue |
| |
| video_url = video_url.split("?")[0] |
| results.append({ |
| "url": video_url, |
| "title": d.get("title", "Reddit Video")[:60], |
| "source": f"Reddit/r/{sub}", |
| "duration": reddit_video.get("duration", 30) |
| }) |
| if len(results) >= count: |
| return results |
| except Exception as e: |
| print(f"Reddit hata ({sub}): {e}") |
|
|
| return results |
|
|
| |
| |
| |
| def fetch_archive_videos(category, count=3): |
| queries = ARCHIVE_QUERIES.get(category, ARCHIVE_QUERIES["✨ Hepsi Karışık"]) |
| query = random.choice(queries) |
| results = [] |
|
|
| try: |
| params = { |
| "q": f"{query} AND mediatype:movies", |
| "fl[]": ["identifier", "title"], |
| "rows": 20, |
| "output": "json", |
| "sort[]": "downloads desc" |
| } |
| r = requests.get("https://archive.org/advancedsearch.php", |
| params=params, timeout=15) |
| items = r.json().get("response", {}).get("docs", []) |
|
|
| random.shuffle(items) |
| for item in items[:10]: |
| ident = item.get("identifier", "") |
| if not ident: |
| continue |
| try: |
| meta_r = requests.get( |
| f"https://archive.org/metadata/{ident}", |
| timeout=10 |
| ) |
| files = meta_r.json().get("files", []) |
| |
| mp4s = [f for f in files if f.get("name", "").endswith(".mp4")] |
| if not mp4s: |
| continue |
| |
| mp4s.sort(key=lambda x: int(x.get("size", 999999999))) |
| chosen = mp4s[0] |
| video_url = f"https://archive.org/download/{ident}/{chosen['name']}" |
| results.append({ |
| "url": video_url, |
| "title": item.get("title", ident)[:60], |
| "source": "Archive.org" |
| }) |
| if len(results) >= count: |
| break |
| except: |
| continue |
| except Exception as e: |
| print(f"Archive hata: {e}") |
|
|
| return results |
|
|
| |
| |
| |
| def fetch_pexels(query, count=2): |
| if not PEXELS_API_KEY: |
| return [] |
| try: |
| headers = {"Authorization": PEXELS_API_KEY} |
| params = {"query": query, "per_page": count} |
| r = requests.get("https://api.pexels.com/videos/search", |
| headers=headers, params=params, timeout=15) |
| videos = r.json().get("videos", []) |
| results = [] |
| for v in videos: |
| files = v.get("video_files", []) |
| good = [f for f in files if 400 <= f.get("width", 0) <= 1280] |
| best = sorted(good or files, key=lambda x: x.get("width", 0))[-1] |
| results.append({ |
| "url": best["link"], |
| "title": f"Pexels-{v.get('id','')}", |
| "source": "Pexels" |
| }) |
| return results |
| except Exception as e: |
| print(f"Pexels hata: {e}") |
| return [] |
|
|
| |
| |
| |
| def download_video(url, source="", timeout=90): |
| out = str(WORK_DIR / f"dl_{uid()}.mp4") |
| try: |
| r = requests.get(url, timeout=timeout, stream=True, |
| headers={"User-Agent": "VideoBot/1.0"}) |
| if r.status_code != 200: |
| return None |
| with open(out, "wb") as f: |
| for chunk in r.iter_content(16384): |
| f.write(chunk) |
| size = os.path.getsize(out) |
| if size > 50_000: |
| return out |
| os.remove(out) |
| except Exception as e: |
| print(f"İndirme hatası ({source}): {e}") |
| return None |
|
|
| |
| |
| |
| def get_duration(path): |
| try: |
| r = subprocess.run( |
| ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", path], |
| capture_output=True, text=True, timeout=15 |
| ) |
| return float(json.loads(r.stdout)["format"]["duration"]) |
| except: |
| return 30.0 |
|
|
| def cut_clip(path, start, duration): |
| out = str(WORK_DIR / f"cut_{uid()}.mp4") |
| try: |
| subprocess.run([ |
| "ffmpeg", "-y", "-ss", str(start), "-i", path, |
| "-t", str(duration), |
| "-c:v", "libx264", "-preset", "ultrafast", |
| "-c:a", "aac", "-avoid_negative_ts", "make_zero", out |
| ], capture_output=True, timeout=60, check=True) |
| if os.path.exists(out) and os.path.getsize(out) > 1000: |
| return out |
| except Exception as e: |
| print(f"Kesme hatası: {e}") |
| return None |
|
|
| def normalize_clip(path): |
| out = str(WORK_DIR / f"norm_{uid()}.mp4") |
| try: |
| subprocess.run([ |
| "ffmpeg", "-y", "-i", path, |
| "-vf", "scale=1280:720:force_original_aspect_ratio=decrease," |
| "pad=1280:720:(ow-iw)/2:(oh-ih)/2,setsar=1", |
| "-c:v", "libx264", "-preset", "ultrafast", |
| "-c:a", "aac", "-ar", "44100", "-ac", "2", "-r", "30", out |
| ], capture_output=True, timeout=60, check=True) |
| if os.path.exists(out) and os.path.getsize(out) > 1000: |
| return out |
| except: |
| pass |
| return path |
|
|
| def merge_clips(clips): |
| valid = [c for c in clips if c and os.path.exists(c)] |
| if not valid: |
| return None |
| if len(valid) == 1: |
| out = str(WORK_DIR / f"final_{uid()}.mp4") |
| shutil.copy(valid[0], out) |
| return out |
|
|
| list_file = str(WORK_DIR / f"list_{uid()}.txt") |
| out = str(WORK_DIR / f"final_{uid()}.mp4") |
| with open(list_file, "w") as f: |
| for c in valid: |
| f.write(f"file '{os.path.abspath(c)}'\n") |
| try: |
| subprocess.run([ |
| "ffmpeg", "-y", "-f", "concat", "-safe", "0", |
| "-i", list_file, "-c:v", "libx264", "-preset", "ultrafast", |
| "-c:a", "aac", out |
| ], capture_output=True, timeout=180, check=True) |
| if os.path.exists(out) and os.path.getsize(out) > 1000: |
| return out |
| except Exception as e: |
| print(f"Birleştirme hatası: {e}") |
| return None |
|
|
| def add_watermark(path, text="@shorts"): |
| out = str(WORK_DIR / f"wm_{uid()}.mp4") |
| safe = text.replace("'", "") |
| try: |
| subprocess.run([ |
| "ffmpeg", "-y", "-i", path, |
| "-vf", f"drawtext=text='{safe}':fontsize=36:fontcolor=white@0.7:" |
| "borderw=2:bordercolor=black@0.5:x=20:y=20", |
| "-c:v", "libx264", "-preset", "ultrafast", "-c:a", "copy", out |
| ], capture_output=True, timeout=60, check=True) |
| if os.path.exists(out) and os.path.getsize(out) > 1000: |
| return out |
| except: |
| pass |
| return path |
|
|
| |
| |
| |
| def upload_to_drive(file_path, filename, token_json, folder_name="Video Fabrikası"): |
| if not token_json or not token_json.strip(): |
| return False, "Drive token girilmedi." |
| try: |
| creds = Credentials.from_authorized_user_info( |
| json.loads(token_json), DRIVE_SCOPES) |
| if not creds.valid and creds.expired and creds.refresh_token: |
| creds.refresh(Request()) |
| service = build("drive", "v3", credentials=creds) |
|
|
| q = f"mimeType='application/vnd.google-apps.folder' and name='{folder_name}' and trashed=false" |
| res = service.files().list(q=q, fields="files(id)").execute() |
| folders = res.get("files", []) |
| if folders: |
| folder_id = folders[0]["id"] |
| else: |
| folder = service.files().create( |
| body={"name": folder_name, |
| "mimeType": "application/vnd.google-apps.folder"}, |
| fields="id").execute() |
| folder_id = folder["id"] |
|
|
| media = MediaFileUpload(file_path, mimetype="video/mp4", resumable=True) |
| uploaded = service.files().create( |
| body={"name": filename, "parents": [folder_id]}, |
| media_body=media, fields="id,webViewLink" |
| ).execute() |
|
|
| return True, f"✅ Drive'a yüklendi!\n🔗 {uploaded.get('webViewLink', uploaded['id'])}" |
| except Exception as e: |
| return False, f"Drive hatası: {e}" |
|
|
| |
| |
| |
| def main_process(category, clip_duration, clip_count, watermark_text, drive_token): |
| logs = "" |
| def log(msg): |
| nonlocal logs |
| logs += msg + "\n" |
| return logs |
|
|
| all_clips = [] |
| clip_count = int(clip_count) |
| clip_duration = int(clip_duration) |
|
|
| try: |
| log(f"🚀 Başlıyor | Kategori: {category} | {clip_count} klip × {clip_duration}sn") |
| log("─" * 45) |
| yield logs |
|
|
| |
| log("\n📡 1. Videolar aranıyor...") |
| yield logs |
|
|
| sources = [] |
|
|
| |
| log(" → Reddit taranıyor...") |
| yield logs |
| reddit = fetch_reddit_videos(category, count=clip_count) |
| sources.extend(reddit) |
| log(f" ✅ Reddit: {len(reddit)} video bulundu") |
| yield logs |
|
|
| |
| log(" → Archive.org taranıyor...") |
| yield logs |
| archive = fetch_archive_videos(category, count=clip_count) |
| sources.extend(archive) |
| log(f" ✅ Archive.org: {len(archive)} video bulundu") |
| yield logs |
|
|
| |
| if len(sources) < clip_count and PEXELS_API_KEY: |
| log(" → Pexels'tan tamamlanıyor...") |
| yield logs |
| query = random.choice(ARCHIVE_QUERIES.get(category, ["nature"])) |
| pexels = fetch_pexels(query, count=clip_count) |
| sources.extend(pexels) |
| log(f" ✅ Pexels: {len(pexels)} video eklendi") |
| yield logs |
|
|
| if not sources: |
| log("\n💥 Hiç kaynak bulunamadı. İnternet bağlantısını kontrol et.") |
| yield logs |
| return |
|
|
| random.shuffle(sources) |
| log(f"\n Toplam {len(sources)} kaynak, {clip_count} tanesi kullanılacak.") |
| yield logs |
|
|
| |
| log("\n📥 2. İndiriliyor ve kesiliyor...") |
| yield logs |
|
|
| used = 0 |
| for i, src in enumerate(sources): |
| if used >= clip_count: |
| break |
|
|
| log(f"\n [{used+1}/{clip_count}] {src['source']}") |
| log(f" 📌 {src['title'][:50]}") |
| yield logs |
|
|
| path = download_video(src["url"], src["source"]) |
| if not path: |
| log(" ❌ İndirilemedi, sonraki deneniyor...") |
| yield logs |
| continue |
|
|
| size_kb = os.path.getsize(path) // 1024 |
| log(f" ✅ İndirildi ({size_kb} KB)") |
| yield logs |
|
|
| total = get_duration(path) |
| if total < clip_duration: |
| start = 0 |
| else: |
| |
| max_start = total - clip_duration |
| start = random.uniform(total * 0.1, min(total * 0.6, max_start)) |
|
|
| cut = cut_clip(path, int(start), clip_duration) |
| if not cut: |
| log(" ⚠️ Kesilemedi, atlanıyor.") |
| yield logs |
| continue |
|
|
| norm = normalize_clip(cut) |
| all_clips.append(norm) |
| used += 1 |
| log(f" 🎬 Klip {used} hazır") |
| yield logs |
|
|
| if not all_clips: |
| log("\n💥 Hiç klip oluşturulamadı.") |
| yield logs |
| return |
|
|
| |
| log(f"\n🎞️ 3. {len(all_clips)} klip birleştiriliyor...") |
| yield logs |
|
|
| final = merge_clips(all_clips) |
| if not final: |
| log("💥 Birleştirme başarısız.") |
| yield logs |
| return |
|
|
| size_mb = os.path.getsize(final) // 1024 // 1024 |
| log(f" ✅ Video hazır! ({size_mb} MB, ~{len(all_clips)*clip_duration}sn)") |
| yield logs |
|
|
| |
| if watermark_text.strip(): |
| log(f"\n✍️ 4. Watermark ekleniyor: {watermark_text}") |
| yield logs |
| final = add_watermark(final, watermark_text.strip()) |
| log(" ✅ Eklendi") |
| yield logs |
|
|
| |
| log("\n☁️ 5. Google Drive'a yükleniyor...") |
| yield logs |
|
|
| filename = f"{category.split()[1]}_{uid()}.mp4" |
| success, result = upload_to_drive(final, filename, drive_token) |
| log(f" {result}") |
| yield logs |
|
|
| |
| cleanup_work() |
| log("\n" + "─" * 45) |
| log("🎉 TAMAMLANDI!") |
| yield logs |
|
|
| except Exception as e: |
| log(f"\n💥 KRİTİK HATA: {e}") |
| cleanup_work() |
| yield logs |
|
|
| |
| |
| |
| def check_status(): |
| lines = ["── SİSTEM DURUMU ──────────────────"] |
| lines.append("✅ Reddit (API key gerekmez)") |
| lines.append("✅ Archive.org (API key gerekmez)") |
| lines.append(f"{'✅' if PEXELS_API_KEY else '⚠️ '} Pexels (isteğe bağlı fallback)") |
| try: |
| subprocess.run(["ffmpeg", "-version"], capture_output=True, check=True) |
| lines.append("✅ FFmpeg") |
| except: |
| lines.append("❌ FFmpeg bulunamadı") |
| lines.append("────────────────────────────────────") |
| lines.append("🚀 Hazır! Reddit + Archive.org her zaman çalışır.") |
| return "\n".join(lines) |
|
|
| |
| |
| |
| with gr.Blocks( |
| theme=gr.themes.Soft(primary_hue="violet", secondary_hue="purple"), |
| title="🎬 Video Fabrikası" |
| ) as demo: |
|
|
| gr.Markdown(""" |
| # 🎬 Video Fabrikası |
| **Reddit + Archive.org → Otomatik kes & birleştir → Drive'a kaydet** |
| """) |
|
|
| |
| with gr.Row(): |
| drive_token_input = gr.Textbox( |
| label="☁️ Google Drive Token", |
| placeholder='{"token": "...", "refresh_token": "..."}', |
| type="password", scale=4 |
| ) |
|
|
| with gr.Tab("📊 Durum"): |
| status_box = gr.Textbox(value=check_status(), lines=8, |
| interactive=False, label="Sistem Durumu") |
| gr.Button("🔄 Yenile").click(check_status, outputs=status_box) |
|
|
| with gr.Tab("🚀 Video Üret"): |
| with gr.Row(): |
| with gr.Column(scale=2): |
| category_input = gr.Dropdown( |
| choices=list(REDDIT_SUBS.keys()), |
| value="✨ Hepsi Karışık", |
| label="📂 Kategori" |
| ) |
| with gr.Row(): |
| clip_count_input = gr.Slider( |
| 2, 8, value=4, step=1, |
| label="🎞️ Klip sayısı" |
| ) |
| clip_duration_input = gr.Slider( |
| 5, 30, value=10, step=5, |
| label="⏱️ Klip süresi (sn)" |
| ) |
| watermark_input = gr.Textbox( |
| label="✍️ Watermark (boş bırakılabilir)", |
| placeholder="@kanaladi", |
| value="" |
| ) |
|
|
| with gr.Column(scale=1): |
| gr.Markdown(""" |
| **💡 Bilgi:** |
| - Reddit & Archive.org tamamen ücretsiz |
| - API key gerekmez |
| - 4 klip × 10sn = ~40sn video |
| - Drive token olmadan çalışır |
| (yükleme atlanır) |
| """) |
|
|
| run_btn = gr.Button( |
| "🚀 Tam Otomatik Çalıştır", |
| variant="primary", size="lg" |
| ) |
| log_output = gr.Textbox( |
| label="📋 Canlı Loglar", |
| lines=25, interactive=False |
| ) |
|
|
| run_btn.click( |
| main_process, |
| inputs=[category_input, clip_duration_input, |
| clip_count_input, watermark_input, drive_token_input], |
| outputs=log_output |
| ) |
|
|
| with gr.Tab("🔑 Drive Token"): |
| gr.Markdown(""" |
| ### Termux'ta token al: |
| |
| **1. Script oluştur:** |
| ``` |
| nano drive_token.py |
| ``` |
| |
| **2. İçine yaz:** |
| ```python |
| from google_auth_oauthlib.flow import InstalledAppFlow |
| SCOPES = ['https://www.googleapis.com/auth/drive.file'] |
| flow = InstalledAppFlow.from_client_secrets_file('sok.json', SCOPES) |
| auth_url, _ = flow.authorization_url( |
| prompt='consent', |
| redirect_uri='urn:ietf:wg:oauth:2.0:oob' |
| ) |
| print(auth_url) |
| code = input("Kodu gir: ") |
| flow.fetch_token(code=code, redirect_uri='urn:ietf:wg:oauth:2.0:oob') |
| print(flow.credentials.to_json()) |
| ``` |
| |
| **3. Çalıştır:** |
| ``` |
| python3 drive_token.py |
| ``` |
| |
| **4.** Çıkan JSON'u üstteki Drive Token kutusuna yapıştır. |
| """) |
|
|
| with gr.Tab("ℹ️ Hakkında"): |
| gr.Markdown(""" |
| ### Kaynaklar |
| - **Reddit** — Viral, komedi, hayvan, spor videoları (ücretsiz) |
| - **Archive.org** — Kamu domain videolar (ücretsiz) |
| - **Pexels** — Stok video fallback (isteğe bağlı) |
| |
| ### HF Secrets (İsteğe Bağlı) |
| - `PEXELS_API` — Pexels fallback için |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=False) |
|
|