Spaces:
Running on Zero
Running on Zero
Download app.py from Mr-profik/GenerationVideoAi: direct link, hf CLI and curl.
- Browser
- Download file 23.3 kB
-
https://huggingface.co/spaces/Mr-profik/GenerationVideoAi/resolve/main/app.py
- Command line
-
hf download hf://spaces/Mr-profik/GenerationVideoAi/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Mr-profik/GenerationVideoAi/resolve/main/app.py
23.3 kB
| # -*- coding: utf-8 -*- | |
| import os | |
| import random | |
| from pathlib import Path | |
| from datetime import datetime | |
| import gradio as gr | |
| import spaces | |
| import torch | |
| from diffusers import CogVideoXPipeline | |
| from diffusers.utils import export_to_video | |
| # ============================================================ | |
| # CONFIG | |
| # ============================================================ | |
| MODEL_ID = "zai-org/CogVideoX-2b" | |
| OUTPUT_DIR = Path("outputs") | |
| OUTPUT_DIR.mkdir(exist_ok=True) | |
| CHANNEL_URL = "https://t.me/Profik_Studio" | |
| # ============================================================ | |
| # TEXT | |
| # ============================================================ | |
| TEXT = { | |
| "ru": { | |
| "title": "AI Video Studio", | |
| "subtitle": "Создавай видео из текста с помощью CogVideoX", | |
| "generator": "🎬 Генератор", | |
| "history": "🕘 История", | |
| "settings": "⚙️ Настройки", | |
| "prompt": "Описание видео", | |
| "prompt_placeholder": "Например: кинематографичный город будущего ночью, неоновые огни, камера плавно летит между зданиями", | |
| "negative": "Негативный промпт", | |
| "negative_placeholder": "Размыто, плохое качество, артефакты, текст, логотипы", | |
| "frames": "Количество кадров", | |
| "fps": "FPS", | |
| "steps": "Количество шагов", | |
| "guidance": "Сила следования промпту", | |
| "seed": "Seed", | |
| "random_seed": "🎲 Случайный", | |
| "generate": "🚀 СОЗДАТЬ ВИДЕО", | |
| "result": "🎥 Результат", | |
| "examples": "💡 Примеры", | |
| "subscribe": "📢 Подписаться на @Profik_Studio", | |
| "sub_info": "Подпишись на канал и нажми кнопку ниже.", | |
| "sub_done": "✅ Я подписался", | |
| "sub_status": "🔒 Сначала подтверди подписку.", | |
| "sub_ok": "🟢 Доступ открыт! Можно создавать видео.", | |
| "not_subscribed": "🔒 Сначала нажми «Я подписался».", | |
| "history_empty": "История пока пустая.", | |
| "history_title": "История генераций", | |
| "settings_title": "Настройки", | |
| "model": "Модель", | |
| "device": "Устройство", | |
| "free": "Работает через Hugging Face ZeroGPU", | |
| "footer": "AI Video Studio • Profik", | |
| "creating": "⏳ Создаю видео...", | |
| "done": "✅ Видео готово!", | |
| "error": "❌ Ошибка", | |
| }, | |
| "en": { | |
| "title": "AI Video Studio", | |
| "subtitle": "Create videos from text with CogVideoX", | |
| "generator": "🎬 Generator", | |
| "history": "🕘 History", | |
| "settings": "⚙️ Settings", | |
| "prompt": "Video description", | |
| "prompt_placeholder": "Example: cinematic futuristic city at night, neon lights, camera smoothly flying between skyscrapers", | |
| "negative": "Negative prompt", | |
| "negative_placeholder": "Blur, low quality, artifacts, text, logos", | |
| "frames": "Number of frames", | |
| "fps": "FPS", | |
| "steps": "Inference steps", | |
| "guidance": "Guidance scale", | |
| "seed": "Seed", | |
| "random_seed": "🎲 Random", | |
| "generate": "🚀 CREATE VIDEO", | |
| "result": "🎥 Result", | |
| "examples": "💡 Examples", | |
| "subscribe": "📢 Subscribe to @Profik_Studio", | |
| "sub_info": "Subscribe to the channel and press the button below.", | |
| "sub_done": "✅ I subscribed", | |
| "sub_status": "🔒 Confirm your subscription first.", | |
| "sub_ok": "🟢 Access unlocked! You can create videos.", | |
| "not_subscribed": "🔒 Press «I subscribed» first.", | |
| "history_empty": "History is empty.", | |
| "history_title": "Generation history", | |
| "settings_title": "Settings", | |
| "model": "Model", | |
| "device": "Device", | |
| "free": "Powered by Hugging Face ZeroGPU", | |
| "footer": "AI Video Studio • Profik", | |
| "creating": "⏳ Creating video...", | |
| "done": "✅ Video ready!", | |
| "error": "❌ Error", | |
| }, | |
| } | |
| # ============================================================ | |
| # CSS | |
| # ============================================================ | |
| CSS = r""" | |
| :root { | |
| --bg: #050607; | |
| --panel: #0b0f10; | |
| --panel2: #101617; | |
| --border: #1c292b; | |
| --green: #39ff88; | |
| --green2: #16c968; | |
| --text: #f2f7f5; | |
| --muted: #82918d; | |
| } | |
| body { | |
| background: | |
| radial-gradient(circle at 15% 10%, rgba(57,255,136,.08), transparent 30%), | |
| radial-gradient(circle at 90% 20%, rgba(0,180,255,.07), transparent 25%), | |
| #050607 !important; | |
| color: var(--text) !important; | |
| } | |
| .gradio-container { | |
| max-width: 1250px !important; | |
| margin: auto !important; | |
| background: transparent !important; | |
| } | |
| .hero { | |
| margin-top: 18px; | |
| padding: 42px 30px; | |
| border: 1px solid var(--border); | |
| border-radius: 28px; | |
| background: | |
| linear-gradient(135deg, rgba(57,255,136,.08), rgba(0,0,0,.1)), | |
| rgba(10,14,15,.92); | |
| box-shadow: 0 0 60px rgba(57,255,136,.06); | |
| } | |
| .logo { | |
| width: 68px; | |
| height: 68px; | |
| border-radius: 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 34px; | |
| background: linear-gradient(135deg, #39ff88, #0d7b43); | |
| color: #001a0c; | |
| box-shadow: 0 0 35px rgba(57,255,136,.25); | |
| margin-bottom: 20px; | |
| } | |
| .hero h1 { | |
| font-size: 46px !important; | |
| margin: 0 !important; | |
| font-weight: 900 !important; | |
| letter-spacing: -2px; | |
| } | |
| .hero p { | |
| color: var(--muted); | |
| font-size: 17px; | |
| margin-top: 10px; | |
| } | |
| .card { | |
| background: rgba(11,15,16,.9); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 18px; | |
| } | |
| .menu-card { | |
| min-height: 95px; | |
| padding: 22px; | |
| border: 1px solid var(--border); | |
| border-radius: 18px; | |
| background: linear-gradient(145deg, #0d1213, #080b0c); | |
| transition: .2s; | |
| } | |
| .menu-card:hover { | |
| border-color: rgba(57,255,136,.5); | |
| transform: translateY(-2px); | |
| } | |
| .menu-icon { | |
| font-size: 28px; | |
| } | |
| .menu-title { | |
| font-weight: 800; | |
| margin-top: 8px; | |
| } | |
| .menu-desc { | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .subscribe { | |
| padding: 20px; | |
| border: 1px solid rgba(57,255,136,.22); | |
| border-radius: 20px; | |
| background: rgba(57,255,136,.035); | |
| } | |
| .status { | |
| padding: 12px 16px; | |
| border-radius: 12px; | |
| background: #0a1010; | |
| border: 1px solid var(--border); | |
| } | |
| button { | |
| border-radius: 12px !important; | |
| } | |
| .primary-btn { | |
| background: linear-gradient(135deg, #39ff88, #13b95d) !important; | |
| color: #00180a !important; | |
| border: 0 !important; | |
| font-weight: 900 !important; | |
| } | |
| .generate-btn { | |
| min-height: 58px !important; | |
| font-size: 17px !important; | |
| letter-spacing: .5px; | |
| } | |
| textarea, input { | |
| background: #080c0d !important; | |
| border-color: #1b282a !important; | |
| } | |
| footer { | |
| text-align: center; | |
| color: #53615e; | |
| padding: 30px; | |
| } | |
| .example-box { | |
| padding: 15px; | |
| border-radius: 14px; | |
| border: 1px solid var(--border); | |
| background: #090d0e; | |
| color: #a8b4b0; | |
| } | |
| """ | |
| # ============================================================ | |
| # LOAD MODEL | |
| # ============================================================ | |
| print("Loading CogVideoX...") | |
| pipe = CogVideoXPipeline.from_pretrained( | |
| MODEL_ID, | |
| torch_dtype=torch.float16, | |
| ) | |
| pipe.enable_model_cpu_offload() | |
| try: | |
| pipe.vae.enable_tiling() | |
| except Exception: | |
| pass | |
| print("CogVideoX loaded successfully.") | |
| # ============================================================ | |
| # STATE | |
| # ============================================================ | |
| history = [] | |
| # ============================================================ | |
| # SUBSCRIPTION | |
| # ============================================================ | |
| def unlock_generator(): | |
| return True, "🟢 **Доступ открыт! Можно создавать видео.**" | |
| # ============================================================ | |
| # RANDOM SEED | |
| # ============================================================ | |
| def randomize_seed(): | |
| return random.randint(0, 2_147_483_647) | |
| # ============================================================ | |
| # HISTORY HTML | |
| # ============================================================ | |
| def history_html(lang="ru"): | |
| t = TEXT.get(lang, TEXT["ru"]) | |
| if not history: | |
| return f""" | |
| <div class="card"> | |
| <h3>{t["history_title"]}</h3> | |
| <p style="color:#82918d">{t["history_empty"]}</p> | |
| </div> | |
| """ | |
| blocks = [] | |
| for item in reversed(history[-20:]): | |
| blocks.append( | |
| f""" | |
| <div class="card" style="margin-bottom:12px"> | |
| <div style="font-size:12px;color:#687773"> | |
| {item["time"]} | |
| </div> | |
| <div style="font-weight:800;margin-top:7px"> | |
| {item["prompt"][:180]} | |
| </div> | |
| <div style="font-size:12px;color:#82918d;margin-top:6px"> | |
| Seed: {item["seed"]} | |
| </div> | |
| <a href="{item["file"]}" target="_blank"> | |
| 🎥 Open video | |
| </a> | |
| </div> | |
| """ | |
| ) | |
| return "\n".join(blocks) | |
| # ============================================================ | |
| # GENERATION | |
| # ============================================================ | |
| def generate_video( | |
| prompt, | |
| negative_prompt, | |
| frames, | |
| steps, | |
| fps, | |
| guidance, | |
| seed, | |
| unlocked, | |
| progress=gr.Progress(), | |
| ): | |
| if not unlocked: | |
| return None, "🔒 Сначала подпишись на @Profik_Studio и нажми «Я подписался»." | |
| if not prompt or not prompt.strip(): | |
| return None, "❌ Напиши описание видео." | |
| try: | |
| seed = int(seed) | |
| if seed < 0: | |
| seed = randomize_seed() | |
| except Exception: | |
| seed = randomize_seed() | |
| try: | |
| frames = int(frames) | |
| steps = int(steps) | |
| fps = int(fps) | |
| guidance = float(guidance) | |
| frames = max(8, min(frames, 49)) | |
| steps = max(1, min(steps, 50)) | |
| fps = max(1, min(fps, 30)) | |
| guidance = max(1.0, min(guidance, 15.0)) | |
| generator = torch.Generator(device="cuda").manual_seed(seed) | |
| progress(0.05, desc="Loading generation settings...") | |
| result = pipe( | |
| prompt=prompt.strip(), | |
| negative_prompt=( | |
| negative_prompt.strip() | |
| if negative_prompt and negative_prompt.strip() | |
| else None | |
| ), | |
| num_frames=frames, | |
| num_inference_steps=steps, | |
| guidance_scale=guidance, | |
| generator=generator, | |
| ) | |
| progress(0.9, desc="Exporting video...") | |
| frames_out = result.frames[0] | |
| filename = ( | |
| OUTPUT_DIR | |
| / f"video_{seed}_{random.randint(1000,9999)}.mp4" | |
| ) | |
| export_to_video( | |
| frames_out, | |
| str(filename), | |
| fps=fps, | |
| ) | |
| history.append( | |
| { | |
| "time": datetime.now().strftime("%d.%m.%Y %H:%M"), | |
| "prompt": prompt.strip(), | |
| "seed": seed, | |
| "file": str(filename), | |
| } | |
| ) | |
| progress(1.0, desc="Done!") | |
| return str(filename), f"✅ Видео готово! Seed: `{seed}`" | |
| except Exception as e: | |
| print("GENERATION ERROR:", repr(e)) | |
| return None, f"❌ Ошибка генерации: `{str(e)}`" | |
| # ============================================================ | |
| # EXAMPLES | |
| # ============================================================ | |
| EXAMPLES_RU = [ | |
| [ | |
| "Кинематографичный футуристический город ночью, неоновые вывески, дождь, камера плавно летит между небоскрёбами", | |
| "размыто, низкое качество, текст, логотип", | |
| 49, | |
| 8, | |
| 20, | |
| 6.0, | |
| 42, | |
| ], | |
| [ | |
| "Красивый океан на закате, огромные волны, золотой солнечный свет, плавное движение камеры", | |
| "низкое качество, артефакты, текст", | |
| 49, | |
| 8, | |
| 20, | |
| 6.0, | |
| 123, | |
| ], | |
| [ | |
| "Фантастический космический корабль летит возле огромной планеты, кинематографичный свет", | |
| "размыто, шум, плохое качество", | |
| 49, | |
| 10, | |
| 20, | |
| 6.5, | |
| 777, | |
| ], | |
| ] | |
| # ============================================================ | |
| # UI | |
| # ============================================================ | |
| with gr.Blocks() as demo: | |
| # IMPORTANT: | |
| # CSS is injected here because Gradio 6 does not accept | |
| # css= inside gr.Blocks() in this environment. | |
| gr.HTML(f"<style>{CSS}</style>") | |
| unlocked = gr.State(False) | |
| # -------------------------------------------------------- | |
| # HERO | |
| # -------------------------------------------------------- | |
| gr.HTML( | |
| """ | |
| <div class="hero"> | |
| <div class="logo">🎬</div> | |
| <h1>AI Video Studio</h1> | |
| <p> | |
| Text → Video • CogVideoX-2b • Hugging Face ZeroGPU | |
| </p> | |
| </div> | |
| """ | |
| ) | |
| # -------------------------------------------------------- | |
| # LANGUAGE | |
| # -------------------------------------------------------- | |
| with gr.Row(): | |
| language = gr.Radio( | |
| choices=["ru", "en"], | |
| value="ru", | |
| label="🌐 Language", | |
| info="Русский / English", | |
| ) | |
| # -------------------------------------------------------- | |
| # MENU | |
| # -------------------------------------------------------- | |
| with gr.Row(): | |
| gr.HTML( | |
| """ | |
| <div class="menu-card"> | |
| <div class="menu-icon">🎬</div> | |
| <div class="menu-title">Генератор</div> | |
| <div class="menu-desc">Создание видео из текста</div> | |
| </div> | |
| """ | |
| ) | |
| gr.HTML( | |
| """ | |
| <div class="menu-card"> | |
| <div class="menu-icon">🕘</div> | |
| <div class="menu-title">История</div> | |
| <div class="menu-desc">Последние генерации</div> | |
| </div> | |
| """ | |
| ) | |
| gr.HTML( | |
| """ | |
| <div class="menu-card"> | |
| <div class="menu-icon">⚙️</div> | |
| <div class="menu-title">Настройки</div> | |
| <div class="menu-desc">Модель и параметры</div> | |
| </div> | |
| """ | |
| ) | |
| # -------------------------------------------------------- | |
| # SUBSCRIPTION | |
| # -------------------------------------------------------- | |
| with gr.Group(elem_classes="subscribe"): | |
| gr.Markdown( | |
| """ | |
| ## 📢 Подпишись на канал | |
| Чтобы пользоваться генератором, подпишись на: | |
| **[@Profik_Studio](https://t.me/Profik_Studio)** | |
| """ | |
| ) | |
| with gr.Row(): | |
| subscribe_btn = gr.Button( | |
| "📢 Подписаться на канал", | |
| link=CHANNEL_URL, | |
| ) | |
| subscribed_btn = gr.Button( | |
| "✅ Я подписался", | |
| variant="primary", | |
| ) | |
| subscription_status = gr.Markdown( | |
| "🔒 Генератор заблокирован. После подписки нажми **«Я подписался»**." | |
| ) | |
| subscribed_btn.click( | |
| fn=unlock_generator, | |
| outputs=[ | |
| unlocked, | |
| subscription_status, | |
| ], | |
| ) | |
| # -------------------------------------------------------- | |
| # TABS | |
| # -------------------------------------------------------- | |
| with gr.Tabs(): | |
| # ==================================================== | |
| # GENERATOR | |
| # ==================================================== | |
| with gr.Tab("🎬 Генератор"): | |
| with gr.Row(): | |
| with gr.Column(scale=7): | |
| prompt = gr.Textbox( | |
| label="Описание видео", | |
| placeholder=( | |
| "Например: кинематографичный город будущего " | |
| "ночью, неоновые огни..." | |
| ), | |
| lines=7, | |
| ) | |
| negative_prompt = gr.Textbox( | |
| label="Негативный промпт", | |
| placeholder=( | |
| "Размыто, плохое качество, артефакты, " | |
| "текст, логотипы" | |
| ), | |
| lines=3, | |
| ) | |
| with gr.Row(): | |
| frames = gr.Slider( | |
| minimum=8, | |
| maximum=49, | |
| value=49, | |
| step=1, | |
| label="Кадры", | |
| ) | |
| fps = gr.Slider( | |
| minimum=8, | |
| maximum=30, | |
| value=20, | |
| step=1, | |
| label="FPS", | |
| ) | |
| with gr.Row(): | |
| steps = gr.Slider( | |
| minimum=1, | |
| maximum=50, | |
| value=8, | |
| step=1, | |
| label="Шаги", | |
| ) | |
| guidance = gr.Slider( | |
| minimum=1, | |
| maximum=15, | |
| value=6, | |
| step=0.5, | |
| label="Guidance", | |
| ) | |
| with gr.Row(): | |
| seed = gr.Number( | |
| value=42, | |
| precision=0, | |
| label="Seed", | |
| ) | |
| random_seed_btn = gr.Button( | |
| "🎲 Случайный" | |
| ) | |
| random_seed_btn.click( | |
| fn=randomize_seed, | |
| outputs=seed, | |
| ) | |
| generate_btn = gr.Button( | |
| "🚀 СОЗДАТЬ ВИДЕО", | |
| variant="primary", | |
| elem_classes="generate-btn", | |
| ) | |
| # ------------------------------------------------ | |
| # RESULT | |
| # ------------------------------------------------ | |
| with gr.Column(scale=5): | |
| gr.Markdown("## 🎥 Результат") | |
| video_output = gr.Video( | |
| label="Видео", | |
| interactive=False, | |
| height=420, | |
| ) | |
| status_output = gr.Markdown( | |
| "🔒 Подтверди подписку выше." | |
| ) | |
| # ---------------------------------------------------- | |
| # GENERATE | |
| # ---------------------------------------------------- | |
| generate_btn.click( | |
| fn=generate_video, | |
| inputs=[ | |
| prompt, | |
| negative_prompt, | |
| frames, | |
| steps, | |
| fps, | |
| guidance, | |
| seed, | |
| unlocked, | |
| ], | |
| outputs=[ | |
| video_output, | |
| status_output, | |
| ], | |
| ) | |
| # ---------------------------------------------------- | |
| # EXAMPLES | |
| # ---------------------------------------------------- | |
| gr.Markdown("## 💡 Примеры") | |
| gr.Examples( | |
| examples=EXAMPLES_RU, | |
| inputs=[ | |
| prompt, | |
| negative_prompt, | |
| frames, | |
| steps, | |
| fps, | |
| guidance, | |
| seed, | |
| ], | |
| label="Готовые промпты", | |
| ) | |
| # ==================================================== | |
| # HISTORY | |
| # ==================================================== | |
| with gr.Tab("🕘 История"): | |
| history_display = gr.HTML( | |
| history_html("ru") | |
| ) | |
| refresh_history = gr.Button( | |
| "🔄 Обновить историю" | |
| ) | |
| refresh_history.click( | |
| fn=lambda: history_html("ru"), | |
| outputs=history_display, | |
| ) | |
| # ==================================================== | |
| # SETTINGS | |
| # ==================================================== | |
| with gr.Tab("⚙️ Настройки"): | |
| gr.Markdown( | |
| """ | |
| # ⚙️ Настройки | |
| **Модель** | |
| `zai-org/CogVideoX-2b` | |
| **Генерация** | |
| Видео создаётся через Hugging Face ZeroGPU. | |
| **API-ключ** | |
| Не требуется. | |
| **Telegram Bot Token** | |
| Не требуется. | |
| **Стоимость** | |
| Бесплатно в рамках доступности ZeroGPU. | |
| """ | |
| ) | |
| gr.Markdown( | |
| """ | |
| ### 📢 Канал | |
| [@Profik_Studio](https://t.me/Profik_Studio) | |
| """ | |
| ) | |
| # -------------------------------------------------------- | |
| # FOOTER | |
| # -------------------------------------------------------- | |
| gr.HTML( | |
| """ | |
| <footer> | |
| AI Video Studio • CogVideoX-2b • Profik Studio | |
| </footer> | |
| """ | |
| ) | |
| # ============================================================ | |
| # LAUNCH | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| demo.launch() |