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