YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont from gtts import gTTS import os import time from typing import List, Dict import gradio as gr import random import textwrap import subprocess import sys from pathlib import Path import shutil from tqdm import tqdm import threading import queue
class SimpleBrainrotGenerator: def init(self): # Создаем необходимые директории for path in ['assets/videos', 'assets/audio', 'temp', 'output']: Path(path).mkdir(parents=True, exist_ok=True)
self.video_paths = {
"minecraft_adventure": "assets/videos/minecraft_adventure.mp4",
"minecraft_night": "assets/videos/minecraft_night.mp4",
"minecraft_build": "assets/videos/minecraft_build.mp4",
"gta_racing": "assets/videos/gta_racing.mp4",
"subway_surfer": "assets/videos/subway_surfer.mp4"
}
self.audio_paths = {
"no_background": None,
"running": "assets/audio/running.mp3",
"future": "assets/audio/future.mp3",
"monke": "assets/audio/monke.mp3",
"travel": "assets/audio/travel.mp3",
"wii": "assets/audio/wii.mp3"
}
def check_files_exist(self):
"""Проверяет наличие всех необходимых файлов"""
missing_files = []
# Проверяем видео файлы
for video_name, video_path in self.video_paths.items():
if not os.path.exists(video_path):
missing_files.append(f"Video file missing: {video_path}")
# Проверяем аудио файлы
for audio_name, audio_path in self.audio_paths.items():
if audio_path and not os.path.exists(audio_path):
missing_files.append(f"Audio file missing: {audio_path}")
return missing_files
def create_text_overlay(self, text: str, width: int, height: int) -> np.ndarray:
"""Создает наложение текста с фоном"""
img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Пробуем разные шрифты
font_size = 40
try:
font = ImageFont.truetype("arial.ttf", font_size)
except:
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", font_size)
except:
font = ImageFont.load_default()
# Разбиваем текст на строки
max_chars = width // 20
lines = textwrap.wrap(text, width=max_chars)
# Рассчитываем размеры фона
line_height = 50
text_height = len(lines) * line_height
padding = 20
# Рисуем полупрозрачный фон
bg_y = (height - text_height) // 2
bg_rect = [(padding, bg_y - padding),
(width - padding, bg_y + text_height + padding)]
draw.rectangle(bg_rect, fill=(0, 0, 0, 128))
# Рисуем текст
y = bg_y
for line in lines:
# Центрируем текст
bbox = draw.textbbox((0, 0), line, font=font)
text_width = bbox[2] - bbox[0]
x = (width - text_width) // 2
# Рисуем обводку
outline_color = (0, 0, 0)
for dx, dy in [(1,1), (-1,-1), (1,-1), (-1,1)]:
draw.text((x + dx, y + dy), line, font=font, fill=outline_color)
# Рисуем текст
draw.text((x, y), line, font=font, fill=(255, 255, 255, 255))
y += line_height
return np.array(img)
def get_video_duration(self, filepath: str) -> float:
"""Получает длительность видео в секундах"""
cap = cv2.VideoCapture(filepath)
if not cap.isOpened():
raise RuntimeError(f"Could not open video file: {filepath}")
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
cap.release()
if fps <= 0 or frame_count <= 0:
raise RuntimeError(f"Invalid video properties: fps={fps}, frames={frame_count}")
return frame_count / fps
def create_video(self, text: str, background_video: str, background_audio: str,
voice_type: str, language: str, progress=gr.Progress()) -> str:
"""Создает видео с текстовым и звуковым сопровождением"""
# Проверяем наличие файлов
missing_files = self.check_files_exist()
if missing_files:
raise ValueError(f"Missing required files:\n" + "\n".join(missing_files))
# Создаем временную директорию с использованием Path
temp_dir = Path(f"temp/temp_{int(time.time())}_{random.randint(1000,9999)}")
frames_dir = temp_dir / "frames"
temp_dir.mkdir(parents=True, exist_ok=True)
frames_dir.mkdir(parents=True, exist_ok=True)
try:
progress(0.1, "Генерация озвучки...")
# Генерируем озвучку
voice_path = temp_dir / "voice.mp3"
tts = gTTS(text=text, lang=language, slow=False)
tts.save(str(voice_path))
progress(0.2, "Обработка видео...")
# Получаем параметры видео
video_path = self.video_paths[background_video]
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open video file: {video_path}")
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
if width <= 0 or height <= 0 or fps <= 0:
raise RuntimeError(f"Invalid video properties: width={width}, height={height}, fps={fps}")
# Создаем текстовое наложение
text_overlay = self.create_text_overlay(text, width, height)
# Определяем длительность на основе озвучки
voice_duration = self.get_video_duration(str(voice_path))
total_frames = max(int(voice_duration * fps), 1) # Минимум 1 кадр
progress(0.3, "Генерация кадров...")
frames_generated = 0
# Генерируем кадры с прогресс-баром
for frame_idx in tqdm(range(total_frames), desc="Generating frames"):
ret, frame = cap.read()
if not ret:
# Если видео закончилось, начинаем сначала
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
ret, frame = cap.read()
if not ret:
raise RuntimeError("Failed to read video frame")
# Конвертируем в RGBA для наложения
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2BGRA)
# Анимируем появление текста
alpha = min(1.0, frame_idx / (fps * 2))
# Добавляем эффект покачивания текста
shift_y = int(np.sin(frame_idx * 0.1) * 5)
shifted_overlay = np.roll(text_overlay, shift_y, axis=0)
# Накладываем текст
frame = cv2.addWeighted(frame, 1, shifted_overlay, alpha, 0)
# Сохраняем кадр
frame_path = frames_dir / f"frame_{frame_idx:05d}.png"
if cv2.imwrite(str(frame_path), frame):
frames_generated += 1
else:
print(f"Warning: Failed to save frame {frame_idx}", file=sys.stderr)
# Обновляем прогресс
progress(0.3 + (0.4 * frame_idx / total_frames),
f"Обработка кадра {frame_idx + 1}/{total_frames}")
cap.release()
if frames_generated == 0:
raise RuntimeError("Failed to generate any frames")
# Генерируем имя выходного файла
output_dir = Path("output")
output_path = output_dir / f"video_{int(time.time())}_{random.randint(1000,9999)}.mp4"
progress(0.7, "Сборка видео...")
# Собираем видео из кадров
frame_pattern = str(frames_dir / "frame_%05d.png")
temp_video = temp_dir / "temp_video.mp4"
# Создаем временное видео без звука
result = subprocess.run([
"ffmpeg", "-y",
"-framerate", str(fps),
"-i", frame_pattern,
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-preset", "ultrafast",
str(temp_video)
], capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg error during video creation: {result.stderr}")
progress(0.9, "Добавление звука...")
# Добавляем звук
if background_audio != "no_background":
bg_audio_path = self.audio_paths[background_audio]
result = subprocess.run([
"ffmpeg", "-y",
"-i", str(temp_video),
"-i", str(voice_path),
"-i", bg_audio_path,
"-filter_complex", "[1:a][2:a]amix=inputs=2:duration=first:weights=5 1[a]",
"-map", "0:v",
"-map", "[a]",
"-c:v", "copy",
"-c:a", "aac",
str(output_path)
], capture_output=True, text=True)
else:
result = subprocess.run([
"ffmpeg", "-y",
"-i", str(temp_video),
"-i", str(voice_path),
"-c:v", "copy",
"-c:a", "aac",
str(output_path)
], capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg error during audio addition: {result.stderr}")
progress(1.0, "Готово!")
return str(output_path)
except Exception as e:
raise RuntimeError(f"Error creating video: {str(e)}")
finally:
# Очистка временных файлов
try:
shutil.rmtree(temp_dir)
except Exception as e:
print(f"Warning: Failed to cleanup temp files: {str(e)}", file=sys.stderr)
def create_gradio_interface(): generator = SimpleBrainrotGenerator()
def generate(text: str, language: str, background_video: str, background_audio: str,
progress=gr.Progress()) -> str:
try:
return generator.create_video(
text=text,
background_video=background_video,
background_audio=background_audio,
voice_type="standard", # Используем стандартный голос
language=language,
progress=progress
)
except Exception as e:
raise gr.Error(str(e))
interface = gr.Interface(
fn=generate,
inputs=[
gr.Textbox(
label="Текст",
placeholder="Введите текст для озвучки...",
lines=3
),
gr.Dropdown(
choices=["ru", "en", "es", "fr"],
value="ru",
label="Язык озвучки"
),
gr.Dropdown(
choices=list(generator.video_paths.keys()),
value="minecraft_adventure",
label="Фоновое видео"
),
gr.Dropdown(
choices=list(generator.audio_paths.keys()),
value="no_background",
label="Фоновая музыка"
)
],
outputs=gr.Video(label="Готовое видео"),
title="Генератор Видео для TikTok",
description="""
Создавайте видео для TikTok с озвучкой текста и эффектами.
Инструкция:
1. Введите текст для озвучки
2. Выберите язык озвучки
3. Выберите фоновое видео
4. При желании добавьте фоновую музыку
5. Нажмите кнопку Submit и дождитесь генерации видео
"""
)
return interface
if name == "main": interface = create_gradio_interface() interface.launch(share=True) # Added share=True for public access