#!/bin/bash

function out() {
	echo -en "$1"
	echo -n "$2: "
	echo -en "\x1b[m"
}

function err() { (
	out "\x1b[01;31m" "error" # bold red
	echo "$*"
	) >&2
	exit 1
}

function info() {
	out "\x1b[01;32m" "info"; # bold green
	echo "$*"
}

function warn() { (
	out "\x1b[01;33m" "warn"; # bold yellow
	echo "$*"
	) >&2
}

function ggt() {
	local a=$1; local b=$2;
	while [ $a -ne $b ]; do
		if [ $a -gt $b ]; then a=$((a-b)); else b=$((b-a)); fi
	done
	return $a
}

# $1: name of fifo this coprocess will read data from
# $*: 
#function coproc_read() {
#	fifo="$1"; shift
#	mkfifo "$fifo"
#	(bash -c "< $fifo $*"; r=$?; rm "$fifo"; exit $r) &
#}

me="$0";
in="$1";
tmp="$2";
cropleft=0
cropright=0
croptop=0
cropbottom=0
crf=23
audb=-144
audq=-4
stage=-1
track=0
y4mfilter=""
vidffargs=""
x264args=""
verbose=1
palette="default"

chapter_lang=
title=
out_path=

usage="\
usage: $me <in-dvd.img> <tmp-path> [<args ...>]
  -t <title>          DVD title to encode, default: longest
  -k <chapter>        DVD chapter of selected title to encode, default: -1 (all)
  -A \"<a b c d ...>\"  audio track indices to extract, default: all
  -S \"<a b c d ...>\"  subtitle track indices to extract, default: all
  -C <l> <r> <t> <b>  cropping
  -c <crf>            CRF-value for x264, smaller: better, default: 23
  -b <kbit-rate>      Ogg Vorbis bitrate, default: 144
  -q <quality>        Ogg Vorbis quality, default: 4
  -f <ffmpeg-args>    additional FFmpeg video filter arguments,
                      e.g. '-deinterlace'
  -x <x264-args>      additional arguments for x264-encoding,
                      e.g. '--tune <type>' with <type> being a combination of:
                      film, animation, grain, stillimage, fastdecode, zerolatency
  -y <y4m-filter>     yuv4mpeg filter, pipe into command
  -s <stage>           1: chapters
                       2: extract audio
                       4: extract subtitles
                       8: process subtitles
                      16: encode audio files
                      32: encode video
                      64: save output file
  -P <palette>        override custom subtitle palette extracted from DVD IFO
                      (16 comma-separated hex values) or empty string for default
  -v                  display video during encoding
  -T <title>          human readable title of the movie for Matroska container
                      (needed for unattended output)
  -o <output-path>    path to filename to save encoded Matroska video
                      (needed for unattended output)
  -L <language>       language the chapter descriptions are provided in
                      (needed for unattended output iff (<stage> & 1) == 0,
                       otherwise 'eng' is assumed)"

if ! which lsdvd &>/dev/null; then err "lsdvd not found; check package http://untrepid.com/lsdvd/"; fi
if ! which dvdxchap &>/dev/null; then err "dvdxchap not found; check package http://www.bunkus.org/videotools/ogmtools/"; fi
if ! which ffmpeg &>/dev/null; then err "ffmpeg not found; check http://ffmpeg.org/"; fi
if ! which tccat &>/dev/null; then err "transcode not found; check http://tcforge.berlios.de/"; fi
if ! which subtitle2vobsub &>/dev/null; then err "subtitle2vobsub not found; check http://subtitleripper.sourceforge.net/"; fi
if ! which oggenc &>/dev/null; then err "oggenc not found; check http://www.vorbis.com/"; fi
if ! which x264 &>/dev/null; then err "x264 not found; check package http://www.videolan.org/developers/x264.html"; fi
if ! which bc &>/dev/null; then err "bc not found; check package http://www.gnu.org/software/bc/bc.html"; fi
if ! which dtsdec &>/dev/null; then err "dtsdec not found; check package http://www.videolan.org/developers/libdca.html"; fi
if ! which mkvmerge &>/dev/null; then warn "mkvmerge not installed; check http://www.bunkus.org/videotools/mkvtoolnix"; fi
if ! which yuvplay &>/dev/null; then warn "yuvplay not installed, preview during encoding won't work; check http://mjpeg.sourceforge.net/"; fi

if [ ! -e "$in" -o ! -d "$tmp" ]; then
	err "$usage"
fi
shift
shift

atracks="all"
stracks="all"
chap="-1"

while [ "x$1" != "x" ]; do
	case "$1" in
	"-t")
		track=$2; shift
		;;
	"-k")
		chap=$2; shift
		;;
	"-A")
		atracks="$2"; shift
		;;
	"-S")
		stracks="$2"; shift
		;;
	"-C")
		cropleft=$2; cropright=$3; croptop=$4; cropbottom=$5; shift; shift; shift; shift
		;;
	"-c")
		crf=$2; shift
		;;
	"-b")
		audb=$2; shift
		;;
	"-q")
		audq=$2; shift
		;;
	"-f")
		vidffargs="$2"; shift
		;;
	"-x")
		x264args="$2"; shift
		;;
	"-y")
		y4mfilter="$2"; shift
		;;
	"-s")
		stage=$2; shift
		;;
	"-P")
		palette="$2"; shift
		;;
	"-v")
		verbose=$((verbose+1))
		;;
	"-T")
		title="$2"; shift
		;;
	"-o")
		out_path="$2"; shift
		;;
	"-L")
		chapter_lang="$2"; shift
		;;
	*)
		err "$usage"
		;;
	esac
	shift
done

if [ -z "$title" -o -z "$out_path" ]; then
	info "ATTENDED mode, mkvmerge command template will be given at the end"
	stage=$((stage & ~64))	# no automatic assembly
	title="<title>"		# just informative
	chapter_lang="<lang>"	# just informative
	out_path="<out.mkv>"	# just informative
elif [ $((stage & 64)) -ne 0 ]; then
	info "UNATTENDED mode, output will be saved to '$out_path'"
else
	info "UNATTENDED mode but (stage & 64) == 0, so output file won't be generated"
fi

# search default track
if [ $track -eq 0 ]; then
	track=$(lsdvd "$in")
	if [ $? -ne 0 ]; then err "lsdvd failed"; fi
	track=$(echo "$track" | grep Longest | awk '{ print $3+0 }')
fi

# set default audio encoding style (VBR or quality based)
if [ $audb -le 0 -a $audq -le 0 ]; then
	audq=$((-audq))
fi

# 21; 3: lang-idx, 5: lang",", 7: format, 9: freq, 11: "drc", "16bit", 13: channels, 20: id
audio=( $(lsdvd -t $track -a "$in" | grep "Audio:") )
if [ "$atracks" = "all" ]; then
	naud=$((${#audio[@]}/21))
	atracks=$(seq 0 $((naud-1)))
else
	naud=0
	for idx in $atracks; do naud=$((naud+1)); done
fi
# 11; 3: lang-idx, 5: lang",", 10: id
subs=( $(lsdvd -t $track -s "$in" | grep "Subtitle:") )
if [ "$stracks" = "all" ]; then
	nsub=$((${#subs[@]}/11))
	stracks=$(seq 0 $((nsub-1)))
else
	nsub=0
	for idx in $stracks; do nsub=$((nsub+1)); done
fi

pal=$(lsdvd -t $track -P "$in" | grep Palette: | cut -d: -f2 | xargs | sed 's/ /,/g')
if [ $chap -eq -1 ]; then
	pattern="^Title:"
else
	pattern="Chapter: 0*$chap"
fi
len=$(lsdvd -xt $track "$in" | grep -E "$pattern" | sed 's/^.*Length: \(..\):\(..\):\([0-9.]*\)[^0-9.].*$/(\1*60+\2)*60+\3/' | bc)
# 17; 5: fps, 7: PAL/NTSC, 10: aspect ratio, 12: width, 14: height, display format: 16 (Letterbox)
mov=( $(lsdvd -t $track -v "$in" | grep VTS:) )
frames=$(echo $len*${mov[5]/,/} | bc | cut -d. -f1)
movw=${mov[12]/,/}
movh=${mov[14]/,/}

# pixel aspect ratio
movas=${mov[10]/,/}			# display aspect
movdw=$(echo ${movh}*${movas} | bc)	# display width
if [ $((movdw & 1)) -eq 1 ]; then movdw=$((movdw+1)); fi
ggt $movdw $movw
k=$?
movpx0=$((movdw/k));			# nominator of pixel aspect ratio
movpx1=$((movw/k));			# denominator of pixel aspect ratio
# cropped stuff
movnh=$((movh-croptop-cropbottom))
movnw=$((movw-cropleft-cropright))
movdnw=$((movnw*movpx0/movpx1))		# display width of cropped viech
ggt $movdnw $movnh
k=$?
movdnas="$((movdnw/k)):$((movnh/k))"	# cropped display aspect ratio
info "track  : ${track}, frames : ${frames}, len: $len sec"
info "org    : w: $movw, h: $movh;     display: w: $movdw, h: $movh, aspect: $movas, pixel aspect: $movpx0:$movpx1"
info "cropped: w: $movnw, h: $movnh; new display: w: $movdnw, h: $movnh, aspect: $movdnas"
if [ $((movnw&15)) -ne 0 ]; then warn "width not divisible by 16; this leads to poorer compression"; fi
if [ $((movnh&15)) -ne 0 ]; then warn "height not divisible by 16; this leads to poorer compression"; fi

if [ "$palette" != default ]; then
	pal="$palette"
fi

if test $chap -eq -1 -a $((stage & 1)) -ne 0; then
	info "extracting chapter information"
	dvdxchap -t $track "$in" > $tmp/chapters
	if [ $? -ne 0 ]; then err "dvdxchap failed"; fi
	if [ $((stage & 64)) -ne 0 -a -z "$chapter_lang" ]; then
		chapter_lang=eng
	fi
fi

if [ $((stage & 6)) -ne 0 ]; then
	cmd="tccat -T $track,$chap -i \"$in\" 2>/dev/null | tee"
fi
if [ $((stage & 2)) -ne 0 -a $naud -gt 0 ]; then
	info "extracting $naud audio-tracks from title $track:"
	echo -en "\t"
	for idx in $atracks; do
		fmt=${audio[idx*21+7]/,/}
		if [ $fmt = "lpcm" ]; then fmt=pcm; fi
		echo -en " ${audio[$idx*21+3]} ($fmt @${audio[$idx*21+20]}, ${audio[idx*21+13]/,/} chans)"
		cmd+=" >(tcextract -t vob -x $fmt -a $idx | tee $tmp/a$idx.$fmt | "
		if [ $fmt = "dts" ]; then
			cmd+="dtsdec -r -o wav | ffmpeg -i - -f s16le - 2>/dev/null"
		elif [ $fmt = "pcm" ]; then
			cmd+="ffmpeg -f s16le -ac ${audio[idx*21+13]/,/} -ar ${audio[idx*21+9]/,/} -i - -ac 2 -ar 48000 -f s16le - 2>/dev/null"
		else
			cmd+="tcdecode -x $fmt -y pcm"
		fi
		cmd+=" | tcscan -x pcm | grep 'volume rescale' | cut -d= -f3 > $tmp/a$idx.rescale)"
	done
	echo
fi
if [ $((stage & 4)) -ne 0 -a $nsub -gt 0 ]; then
	info "extracting $nsub subtitle-tracks from title $track:"
	echo -en "\t"
	for idx in $stracks; do
		lang=${subs[$idx*11+3]}
		id=${subs[$idx*11+10]/,/}
		echo -en " $lang ($id)"
		cmd+=" >(tcextract -t vob -x ps1 -a $id > $tmp/sub.$idx)"
	done
	echo
fi
if [ $((stage & 6)) -ne 0 -a $((naud+nsub)) -gt 0 ]; then
	cmd+=" >/dev/null"
	info "running '$cmd'"
	bash -c "$cmd"
fi

function have_lang2() {
	mkvmerge --list-languages | cut -d'|' -f3 | grep -q $1
}

if [ $((stage & 8)) -ne 0 ]; then
	info "processing subtitles into vobsub"
	if [ ${#subs[@]} -gt 0 -a "${subs[3]}" != "en" ] && have_lang2 ${subs[3]}; then
		warn "please check that the first track's language in $tmp/vobsub.idx is '${subs[3]}'"
	fi
	rm -f "$tmp"/vobsub.{idx,sub}
	info "pal: $pal"
	for idx in $stracks; do
		lang=${subs[$idx*11+3]}
		if have_lang2 $lang; then lang=,$lang; else lang=; fi
		cmd="subtitle2vobsub -p $tmp/sub.$idx -o $tmp/vobsub -s $movw,$movh"
		if [ -n "$pal" ]; then
			cmd+=" -c $pal"
		fi
		cmd+=" -a ${idx}${lang} 2>/dev/null"
		info "running '$cmd'"
		LC_ALL=C bash -c "$cmd"
	done
fi

# circumvent problem in bash which at this point has not yet written the result
# into a0.rescale (der_maulwurf1.img, -t 1)
sync

if [ $((stage & 16)) -ne 0 ]; then
	info "encoding $naud audio tracks..."
	for idx in $atracks; do
		fmt=${audio[idx*21+7]/,/}
		if [ $fmt = "lpcm" ]; then fmt=pcm; fi
		if [ ! -s $tmp/a$idx.$fmt ]; then
			warn "skipping audio track $idx, doesn't have any data..."
			continue
		fi
		freq=${audio[idx*21+9]/,/}
		vol=$(echo 256*$(cat $tmp/a$idx.rescale) | bc | cut -d. -f1)
		nch=${audio[idx*21+13]/,/}
		nch=$((nch>1 ? 2 : 1))
		tb=$(tcprobe -R -i $tmp/a$idx.$fmt)
		if [ $? -ne 0 ]; then
			tb=$(tcprobe -M -R -i $tmp/a$idx.$fmt);
			if [ $? -ne 0 ]; then tb=""; fi
		fi
		tbc=$audb
		if [ "$tb" ]; then
			tb=$(echo "$tb" | grep ID_AUDIO_BITRATE | cut -d= -f2)
			if [ "$tb" -lt "$audb" ]; then
				info "adjusting audio bitrate from $audb to $tb for track $idx as input bitrate is lower"
				tbc=$tb
			fi
		else
			warn "failed to probe $tmp/a$idx.$fmt for bitrate, maybe empty or corrupt?"
		fi
		if [ $fmt = "dts" ]; then
			cmd="dtsdec -r -o wav $tmp/a$idx.$fmt | ffmpeg -i -"
		elif [ $fmt = "pcm" ]; then
			cmd="ffmpeg -f s16le -ac ${audio[idx*21+13]/,/} -ar $freq -i $tmp/a$idx.$fmt"
		else
			cmd="tcdecode -i $tmp/a$idx.$fmt -x $fmt -y pcm 2>/dev/null | ffmpeg -ac 2 -ar $freq -f s16le -i -"
		fi
		cmd+=" -vol $vol -ac $nch -f s16le - 2>/dev/null | oggenc -r -R $freq"
		if [ $tbc -gt 0 ]; then
			cmd+=" -b $tbc"
		else
			cmd+=" -q $audq"
		fi
		cmd+=" -C $nch -o $tmp/a$idx.ogg -"
		info "running '$cmd'"
		bash -c "$cmd" &
	done
	wait
	info ""
fi

if [ $((stage & 32)) -ne 0 ]; then
	cmd="tccat -T $track,$chap -i \"$in\" 2>/dev/null"
	cmd+=" | ffmpeg -i - -an -sn"
	if [ $cropleft -gt 0 -o $cropright -gt 0 -o $croptop -gt 0 -o $cropbottom -gt 0 ]; then
		cmd+=" -vf crop=$movnw:$movnh:$cropleft:$croptop"
	fi
	# cmd+=" -aspect $movdnas"
	cmd+=" $vidffargs -f yuv4mpegpipe - 2>/dev/null"
	if [ -n "$y4mfilter" ]; then
		cmd+=" | $y4mfilter"
	fi
	if [ $verbose -gt 1 ]; then
		cmd+=" | tee >(yuvplay -c -v 0)"
	fi
	cmd+=" | x264 --crf $crf -b 5 --b-adapt 2 --b-pyramid normal -r 8 --direct auto "
	cmd+=" --me umh --merange 24 -m 10 -t 2 --qpstep 6"
	#cmd+=" | x264 --crf $crf -b 8 --b-adapt 2 --b-pyramid normal -r 16 --direct auto --rc-lookahead 60 --qpstep 6 -A all"
	#cmd+=" --me tesa --merange 32 -m 10 -t 2"
	cmd+=" --non-deterministic --no-interlaced $x264args --frames $frames --sar $movpx0:$movpx1 -o $tmp/out264.mkv --demuxer y4m -"
	cmd+=" |& tee -i >(sed 's/^.*\r\([^\r]*\)$/\1/' > $tmp/x264.log)"

	info "done so far, encoding video with $frames frames:"
	info "'$cmd'"
	bash -c "$cmd"
	# otherwise x264.log won't be written; same error as above :/
	sync
fi


cmd="mkvmerge -o '$out_path' --title '$title'"
if [ $chap -eq -1 ]; then
	cmd+=" --chapter-language '$chapter_lang' --chapters $tmp/chapters"
fi
cmd+=" $tmp/out264.mkv"
for idx in $atracks; do
	if [ -e $tmp/a$idx.ogg ]; then
		cmd+=" --language 0:${audio[$idx*21+3]/,/} $tmp/a$idx.ogg"
	fi
done
if [ $nsub -gt 0 ]; then
	if [ -f "$tmp"/vobsub.idx ]; then
		for idx in $stracks; do
			cmd+=" --default-track $idx:0"
		done
		cmd+=" $tmp/vobsub.idx"
	else
		warn "no vobsub file found, although $nsub subtitles were requested"
	fi
fi
if [ -f "$tmp"/x264.log ]; then
	cmd+=" --attachment-description \"x264 encode log\" --attachment-name x264.log --attachment-mime-type text/plain --attach-file-once \"$tmp\"/x264.log"
fi

if [ $((stage & 64)) -ne 0 ]; then
	info "'$cmd'"
	sleep .5
	sync
	bash -c "$cmd"
else
	info "use the following command to assemble it all into a mkv-file"
	info "  '$cmd'"
fi
