#!/usr/bin/env python3
"""Create a matched CYD .raw/.pcm pair from an original video with audio."""
import argparse
from pathlib import Path
import shutil,subprocess,tempfile

def soundtrack_filter(source, samples):
    # Movie-only speaker leveling. Float-source processing precedes u8 quantization.
    return ('aformat=channel_layouts=mono,highpass=f=120,lowpass=f=5500,'
            'dynaudnorm=f=150:g=7:p=0.85:m=30:r=0.22:b=1:t=0.001,'
            'acompressor=threshold=0.12:ratio=2:attack=10:release=150:makeup=1.8,'
            'aresample=16000:async=1:first_pts=0,'
            'alimiter=limit=0.90:level=false:latency=true,'
            f'apad,atrim=end_sample={samples}')

def convert(source, output):
    source=Path(source).resolve();output=Path(output).resolve().with_suffix('.raw')
    pcm=output.with_suffix('.pcm')
    if not source.is_file():raise ValueError('Source video does not exist')
    if output.exists() or pcm.exists():raise ValueError('Output already exists; choose a new name')
    if not shutil.which('ffmpeg'):raise ValueError('ffmpeg must be installed')
    output.parent.mkdir(parents=True,exist_ok=True)
    with tempfile.TemporaryDirectory(prefix='cyd-movie-',dir=output.parent) as tmp:
        rawtmp=Path(tmp)/'video.raw';pcmtmp=Path(tmp)/'audio.pcm'
        base=['ffmpeg','-hide_banner','-loglevel','error','-nostdin','-y','-i',str(source)]
        subprocess.run(base+['-map','0:v:0','-an','-vf','fps=24:start_time=0,scale=192:144:force_original_aspect_ratio=decrease,pad=192:144:(ow-iw)/2:(oh-ih)/2','-pix_fmt','rgb565le','-f','rawvideo',str(rawtmp)],check=True)
        size=rawtmp.stat().st_size
        if not size or size%55296:raise ValueError('Video conversion produced incomplete frames')
        frames=size//55296;samples=(frames*16000+23)//24
        subprocess.run(base+['-map','0:a:0','-vn','-af',soundtrack_filter(source,samples),'-ac','1','-ar','16000','-c:a','pcm_u8','-f','u8',str(pcmtmp)],check=True)
        if pcmtmp.stat().st_size!=samples:raise ValueError('Audio length mismatch')
        # Both files are complete before either final name is published.
        rawtmp.rename(output)
        try:pcmtmp.rename(pcm)
        except OSError:
            output.unlink();raise
    print(f'{frames} frames, {samples} audio samples ({frames/24:.2f} seconds)')
    print(f'Copy both files into /movies on the SD card:\n{output}\n{pcm}')

if __name__=='__main__':
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('source',help='Original video containing its soundtrack')
    parser.add_argument('output',help='New output .raw path')
    args=parser.parse_args()
    try:convert(args.source,args.output)
    except (ValueError,subprocess.CalledProcessError,OSError) as error:parser.exit(1,f'Conversion failed: {error}\n')
