Shorts / app.py
Trkaga's picture
Upload app.py
20ad3fb verified
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
# ─────────────────────────────────────────
# AYARLAR
# ─────────────────────────────────────────
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
# ─────────────────────────────────────────
# REDDIT
# ─────────────────────────────────────────
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"]
# Sadece video postları
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
# DASH url düzelt
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
# ─────────────────────────────────────────
# ARCHIVE.ORG
# ─────────────────────────────────────────
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", [])
# mp4 dosyalarını bul
mp4s = [f for f in files if f.get("name", "").endswith(".mp4")]
if not mp4s:
continue
# En küçük mp4'ü seç (hız için)
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
# ─────────────────────────────────────────
# PEXELS (fallback)
# ─────────────────────────────────────────
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 []
# ─────────────────────────────────────────
# İNDİR
# ─────────────────────────────────────────
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: # En az 50KB
return out
os.remove(out)
except Exception as e:
print(f"İndirme hatası ({source}): {e}")
return None
# ─────────────────────────────────────────
# FFMPEG
# ─────────────────────────────────────────
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
# ─────────────────────────────────────────
# GOOGLE DRIVE
# ─────────────────────────────────────────
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}"
# ─────────────────────────────────────────
# ANA SÜREÇ
# ─────────────────────────────────────────
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
# 1. Kaynak topla
log("\n📡 1. Videolar aranıyor...")
yield logs
sources = []
# Reddit
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
# Archive.org
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
# Pexels fallback
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
# 2. İndir + Kes
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:
# Videonun %20-%70 arasından kes (başı ve sonu genelde boş)
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
# 3. Birleştir
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
# 4. Watermark
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
# 5. Drive
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
# 6. Temizlik
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
# ─────────────────────────────────────────
# DURUM
# ─────────────────────────────────────────
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)
# ─────────────────────────────────────────
# UI
# ─────────────────────────────────────────
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**
""")
# Drive token - her zaman üstte
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)