#!/usr/bin/perl

# (c) G. Finch (salsaman) 2009 - 2015

# released under the GNU GPL 3 or later
# see file COPYING or www.gnu.org for details

#######################################################################
# LiVES ffmpeg plugin v2.10
# v 2.1 - add experimental 3gp support
# v 2.2 - format changed in ffmpeg from image to image2, update
# v 2.3 add youtube and flv formats
# v 2.4 allow encoding of .png images
# v 2.5 add threading support and experimental webm encoding
# v 2.6 made threading support optional because of instability problems; versioning string changed in ffmpeg
# v 2.7 fixed webm encoding support, removed asf format
# v 2.8 always prompt for threads
# v 2.9 check for avconv, change -quality to -qscale
# v 2.10 move -meta options after png/audio
# v 2.11 change fussy order of -pass option
# v 2.12 add h264/aac/mp4, wmv2, ffv1 and mjpeg; show missing libraries
# v 2.13 add qtrel, add quality settings
#######################################################################

if (!defined($standalone)) {
    my $smogrify;
    if ($^O eq "MSWin32") {
	$smogrify="smogrify";
    }
    else {
	$smogrify=`which smogrify`;
	chomp($smogrify);
    }
    if ($smogrify ne "") {
	require $smogrify;
    }
}
else {
    $command=$ARGV[0];
}


if ($command eq "version") {
    print "ffmpeg encoder plugin v2.13\n";
    exit 0;
}


if ($command eq "init") {
    # perform any initialisation needed
    # On error, print error message and exit 1
    # otherwise exit 0
    
    if (&location("ffmpeg") eq "" && &location("avconv") eq "") {
        print "The ffmpeg or avconv binary was not found, please install it and try again !";
	exit 1;
    }

    ##############################
    print "initialised\n";
    exit 0;
}
    
    

if ($command eq "get_capabilities") {
    # return capabilities - this is a bitmap field
    # bit 0 - takes extra parameters (using RFX request)
    # bit 1 - unused
    # bit 2 - can encode png
    # bit 3 - not pure perl
    print "5\n";
    exit 0;
}


if ($command eq "get_formats") {
   # for each format: -
   # return format_name|display_name|audio_types|restrictions|default_extension|

   # audio types are: 0 - cannot encode audio, 1 - can encode using
   #  mp3, 2 - can encode using pcm, 3 - can encode using pcm and mp3
   # 4 - mp2, 8 - vorbis, 16 - AC3, 32 - AAC, 64 - AMR-NB

   # restrictions: 'fps=xx.yy' which
   # means "can only encode at xx.yy frames per second", size=x X y, arate=audio rate 
    # - otherwise set it to 'none'

    $tool="ffmpeg";

    if (&location("ffmpeg") eq "") {
	$tool="avconv";
    }

    $has_mpeg4=(system("$tool -encoders 2>/dev/null | grep \" mpeg4 \" >/dev/null")==0);
    $has_webm=(system("$tool -encoders 2>/dev/null | grep vpx >/dev/null")==0);
    $has_qtrle=(system("$tool -encoders 2>/dev/null | grep qtrle >/dev/null")==0);
    $has_wmv2=(system("$tool -encoders 2>/dev/null | grep wmv2 >/dev/null")==0);
    $has_ffv1=(system("$tool -encoders 2>/dev/null | grep ffv1 >/dev/null")==0);
    $has_mjpeg=(system("$tool -encoders 2>/dev/null | grep mjpeg >/dev/null")==0);
    $has_x264=(system("$tool -encoders 2>/dev/null | grep x264 >/dev/null")==0);
    $has_flv=(system("$tool -encoders 2>/dev/null | grep flv >/dev/null")==0);
    $has_3gp=(system("$tool -encoders 2>/dev/null | grep h263 >/dev/null")==0);

    $has_aac=(system("$tool -encoders 2>/dev/null | grep aac >/dev/null")==0);
    $has_vorbis=(system("$tool -encoders 2>/dev/null | grep vorbis >/dev/null")==0);
    $has_amr=(system("$tool -encoders 2>/dev/null | grep amr >/dev/null")==0);
    $has_mp3=(system("$tool -encoders 2>/dev/null | grep mp3 >/dev/null")==0);
    $has_mp2=(system("$tool -encoders 2>/dev/null | grep mp2 >/dev/null")==0);
    $has_wmav2=(system("$tool -encoders 2>/dev/null | grep wmav2 >/dev/null")==0);

    $errwebm=$errx264=$errflv=$err3gp=$errwmv2=$errffv1=$errmjpeg=$errqtrle=$errmpeg4="";

    $erraac=$errvorbis=$erramraac=$errmp3aac=$errmp3mp2=$errwmav2="";
    
    unless ($has_mpeg4) {
	$errmpeg4=" (MISSING mpeg4 support) ";
    }

    unless ($has_webm) {
	$errwebm=" (MISSING libvxp support) ";
    }

    unless ($has_wmv2) {
	$errwmv2=" (MISSING wmv2 support) ";
    }

    unless ($has_ffv1) {
	$errffv1=" (MISSING ffv1 support) ";
    }

    unless ($has_mjpeg) {
	$errmjpeg=" (MISSING mjpeg support) ";
    }

    unless ($has_x264) {
	$errx264=" (MISSING libx264 support) ";
    }

    unless ($has_flv) {
	$errflv=" (MISSING flv support) ";
    }

    unless ($has_qtrle) {
	$errflv=" (MISSING QuickTime support) ";
    }

    unless ($has_3gp) {
	$err3gp=" (MISSING h263 support) ";
    }

    unless ($has_aac) {
	$erraac=" (MISSING aac support) ";
    }

    unless ($has_vorbis) {
	$errvorbis=" (MISSING libvorbis support) ";
    }

    unless ($has_wmav2) {
	$errwmav2=" (MISSING wmav2 support) ";
    }

    unless ($has_amr || $has_aac) {
	$erramraac=" (MISSING amr or aac support) ";
    }

    unless ($has_mp3 || $has_aac) {
	$errmp3aac=" (MISSING mp3 or aac support) ";
    }
    
    unless ($has_mp3 || $has_mp2) {
	$errmp3mp2=" (MISSING mp3 or mp2 audio support) ";
    }

    
    print "x264|h264/aac/mp4 high quality $errx264$erraac|32|vstep=2,hstep=2|mp4|frame=|\n";

    print "webmh|webm high quality$errwebm$errvorbis|8|none|webm|frame=|\n";
    print "webmm|webm medium quality$errwebm$errvorbis|8|none|webm|frame=|\n";
    print "webml|webm low (stream) quality$errwebm$errvorbis|8|none|webm|frame=|\n";

    print "qtrle|QuickTime animation$errqtrle|0|none|mov|frame=|\n";
    
    print "flv|flv$errflv$errmp3aac|33|arate=44100;22050;11025|flv|frame=|\n";
    print "flv-youtubex|flv (optimised for youtube)$errflv$errmp3aac|33|arate=44100;22050;11025,aspect=1.3333:1|flv|frame=|\n";

    print "divx|divx/avi (25 fps)$errmpeg4$errmp3mp2|5|fps=25.00|avi|frame=|\n";

    print "wmv2|wmv2/wma2/asf (low quality)$errwmv2$errwmav2|256|arate=44100;22050;11025|wmv|frame=|\n";
    print "ffv1|ffv1 (lossless)$errffv1|2|none|avi|frame=|\n";
    print "mjpeg|mjpg$errmjpeg|2|none|avi|frame=|\n";

    print "3gp_h263|3gp (h263)$err3gp$erramraac|96|size=176x144,arate=8000|3gp|frame=|\n";
    print "3gp_mp4|3gp (mp4)$err3gp$erramraac|96|size=176x144,arate=8000|3gp|frame=|\n";
    exit 0;
}


if ($command eq "get_rfx") {
    # nice example of how to give the user a choice of options

    if ($otype eq "flv"||$otype eq "flv-youtube"||$otype eq "flv-youtubex"||$otype eq "divx") {
	$aq=&rc_get("encoder_acodec");
	if ($aq==0) {
	    ## mp3 codec
	    # mandatory section
	    print "<define>\n";
	    print "|1.7\n";
	    print "</define>\n";
	    
	    # mandatory section
	    print "<language_code>\n";
	    print "0xF0\n";
	    print "</language_code>\n";
	    
	    # optional section
	    print "<params>\n";
	    print "threads|Number of threads|num0|1|1|16|\n";
	    if ($otype eq "divx") {
		print "quality|Video Quality (1 = Best quality, largest file, 31 = Lowest quality, smallest file)|num0|1|3|31|\n";
		print "quality|Audio Quality (1 = Best quality, largest file, 31 = Lowest quality, smallest file)|num0|1|4|31|\n";
	    }
	    print "mp3|Use _mp3 audio codec|bool|1|1\n";
	    print "mp3lame|Use mp3_lame audio codec|bool|0|1\n";
	    print "</params>\n";

	    print "<param_window>\n";
	    print "layout|p0|\n";
	    print "</param_window>\n";
	}
	else {
	    # mandatory section
	    print "<define>\n";
	    print "|1.7\n";
	    print "</define>\n";
	    
	    # mandatory section
	    print "<language_code>\n";
	    print "0xF0\n";
	    print "</language_code>\n";
	    
	    # optional section
	    print "<params>\n";
	    print "threads|Number of threads|num0|1|1|16|\n";
	    if ($otype eq "divx") {
		print "quality|Video Quality (1 = Best quality, largest file, 31 = Lowest quality, smallest file)|num0|1|3|31|\n";
		print "quality|Audio Quality (1 = Best quality, largest file, 31 = Lowest quality, smallest file)|num0|1|4|31|\n";
	    }
	    print "</params>\n";

	    print "<param_window>\n";
	    print "layout|p0|\n";
	    print "</param_window>\n";
	}
    }
    else {
	# mandatory section
	print "<define>\n";
	print "|1.7\n";
	print "</define>\n";
	
	# mandatory section
	print "<language_code>\n";
	print "0xF0\n";
	print "</language_code>\n";
	
	# optional section
	print "<params>\n";
	
	print "threads|Number of threads|num0|1|1|16|\n";

	if ($otype eq "x264" || $otype eq "qtrle") {
	    print "quality|Quality (1 = Best quality, largest file, 31 = Lowest quality, smallest file)|num0|1|1|31|\n";
	}
	if ($otype eq "x264") {
	    print "opwidth|Width|num0|$hsize|4|65536|\n";
	    print "opheight|Height|num0|$vsize|4|65536|\n";
	    print "yuv420|Force YUV420P format (unchecking this MAY result in higher quality, but may not play on all players)|bool|1|0\n";
	}
	print "</params>\n";

	print "<param_window>\n";
	print "layout|p0|\n";
	if ($otype eq "x264") {
	    print "layout|\"Override output frame size\"|p2|p3|\n";
	    print "special|aspect|2|3|\n";
	}
	print "</param_window>\n";
    }
}


if ($command eq "encode") {
    # encode

    if ($^O eq "MSWin32") {
	$nulfile="NUL";
	$exe=".exe";
    } else {
	$nulfile="/dev/null";
	$exe="";
    }

    # test first for avconv; otherwise Ubuntu complains
    $encoder_command="avconv$exe";
    system("$encoder_command -version >testfile 2>$nulfile");

    if (-z "testfile") {
	$encoder_command="ffmpeg$exe";
	system("$encoder_command -version >testfile 2>$nulfile");
    }

    $ffver=`grep libavformat testfile | grep -v configuration`;

    $ffver=(split(/\./,$ffver))[0];

    $ffver=(split(" ",$ffver))[-1];

    unlink "testfile";

    $nthreads=$ARGV[14];

    $fwidth = $fheight = 0;
    
    if ($otype eq "x264" || $otype eq "qtrle" || $otype eq "divx") {
	$quality=$ARGV[14];
	if ($otype eq "divx") {
	    $aquality=$ARGV[15];
	    $usemp3=$ARGV[16];
	    $usemp3lame=$ARGV[17];
	}
	elsif ($otype eq "x264") {
	    $fwidth=$ARGV[15];
	    $fheight=$ARGV[16];
	    $force420=$ARGV[17];
	}
    }
    else {
	$usemp3=$ARGV[14];
	$usemp3lame=$ARGV[15];
    }
    
    $vid_length=($end-$start+1)/$fps;

    $err=">/dev/null 2>&1";
    if (defined($DEBUG_ENCODERS)) {
	$err="1>&2";
    }

    if ($otype eq "") {
	$otype="divx";
	&rc_set("output_type",$otype);
    }

    $ncpus=&get_num_procs;

    # default seems to be divx
    $vcodec="";
    
    if ($otype eq "asf") {
	$vcodec="-f asf";
    }
    elsif ($otype eq "3gp_h263") {
	$vcodec="-f h263";
    }
    elsif ($otype eq "3gp_mp4") {
	$vcodec="-f mp4";
    }
    elsif ($otype eq "wmv2") {
	$vcodec="-c:v wmv2";
    }
    elsif ($otype eq "divx") {
	$vcodec="-c:v mpeg4 -vtag divx -q:v $quality";
    }
    elsif ($otype eq "ffv1") {
	if ($img_ext eq ".png") {
	    $format="bgr24";
	}
	else {
	    #jpeg
	    $format="yuvj420p";
	}
	$vcodec="-c:v ffv1 -pix_fmt $format";
    }
    elsif ($otype eq "mjpeg") {
	$vcodec="-c:v mjpeg";
    }
    elsif ($otype eq "flv"||$otype eq "flv-youtubex") {
	$vcodec="-f flv";
    }
    elsif ($otype eq "webmh") {
	$vcodec="-f webm -c:v libvpx -g 60 -qscale 1 -b 1G";
    }
    elsif ($otype eq "webmm") {
	$vcodec="-f webm -c:v libvpx -g 60 -qscale 1 -b 500M";
    }
    elsif ($otype eq "webml") {
	$vcodec="-f webm -c:v libvpx -g 60 -qscale 31";
    }
    elsif ($otype eq "x264") { 
	$vcodec="-c:v libx264 -qscale $quality -s $fwidth"."X$fheight";
	if ($force420) {
	    $vcodec.=" -pix_fmt yuv420p";
	}
    }
    elsif ($otype eq "qtrle") { 
	$vcodec="-c:v qtrle -qscale $quality";
    }

    if ($otype eq "flv-youtubex") {
	$vcodec.=" -b 1024k -bt 256k -maxrate 1024k -minrate 1024k -bufsize 8192k";
    }

    # video stream
    $audio_com="";
    unless ($audiofile eq "") {
	$aq=&rc_get("encoder_acodec");
	$audio_com="-i $audiofile";
	if ($aq==1) {
	    #pcm
	    $audio_com.=" -acodec copy";
	}
	elsif ($aq==5) {
	    $audio_com.=" -acodec aac -strict experimental";
	}
	elsif ($aq==6) {
	    $audio_com.=" -acodec amr_nb";
	}
	elsif ($aq==0) {
	    if ($usemp3lame) {
		$audio_com.=" -acodec libmp3lame";
	    }
	    else {
		$audio_com.=" -acodec mp3";
	    }
	}
	elsif ($aq==3) {
	    $audio_com.=" -acodec libvorbis";
	    if ($otype eq "webmh") {
		$audio_com.=" -aq 10";
	    }
	    elsif ($otype eq "webmm") {
		$audio_com.=" -aq 5";
	    }
	    elsif ($otype eq "webml") {
		$audio_com.=" -aq 1";
	    }
	}
	else {
	    $audio_com.=" -acodec mp2";
	}

	if ($otype eq "flv-youtubex") {
	    $audio_com.=" -ab 128k -ar $arate";
	}
	elsif ($otype eq "flv") {
	    $audio_com.=" -ab 192k -ar $arate";
	}
	elsif ($otype eq "3gp_h263" || $otype eq "3gp_mp4") {
	    $audio_com.=" -ac 1 -ab 12 -ar 8000";
	}
	elsif ($otype eq "wmv2") {
	    $audio_com.=" -acodec wmav2";
	}
	elsif ($otype eq "divx") {
	    $audio_com.=" -q:a $aquality";
	}

    }

    if ($ffver>=52) {
	$metadata="-metadata comment=\"$comment\" -metadata author=\"$author\" -metadata title=\"$title\"";
    } else {
	$metadata="-comment \"$comment\" -author \"$author\" -title \"$title\"";
    }
    
    
    # unfortunately this does not work yet...
    if ($otype eq "theora") {
	$syscom="ffmpeg2theora -o \"$nfile\" -f image2 -i %8d$img_ext $audio_com -d off $err";
    }
    elsif ($otype eq "3gp") {
	for $pass (1,2) {
	    $passf="-pass $pass -passlogfile passfile";
	    $syscom="$encoder_command -strict 1 -y -r $fps -f image2 -i %8d$img_ext $audio_com -t $vid_length $vcodec $metadata $passf \"$nfile\" $err";
		if (defined($DEBUG_ENCODERS)) {
		    print STDERR "ffmpeg_encoder command is: $syscom\n";
		}
	    
	    system ($syscom);
	}
    }
    else {
	if ($nthreads>1) {
	    $threads=" -threads $nthreads";
	}
	else {
	    $threads="";
	}

	if ($otype eq "webmh") {
	    for $pass (1,2) {
		$passf="-pass $pass -passlogfile passfile";
		$syscom="$encoder_command $threads -y -r $fps -f image2 -i %8d$img_ext $audio_com -t $vid_length $vcodec $metadata $passf \"$nfile\" $err";

		if (defined($DEBUG_ENCODERS)) {
		    print STDERR "ffmpeg_encoder command is: $syscom\n";
		}

		system ($syscom);
	    }
	}
	else {
	    $syscom="$encoder_command $threads -y -r $fps -f image2 -i %8d$img_ext $audio_com -t $vid_length $vcodec $metadata \"$nfile\" $err";

	    if (defined($DEBUG_ENCODERS)) {
		print STDERR "ffmpeg_encoder command is: $syscom\n";
	    }
	    
	    system ($syscom);

	}

    }

    &sig_complete;
    exit 0;
}


if ($command eq "clear") {
    # this is called after "encode"
    # note that encoding could have been stopped at any time

    unlink "temp.vid";

    unlink glob "passfile*";
    &sig_complete;
    exit 0;
}


if ($command eq "finalise") {
    # do any finalising code
    
    # end finalising code
    print "finalised\n";
    exit 0;
}

exit 0;


###### subroutines #######

sub get_format_request {
    # return the code for how we would like audio and video delivered
    # this is a bitmap field composed of:
    # bit 0 - unset=raw pcm audio; set=pcm audio with wav header
    # bit 1 - unset=all audio; set=clipped audio
    # bit 2 - unset=all frames; set=frames for selection only

    return 7; # clipped pcm wav, frames start at selection
}


sub get_num_procs {
    my $result=`cat /proc/cpuinfo|grep processor`;

    $result=(split (/\n/,$result))[-1];
    $result=(split (/: /,$result))[1];
    return ++$result;
}


sub location {
    # return the location of an executable
    my ($command)=shift;

    if ($^O eq "MSWin32") {
	return "$command.exe";
    }

    my ($location)=smog_system_direct("which \"$command\" 2>$nulfile");
    chomp($location);

    $location;
}
