Короткие видео о товарах хорошо продают в TikTok Shop, Shopee Video, Instagram Reels и YouTube Shorts, но монтировать вручную по ролику на каждый товар, когда товаров целый каталог, нереально. В этой статье небольшой скрипт на Python и ffmpeg на вашем сервере превращает папку с фотографиями товара и коротким сценарием озвучки в готовое вертикальное видео 9:16 с ИИ-озвучкой, вшитыми субтитрами и фоновой музыкой (по желанию), а затем за один пакетный запуск создаёт видео для сотен товаров.
Эта статья продолжает руководство ИИ-озвучка с Kokoro: сначала настройте /opt/tts и tts.py, как описано там. Затем установите шрифты для китайских и японских субтитров:
apt install -y ffmpeg fonts-noto-cjk
fc-list | grep -i "Noto Sans CJK" | head -3
/data/videos/LS-01/
├── 01.jpg # photos, shown in name order
├── 02.jpg
├── 03.jpg
├── script.txt # narration, one subtitle line per line
└── music.mp3 # optional background music
cat > /opt/tts/makevideo.py <<'EOF2'
import os, subprocess, sys
import soundfile as sf
W, H, FPS = 1080, 1920, 30
FONT = os.environ.get("SUB_FONT", "Noto Sans CJK SC")
os.chdir(sys.argv[1])
images = sorted(f for f in os.listdir(".") if f.lower().endswith((".jpg", ".jpeg", ".png", ".webp")))
if not images:
sys.exit("No photos found in " + os.getcwd())
per = sf.info("script.wav").duration / len(images) # each photo stays on screen for an equal share
def ffmpeg(*args):
subprocess.run(["ffmpeg", "-y", "-loglevel", "error", *args], check=True)
with open("clips.txt", "w") as lst:
for i, img in enumerate(images):
clip = f"clip{i:03d}.mp4" # photo centred on a blurred copy of itself, 9:16
ffmpeg("-loop", "1", "-t", f"{per:.3f}", "-i", img, "-filter_complex",
f"[0:v]split[a][b];[a]scale={W}:{H}:force_original_aspect_ratio=increase,crop={W}:{H},boxblur=20[bg];"
f"[b]scale={W}:{H}:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2,format=yuv420p",
"-r", str(FPS), "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", clip)
lst.write(f"file '{clip}'\n")
style = f"FontName={FONT},FontSize=11,Bold=1,Outline=2,Shadow=0,Alignment=2,MarginV=50"
inputs = ["-f", "concat", "-safe", "0", "-i", "clips.txt", "-i", "script.wav"]
if os.path.exists("music.mp3"): # optional background music, quietly under the voice
inputs += ["-stream_loop", "-1", "-i", "music.mp3"]
audio = "[2:a]volume=0.15[m];[1:a][m]amix=inputs=2:duration=first,loudnorm=I=-14:TP=-1.5[a]"
else:
audio = "[1:a]loudnorm=I=-14:TP=-1.5[a]"
ffmpeg(*inputs, "-filter_complex", f"[0:v]subtitles=script.srt:force_style='{style}'[v];{audio}",
"-map", "[v]", "-map", "[a]", "-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
"-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "160k", "-ar", "48000", "-ac", "2", "-shortest", "-movflags", "+faststart", "video.mp4")
for f in ["clips.txt"] + [f"clip{i:03d}.mp4" for i in range(len(images))]:
os.remove(f)
print("done:", os.path.abspath("video.mp4"))
EOF2
Каждая фотография показывается на экране равную долю длительности озвучки. Она размещается по центру поверх размытой полноэкранной копии самой себя, поэтому и горизонтальные, и вертикальные фото заполняют вертикальный кадр. Затем скрипт склеивает клипы, добавляет голос и, если она есть, тихую фоновую музыку, вшивает субтитры, нормализует громкость примерно до -14 LUFS и сохраняет video.mp4.
cd /data/videos/LS-01
TTS_LANG=z TTS_VOICE=zf_xiaobei /opt/tts/bin/python /opt/tts/tts.py script.txt
/opt/tts/bin/python /opt/tts/makevideo.py /data/videos/LS-01
На выходе получается видео 1080x1920, 30 кадров/с, H.264 со звуком AAC — этот формат принимают все крупные платформы коротких видео. Субтитры расположены с отступом от нижнего края кадра, чтобы их не перекрывали кнопки большинства платформ. Для японских субтитров запустите вторую команду с SUB_FONT="Noto Sans CJK JP". Чтобы изменить размер или положение субтитров, поменяйте FontSize и MarginV в строке style скрипта.
for d in /data/videos/*/; do
[ -f "$d/video.mp4" ] && continue # skip products that are already done
TTS_LANG=z TTS_VOICE=zf_xiaobei /opt/tts/bin/python /opt/tts/tts.py "$d/script.txt" &&
/opt/tts/bin/python /opt/tts/makevideo.py "$d" || echo "FAILED: $d"
done
Длинные пакетные задания запускайте через nohup или внутри screen, чтобы они продолжали работать после выхода из системы. В нашем тесте 16-секундное видео рендерилось около пяти секунд; основное время уходит на кодирование видео, поэтому многоядерный процессор заметно ускоряет работу.
volume=0.15 в скрипте.Да, но клипы нужно так же обрезать по длительности и масштабировать, а для этого придётся доработать скрипт. Проще сгенерировать голос и SRT по руководству по озвучке и совместить их с вашими клипами в видеоредакторе.
Переведите script.txt и запустите обе команды с голосом для нужного языка. Для уже готовых видео см. ИИ-дубляж видео на другой язык.
Не хватает шрифта. Установите fonts-noto-cjk, как в шаге 1, и проверьте, что fc-list показывает «Noto Sans CJK SC».
Если проблема не решена, создайте тикет — техническая поддержка IMIDC работает 24/7. Укажите IP сервера, версию ОС, структуру папок и полный текст ошибки, чтобы мы быстрее нашли причину.