#!/bin/bash
set -e
fail () { echo >&2 "bikecams-unify: error: $1"; exit 127; }

# example usages:
# ~/an-things/general/bikecams-unify --offset-file=/home/ian/junk/d/offset --pixelate=7800-8010:380x240@260x632:40 --tmpdir=/volatile/ian/tmp/d {front,rear}/avi_0005.avi ~/keep/bikecams/demo/there.ogg
#  ~/an-things/general/bikecams-unify --offset-file=/home/ian/junk/d/offset2 --tmpdir=/volatile/ian/tmp/d {front,rear}/avi_0006.avi ~/keep/bikecams/demo/back.ogg

basis=0
offset=true
video_setting=-v0
audio_setting=''
shrink=''
offset_file=''
max=''
pixelates=''
suppress_audio=false

while [ $# != 0 ]; do
	val="${1#*=}"
	case "$1" in
	--tmpdir=*)		tmpdir="$val" ;;
	--frames=*)		max="$val" ;;
	--suppress-audio)	suppress_audio=true ;;
	--start-frame=*)	basis="$val" ;;
	--no-offset)		offset=false ;;
	--offset-file=*)	offset_file="$val" ;;
	--video-setting=*)	video_setting="$val" ;;
	--audio-setting=*)	audio_setting="$val" ;;
	--shrink=*)		shrink="$val" ;;
	--pixelate=*-*:*x*@*x*:*) pixelates="$pixelates $val" ;;
	# <startframe>-<endframe>:<width>x<height>@<left>x<top>:<macropixel>
	#  where sizes are in pre-shrunk pixels (ie out of 640x970)
	#  and a good value for macropixel is 10-50ish.
	#  and frames are of course 1/30s
	--)			shift; break ;;
	-*)			fail "unknown option \`$1'" ;;
	*)			break ;;
	esac
	shift
done

test $# = 4 || \
 fail 'usage: .../bikecams-unify [--tmpdir=T] FRONT.avi REAR.avi|none OUTPUT.ogg OUTPUT.mpg'

front="$1"
rear="$2"
output="$3"
moutput="$4"

if [ "x$rear" = xnone ]; then
	no_rear=true
	offset=false
else
	no_rear=false
fi

if [ "x$tmpdir" = x ]; then
	trap 'set +e; wait; rm -rf -- "$tmpdir"; exit 127' 0
	tmpdir="`mktemp -dt`"
	rm_tmpdir=true
else
	rm -rf -- "$tmpdir"
	mkdir -- "$tmpdir"
	rm_tmpdir=false
fi

frame=frame%06d.jpg
oframe=frame%06d.ppm

if [ "$max" ]; then
	min_explode=$(( 40 * 30 ))
	explode=$(( $max + $basis + 30 ))
	if [ $explode -lt $min_explode ]; then explode=$min_explode; fi
	t_max="-t $(( ($explode + 29) / 30 ))"
fi

x () { echo >&2 "+ $*"; "$@"; }
dmjpeg () {
	mkdir -p "$tmpdir/$2"
	x ffmpeg $t_max \
		-i "$1" -f image2 "$tmpdir/$2/$frame" \
		-f wav "$tmpdir/$2-in.wav"
	x sox -V3 "$tmpdir/$2-in.wav" "$tmpdir/$2.sb"
}

dmjpeg "$front" front
$no_rear || dmjpeg "$rear" rear

if $offset; then
	if [ "x$offset_file" = x ]; then offset_file="$tmpdir"/offset; fi
	if [ -f "$offset_file" ]; then
		echo "reading offset from $offset_file"
	else
		x ${0/unify/audio} --not-interactive \
			<"$tmpdir"/front.sb \
			3<"$tmpdir"/rear.sb \
			>"$offset_file"
	fi
	read <"$offset_file" offset_ausamples offset_seconds offset_frames
else
	offset_ausamples=0
	offset_seconds=0
	offset_frames=0
fi

# weird noise thing:
#  sox -c1 -r8000 d/tmp/rear.sb d/tmp/t.wav noiseprof prof
#  sox -v0.5 -c1 -r8000 d/tmp/rear.sb d/tmp/t.wav noisered prof 0.1


basis_ausamples=$(( ($basis * 800) / 3 ))

initoffsets () {
	if [ $offset_ausamples -lt 0 ]; then
		atrimfront=$(( -$offset_ausamples ))
		atrimrear=0
		ifront=$(( $basis-$offset_frames ))
		irear=$basis
	else
		atrimfront=0
		atrimrear=$(( $offset_ausamples ))
		ifront=$basis
		irear=$(( $basis+$offset_frames ))
	fi
	icomposed=0
}

perframeloop () {
	ifile front
	$no_rear || ifile rear
	icomposed=$(( $icomposed + 1 ))
	if [ "$max" ] && [ "$max" -a "$icomposed" -gt "$max" ]; then break; fi
	f=`printf "%s/composed/$oframe" "$tmpdir" "$icomposed"`
}

ifile () { eval '
	i'$1'=$(( $i'$1' + 1))
	f=`printf "%s/$frame" "$tmpdir"/'$1' $i'$1'`
	if ! test -f "$f"; then break; fi
	f'$1'="$f"
 '
}

pbmmake -black 1 10 >"$tmpdir"/border

mkpipe () {
	rm -f -- "$1"
	mkfifo -m600 -- "$1"
}

rm -rf "$tmpdir"/composed
mkdir "$tmpdir"/composed

if [ "$max" ]; then
	sox_trim_len=$(( ($max * 800 + 2) / 3 ))s
fi

trimaudio () {
	trim=$(( $2 + $basis_ausamples ))
	if $suppress_audio; then
		x sox -V3 -c1 -r8000 -n "$tmpdir/$1-out".wav trim 0 800s
	else
		x sox -V3 "$tmpdir/$1-in".wav "$tmpdir/$1-out".wav \
			trim ${trim}s $sox_trim_len
	fi
}

initoffsets

trimaudio front $atrimfront
$no_rear || trimaudio rear $atrimrear

if $no_rear; then
	ln "$tmpdir"/front-out.wav "$tmpdir"/output.wav
	total_height=480
else
	x sox -V3 -M "$tmpdir"/rear-out.wav "$tmpdir"/front-out.wav \
	  -c2 -2 "$tmpdir"/output.wav
	total_height=970
fi

mkpipe "$tmpdir"/result.ppm
mkpipe "$tmpdir"/resultm.ppm

forked () {
	eval '
		'$1'_pid=$!
		forked="$forked '$1'"
	'
}

x ${0/bikecams-unify/ppm2theora-derivative} \
	$audio_setting $video_setting \
	-o "$output" -f 30 -F 1 \
	"$tmpdir"/output.wav "$tmpdir"/result.ppm &
forked theora

x ffmpeg -y -r 30 \
        -f image2pipe -i "$tmpdir"/resultm.ppm \
        -i "$tmpdir/output.wav" \
        -b 500 -ar 44100 "$moutput" \
        &
forked ffmpeg

exec 3>"$tmpdir"/result.ppm
exec 4>"$tmpdir"/resultm.ppm

pipe_mangle () {
	x "$@" <"$wf" >"$wf.new"
	mv -f "$wf.new" "$wf"
}

p_delim () {
	lhs=${rhs%%${1}*}
	rhs=${rhs#*${1}}
}

while true; do
	perframeloop
	printf "[%d]" $icomposed
	pfront="$ffront.ppm"
	prear="$frear.ppm"
	wf="$tmpdir/composed/work$icomposed.ppm"
	tf1="$tmpdir/composed/temp1-$icomposed.ppm"
	tf2="$tmpdir/composed/temp2-$icomposed.ppm"
	djpeg -pnm "$ffront" >"$pfront"
	$no_rear || djpeg -pnm "$frear" >"$prear"
	if $no_rear; then
		ln -f "$pfront" "$wf"
	else
		pnmcat -tb -black "$pfront" "$tmpdir"/border "$prear" >"$wf"
	fi
	for p in $pixelates; do
		rhs=$p
		p_delim -; if [ $icomposed -lt $lhs ]; then continue; fi
		p_delim :; if [ $icomposed -gt $lhs ]; then continue; fi
		p_delim x; p_width=$lhs; p_delim @; p_height=$lhs
		p_delim x; p_left=$lhs; p_delim :; p_top=$lhs
		p_macropixel=$rhs
		pnmcut $p_left $p_top $p_width $p_height <"$wf" >"$tf1"
		pnmscale -reduce $p_macropixel <"$tf1" >"$tf2"
		pnmscale -xsize $p_width -ysize $p_height <"$tf2" >"$tf1"
		pipe_mangle pnmpaste "$tf1" $p_left $p_top
		rm -f -- "$tf"
	done
	if [ "$shrink" ]; then
		pipe_mangle pnmscale -nomix -reduce $shrink
	fi
	cat "$wf" >&3
	cat "$wf" >&4
	rm -f -- "$pfront" "$prear" "$wf"
done

echo '(done-compose)'
exec 3<&-
exec 4<&-
sleep 1
echo

for f in $forked; do
	eval "pid=\$${f}_pid"
	printf "... %s: " $f
	set +e; wait $pid; rc=$?; set -e
	if [ $rc = 0 ]; then
		echo 'ok'
	else
		echo "failed: $rc"
		fail "$f failed"
	fi
done

if $rm_tmpdir; then
	trap '' 0
	rm -rf -- "$tmpdir"
fi

printf '==== completed ok - wrote %s %s ====\n' "$output" "$moutput"
