#!/usr/bin/perl
############################################################################
# Credits
#
# nathan jahnke <njahnke@gmail.com>
# Ian Bennett
# Philip "ballofsnow" Cornell
# Brett "Psonar" Ables
#
# Anri is distributed under the terms of the Gnu General Public License 3.0:
# http://www.gnu.org/licenses/gpl-3.0.txt
############################################################################
############################################################################
# Initialization
#
# Some obvious stuff in here. Desktop location is set using the win32api
# module under Windows.
############################################################################
use warnings;
use strict 'subs';
package anri;
#version of this software, used in building the path to the executable, so must match the value from the installer
$version = '4a1';
#name of this file
$anripl = 'anri.pl';
#cute prompt for user input
$prompt = 'ANRI> ';
#what os are we running? unix is default since there are so many possible flavors
$os = 'unix';
$os = 'windows' if "$^O" eq "MSWin32";
if ($os eq 'windows') {
#the below api calls probably require the win32api module(?) to be installed which will have to be included in the anri installer ...
#get the name of the work directory. this is the desktop by default under windows
use Win32 qw(CSIDL_DESKTOPDIRECTORY);
$desktop = Win32::GetFolderPath(CSIDL_DESKTOPDIRECTORY);
#get the path to program files so we can build the anri program directory
use Win32 qw(CSIDL_PROGRAM_FILES);
$programfiles = Win32::GetFolderPath(CSIDL_PROGRAM_FILES);
#anri program directory
$anri_dir = "${programfiles}/anri_${version}";
#name of the directory containing dgindex.exe under anri's program directory and of the executable itself
$dgmpgdec_dir = "${anri_dir}/dgmpgdec152";
$dgmpgdec = 'dgmpgdec.exe';
#name of the directory containing virtualdub.exe under anri's program directory and of the executables both gui and cli
$vdub_dir = "${anri_dir}/VirtualDub-1.8.5";
$vdubgui = 'virtualdub.exe';
$vdubcli = 'vdub.exe';
#our encoders
$x264 = 'x264.exe';
$naac = 'neroaacenc.exe';
$mp4box = 'mp4box.exe';
#some anri support files
$directshowsourcelistfile = 'directshowsource_list.txt';
#set some keys in the registry to make vdub show only the input video pane (having both the input and output panes open as is default tends to confuse users)
&update_registry_key("CUser/Software/Freeware/VirtualDub/Persistence/Update input pane", "0x0001", "REG_DWORD");
&update_registry_key("CUser/Software/Freeware/VirtualDub/Persistence/Update output pane", "0x0000", "REG_DWORD");
#os-specific commands
$clear = "CLS";
#or, for debugging ...
$clear = "";
############################################################################
# Color Initialization
#
# Edit by: Psonar - Brett Ables
# Below is the code necessary to use the CECHO.exe function
# to add color to anrichan. Run CECHO.exe /? for usage help.
# CECHO.exe was written by Thomas Polaert.
#
# The environment variable CECHO is set to the absolute path
# of the CECHO.exe program so that %CECHO% may be used
# to call the function regardless of the working directory
#
# RESET_COLOR is used to reset the default color scheme
# after CECHO has been used to output different colors.
#
# Color is a DOS function that affects the whole command
# window at once. It is used only once here to initialize the
# background color of the window. 80 is the same as {black on gray}.
#
# CECHO is used in out_cls, out_info, out_error, and out_section.
############################################################################
$cecho = "${anri_dir}/cecho.exe";
$black_on_gray = '{black on gray}';
$gray_on_black = '{gray on black}';
$blue_on_black = '{blue on black}';
$white_on_black = '{white on black}';
$aqua_on_black = '{aqua on black}';
$blue_on_black = '{blue on black}';
$teal_on_gray = '{teal on gray}';
$navy_on_gray = '{navy on gray}';
$maroon_on_gray = '{maroon on gray}';
$maroon_on_silver = '{maroon on silver}';
$white_on_gray = '{white on gray}';
$red_on_gray = '{red on gray}';
$reset_color = "\"${cecho}\" ${black_on_gray}";
system("Color 80");
} else {
#FIXME UNIX DESKTOP
#color initialization
#os-specific commands
$clear = "clear";
}
#FIXME LOGFILE STUFF
#FIXME EXTERNAL CONFIGURATION FILE
#main menu
#TODO: using_settings
############################################################################
# Main menu
#
# Anri's home.
############################################################################
&out_cls;
&out_info("ON HERMESUS LUC ARSUS ESTARIAS AUC ELTRAS LI CELES! Now, let's get to work!");
&out_section("Main");
@mainmenu = (
['Rip DVD to hard drive (required for DVD material)', '&rip_dvd'],
['Start new project', '&project'],
['Extract sample', '&proc_sample'],
['Station ID preview', '&statid_sample'],
['Exit', 'exit 0;'],
);
&out_menu(@mainmenu);
while (<STDIN>) {
chomp;
if (m/^([1-5])$/) {
#execute the contents of this value in the array
eval($mainmenu[$1-1][1]);
last;
}
print "Invalid input.\n\n";
&out_menu(@mainmenu);
}
############################################################################
# Sample extraction procedure
#
# Sample extraction is basically regular mode minus a bunch of questions.
# This procedure gets called when a parameter "sample" has been sent to
# anri.bat. There isn't much documentation about how this works since this
# was meant to be short. One thing that's different is that the sample video
# will have separated fields. This is to avoid the problem with interlacing
# and the Yv12 colorspace. The fields are then weaved back together to
# determine DFnD.
############################################################################
sub proc_sample {
# &out_cls;
chdir($desktop);
#FIXME no_pause_before_indexing?
$projname = 'sample';
&make_projdir;
&load_plugins;
&out_cls_section("Sample Extraction");
&echo('DVD/MPEG source [y,n]');
print $prompt;
while (<STDIN>) {
chomp;
if (m/^[yn]$/) {
#y becomes 1 (true), n becomes 0 (false)
tr/yn/10/;
$_ ? &index_dvd : &q_avi;
last;
}
print "Invalid input.\n\n";
print $prompt;
}
&cp("${projname}.avs","${projname}_trimtemp.avs");
&echotofile("lanczos4resize(320,240)\n",">>","${projname}_trimtemp.avs");
&out_info("This sample will be 300 frames long. Pick the starting frame of a scene with");
&out_info("action.");
&echo("");
&out_info("Press ENTER to continue ...");
<STDIN>;
if ($os eq 'windows') {
system("START \"trimming vdub\" \"${vdub_dir}/${vdubgui}\" ${projname}_trimtemp.avs");
} else {
#how are we going to do this in unix? your guess is as good as mine
}
print $prompt;
while (<STDIN>) {
chomp;
if (m/^[0-9]+$/) {
$startframe = $_;
last;
}
print "Invalid input.\n\n";
print $prompt;
}
&echotofile("trim(${startframe},".($startframe+299).")\n",">>","${projname}.avs");
&echotofile("AssumeTFF\nSeparateFields\nconverttoyv12\n",">>","${projname}.avs");
&onepass("${desktop}/${projname}/${projname}.avs","${desktop}/${projname}/${projname}","128000","19",1);
&cp("${desktop}/${projname}/${projname}.mp4","${desktop}");
# @todel = (
# "${projname}*.avs",
# "${projname}.bak",
# "${projname}*.d2v",
# "${projname}VTS*",
# "log*.txt",
# );
chdir("${desktop}");
&rm("${desktop}/${projname}");
&echo("\n\n${white_on_gray}Finished. You will find a sample.mp4 file on your desktop. Feel free to rename it something more descriptive like nameofgame_sample.mp4.");
&echo("\n${red_on_gray} - READ -${white_on_gray} You will see that the video has been resized vertically.");
&echo("${red_on_gray}THIS IS NORMAL${white_on_gray} and will make it easier for the techies to help you.");
system($reset_color);
<STDIN>;
exit 0;
}
############################################################################
# File Handling
#
# Handle files in project directory.
############################################################################
sub make_projdir {
chdir($desktop);
mkdir($projname);# if !-e $projname;
chdir($projname);
&cp("${anri_dir}/silence_stereo_48000.wav","${desktop}/${projname}");
&cp("${dgmpgdec_dir}/infotemplate.avs","${desktop}/${projname}");
&cp("${anri_dir}/ntsc_d1.png","${desktop}/${projname}");
}
sub wipe_avs {
chdir("${desktop}/${projname}");
&rm("*.avs") if -e "${projname}.avs";
&cp("${projname}.bak","${projname}.avs");
&cp("plugins.bak","plugins.avs");
}
#load_plugins makes a file plugins.avs in the project directory that contains the proper loadplugin() or import() declarations for every plugin (defined as a file ending in .dll or .avs) found under $anri_dir/plugins.
sub load_plugins {
&rm("plugins.avs") if -e "plugins.avs";
opendir(PLUGINS, "${anri_dir}/plugins");
@plugins = readdir(PLUGINS);
closedir(PLUGINS);
for (@plugins) {
if (m/.*\.([dll]+)$/i) {
&echotofile("loadplugin(\"${anri_dir}/plugins/${_}\")\n",">>","plugins.avs");
} elsif (m/.*\.([avs]+)$/i) {
&echotofile("import(\"${anri_dir}/plugins/${_}\")\n",">>","plugins.avs");
}
}
&cp("plugins.avs","plugins.bak");
}
############################################################################
# Windows
#
# These subroutines make use of the win32api module.
############################################################################
#fullpathtokey, data, type
sub update_registry_key {
use Win32::TieRegistry(Delimiter=>"/",ArrayValues=>0);
$Registry->{$_[0]} = [ $_[1], $_[2] ];
}
############################################################################
# System
#
# These subroutines send OS-dependent shell commands.
############################################################################
#string, > or >>, filepath
#not much here yet but may need to modify in the future
sub echotofile {
open(OUTFILE, "${_[1]}${_[2]}");
print OUTFILE $_[0];
close(OUTFILE);
}
#path
#/D means change even if cwd is on a different drive letter
# sub cd {
# $flags = '';
# $flags = ' /D' if $os eq 'windows';
#
# #for debugging
# $command = "cd${flags} \"${_[0]}\"";
# print "$command\n";
# system($command);
# }
#path, newpath (both can be relative)
#/Y means overwrite (i.e. automatically say Yes to the overwrite question)
#/B means force binary copy (do not attempt to translate line endings)
#-R means recursive (copy directories and the files in them as well as just files)
sub cp {
if ($os eq 'windows') {
for (@_) {
#COPY gives me errors unless the path delimiter is \ under doze
s,/,\\,g if m,/,;
}
}
$command = 'cp';
$command = 'COPY' if $os eq 'windows';
$flags = ' -R';
$flags = ' /Y /B' if $os eq 'windows';
$redirect = '';
$redirect = ' > NUL' if $os eq 'windows';
$command = "${command}${flags} \"${_[0]}\" \"${_[1]}\"${redirect}";
#for debugging
# print $command."\n";
system($command);
}
#path
#-r means recursive (delete directories and the files in them as well as just files)
#-f means force (don't ask for confirmation, just do it - may be necessary on some red hat linux installs)
#/Q means don't ask for confirmation
#/S means recursive (delete directories and the files in them as well as just files)
sub rm {
if ($os eq 'windows') {
for (@_) {
#COPY gives me errors unless the path delimiter is \ under doze
s,/,\\,g if m,/,;
}
}
$command = 'rm';
$command = 'DEL' if $os eq 'windows';
$flags = ' -rf';
$flags = ' /Q /F' if $os eq 'windows';
for (@_) {
#is this a directory?
if (-d "$_") {
$command = 'RD' if $os eq 'windows';
$flags = ' /Q /S' if $os eq 'windows';
}
system("${command}${flags} \"${_}\"");
}
}
#lol it says subtitle - set the console's title
sub title {
if ($os eq 'windows') {
system('TITLE '.$_[0]);
} else {
system('declare -x PROMPT_COMMAND=\'printf "\e]0;'.$_[0].'\a"\'');
}
}
############################################################################
# Input
#
# These subroutines get data from the user.
############################################################################
############################################################################
# function: q_avi
#
# Ask the user if he wants to do manual input of file paths, or automatically
# by loading the files in a specified directory alphabetically.
############################################################################
sub q_avi {
$maxaudiobitrate = 320000;
&out_cls_section("AVI SOURCE");
&out_info("Enter the path to each avi file [i]ndividually or [a]utomatically. Note that if you choose automatic, Anri will load the files in an alphabetical manner. The files must have the same resolution and framerate to be joined together.");
&echo("");
print $prompt;
while (<STDIN>) {
chomp;
if (m/^([ia])$/) {
#i becomes 1 (true), a becomes 0 (false)
tr/ia/10/;
$_ ? &q_avi_i : &q_avi_a;
last;
}
print "Invalid input.\n\n";
print $prompt;
}
#IF "%auto%"=="y" GOTO avimethod_skip
#:avimethod_skip
#AVI files loaded, generate avs source lines.
&echotofile("import(\"${anri_dir}/sda.avs\")\n",">>","${projname}.avs");
&echotofile("import(\"${desktop}/${projname}/plugins.avs\")\n",">>","${projname}.avs");
$alignedsplice = '';
for $avifile (@avifiles) {
&echotofile($alignedsplice,">>","${projname}.avs");
if ($os eq 'windows') {
@fourccstuff = qx("${anri_dir}/cfourcc.exe" "${avifile}");
$fourcc = join('',@fourccstuff);
#"${anri_dir}/cfourcc.exe" "%avifolder%%%~G" >> running_log.txt
$fourcc =~ s/.*Use : (....).*/$1/s;
open(DSHOW, "${anri_dir}/${directshowsourcelistfile}");
@directshowsourcelist=<DSHOW>;
close(DSHOW);
for (@directshowsourcelist) {
chomp;
$use_dshow{lc($_)} = 1;
}
$sourcedecverb = 'avisource';
$sourcedecverb = 'directshowsource' if $use_dshow{lc($fourcc)};
&echotofile("${sourcedecverb}(\"${avifile}\")",">>","${projname}.avs");
$alignedsplice = "++\\\n";
} else {
#unix avi source declaration stuff goes here?
}
}
#newline needed
&echotofile("\nconverttoyuy2()\n",">>","${projname}.avs");
&cp("${projname}.avs","${projname}.bak");
}
############################################################################
# function: q_avi_i
#
# Manually add each file path to @avifiles. Do some validation; no
# resolution check yet.
############################################################################
sub q_avi_i {
&echo("");
&out_info('Path to source video file e.g. c:\path to\video.avi without quotes.');
&out_info(" - Type n to quit -");
&echo("");
#:q_avi_i_p2
print $prompt;
while (<STDIN>) {
chomp;
#error codes' precedence is descending, e.g. empty string is first because it is the most serious error
if ($_ eq '') {
$errormsg="You must enter a path, or type n to quit.";
} elsif (m/^n$/i) {
if (@avifiles < 1) {
$errormsg="You must load at least one avi file.";
} else {
last;
}
} elsif (!m/.*\.av[si]$/i) {
$errormsg="Must be an avi file, try again.";
} elsif (! -e $_) {
$errormsg="File does not exist, try again.";
} else {
push(@avifiles,$_);
&out_info('avi #'.@avifiles.' loaded successfully!');
&echo("");
print $prompt;
next;
}
&out_error($errormsg);
print $prompt;
}
}
############################################################################
# function: q_avi_a
#
# Automatic file loading. User just has to specify a directory and it will
# load the files alphabetically.
############################################################################
sub q_avi_a {
&echo("");
#saved this string in a variable for use later on down
$enterhelp = 'Enter the path to the avi folder e.g. c:\my video folder\ without quotes.';
&out_info($enterhelp);
print $prompt;
#Validate avifolder. Check for blank, then :\ for full path if windows, then see whether it exists.
while (<STDIN>) {
@avifiles = ();
chomp;
if (m/^$/) {
$errormsg="You must enter a path.";
} elsif (m/^.[^:][^\\].*/) {
$errormsg="Must be full path.";
} elsif (! -e $_) {
$errormsg="Folder does not exist.";
} else {
$avifolder = $_;
#remove any and all / or \ characters from the end of the path
$avifolder =~ s/[\/]*$//;
#build list
for (<$avifolder/*.avi>) {
push(@avifiles,$_);
print $_."\n";
}
#user can accept if files were found
if (@avifiles == 0) {
$errormsg = 'No avi files found in that folder!';
} else {
#Ask user if the list is good.
&echo('Continue [y] or rescan [n]?');
print $prompt;
while (<STDIN>) {
chomp;
last if (m/^[yn]$/);
print "Invalid input.\n\n";
print $prompt;
}
#do we need to do this whole thing again?
last if $_ eq 'y';
$errormsg = $enterhelp;
}
}
&out_error($errormsg);
print $prompt;
}
}
############################################################################
# Output
#
# These subroutines send data to the console.
############################################################################
sub echo {
$_[0] = '' if not defined $_[0];
if ($os eq 'windows') {
system("\"${cecho}\" ".$_[0].'{\n}');
} else {
print $_[0]."\n";
}
}
sub out_cls {
system($clear);
&echo("${gray_on_black} ");
&echo("${blue_on_black}===============================================================================");
&echo("${white_on_black} Speed Demos Archive Anri ${version}");
&echo("${aqua_on_black} http://speeddemosarchive.com/ http://www.metroid2002.com/ ");
&echo("${blue_on_black}===============================================================================");
&echo("");
system($reset_color);
}
sub out_info {
$_[0] = '' if not defined $_[0];
&echo("${white_on_gray}$_[0]");
system($reset_color);
}
sub out_error {
$_[0] = '' if not defined $_[0];
&echo("${maroon_on_gray}[!]");
&echo("${maroon_on_gray}[!] ${maroon_on_silver}".$_[0]);
&echo("${maroon_on_gray}[!]");
system($reset_color);
&echo("");
}
sub out_section {
$_[0] = '' if not defined $_[0];
&title("Anri-chan ${version} - ".$_[0]);
&echo("${teal_on_gray}-------------------------");
&echo("${navy_on_gray}".$_[0]."");
&echo("${teal_on_gray}-------------------------");
system($reset_color);
&echo("");
}
sub out_cls_section {
&out_cls;
&out_section($_[0]);
}
sub out_cls_info {
&out_cls;
&out_info($_[0]);
}
#print the numbered english half of a 2d array consisting of english,command pairs and show the anri prompt at the end
sub out_menu {
for ($i = 0; $i < @_; $i++) {
print " ".($i+1).". ".$_[$i][0]."\n";
}
print "\n${prompt}";
}
############################################################################
# Encoding
#
# Encode video in OS-specific ways.
############################################################################
#input filename, output basename, output audio bitrate in baud, x264 minimum quantizer, delete tempfiles boolean
sub onepass {
system("\"${anri_dir}/${x264}\" --qp ${_[3]} --ref 8 --mixed-refs --no-fast-pskip --bframes 5 --b-rdo --bime --weightb --nf --direct auto --subme 7 --analyse p8x8,b8x8,i4x4,p4x4 --threads auto --thread-input --progress --no-psnr --no-ssim --output \"${_[1]}_video.mp4\" \"${_[0]}\"");
system("\"${vdub_dir}/${vdubcli}\" \"${_[0]}\" /i \"${anri_dir}/audioout.vcf\" \"${_[1]}_temp.wav\"");
system("\"${anri_dir}/${naac}\" -br ${_[2]} -lc -if \"${_[1]}_temp.wav\" -of \"${_[1]}_audio.mp4\"");
system("\"${anri_dir}/${mp4box}\" -tmp . -new -add \"${_[1]}_video.mp4\" -add \"${_[1]}_audio.mp4\" \"${_[1]}.mp4\"");
if (${_[4]}) {
@todel = (
"${_[1]}_video.mp4",
"${_[1]}_temp.wav",
"${_[1]}_audio.mp4",
);
&rm(@todel);
}
}