Spaces:
Runtime error
Runtime error
Download app.py from GENAI-UTT/Video-Generation-from-audio: direct link, hf CLI and curl.
- Browser
- Download file 5.94 kB
-
https://huggingface.co/spaces/GENAI-UTT/Video-Generation-from-audio/resolve/main/app.py
- Command line
-
hf download hf://spaces/GENAI-UTT/Video-Generation-from-audio/app.py
-
curl -L -o app.py https://huggingface.co/spaces/GENAI-UTT/Video-Generation-from-audio/resolve/main/app.py
5.94 kB
| import streamlit as st | |
| import os | |
| import whisper | |
| import tempfile | |
| import time | |
| import requests | |
| import fal_client | |
| from openai import OpenAI | |
| from moviepy import VideoFileClip, concatenate_videoclips, AudioFileClip | |
| from dotenv import load_dotenv | |
| # --- CONFIGURATION INITIALE --- | |
| load_dotenv() | |
| client = OpenAI(api_key=os.environ.get("OPENAI_KEY")) | |
| # On fixe la durée cible pour coller aux 129 frames à 24 fps de Fal.ai | |
| TARGET_DURATION = 5.375 | |
| STORY_SEED = 42 | |
| st.set_page_config(page_title="AI Movie Meme Generator", page_icon="🎬") | |
| st.title("🎬 AI Video Meme Generator") | |
| st.info("Transformez vos audios en histoires animées cohérentes.") | |
| # --- CHARGEMENT DU MODÈLE WHISPER (Cache pour éviter de recharger) --- | |
| def load_whisper(): | |
| return whisper.load_model("base") | |
| model_whisper = load_whisper() | |
| # --- FONCTIONS DE TRANSCRIPTION --- | |
| def get_segments(audio_path): | |
| result = model_whisper.transcribe(audio_path) | |
| segments_list = [] | |
| current_text = "" | |
| current_start = 0 | |
| for segment in result["segments"]: | |
| current_text += segment["text"] + " " | |
| if segment["end"] - current_start >= TARGET_DURATION: | |
| segments_list.append(current_text.strip()) | |
| current_text = "" | |
| current_start = segment["end"] | |
| if current_text.strip(): | |
| segments_list.append(current_text.strip()) | |
| return segments_list | |
| # --- FONCTIONS GPT (PROMPTS) --- | |
| def generate_bible_and_prompts(segments): | |
| full_text = " ".join(segments) | |
| # 1. Bible Visuelle | |
| bible_resp = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| {"role": "system", "content": "You are a Concept Artist. Define a consistent visual style and characters for this story in 3 sentences (English)."}, | |
| {"role": "user", "content": full_text} | |
| ] | |
| ) | |
| bible = bible_resp.choices[0].message.content.strip() | |
| # 2. Prompts des scènes | |
| prompts = [] | |
| for seg in segments: | |
| resp = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| {"role": "system", "content": f"VISUAL BIBLE: {bible}\nCreate a single fluid video prompt in English for this scene. Include '720p', 'high detail', 'smooth motion'."}, | |
| {"role": "user", "content": seg} | |
| ] | |
| ) | |
| prompts.append(resp.choices[0].message.content.strip()) | |
| return bible, prompts | |
| # --- FONCTIONS VIDÉO (FAL.AI) --- | |
| def generate_all_clips(prompts, temp_dir): | |
| video_paths = [] | |
| for i, p in enumerate(prompts): | |
| st.write(f"⏳ Génération du segment {i+1}/{len(prompts)}...") | |
| handler = fal_client.submit( | |
| "fal-ai/hunyuan-video", | |
| arguments={ | |
| "prompt": p, | |
| "video_size": "720p_portrait", | |
| "num_frames": 129, | |
| "fps": 24, | |
| "seed": STORY_SEED, | |
| "guidance_scale": 7.0 | |
| } | |
| ) | |
| result = fal_client.result("fal-ai/hunyuan-video", handler.request_id) | |
| # Téléchargement | |
| path = os.path.join(temp_dir, f"part_{i}.mp4") | |
| with open(path, "wb") as f: | |
| f.write(requests.get(result['video']['url']).content) | |
| video_paths.append(path) | |
| return video_paths | |
| # --- INTERFACE UTILISATEUR (STREAMLIT) --- | |
| uploaded_audio = st.file_uploader("Étape 1 : Importez votre audio (mp3, wav)", type=["mp3", "wav"]) | |
| if uploaded_audio: | |
| # Sauvegarde temporaire de l'audio pour Whisper et MoviePy | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_audio: | |
| tmp_audio.write(uploaded_audio.read()) | |
| st.session_state.audio_path = tmp_audio.name | |
| # BOUTON 1 : TRANSCRIPTION | |
| if st.button("Étape 2 : Analyser l'histoire"): | |
| with st.spinner("Transcription et découpage..."): | |
| st.session_state.segments = get_segments(st.session_state.audio_path) | |
| st.success(f"Histoire découpée en {len(st.session_state.segments)} scènes.") | |
| for s in st.session_state.segments: | |
| st.write(f"- {s}") | |
| # BOUTON 2 : PROMPTS | |
| if "segments" in st.session_state and st.button("Étape 3 : Créer le scénario visuel"): | |
| with st.spinner("GPT-4o-mini travaille sur la cohérence..."): | |
| bible, prompts = generate_bible_and_prompts(st.session_state.segments) | |
| st.session_state.bible = bible | |
| st.session_state.prompts = prompts | |
| st.write(f"🎨 **Style défini :** {bible}") | |
| st.success("Prompts générés avec succès.") | |
| # BOUTON 3 : VIDÉO FINALE | |
| if "prompts" in st.session_state and st.button("🚀 Étape 4 : Générer le film (Fal.ai)"): | |
| with tempfile.TemporaryDirectory() as tmp_video_dir: | |
| with st.status("Production de la vidéo en cours...", expanded=True) as status: | |
| # Génération | |
| clips = generate_all_clips(st.session_state.prompts, tmp_video_dir) | |
| # Assemblage | |
| status.update(label="Montage final et synchronisation audio...") | |
| video_clips = [VideoFileClip(c) for c in clips] | |
| final_v = concatenate_videoclips(video_clips, method="compose") | |
| # Ajout de l'audio | |
| audio_bg = AudioFileClip(st.session_state.audio_path) | |
| final_v = final_v.set_audio(audio_bg.subclip(0, final_v.duration)) | |
| output_path = "final_meme_video.mp4" | |
| final_v.write_videofile(output_path, codec="libx264", audio_codec="aac") | |
| status.update(label="Film terminé !", state="complete") | |
| # Affichage final | |
| st.video(output_path) | |
| with open(output_path, "rb") as f: | |
| st.download_button("💾 Télécharger ma vidéo", f, "mon_film_ia.mp4") |