Files
wiki/tasks/karaoke/video_bg.py
2026-04-30 01:20:01 +03:00

153 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
video_bg.py — Pexels API → скачать видео-клип для фона
Если Pexels не доступен → генерируем чёрный видео-файл.
"""
import os
import subprocess
import requests
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(os.path.expanduser("~/.openclaw/.env"))
import shutil
FFMPEG = os.environ.get("FFMPEG_BIN") or shutil.which("ffmpeg") or os.path.expanduser("~/bin/ffmpeg")
FFPROBE = os.environ.get("FFPROBE_BIN") or shutil.which("ffprobe") or FFMPEG # fallback to ffmpeg
def search_pexels(query: str, per_page: int = 3) -> list[dict]:
"""Поиск видео на Pexels. Возвращает список видео-объектов."""
api_key = os.environ.get("PEXELS_API_KEY", "")
if not api_key:
print("[video_bg] PEXELS_API_KEY не задан, пропускаем поиск.")
return []
url = "https://api.pexels.com/videos/search"
params = {
"query": query,
"per_page": per_page,
"orientation": "landscape",
"size": "medium",
}
headers = {"Authorization": api_key}
try:
resp = requests.get(url, params=params, headers=headers, timeout=15)
resp.raise_for_status()
data = resp.json()
except Exception as e:
print(f"[video_bg] Ошибка Pexels API: {e}")
return []
return data.get("videos", [])
def pick_video(video_list: list[dict]) -> str | None:
"""Выбрать URL лучшего видео (предпочитаем 720p, берём любое доступное)."""
for v in video_list:
files = v.get("video_files", [])
# Сначала ищем 720p (1280x720)
for f in files:
if f.get("width") == 1280:
return f.get("link") or f.get("file")
# Потом любое HD ≥ 1280
for f in sorted(files, key=lambda x: x.get("width", 0)):
if f.get("width", 0) >= 1280:
return f.get("link") or f.get("file")
# Любое что есть
if files:
return files[0].get("link") or files[0].get("file")
return None
def download_url(url: str, dest: str) -> str:
"""Скачать файл по URL в dest."""
print(f"[video_bg] Скачиваем {url[:80]}")
resp = requests.get(url, stream=True, timeout=60)
resp.raise_for_status()
with open(dest, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
return dest
def get_duration(path: str) -> float:
"""Длительность видео в секундах через ffmpeg -i."""
result = subprocess.run(
[FFMPEG, "-i", path],
capture_output=True, text=True
)
import re
m = re.search(r"Duration: (\d+):(\d+):([\d.]+)", result.stderr)
if m:
h, mn, s = m.groups()
return int(h) * 3600 + int(mn) * 60 + float(s)
raise RuntimeError(f"Не удалось получить длительность: {path}")
def create_black_video(duration: float, dest: str, width: int = 1280, height: int = 720):
"""Создать чёрное видео заданной длительности."""
print(f"[video_bg] Создаём чёрный фон {duration:.1f}s…")
cmd = [
FFMPEG, "-f", "lavfi", "-i", f"color=c=black:s={width}x{height}:r=30:d={duration}",
"-c:v", "libx264", "-pix_fmt", "yuv420p", "-y", dest
]
subprocess.run(cmd, check=True, capture_output=True)
return dest
def loop_video(source: str, target_duration: float, dest: str):
"""Зациклить видео до нужной длительности."""
src_dur = get_duration(source)
if src_dur >= target_duration:
# Обрезать лишнее
cmd = [
FFMPEG, "-i", source, "-t", str(target_duration),
"-c:v", "libx264", "-pix_fmt", "yuv420p", "-an", "-y", dest
]
else:
# Зациклить через stream_loop
loops = int(target_duration / src_dur) + 1
cmd = [
FFMPEG, "-stream_loop", str(loops - 1), "-i", source,
"-t", str(target_duration),
"-c:v", "libx264", "-pix_fmt", "yuv420p", "-an", "-y", dest
]
subprocess.run(cmd, check=True, capture_output=True)
return dest
def get_bg_video(search_query: str, audio_duration: float,
output_dir: str) -> str:
"""
Основной entry point.
1. Ищет видео на Pexels по query
2. Скачивает и зацикливает до audio_duration
3. Если Pexels недоступен — создаёт чёрный фон
Возвращает путь к видео-файлу.
"""
os.makedirs(output_dir, exist_ok=True)
raw_path = os.path.join(output_dir, "raw_bg.mp4")
bg_path = os.path.join(output_dir, "background.mp4")
videos = search_pexels(search_query)
url = pick_video(videos)
if url:
try:
download_url(url, raw_path)
loop_video(raw_path, audio_duration, bg_path)
print(f"[video_bg] Видео-фон готов: {bg_path}")
return bg_path
except Exception as e:
print(f"[video_bg] Ошибка скачивания: {e}")
# Fallback: чёрный фон
create_black_video(audio_duration, bg_path)
return bg_path