chore: initial commit — video processing pipeline
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
input/*
|
||||
!input/.gitkeep
|
||||
output/*
|
||||
!output/.gitkeep
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
cd /d D:\My_clips\scripts
|
||||
docker compose up -d
|
||||
echo ✅ Контейнер запущен
|
||||
pause
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
cd /d D:\My_clips\scripts
|
||||
docker compose run --rm processor python process.py
|
||||
echo ✅ Готово. Файлы в output\
|
||||
pause
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
cd /d D:\My_clips\scripts
|
||||
docker compose down
|
||||
echo ✅ Контейнер остановлен
|
||||
pause
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
# Настройки обработки видео
|
||||
processing:
|
||||
# Отразить видео по горизонтали
|
||||
mirror: true
|
||||
|
||||
speed: 1.1
|
||||
|
||||
# Фильтр цвета (выбери один):
|
||||
# - none: без изменений
|
||||
# - warm: теплее
|
||||
# - cool: холоднее
|
||||
# - vintage: винтаж
|
||||
filter: vintage
|
||||
|
||||
# Качество вывода (0-51, меньше = лучше, 18-23 оптимально)
|
||||
crf: 21
|
||||
@@ -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"]
|
||||
@@ -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]
|
||||
@@ -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()
|
||||
@@ -0,0 +1,2 @@
|
||||
watchdog==3.0.0
|
||||
pyyaml==6.0.1
|
||||
Reference in New Issue
Block a user