CCCPaste

2905bf1be5158

def _docker_ffmpeg_args(cpus=None, cpuset=None, use_gpu=False):
    args = ['docker','run','--rm',
            '-e','DOCKER_CONFIG=/dev/null']
    if cpus:   args += ['--cpus', str(cpus)]
    if cpuset: args += ['--cpuset-cpus', str(cpuset)]
    if use_gpu:
        args += ['--gpus=all',
                 '-e','NVIDIA_VISIBLE_DEVICES=all',
                 '-e','NVIDIA_DRIVER_CAPABILITIES=all']
    args += [
        '--security-opt','seccomp=unconfined',
        '--cap-add','SYS_NICE',
        '-v', docker_mount_point,
        'ghcr.io/aperim/nvidia-cuda-ffmpeg:latest',
    ]
    return args

def _nvenc_cmd(
    file_path: str,
    out_path: str,
    vkbps: int,
    vf_h: int,
    is_10bit: bool,          # non usato qui
    n_audio: int,
    ext_low: str,            # compat
    audio_map_relidx: list[int] | None = None,
    force_stereo: bool = FORCE_STEREO,
    use_dplii_matrix: bool = True,
    keep_subtitles: bool = True,        # nuovo: se True, mantieni i sottotitoli
) -> list[str]:
    cmd = _docker_ffmpeg_args(use_gpu=True) + [
        '-y','-nostdin','-hide_banner','-loglevel','error',
        '-hwaccel','cuda',
        '-i', file_path,
        '-vf', f'scale=-2:{vf_h}:force_original_aspect_ratio=decrease',
    ]

    # --- Mapping ---
    # Video principale
    cmd += ['-map','0:v:0']

    # Audio: o filtro esplicito o tutte
    if audio_map_relidx is not None:
        cmd += ['-map','-0:a']  # drop tutte le audio
        for idx in sorted(audio_map_relidx):
            cmd += ['-map', f'0:a:{idx}']
        eff_n_audio = len(audio_map_relidx)
    else:
        cmd += ['-map','0:a?']
        eff_n_audio = n_audio

    # Sottotitoli (se richiesto)
    if keep_subtitles:
        cmd += ['-map','0:s?']
        eff_n_subs = 1  # serve solo per flag copy; non puntiamo gli indici qui
    else:
        eff_n_subs = 0

    # --- Video (come tuo originale NVENC) ---
    cmd += [
        '-c:v','hevc_nvenc',
        '-rc','vbr_hq','-multipass','fullres',
        '-b:v', f'{int(vkbps)}k',
        '-maxrate', f'{int(vkbps*1.08)}k',
        '-bufsize', f'{int(vkbps*2.0)}k',
        '-rc-lookahead','32',
        '-spatial_aq','1','-temporal_aq','1','-aq-strength','8',
        '-bf','3','-b_ref_mode','middle',
        '-g','240',
    ]

    # --- Audio ---
    if eff_n_audio > 0:
        cmd += ['-c:a','libfdk_aac']
        # Default globale (copre eventuali stream non indicizzati)
        cmd += ['-b:a', f'{AUDIO_KBPS_PER_TRACK}k']

        # Impostazioni per ciascuna traccia audio
        for i in range(eff_n_audio):
            cmd += [f'-b:a:{i}', f'{AUDIO_KBPS_PER_TRACK}k']
            if force_stereo:
                cmd += [f'-ac:a:{i}', '2']
                if use_dplii_matrix:
                    # indica encoding matrix dolby pro logic II (metadato utile)
                    cmd += [f'-filter:a:{i}', 'aresample=matrix_encoding=dplii']
            # Titolo pulito della traccia
            cmd += [f'-metadata:s:a:{i}', f'title=AAC LC 2.0 {AUDIO_KBPS_PER_TRACK} kbps']

        # Disposition: prima audio default, le altre non-default
        cmd += ['-disposition:a:0','default']
        for i in range(1, eff_n_audio):
            cmd += [f'-disposition:a:{i}','0']  # rimuove default/forced se ereditati
    else:
        cmd += ['-an']  # nessuna traccia audio

    # --- Sottotitoli: copia "as-is" (MKV è perfetto per srt/ass/pgs/vobsub) ---
    if keep_subtitles and eff_n_subs:
        cmd += ['-c:s','copy']
        # opzionale: assicurati che nessun sottotitolo sia "default"
        # (evita sorprese quando ffmpeg ereditasse i flag)
        cmd += ['-disposition:s','0']

    # --- Metadati/Capitoli del container ---
    # Puliamo: BPS/NUMBER_OF_BYTES/encoder ecc. (ri-setteremo noi dopo il size-check)
    cmd += ['-map_metadata','-1']

    # Matroska container (lavoriamo solo in mkv)
    cmd.append(out_path)
    return cmd

def _run(cmd: List[str]) -> Tuple[int, float, str]:
    start = time.time()
    p = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
    elapsed = time.time() - start
    return p.returncode, elapsed, (p.stderr.decode('utf-8', errors='ignore') if p.stderr else "")

def run_encode_once(out_path: str, ctx: EncodeContext, plan: TargetPlan, audio_map_relidx: Optional[List[int]] = None) -> Tuple[int, float, int]:
    try:
        if os.path.exists(out_path):
            os.remove(out_path)
    except Exception:
        pass

    cmd = _nvenc_cmd(
        file_path=ctx.file_path,
        out_path=out_path,
        vkbps=int(plan.v_kbps),
        vf_h=int(plan.vf_height),
        is_10bit=ctx.is_10bit,
        n_audio=ctx.n_audio,
        ext_low=Path(ctx.file_path).suffix.lower(),
        audio_map_relidx=audio_map_relidx
    )

    eff_n_audio = (len(audio_map_relidx) if audio_map_relidx is not None else ctx.n_audio)
    logger.info(
        "[encode] start: out=%s, v=%s kb/s, a_total=%s kb/s, total=%s kb/s, vf_h=%s, audio_streams=%d, dur=%s (%.0fs)",
        out_path, plan.v_kbps, plan.target_audio_kbps_total, plan.safe_total_kbps,
        plan.vf_height, eff_n_audio, _fmt_hms(ctx.duration_s), ctx.duration_s
    )

    rc, elapsed, stderr = _run(cmd)
    out_size = os.path.getsize(out_path) if (rc == 0 and os.path.exists(out_path)) else -1

    if rc != 0:
        tail = (stderr[-400:] if stderr else "")
        logger.error(
            "[encode] failed: rc=%s, elapsed=%.1fs, out_exists=%s, stderr_tail=%s",
            rc, elapsed, os.path.exists(out_path), tail
        )
    else:
        # bitrate effettivo totale e fattore realtime
        eff_total_kbps = int((out_size * 8) / max(1.0, ctx.duration_s) / 1000)
        delta_pct = ( (eff_total_kbps / plan.safe_total_kbps - 1.0) * 100.0 ) if plan.safe_total_kbps else 0.0
        rt_factor = (ctx.duration_s / max(1e-6, elapsed))
        logger.info(
            "[encode] done: rc=0, elapsed=%.1fs (%.2fx realtime), size=%s (%d B), eff_total=%d kb/s "
            "(plan_total=%s, Δ=%.2f%%)",
            elapsed, rt_factor, _fmt_bytes(out_size), out_size,
            eff_total_kbps, plan.safe_total_kbps, delta_pct
        )

    return rc, elapsed, out_size