chore: initial commit — video processing pipeline

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-19 22:29:29 +03:00
commit 59b16c2673
12 changed files with 164 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
input/*
!input/.gitkeep
output/*
!output/.gitkeep
__pycache__/
*.pyc
+5
View File
@@ -0,0 +1,5 @@
@echo off
cd /d D:\My_clips\scripts
docker compose up -d
echo ✅ Контейнер запущен
pause
+5
View File
@@ -0,0 +1,5 @@
@echo off
cd /d D:\My_clips\scripts
docker compose run --rm processor python process.py
echo ✅ Готово. Файлы в output\
pause
+5
View File
@@ -0,0 +1,5 @@
@echo off
cd /d D:\My_clips\scripts
docker compose down
echo ✅ Контейнер остановлен
pause
+20
View File
@@ -0,0 +1,20 @@
# My_clips — video batch processor
Docker-based pipeline: input/ → ffmpeg (mirror/speed/filter) → output/
## Git
Remote: `gitea:Dagur/my-clips.git` (self-hosted Gitea, SSH port 222)
User: Dagur <kutspv@yandex.ru>
**Commit rules:**
- Commit at logical checkpoints — after completing a feature, fix, or refactor
- Push after every commit
- Use conventional commits: `feat:`, `fix:`, `refactor:`, `chore:`, `docs:`
- Commit message in Russian if project context is Russian
- Never commit: .env, credentials, secrets, large binaries, input/*.mp4, output/*.mp4
- Keep commits small and focused — one thing per commit
**Before committing:**
- Verify no secrets in staged files (`git diff --staged`)
- Verify it's not a large binary file
+16
View File
@@ -0,0 +1,16 @@
# Настройки обработки видео
processing:
# Отразить видео по горизонтали
mirror: true
speed: 1.1
# Фильтр цвета (выбери один):
# - none: без изменений
# - warm: теплее
# - cool: холоднее
# - vintage: винтаж
filter: vintage
# Качество вывода (0-51, меньше = лучше, 18-23 оптимально)
crf: 21
View File
View File
+16
View File
@@ -0,0 +1,16 @@
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
wget \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY process.py .
CMD ["python", "process.py"]
+16
View File
@@ -0,0 +1,16 @@
services:
processor:
build: .
volumes:
- ../input:/workspace/input
- ../output:/workspace/output
- ../config:/workspace/config
environment:
- PYTHONUNBUFFERED=1
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
+73
View File
@@ -0,0 +1,73 @@
import sys
import yaml
import subprocess
from pathlib import Path
def main():
with open('/workspace/config/settings.yaml', 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
p = config['processing']
# Видео-фильтры
vf_parts = []
if p.get('mirror'):
vf_parts.append('hflip')
speed = p.get('speed', 1.0)
if speed != 1.0:
vf_parts.append(f'setpts={1/speed:.3f}*PTS')
f_type = p.get('filter', 'none')
if f_type == 'warm':
vf_parts.append('eq=saturation=1.15:contrast=1.05:brightness=0.02')
elif f_type == 'cool':
vf_parts.append('eq=saturation=0.85:contrast=1.0:brightness=-0.02')
elif f_type == 'vintage':
vf_parts.append('eq=contrast=1.1:saturation=0.8:brightness=0.05')
vf = ','.join(vf_parts) if vf_parts else None
# Аудио-фильтры (только для скорости)
af = f'atempo={speed}' if speed != 1.0 else None
input_dir = Path('/workspace/input')
output_dir = Path('/workspace/output')
output_dir.mkdir(exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in ('.mp4', '.mov', '.avi', '.mkv')]
if not files:
print("⚠️ Папка input пуста")
sys.exit(0)
print(f"📋 mirror={p['mirror']}, speed={p['speed']}, filter={p['filter']}")
print(f"🎬 Найдено: {len(files)}\n")
for idx, src in enumerate(files, 1):
dst = output_dir / f"processed_{src.stem}.mp4"
print(f"[{idx}/{len(files)}] {src.name} -> ", end="", flush=True)
cmd = ['ffmpeg', '-y', '-i', str(src)]
if vf:
cmd.extend(['-vf', vf])
if af:
cmd.extend(['-af', af])
cmd.extend([
'-c:v', 'libx264',
'-c:a', 'aac',
'-preset', 'fast',
'-crf', str(p.get('crf', 21)),
str(dst)
])
try:
subprocess.run(cmd, capture_output=True, text=True, check=True)
print("")
except subprocess.CalledProcessError as e:
print("")
print(f"🔍 STDERR: {e.stderr}")
if __name__ == '__main__':
main()
+2
View File
@@ -0,0 +1,2 @@
watchdog==3.0.0
pyyaml==6.0.1