Mr-profik's picture
Update app.py
4507193 verified
Raw History Blame Contribute Delete
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
# ============================================================
@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"<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()