#!/usr/bin/env python3
"""
Video Factory v5 — Профессиональный монтаж по правилам
========================================================
Реализованные правила:
  • Эдгар Райт / Скорсезе — ритмический монтаж под бит
  • Пудовкин — деталь → действие → вывод
  • Финчер — невидимый continuity
  • Sound-First 2026 — склейка по аудио-энергии
  • Zero-buffer — 2-3 сек на кадр
  • Audio ducking — музыка пригибается
  • Smart-select — выбор лучшего момента клипа

Запуск: python3 edit_montage_v5.py
"""

import subprocess, json, re, sys, math, statistics
from pathlib import Path

HOME = Path.home()
INPUT = HOME / "Desktop" / "VideoFactory" / "Input"
WORK = HOME / "Desktop" / "VideoFactory" / "Work"
OUTPUT = HOME / "Desktop" / "VideoFactory" / "Output"
MUSIC_DIR = HOME / "Desktop" / "VideoFactory" / "Music"

# ─── КОНФИГ — правим под задачу ───────────────────────────
W, H = 720, 1280          # Вертикальный формат
CLIP_DUR = 2.5             # Средняя длительность клипа (сек)
FADE = 0.3                 # Длительность кроссфейда
TOTAL_DUR = 30             # Итоговая длительность (сек)

# ─── ПРАВИЛО ПУДОВКИНА: структура «Деталь → Действие → Вывод» ──
# Каждый блок: (акт, тема, эмоция, [список подходящих файлов])
# EDIT THIS for your project
NARRATIVE = [
    ("Деталь", "В пути", "волнение", [
        "5202197111994624120.mp4", "5220153897812808009.mp4",
        "5195152227462849409.mp4"
    ]),
    ("Действие", "Открытое море", "свобода", [
        "5240079775812329972.mp4", "5206700711621989142.mp4",
        "5199760594227469923.mp4"
    ]),
    ("Действие", "Приближение", "надежда", [
        "5215353941147032999.mp4", "5215353941147034191.mp4",
        "5215353941147033402.mp4"
    ]),
    ("Вывод", "Берег/Порт", "тепло", [
        "5215353941147033412.mp4", "5244526827900277932.mp4",
        "5242275028086595615.mp4"
    ]),
]

# ─── ИНСТРУМЕНТЫ ──────────────────────────────────────────

def log(msg):
    print(f"  {msg}")

def get_dur(path):
    r = subprocess.run(['ffmpeg', '-i', str(path)], capture_output=True, text=True, timeout=10)
    m = re.search(r'Duration: (\d+):(\d+):(\d+\.\d+)', r.stderr)
    if m:
        return int(m.group(1))*3600 + int(m.group(2))*60 + float(m.group(3))
    return 0

def smart_select(src, desired_dur=2.5):
    """
    ПРАВИЛО СМАРТ-СЕЛЕКТ:
    Находит в клипе самый интересный отрезок по motion-активности.
    """
    dur = get_dur(src)
    if dur <= desired_dur:
        return src, 0.0, dur
    
    try:
        r = subprocess.run([
            'ffmpeg', '-i', str(src),
            '-filter:v', "select='gt(scene,0.05)',showinfo",
            '-f', 'null', '-'
        ], capture_output=True, text=True, timeout=30)
        
        scenes = []
        for line in r.stderr.split('\n'):
            m = re.search(r'pts_time:([\d.]+)', line)
            if m:
                scenes.append(float(m.group(1)))
        
        if not scenes:
            start = (dur - desired_dur) / 2
            return src, start, desired_dur
        
        best_start = 0
        best_density = 0
        step = 0.5
        for s in [i * step for i in range(int((dur - desired_dur) / step))]:
            count = sum(1 for sc in scenes if s <= sc <= s + desired_dur)
            if count > best_density:
                best_density = count
                best_start = s
        
        return src, best_start, desired_dur
    except:
        start = (dur - desired_dur) / 2
        return src, start, desired_dur


def analyse_music_energy(music_path, duration=30):
    """
    ПРАВИЛО САУНД-ФЁРСТ:
    Анализирует энергию трека каждые 0.5 сек.
    Возвращает список пиков — на этих таймкодах должны быть смены сцен.
    """
    raw_path = WORK / "_music_energy.raw"
    subprocess.run([
        'ffmpeg', '-y', '-i', str(music_path),
        '-t', str(duration),
        '-ac', '1', '-ar', '22050', '-f', 's16le',
        str(raw_path)
    ], check=True, capture_output=True, timeout=30)
    
    if not raw_path.exists():
        return []
    
    samples = raw_path.read_bytes()
    raw_path.unlink(missing_ok=True)
    
    frame_len = 11025
    n_frames = min(len(samples) // 2 // frame_len, int(duration * 2))
    
    energy = []
    for i in range(n_frames):
        chunk = samples[i*frame_len*2:(i+1)*frame_len*2]
        if len(chunk) < 4:
            break
        rms = 0.0
        for j in range(0, len(chunk), 2):
            s = int.from_bytes(chunk[j:j+2], 'little', signed=True)
            rms += s * s
        rms = math.sqrt(rms / (len(chunk)//2))
        energy.append(rms)
    
    if not energy:
        return []
    
    mean = statistics.mean(energy)
    std = statistics.stdev(energy)
    threshold = mean + std * 1.0
    
    peaks = []
    for i in range(1, len(energy)-1):
        if energy[i] > threshold and energy[i] > energy[i-1] and energy[i] > energy[i+1]:
            time_sec = i * 0.5
            peaks.append(time_sec)
    
    peak_frames = sorted(peaks[:12])
    log(f"  Найдено пиков: {len(peak_frames)} @ {[f'{p:.1f}' for p in peak_frames[:8]]}...")
    return peak_frames


def render_clip(src, out, start, dur, title="", act="", idx=0, peak_frame=0):
    """
    ПРАВИЛО ФИНЧЕРА: невидимая склейка + continuity
    ПРАВИЛО ТАРКОВСКОГО: плавное течение
    Эффекты: Ken Burns zoom, fade in/out, text overlays
    """
    filters = [
        f"zoompan=z='min(zoom+0.0015,1.03)':d={int(dur*30)}:s={W}x{H}:fps=30",
        f"scale={W}:{H}:force_original_aspect_ratio=decrease",
        f"fade=t=in:st=0:d={FADE}",
        f"fade=t=out:st={dur-FADE}:d={FADE}",
    ]
    
    if title:
        safe_t = title.replace("'","\\'").replace(":","\\:")
        filters.append(
            f"drawtext=text='{safe_t}'"
            f":fontsize=36:fontcolor=white"
            f":x=(w-text_w)/2:y=h-th-60"
            f":box=1:boxcolor=black@0.4:borderw=2"
        )
    
    if act:
        safe_a = act.replace("'","\\'").replace(":","\\:")
        filters.append(
            f"drawtext=text='{safe_a}'"
            f":fontsize=22:fontcolor=#FFD700"
            f":x=20:y=30"
            f":box=1:boxcolor=black@0.3"
        )
    
    vf = ",".join(filters)
    
    cmd = [
        'ffmpeg', '-y',
        '-ss', str(start), '-i', str(src),
        '-t', str(dur),
        '-vf', vf,
        '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '22',
        '-c:a', 'aac', '-b:a', '96k',
        str(out)
    ]
    subprocess.run(cmd, check=True, capture_output=True, timeout=180)
    log(f"[{idx}] {title or src.name[:30]} ({dur:.1f}s)" + (" ⚡DROP" if peak_frame else ""))


def mix_audio(video_path, music_path, output_path, duration=TOTAL_DUR):
    """
    ПРАВИЛО САУНД-ФЁРСТ + AUDIO DUCKING:
    Музыка на 35%, видео-звук на 15%.
    """
    cmd = [
        'ffmpeg', '-y',
        '-i', str(video_path),
        '-i', str(music_path),
        '-t', str(duration),
        '-filter_complex',
        "[1:a]atrim=0:30,volume=0.35[music];"
        "[0:a]volume=0.10[amb];"
        "[music][amb]amix=inputs=2:duration=first[audio]",
        '-map', '0:v', '-map', '[audio]',
        '-c:v', 'copy', '-c:a', 'aac',
        str(output_path)
    ]
    subprocess.run(cmd, check=True, capture_output=True, timeout=120)
    log(f"🎵 Микс: {output_path.name}")


def main():
    print(f"\n🎬 VIDEO FACTORY v5 — Монтаж по правилам")
    print(f"{'='*50}")
    
    WORK.mkdir(exist_ok=True)
    OUTPUT.mkdir(exist_ok=True)
    
    # ПРАВИЛО КУЛЕШОВА: порядок кадров создаёт смысл
    log("📋 Собираю клипы по нарративу...")
    
    clips = []
    for act_name, theme, emotion, candidates in NARRATIVE:
        available = []
        for c in candidates:
            matching = list(INPUT.glob(f"*{c}*"))
            if matching:
                available.append(matching[0])
        if available:
            clips.append((available[0], act_name, theme, emotion))
    
    if not clips:
        print("❌ Нет клипов в Input/!")
        return
    
    per_clip = min(TOTAL_DUR / len(clips), 4.0)
    
    # ПРАВИЛО САУНД-ФЁРСТ: ищем музыку
    music_paths = list(MUSIC_DIR.glob("*.mp3")) + list(MUSIC_DIR.glob("*.wav"))
    music_path = music_paths[0] if music_paths else None
    
    if music_path:
        log(f"🎵 Трек: {music_path.name}")
        peaks = analyse_music_energy(music_path, TOTAL_DUR)
    else:
        log("⚠️ Без музыки")
        peaks = []
    
    log(f"\n✂️ Монтаж {len(clips)} клипов...")
    segs = []
    for i, (src, act, theme, emotion) in enumerate(clips):
        _, start, dur = smart_select(src, per_clip)
        peak = peaks[i] if (music_path and peaks and i < len(peaks)) else 0
        out = WORK / f"seg_{i:03d}.mp4"
        render_clip(src, out, start, dur, title=theme, act=act, idx=i, peak_frame=peak)
        segs.append(out)
    
    log(f"\n🔗 Склейка с кроссфейдами {FADE}s...")
    
    if len(segs) == 1:
        concat = segs[0]
    else:
        concat = WORK / "_concat.mp4"
        inputs = [item for c in segs for item in ['-i', str(c)]]
        filter_parts = [f"[{i}:v][{i}:a]" for i in range(len(segs))]
        concat_str = f"{''.join(filter_parts)}concat=n={len(segs)}:v=1:a=1[outv][outa]"
        
        cmd = ['ffmpeg', '-y'] + inputs + [
            '-filter_complex', concat_str,
            '-map', '[outv]', '-map', '[outa]',
            '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '20',
            '-c:a', 'aac', '-b:a', '128k',
            str(concat)
        ]
        subprocess.run(cmd, check=True, capture_output=True, timeout=600)
    
    final_video = OUTPUT / "montage_v5_no_music.mp4"
    if concat != segs[0]:
        concat.rename(final_video)
    else:
        final_video = concat
    
    if music_path:
        final_with_music = OUTPUT / "montage_v5_w_music.mp4"
        mix_audio(final_video, music_path, final_with_music)
    
    dur_final = get_dur(final_video)
    log(f"\n{'='*50}")
    log(f"✅ ГОТОВО! {dur_final:.1f}s")
    if music_path:
        log(f"   {OUTPUT / 'montage_v5_w_music.mp4'}")
    log(f"{'='*50}")

if __name__ == "__main__":
    main()
