CoolFace
Apppublic

Clementrnx/twitchclipper

sourceHugging Faceupdated 9mo agoView on Hugging Face
1likes
app.py152 linesDownload Raw Back to root
1import asyncio2import os3import cv24import json5import numpy as np6import whisper7import requests8import random9import streamlink10import subprocess11import zipfile12import io13import shutil14from pathlib import Path15from PIL import ImageFont, ImageDraw, Image16import streamlit as st17import edge_tts18from datetime import datetime, timedelta19 20# --- SECRETS & CONFIG ---21TWITCH_CID = st.secrets.get("TWITCH_CLIENT_ID", "")22TWITCH_SEC = st.secrets.get("TWITCH_CLIENT_SECRET", "")23OUTPUT_DIR = Path("TwitchClipper_Production")24OUTPUT_DIR.mkdir(exist_ok=True)25CONFIG_FILE = Path("config_webhook.json")26 27def load_webhook():28    if CONFIG_FILE.exists(): return json.load(open(CONFIG_FILE, "r")).get("webhook", "")29    return ""30 31def save_webhook(url):32    with open(CONFIG_FILE, "w") as f: json.dump({"webhook": url}, f)33 34saved_webhook = load_webhook()35 36# --- DESIGN ---37st.set_page_config(page_title="Clementrnxx TikTok Studio", layout="wide")38st.markdown('<h1 style="color: #FFFF00; text-align: center; font-family: Impact; font-size: 3rem;">CLEMENTRNXX TIKTOK STUDIO</h1>', unsafe_allow_html=True)39 40# --- BASES TROLL (On garde les listes complètes ici) ---41FEMMES_BASE = ["POLSKA", "RUBY NIKARA", "MAEVA GHENNAM", "AYA NAKAMURA", "NABILLA", "OPHENYA", "LÉNA SITUATIONS", "AMOURANTH"]42ATTR_BASE = ["LA CHATTE BIEN ÉCARTEE", "LE TROU DE BALLE DÉFONCÉ", "LA GROSSE CHATTE QUI PUE", "L'ANUS BIEN NOIR"]43LIEUX_BASE = ["AUX CHIOTTES DU COURS JU", "AU FIVE DE NANTERRE", "DANS UNE CAVE À ÉVRY", "AU MACDO DE CHÂTELET"]44ACTIONS_BASE = ["RAMONNE", "DÉFONCE", "REPEINT", "EXPLOSE", "PULVÉRISE"]45 46# --- FONCTION TEXTE ---47def draw_3d_text(frame, text, y, font_size, color=(255, 255, 255)):48    img = Image.fromarray(frame)49    draw = ImageDraw.Draw(img)50    try: font = ImageFont.truetype("Impact.ttf", font_size)51    except: font = ImageFont.load_default()52    lines = [text[i:i+25] for i in range(0, len(text), 25)]53    curr_y = y54    for line in lines:55        bbox = draw.textbbox((0, 0), line, font=font)56        x = (frame.shape[1] - (bbox[2] - bbox[0])) // 257        for i in range(5, 0, -1): draw.text((x+i, curr_y+i), line, font=font, fill=(0, 0, 0))58        draw.text((x, curr_y), line, font=font, fill=color)59        curr_y += font_size + 560    return np.array(img)61 62# --- ONGLETS ---63t1, t2, t3 = st.tabs(["[1] CONFIGURATION", "[2] SCANNER VIRAL", "[3] PRODUCTION"])64 65with t1:66    st.info("ℹ️ Identifiants Twitch gérés par Secrets. Configurez uniquement le Webhook ici.")67    new_webhook = st.text_input("Discord Webhook URL", value=saved_webhook)68    if st.button("SAUVEGARDER"):69        save_webhook(new_webhook)70        st.success("Webhook enregistré.")71 72with t2:73    col1, col2 = st.columns(2)74    with col1:75        mode_scan = st.selectbox("Type de Recherche", ["Global (Just Chatting)", "Streamer Liste"])76        period = st.selectbox("Période", ["24h", "2d", "7d", "30d"], index=0)77        min_views = st.number_input("Vues Minimum", value=50) # Baissé par défaut pour tester78    with col2:79        lang = st.selectbox("Langue", ["fr", "en"], index=0)80        min_duration = st.slider("Durée Min (sec)", 5, 60, 10)81        query = st.text_input("Liste Streamers", placeholder="kamet0,etoiles")82        count = st.slider("Max Clips", 1, 50, 10)83 84    if st.button("🚀 LANCER LE SCAN VIRAL"):85        log_area = st.empty()86        try:87            log_area.info("🔑 Connexion Twitch...")88            auth_url = f"https://id.twitch.tv/oauth2/token?client_id={TWITCH_CID}&client_secret={TWITCH_SEC}&grant_type=client_credentials"89            tk_resp = requests.post(auth_url).json()90            91            if "access_token" not in tk_resp:92                log_area.error(f"Erreur Auth: {tk_resp.get('message', 'Vérifiez vos Secrets')}")93            else:94                token = tk_resp["access_token"]95                h = {"Client-ID": TWITCH_CID, "Authorization": f"Bearer {token}"}96                97                # Calcul de la date au format RFC333998                days_val = int(''.join(filter(str.isdigit, period)))99                start_date = (datetime.utcnow() - timedelta(days=days_val)).strftime('%Y-%m-%dT%H:%M:%SZ')100                101                all_raw_clips = []102                103                if mode_scan == "Global (Just Chatting)":104                    log_area.info("📡 Scan Just Chatting...")105                    g_url = "https://api.twitch.tv/helix/games?name=Just Chatting"106                    g_data = requests.get(g_url, headers=h).json().get('data', [])107                    if g_data:108                        # On prend 100 clips direct pour avoir du choix109                        c_url = f"https://api.twitch.tv/helix/clips?game_id={g_data[0]['id']}&first=100&started_at={start_date}"110                        all_raw_clips = requests.get(c_url, headers=h).json().get('data', [])111                else:112                    streamer_names = [s.strip() for s in query.split(",") if s.strip()]113                    for name in streamer_names:114                        log_area.info(f"🔎 Scan de {name}...")115                        u_url = f"https://api.twitch.tv/helix/users?login={name}"116                        u_data = requests.get(u_url, headers=h).json().get('data', [])117                        if u_data:118                            c_url = f"https://api.twitch.tv/helix/clips?broadcaster_id={u_data[0]['id']}&first=50&started_at={start_date}"119                            all_raw_clips.extend(requests.get(c_url, headers=h).json().get('data', []))120 121                # FILTRAGE MANUEL (Plus fiable que les filtres d'URL Twitch)122                log_area.info(f"🧪 Filtrage de {len(all_raw_clips)} clips trouvés...")123                124                filtered = [125                    c for c in all_raw_clips 126                    if c.get('language') == lang 127                    and c.get('view_count', 0) >= min_views 128                    and c.get('duration', 0) >= min_duration129                ]130                131                # Tri par vues132                filtered.sort(key=lambda x: x['view_count'], reverse=True)133                st.session_state.clips = filtered[:count]134                135                if st.session_state.clips:136                    log_area.success(f"✅ {len(st.session_state.clips)} clips prêts !")137                    for c in st.session_state.clips:138                        st.write(f"📺 **{c['broadcaster_name']}** | 📈 {c['view_count']} vues | ⏳ {int(c['duration'])}s | *{c['title']}*")139                else:140                    log_area.warning(f"Rien trouvé. Essaie de baisser 'Vues Minimum' ou vérifie la langue.")141                    142        except Exception as e:143            log_area.error(f"Erreur Critique: {e}")144 145with t3:146    if 'clips' in st.session_state and st.session_state.clips:147        mode_titre = st.radio("Style", ["Normal", "Troll"], horizontal=True)148        if st.button("⚡ PRODUIRE LE PACK"):149            status = st.empty()150            pb = st.progress(0)151            # Logique de production... (Utilise la même fonction process_video que précédemment)152            st.success("Production lancée (Simulé pour cet exemple, intègre ton code de rendu ici)")