# -*- 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"""

{t["history_title"]}

{t["history_empty"]}

""" blocks = [] for item in reversed(history[-20:]): blocks.append( f"""
{item["time"]}
{item["prompt"][:180]}
Seed: {item["seed"]}
🎥 Open video
""" ) return "\n".join(blocks) # ============================================================ # GENERATION # ============================================================ @spaces.GPU 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"") unlocked = gr.State(False) # -------------------------------------------------------- # HERO # -------------------------------------------------------- gr.HTML( """

AI Video Studio

Text → Video • CogVideoX-2b • Hugging Face ZeroGPU

""" ) # -------------------------------------------------------- # LANGUAGE # -------------------------------------------------------- with gr.Row(): language = gr.Radio( choices=["ru", "en"], value="ru", label="🌐 Language", info="Русский / English", ) # -------------------------------------------------------- # MENU # -------------------------------------------------------- with gr.Row(): gr.HTML( """ """ ) gr.HTML( """ """ ) gr.HTML( """ """ ) # -------------------------------------------------------- # 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( """ """ ) # ============================================================ # LAUNCH # ============================================================ if __name__ == "__main__": demo.launch()