#!/usr/bin/perl

# smogrify - Copyright G. Finch (salsaman+lives@gmail.com) 2003 - 2018
# Released under the GPL 3 or later - see file COPYING or www.gnu.org for details

## TODO:

#use Locale::gettext;
#use POSIX;     # Needed for setlocale()
#setlocale(LC_MESSAGES, "");
#textdomain("LiVES");
#print gettext("Welcome to my program"), "\n";

# stop astyle indenting
#=begin comment
#   // *INDENT-OFF*
#    =end comment
#    =cut

my $USE_STRICT = 1;

if ($USE_STRICT) {
    use strict;
}

my $USE_WARNINGS = 1;
if ($USE_WARNINGS) {
    use warnings;
}


use File::Copy;
use File::Find;
#use File::Touch;  y// would be nice
use File::Path 'make_path';
use File::Path 'remove_tree';

use Cwd qw(cwd);


##################################################################
use POSIX qw(locale_h);
use locale;
setlocale(LC_NUMERIC, "C");

# these MUST match with the definition in the GUI

my $rfx_builder_multi = "build-lives-rfx-plugin-multi";

my $GUI_NAME = "LiVES";
my $MYNAME = $0;

###################################################################

# default values

###################################################################
# Do not change these except for testing !

my $background = 1;
my $version = "3.2.0";
#my $dyneversion="LIVES-20091209";

###################################################################

my $uid = $ >;
my $gid = $);
$gid =~ s/\W.*//;

my $SYSBIN = "";

#my $SYSBIN = "/bin";
#my $SYSBIN = "/usr/bin";

if (my $DEBUG_CALLERS) {
    # only callers are LiVES decoders, currently
    if (caller) {
	# from other scripts
	print STDERR "yes\n";
    }
    else {
	# from lives
	print STDERR "no\n";
    }
}


if ($^O eq "MSWin32" || $^O eq "msys") {
    if (!caller) {
	print STDERR "smogrify for $GUI_NAME v$version: This version of smogrify is not for Windows. Exiting with code 1.\n";
    }
    exit 1;
}

#$DEBUG_SMOGRIFY = 1;

my $configfile;
my $configfile_damaged;
my $recconfigfile;
my $recconfigfile_tried;
my $recconfigfile_succeeded;
my $newconfigfile;

my $nulfile;

my $encoder;
my $smog_composite_command;
my $smog_convert_command;

my $has_configfile;
my $bad_configfile;

my $work;
my $panic;

my $opening_preview = 0;
my $pidfile;
my $curworkdir;
my $workdir = "";
my $handle;
my $statusfile;
my $configfile = "";
my $mplay_command = "";

my $DEFTRASH = "_Trash";

###################################################################

if (caller) {
    ## called from "require", allows access to some global vars. and subroutines
    1;
}

if (!$ARGV[0]) {
    print STDERR "This is smogrify (helper app) for $GUI_NAME v$version\n";
    exit 0;
}

if (1) {
    ## cant use my because callers need this
    our $command = $ARGV[0]; ## our because we pass this to encoder plugins
    ## print STDERR "COM is $command\n";
    if ($command eq "version" && !caller) {
	print "smogrify $version\n";
	exit 0;
    }

    if (1) {
	## when called from LiVES, configfile is always passed as a parameter
	## this is just a cross-check

	my @needs_configfile = ("report", "make_thumb", "play", "play_opening_preview", "set_pref_priority", "delete_pref",
				"set_pref", "get_pref",
				"open", "open_test", "save", "append_audio", "trim_audio", "resample_audio", "commit_audio",
				"cdopen", "insert", "get_details", "fill_and_redo_frames", "audioopen");

	if ($command eq "list_commands") {
	    &listemall(@needs_configfile);
	    exit 0;
	}

	if ($USE_WARNINGS) {
	    no warnings 'once';
	    no warnings 'uninitialized';
	}
	if ($USE_STRICT) {
	    no strict;
	}
	my $work = "$WORKDIR";
	if ($USE_STRICT) {
	    use strict;
	}
	if ($USE_WARNINGS) {
	    use warnings 'uninitialized';
	    use warnings 'once';
	}
	if ($work) {
	    $workdir = $work;
	}

	if ($USE_WARNINGS) {
	    no warnings 'once';
	    no warnings 'uninitialized';
	}
	if ($USE_STRICT) {
	    no strict;
	}
	my $config = "$CONFIGFILE";
	if ($USE_STRICT) {
	    use strict;
	}
	if ($USE_WARNINGS) {
	    use warnings 'uninitialized';
	    use warnings 'once';
	}

	if ($config) {
	    $configfile = $config;
	}

	if ($configfile eq "" && ($workdir eq "" || grep(/^$command$/, @needs_configfile) ||
				  $command =~ /^pfxrender_/ || $command =~ /^fxrender_/)) {
	    ## commands that require $configfile

	    # configfile not set
	    print STDERR "smogrify for $GUI_NAME v$version: configfile not defined for command \"$command\", ";
	    print STDERR "cannot continue. Exiting with code 98\n";
	    print STDERR "by starting $GUI_NAME with the -configfile commandline option.\n";
	    print STDERR "smogrify for $GUI_NAME v$version: \@ARGV = " . join(" ", @ARGV) . "\n";
	    exit 98;
	}

	$configfile_damaged = "$configfile.damaged";
	$recconfigfile = "$configfile.recovery";
	$recconfigfile_tried = "$recconfigfile.tried";
	$recconfigfile_succeeded = "$recconfigfile_tried.succeeded";
	$newconfigfile = "$configfile.new";

	$nulfile = "/dev/null";

	$encoder = "ffmpeg_encoder";
	$smog_composite_command = "composite -quiet";
	$smog_convert_command = "convert -quiet";

	$has_configfile = -1;
	$bad_configfile = 0;

	if ($command ne "set_pref" ) {
	    if ($workdir eq "") {
		$has_configfile = 0 + -e $configfile;
		if ($has_configfile) {
		    # can exit 3 here
		    $workdir = &config_get("workdir");
		    if ($workdir eq "") {
			$workdir = &config_get("tempdir");
		    }
		    if ($command ne "report") {
			if ($workdir eq "") {
			    if (!caller) {
				print STDERR "smogrify for $GUI_NAME v$version: workdir not defined for command $command, ";
				print STDERR "cannot continue. Exiting with code 97\.n";
				print STDERR "smogrify for $GUI_NAME v$version: \@ARGV = " . join(" ", @ARGV) . "\n";
			    }
			    exit 97;
			}}}}}}

    if (!caller) {
	#$DEBUG_SM_CMDS = 1;
	if ($USE_WARNINGS) {
	    no warnings 'once';
	}
	if ($USE_STRICT) {
	    no strict;
	}
	if (defined $DEBUG_SM_CMDS) {
	    print STDERR "configfile is $configfile\n";
	    print STDERR "workdir is $workdir\n";
	    print STDERR "command is $command\n";
	    print STDERR join(" ", @ARGV) . "\n";
	}
	if ($USE_STRICT) {
	    use strict;
	}
	if ($USE_WARNINGS) {
	    use warnings 'once';
	}
	## blocking calls

	if ($command eq "report") {
	    ################################### startup checks ########################
	    my $upd;
	    my $recovery_file = "";
	    my $gui_bootstrap_file = "";
	    my $ver_hash;

	    if (defined($ARGV[1])) {
		# only for backwards compatibility; otherwise pass in "-" to do an update or "" for a dry-run
		$gui_bootstrap_file = $ARGV[1];
		$upd = 0;
	    }
	    if ($gui_bootstrap_file eq "-") {
		$upd = 1;
	    }
	    else {
		$upd = 0;
		if ($gui_bootstrap_file eq "") {
		    $gui_bootstrap_file = "-";
		}
	    }

	    if ($upd) {
		unlink $configfile_damaged;
		unlink $recconfigfile_tried;
		unlink $recconfigfile_succeeded;
	    }

	    my $old_version_hash = "";
	    my $old_version = "";

	    if ($has_configfile == -1) {
		$has_configfile = 0 + -e $configfile;
	    }

	    if (!$has_configfile) {
		if ($upd) {
		    $recovery_file = &recover_configfile($configfile);
		    if ($recovery_file ne "") {
			$has_configfile = 1;
			$old_version_hash = "0";
		    }
		}
	    }

	    if ($has_configfile) {
		# found an existing configfile
		# try to get the version
		$old_version = &config_get("version");
		if ($old_version eq "") {
		    #failed  to get version from configfile
		    $has_configfile = 0;
		    if ($upd) {
			# we only accept settings files with a valid version entry
			my $xsize = -s $configfile;
			&smog_rename($configfile, $configfile_damaged);
			unlink $configfile;
			if (0 + -e $configfile) {
			    $bad_configfile = 1;
			    &startup_error(5, "report", "remove_noversion0", "$configfile:\@:");
			}

			if ($old_version_hash eq "0" && (-s $recovery_file > 0 || $recovery_file eq $newconfigfile
							 || -s $newconfigfile == 0)) {
			    &startup_error(5, "report", "config_recover_version0", "$configfile:$recovery_file:$xsize:\@:");
			}
			else {
			    # failed to get version from $configfile. Try to recover from backup
			    $recovery_file = &recover_configfile($configfile);
			    if ($recovery_file ne "") {
				# ok, got a backup, now we check again
				$has_configfile = 1;
				$old_version_hash = "0";
				$old_version = &config_get("version");
				if ($old_version eq "") {
				    # still no joy
				    &smog_rename($configfile, $configfile_damaged);
				    unlink $configfile;
				    if (0 + -e $configfile) {
					$bad_configfile = 1;
					&startup_error(5, "report", "remove_noversion1", "$configfile:\@:");
				    }
				    $has_configfile = 0;
				}
				elsif (!$work) {
				    # recovered from backup, got a version
				    # now look for workdir or tempdir
				    $workdir = &config_get("workdir");
				    if ($workdir eq "") {
					$workdir = &config_get("tempdir");
				    }
				    if ($workdir eq "") {
					&smog_rename($configfile, $configfile_damaged);
					unlink $configfile;
					if (0 + -e $configfile) {
					    $bad_configfile = 1;
					    &startup_error(5, "report", "remove_noworkdir0",
							   "$configfile:\@:");
					}
					$old_version = "";
					$old_version_hash = "0";
					$has_configfile = 0;
				    }}}}}

		    else {
			# configfile has version
			if ($old_version ne "") {
			    $old_version_hash = &version_hash($old_version);
			    if ($old_version_hash < 3000003) {
				if ($work eq "") {
				    #older versions used tempdir instead of workdir
				    my $xworkdir = &config_get("tempdir");
				    if ($xworkdir ne "") {
					$workdir = $xworkdir;
				    }}}}}}}

	    if ($has_configfile && $recovery_file ne "") {
		$panic = 0;
		&smog_copy($configfile, $recconfigfile_succeeded);
		if ($panic || -s $recconfigfile_succeeded != -s $configfile) {
		    unlink $recconfigfile_succeeded;
		    &startup_error(5, "report", "copyto_succeeded0", "$recconfigfile_succeeded:\@:");
		}
		else {
		    unlink $recovery_file;
		    if (0 + -e $recovery_file) {
			&startup_error(5, "report", "remove_recovery0", "$recovery_file:\@:");
		    }
		}
	    }

	    if ($old_version ne "") {
		my $xworkdir;
		$old_version_hash = &version_hash($old_version);
		if (!$work) {
		    if ($old_version_hash < 3000003) {
			#older versions used tempdir instead of workdir
			$xworkdir = &config_get("tempdir");
			if ($workdir eq "") {
			    $xworkdir = &config_get("workdir");
			}
		    }
		    else {
			$xworkdir = &config_get("workdir");
			if ($workdir eq "") {
			    $xworkdir = &config_get("tempdir");
			}
		    }
		    $workdir = $xworkdir;
		}
	    }

	    my $startup_phase = -1;

	    if (!$has_configfile) {
		if ($upd) {
		    unlink $configfile;
		    if (0 + -e $configfile) {
			&startup_error(5, "report", "remove_badconfigfile0", "$configfile:\@:");
		    }
		    # set comment at start of file
		    &config_set("", "");
		    &config_set("startup_phase", -1);
		}
	    } else {
		if ($workdir ne "") {
		    $startup_phase = &config_get("startup_phase");
		    if ($startup_phase eq "" || $startup_phase eq "100") {
			$startup_phase = 0;
			if ($upd) {
			    &config_delete("startup_phase");
			}}}}

	    $ver_hash = &version_hash($version);

	    ###################### all startup tests passed ########################

	    my $msg = "";

	    if ($old_version_hash ne $ver_hash) {
		if ($upd) {
		    ## TODO: move all into front end

		    my $video_open_command = "\"". &get_mplayer_location . "\"";

		    my $audio_player = "sox";
		    my $audio_play_command = &config_get_default("sox_command");

		    if ($audio_play_command eq "") {
			$audio_player = "mplayer";
			$audio_play_command = &config_get_default("mplayer_audio_command");
		    }

		    if (defined($smogrify::dyneversion)) {
			&config_set_if_not_set("lib_dir", "/opt/$smogrify::dyneversion/lib/");
			&config_set_if_not_set("prefix_dir", "/opt/$smogrify::dyneversion/");
		    }

		    # NOTE: the following definitions must match with equivalent keys in preferences.h
		    &config_set_if_not_set("video_open_command", $video_open_command); ## backend only
		    &config_set_if_not_set("audio_play_command", $audio_play_command); ## backend only

		    # resize action when opening images - can be either: "bound" or "none"
		    &config_set_if_not_set("image_resize_action", "none"); ## backend only
		    &config_set_if_not_set("encoder", $encoder); ## TODO: check for encoders, formats; pick best
		    &config_set_if_not_set("gui_theme", "crayons"); ## TODO: check for available themes @ startup
		}
		if ($old_version ne "") {
		    if ($old_version_hash < $ver_hash) {
			&version_check_and_upgrade($upd, $old_version_hash, $old_version);
		    }
		}
	    }


	    if ($has_configfile) {
		my $convert_old_version = &config_get("convert_version");
		my $convert_version = &get_convert_version;
		unless ($convert_old_version eq $convert_version) {
		    if ($upd) {
			&config_set("convert_version", $convert_version);
		    }
		    # throw a warning if using png and convert_version < 6.0.0
		    my $convert_version_hash = &version_hash($convert_version);
		    if ($convert_version_hash < 6000 && $convert_version_hash > 0
			&& &config_get("default_image_format") ne "jpg") {
			$msg .= "You are advised to upgrade to at least version 6.0 of Image Magick if using png files\n";
		    }
		}
	    }

	    if ($upd) {
		# bless this version
		&config_set_priority("version", "$version");
		# can exit with code 3, 4, or 5
		&config_deprecate("tempdir", "workdir", "3.0.3");
	    }

	    if ($gui_bootstrap_file eq "-") {
		print "$version|$workdir|$startup_phase|$old_version_hash|$msg|";
		if (!$upd) {
		    print "\n";
		}
	    }
	    else {
		# only for backwards compatibility with older LiVES.
		if (! (0 + -e $gui_bootstrap_file)) {
		    open OUT, ">", "$gui_bootstrap_file" or exit 5;
		    print OUT "$version|";
		    close OUT;
		}
	    }
	    exit 0;
	}


	if ($command eq "stopsubsub" || $command eq "stopsubsubs" || $command eq "stop_audio") {
	    my $handle = $ARGV[1];
	    ## TODO
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";

	    my $sig = "KILL";
	    my $target_pid;
	    if ($command eq "stop_audio") {
		$pidfile = "$curworkdir/.pidpb";
	    } else {
		$pidfile = "$curworkdir/.pid";
		if (defined($ARGV[2])) {
		    $sig = $ARGV[2];
		}
	    }

	    unless (-d $curworkdir) {
		exit 3;
	    }

	    if (defined(open IN, "<", $pidfile)) {
		sysread IN, $target_pid, 8;
		close IN;
		if (!$target_pid) {
		    exit 1;
		}

		# make sure pidfile has same group and owner, otherwise exit
		my @stat = stat($pidfile);
		if (!($uid == $stat[4] && $gid == $stat[5])) {
		    die "smogrify: Unable to stop process $target_pid.\n";
		}
		elsif ($command eq "stopsubsub" || $command eq "stop_audio") {
		    # kill all subprocesses of target_pid, and target_pid
		    &kill_child_pids($target_pid, $sig);
		}
		elsif ($command eq "stopsubsubs") {
		    # kill all subprocesses of target_pid, but not target_pid
		    &smog_system("pgrep -P $target_pid > \"$workdir/.pids.$target_pid\" 2>/dev/null");
		    if (-s "$workdir/.pids.$target_pid" && defined(open IN, "<", "$workdir/.pids.$target_pid")) {
			while (<IN>) {
			    my $pid = $_;
			    chomp($pid);
			    &kill_child_pids($pid, $sig);
			}
			close IN;
		    }
		    unlink "$workdir/.pids.$target_pid";
		}

		else {
		    print "Attempting to stop process " . $target_pid . " with $command\n";
		    if ($sig eq "KILL") {
			kill $sig, $target_pid;
		    } else {
			## kill child processes of $target_pid
			&smog_system("pkill -$sig -P $target_pid");
			# must not ! >/dev/null 2>&1");
		    }
		}

		if ($sig eq "KILL" && $command ne "stop_audio") {
		    &sig_killed;
		}
		exit 0;
	    }
	    exit 1;
	}


	if ($command eq "new") {
	    # write mini-info file
	    my $handle;
	    my $key;
	    my $infofile;
	    do {
		$handle=int(rand 1000000000) + 65536;
		$curworkdir="$workdir/$handle";
	    } while (-d $curworkdir);

	    unless (mkdir $curworkdir) {
		&sig_system_error("Creating directory \"$curworkdir\"", 0);
		exit 1;
	    }

	    if (defined $ARGV[1]) {
		$key=$ARGV[1];
		$infofile="$workdir/.info.$key";
		if (!defined(open OUT, ">", $infofile)) {
		    &sig_write_error($infofile, 0);
		    exit 1;
		}
		print OUT $handle;
		close OUT;
	    }
	    else {
		print $handle;
	    }
	    exit 0;
	}


	if ($command eq "get_tempdir") {
	    ## deprecated - DO NOT USE, use get_workdir instead
	    my $id;
	    if (defined($ARGV[1])) {
		$id = $ARGV[1];

		open OUT, ">", "/tmp/.smogrify.$id" or exit 1;
		print OUT "$workdir";
		close OUT;
	    } else {
		print $workdir;
	    }
	    exit 0;
	}


	if ($command eq "get_workdir") {
	    # safer version - writes to stdout
	    print $workdir;
	    exit 0;
	}


	if ($command eq "set_clip_value") {
	    if ($USE_STRICT) {
		no strict;
	    }
	    $prefs_file = $ARGV[1];
	    if ($USE_STRICT) {
		use strict;
	    }
	    shift(@ARGV);
	    $command = "set_pref";
	}


	if ($command eq "set_pref") {
	    my $key = $ARGV[1];
	    shift(@ARGV);
	    shift(@ARGV);
	    my $value = join("_", @ARGV);
	    &config_set($key, $value);
	    exit 0;
	}


	if ($command eq "set_pref_priority") {
	    my $key = $ARGV[1];
	    shift(@ARGV);
	    shift(@ARGV);
	    my $value = join("_", @ARGV);
	    &config_set_priority($key, $value);
	    exit 0;
	}


	if ($command eq "get_clip_value") {
	    $command = "get_pref";
	    if ($USE_STRICT) {
		no strict;
	    }
	    $prefs_file = $ARGV[4];
	    if ($USE_STRICT) {
		use strict;
	    }
	}

	if ($command eq "print_pref") {
	    $command = "get_pref";
	    $ARGV[2] = "-";
	}


	if ($command eq "get_pref") {
	    my $key = $ARGV[1];
	    my $first;
	    my $second;
	    if (defined($ARGV[2])) {
		$first = $ARGV[2];
	    } else {
		$first = $uid;
	    }
	    if (defined($ARGV[3])) {
		$second = $ARGV[3];
	    } else {
		$second = $gid;
	    }
	    my $value = &config_get($key, $configfile);
	    print $value;
	    exit 0;
	}


	if ($command eq "delete_pref") {
	    my $key = $ARGV[1];
	    &config_delete($key);
	    exit 0;
	}


	if ($command eq "pause") {
	    if (!&is_writeable("$workdir/$ARGV[1]/pause", 1)) {
		&sig_write_error("$workdir/$ARGV[1]/pause");
		exit 1;
	    }
	    exit 0;
	}


	if ($command eq "resume") {
	    unlink "$workdir/$ARGV[1]/pause";
	    exit 0;
	}


	if ($command eq "clear_tmp_files") {
	    my $handle = $ARGV[1];
	    $movetotrash = $ARGV[2];
	    #clear old backups (e.g. when saving a set)
	    $curworkdir = "$workdir/$handle";
	    &clean_old($handle, $movetotrash);
	    exit 0;
	}


	if ($command eq "clear_pre_files") {
	    ## TODO: make bg
	    $handle = $ARGV[1];
	    #clear old backups (e.g. when saving a set)
	    $curworkdir = "$workdir/$handle";
	    unlink glob "$curworkdir/*.pre";
	    exit 0;
	}


	if ($command eq "undo_audio") {
	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";
	    &undo_audio;
	    # will abort on failure
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "backup_audio") {
	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";
	    &smog_chdir($curworkdir);
	    &backup_audio(1);
	    if ($panic) {
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "save_frame") {
	    my $hsize = -1;
	    my $vsize = -1;

	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $frame = $ARGV[2];
	    $name = &mkname($frame);
	    $nfile = $ARGV[3];
	    if (defined $ARGV[4]) {
		$hsize = $ARGV[4];
	    }
	    if (defined $ARGV[5]) {
		$vsize = $ARGV[5];
	    }

	    $statusfile = "$curworkdir/.status";

	    # check the file is writable
	    unless (&is_writeable($nfile, 0)) {
		&sig_error("Unable to open output file !", "$GUI_NAME could not write to $nfile.");
	    }

	    $img_ext = &get_img_ext($curworkdir, $frame);
	    $img_prefix = &get_img_prefix($img_ext);

	    if ($hsize == -1 || $vsize == -1) {
		$com = "$smog_convert_command $img_prefix\"$curworkdir/$name$img_ext\" \"$nfile\" >$nulfile 2>&1";
	    } else {
		if ($antialias eq "false") {
		    $com = "$smog_convert_command +antialias $img_prefix\"$curworkdir/$name$img_ext\" -scale $hsize" .
			"x" . "$vsize\\! \"$nfile\" > $nulfile 2>&1";
		} else {
		    $com = "$smog_convert_command -antialias $img_prefix\"$curworkdir/$name$img_ext\" -resize $hsize" .
			"x" . "$vsize\\! \"$nfile\" > $nulfile 2>&1";
		}
	    }

	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    exit 0;
	}


	if ($command eq "restore_details") {
	    $handle = $ARGV[1];
	    $nfile = $ARGV[2];
	    $leave_headers = $ARGV[3];

	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";

	    unlink "$curworkdir/pause";

	    &smog_chdir($curworkdir);

	    $fsize = -s $nfile;
	    $afsize = -s "audio";

	    $img_type = "jpg";

	    unless (-f "header.lives") {
		if (defined(open IN, "< header2")) {
		    read IN, $val, 4;

		    # unfortunately...old clips used whatever endian the machine used
		    $endian = &get_endian;
		    $frames = &getint($val);
		    read IN, $title, 256;
		    read IN, $author, 256;
		    read IN, $comment, 256;
		}

		$title =~ tr/\x00//d;
		$author =~ tr/\x00//d;
		$comment =~ tr/\x00//d;

		# very old backups didn't have a header2
		if (! -f "header2") {
		    unless ($nfile eq "") {
			unlink glob "event.*";
			$img_ext = &get_img_ext($curworkdir);
			$img_prefix = &get_img_prefix($img_ext);
			$frames = &count_frames;
		    }
		}

		# leave headers for restoring VJ sets
		if ($leave_headers == 1) {
		    $fsize = 0;
		}
		else {
		    unlink <header header2>;
		}
	    }
	    else {
		unlink <header header2>;
	    }

	    $name = &mkname(1);

	    if (-f "$name.png") {
		$img_type="png";
	    }

	    unlink "$curworkdir/pause";
	    &sig_complete_stdout($fsize, $afsize, $img_type, $frames, $title, $author, $comment);
	    exit 0;
	}


	if ($command eq "list_plugins") {
	    # prefix_dir pref should be set first
	    # plugins are returned in sort() order
	    my $allow_nonex = $ARGV[1];
	    my $allow_subdirs = $ARGV[2];
	    my $plugindir = $ARGV[3];
	    my $ext = $ARGV[4];
	    my $strip_ext = 0;
	    my $string = "";

	    if ($ext =~ /^-/) {
		$strip_ext = 1;
		$ext = substr($ext, 1, length($ext));
	    }

	    if (-d $plugindir) {
		&smog_chdir($plugindir);
		opendir DIR, $plugindir;
		my @files = readdir(DIR);
		closedir DIR;
		#@files = sort (@files);

		foreach my $plugname (@files) {
		    unless ($plugname =~ /^\./) {
			if (-f $plugname || ($allow_subdirs == 1 && -d "$plugindir/$plugname")) {
			    unless ($allow_nonex) {
				next if ! -x "$plugindir/$plugname";
			    }
			    unless ($plugname eq "") {
				if ($ext eq "" || $plugname =~ /$ext$/) {
				    if ($strip_ext == 1) {
					$plugname = (split(/\./, $plugname))[0];
				    }
				    $string .= "$plugname|";
				}}}}}}

	    else {
		print "\n";
		exit 1;
	    }
	    print "$string\n";
	    exit 0;
	}


	if ($command eq "build_rfx_plugins") {
	    ## TODO: make bg
	    if (&location($rfx_builder_multi) eq "") {
		&sig_error("Unable to locate the program $rfx_builder_multi");
		exit 1;
	    }

	    $type = $ARGV[1];
	    $script_dir = $ARGV[2];
	    $exec_dir = $ARGV[3];
	    &smog_system("$rfx_builder_multi \"$type\" \"$script_dir\" \"$exec_dir\"");
	    exit 0;
	}


	if ($command eq "make_thumb") {
	    ## TODO: make bg
	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";
	    $dwidth = $ARGV[2];
	    $dheight = $ARGV[3];
	    $img_ext = "." . $ARGV[4];
	    $img_prefix = &get_img_prefix($img_ext);
	    $file = $ARGV[5];

	    if (! -d $curworkdir) {
		mkdir ($curworkdir);
	    }

	    $antialias = &config_get("antialias");

	    $imresact = "bound";
	    &get_image_size($file);
	    if ($panic) {
		exit 1;
	    }

	    if (!($vsize * $hsize) || $hsize == -1) {
		&sig_complete_stdout(0, 0);
		exit 0;
	    }

	    &smog_chdir($curworkdir);

	    $name = &mkname(1);

	    if ($antialias eq "false") {
		smog_system("$smog_convert_command +antialias \"$file\" -scale $hsize" .
			    "x$vsize\\! $img_prefix\"$curworkdir/$name$img_ext\" > $nulfile 2>&1");
	    }
	    else {
		smog_system("$smog_convert_command -antialias \"$file\" -resize $hsize" .
			    "x$vsize\\! $img_prefix\"$curworkdir/$name$img_ext\" > $nulfile 2>&1");
	    }
	    &sig_complete_stdout($hsize, $vsize);
	    exit 0;
	}

	if ($command eq "disk_check") {
	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";
	    my $opts = $ARGV[2];
	    my $trashdir = $ARGV[3];
	    my $nitems = &clean_workdir($opts, $trashdir);
	    &sig_complete($nitems);  ## for the f.e. - skip if 0
	    exit 0;
	}


	if ($command eq "plugin_clear") {
	    # this is a special function which does general cleanup
	    # and then does the "clear" command in the plugin
	    # non-perl plugins have to implement this command themselves
	    #
	    ## TODO: make bg

	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";

	    my $start = $ARGV[2];
	    my $end = $ARGV[3];
	    my $plugdir = $ARGV[4];
	    my $type = $ARGV[5];
	    my $rest = $ARGV[6];
	    my $plugin = "";

	    unless ($rest eq "") {
		$plugin = "$plugdir$type/$rest";
	    }

	    &smog_chdir($curworkdir);

	    if ($type eq "encoders" || $type eq "effects/rendered/") {
		$img_ext = &get_img_ext($curworkdir,$start);
		$img_prefix = &get_img_prefix($img_ext);
		if ($type eq "encoders") {
		    if (-f "audio.origbak") {
			unlink "audio";
			&smog_rename("audio.origbak", "audio");
		    }
		    $areq = &get_form_request($plugin);
		    if ($areq & 1 || $areq & 2) {
			unlink <audiodump.wav audioclip>;
		    }
		    if ($areq & 4) {
			&clear_symlinks;
		    }}}

	    &smog_chdir($curworkdir);

	    $com = "\"$plugin\" clear $start $end $img_ext";

	    $smerr = &smog_system($com);

	    if ($smerr) {
		&sig_system_error("Command failed: $com", $smerr);
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "check_for_lock") {
	    # check if there is a lock file for a set, and if so is it in use
	    my $setname = $ARGV[1];
	    my $exename = $ARGV[2];
	    my $pid = $ARGV[3];

	    opendir DIR, "$workdir/$setname";

	    while (my $file = readdir(DIR)) {
		if ($file =~ /^lock\.(.*)/) {
		    #found a lockfile
		    my $proc = $1;
		    unless ($proc eq $pid) {
			my $exe = readlink "/proc/$proc/exe";  #allowed to fail
			if ($exe ne "" && &my_basename($exe) eq $exename) {
			    # and it's in use
			    closedir DIR;
			    print "$proc\n";
			    exit 0;
			}
			unlink "$workdir/$setname/$file";
		    }}}
	    closedir DIR;
	    exit 0;
	}


	if ($command eq "get_recovery_file") {
	    #find first non-in-use recovery file for given uid, gid and filepart
	    my $uid = $ARGV[1];
	    my $gid = $ARGV[2];
	    my $exename = $ARGV[3];
	    my $filepart = $ARGV[4];
	    my $pid = "";
	    if (defined($ARGV[5])) {
		# because we can pick up the same pid again...
		$pid = $ARGV[5];
	    }

	    opendir DIR, $workdir;
	    my @files = readdir(DIR);

	    # sort most recent first (in case of multiple files)
	    @files = sort {(stat "$workdir/$b")[10] <=> (stat "$workdir/$a")[10]}(@files);

	    foreach my $file(@files) {
		if ($file =~ /^$filepart\.$uid\.$gid\.(.*)/) {
		    if (-z $file) {
			# clean up zero length files
			unlink $file;
		    } else {
			# check to make sure pid is not a running instance
			if ($exename ne "" && ($pid eq "" || $pid ne $1)) {
			    my $exe = readlink "/proc/$1/exe";
			    next if ($exe ne "" && &my_basename($exe) eq $exename);
			}
			$output = (split /\./, $file)[-1];
			print $output;
			closedir DIR;
			exit 0;
		    }}}

	    print 0;
	    closedir DIR;
	    exit 0;
	}


	if ($command eq "clean_recovery_files") {
	    #remove non-in-use recovery/layout files for given uid, gid
	    my $uid = $ARGV[1];
	    my $gid = $ARGV[2];
	    my $exename = $ARGV[3];
	    my $pid = "";
	    my $leave_reco = 0;
	    if (defined($ARGV[4])) {
		$pid = $ARGV[4];
	    }
	    if (defined($ARGV[5])) {
		$leave_reco = $ARGV[5];
	    }

	    opendir DIR, $workdir;
	    &smog_chdir($workdir);

	    while (my $file = readdir(DIR)) {
		if ($file =~ /^recovery\.$uid\.$gid\.(.*)/ || $file =~ /^layout\.$uid\.$gid\.(.*)/
		    || (!$leave_reco &&
		    ($file =~ /^recorded-layout\.$uid\.$gid\.(.*)/ ||
		     $file =~ /^recorded_layout_numbering\.$uid\.$gid\.(.*)/))) {
		    if ($pid eq "" || $pid ne $1) {
			my $exe = readlink "/proc/$1/exe";
			next if ($exe ne "" && &my_basename($exe) eq $exename);
			unlink $file;
		    }
		}
	    }
	    closedir DIR;
	    exit 0;
	}


	if ($command eq "get_next_in_set") {
	    # this is a special function for lives-exe. It will look for
	    # clips on the disk that have the marker file "set.$name"
	    # only relevant for pre 0.9.6 versions

	    my $last = $ARGV[1];
	    my $setname = $ARGV[2];
	    my $pid = $ARGV[3];
	    my $found;

	    opendir DIR, $workdir;
	    my @allfiles = readdir(DIR);
	    closedir DIR;

	    foreach my $subdir(@allfiles) {
		if (-f "$workdir/$subdir/set.$setname") {
		    if ($last eq "none") {
			$found = $subdir;
			last;
		    }
		    if ($last eq $subdir) {
			$last = "none";
		    }
		}
	    }

	    if ($found eq "") {
		$found = "none";
	    }

	    &smog_system_sync();

	    if (-d $workdir) {
		my $infofile = "$workdir/.info.$pid";
		open OUT, ">", $infofile;
		print OUT $found;
		close OUT;
	    }
	    exit 0;
	}


	if ($command eq "move_workdir") {
	    ## TODO: make bg
	    $new_work = $ARGV[1];
	    unless ($new_work eq "") {
		# here is where we create a new work directory
		unless (-d $new_work || (mkdir $new_work)) {
		    print STDERR "Smogrify: Unable to create new directory\n$new_work!\n";
		    exit 1;
		}
		&smog_chdir($workdir);
		print STDERR "Smogrify: moving sets from:\n$workdir\nto\n$new_work\n";
	    }
	    else {
		print STDERR "Smogrify: ERROR - no destination directory given !\n";
		exit 1;
	    }
	    &clean_workdir(0, "", $new_work);
	    exit 0;
	}


	if (substr($command, 0, 7) eq "fxinit_") {
	    # call onchange_init
	    $command = substr($command, 7);
	    $handle = $ARGV[1];
	    $dir = $ARGV[2];
	    $width = $ARGV[3];
	    $height = $ARGV[4];

	    $plugin_name = "$dir/$command";
	    splice(@ARGV, 0, 4);

	    $curworkdir = "$workdir/$handle";

	    $ARGV[0] = "onchange_init";
	    require($plugin_name);
	    exit 0;
	}


	if ($command eq "count_frames") {
	    my $img_ext = "." . $ARGV[2];
	    my $start = $ARGV[3];
	    print &count_frames($start, $img_ext);;
	    exit 0;
	}

	
	if ($command eq "restore_trash") {
	    exit 1 if (!defined $ARGV[1]);
	    $trashdir = "$workdir/$ARGV[1]";
	    exit 2 if &is_dangerous($trashdir);
	    if (-d $trashdir) {
		remove_tree($trashdir);
	    }
	    exit 0;
	}


	if ($command eq "get_proj_set") {
	    ## TODO: make bg
	    my $proj_file = $ARGV[1];
	    my $out = &smog_system_direct("tar --exclude=*/* -tzf \"$proj_file\"");
	    unless($out =~/\/$/) {
		exit 1;
	    }
	    chomp $out;
	    chop $out;
	    print $out;

	    exit 0;
	}

	if ($command eq "mv_pre") {
	    ## TODO - make bg
	    $handle = $ARGV[1];
	    $curworkdir = "$workdir/$handle";
	    $statusfile = "$curworkdir/.status";
	    unlink "$curworkdir/pause";
	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $img_ext = ".$ARGV[4]";
	    &sig_pid; ## remove when bg
	    &mv_pre;
	    if ($panic) {
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "clear_symlinks") {
	    ## TODO: make bg
	    $handle = $ARGV[1];
	    &clear_symlinks;
	    exit 0;
	}


	if ($command eq "create_package") {
	    ## TODO: make bg
	    # args are 1: pkg name, 2 dir to pack contents of

	    my $com = "tar czf \"$ARGV[1]\" -C \"$ARGV[2]\" .";

	    my $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }
	    exit 0;
	}


	if ($command eq "import_package") {
	    ## args are 1: file to unpack, 2 dir to unpack in
	    ## TODO: make bg

	    make_path $ARGV[2];
	    chdir $ARGV[2];

	    $com = "tar xzf \"$ARGV[1]\" .";

	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    exit 0;
	}


	if ($command eq "get_details") {
	    #attempt to get file details
	    $handle = $ARGV[1];
	    my $file = $ARGV[2];
	    $img_ext = "." . $ARGV[3];
	    my $scan_type = $ARGV[4];

	    if ($scan_type != 4) {
		$only_first = 1;
	    }
	    else {
		$only_first = 0;
	    }

	    $curworkdir = "$workdir/$handle";

	    &get_file_info($file, $scan_type, 0);
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    if ($asamps == 32) {
		# cant handle this yet, so we will resample on open
		$asamps = 16;
	    }

	    if ($scan_type == 4) {
		$count = $frames;
	    }

	    &sig_complete_stdout($handle, $count, $type, $hsize, $vsize, $bpp, $fps,
				 $f_size, $arate, $achans, $asamps, $signed, 
				 $endian, $af_size, $gamma_type, $title, $author, $comment);

	    exit 0;
	}

	####################################################################
	# run the rest in background
	if ($background == 1) {
	    if (fork()) {
		exit 0;
	    }
	}
	####################################################################
	
	if (!caller && (!defined($ARGV[1]) || $ARGV[1] eq "")) {
	    &usage;
	    exit 1;
	}

	$handle = $ARGV[1];

	$curworkdir = "$workdir/$handle";
	$statusfile = "$curworkdir/.status";

	unless ($handle eq '.' || $command eq "close" || $command eq "play_opening_preview") {
	    if ($command eq "play_opening_preview") {
		$pidfile = "$curworkdir/.pidpb";
	    } else {
		$pidfile = "$curworkdir/.pid";
	    }
	    # tell the world our pid
	    &sig_pid;
	}


	if ($command eq "empty_trash") {
	    &smog_chdir($workdir);
	    my $op = $ARGV[2];  ### "general", "giotrash" or "delete"
	    if ($op eq "general") {
		if (defined($ARGV[3])) {
		    $pat = $ARGV[3];  ### pattern for trashdirs
		    if (&is_dangerous($pat)) {
			print STDERR "smogrify: trash directory pattern $pat points to " .
			    "dangerous location\nABORTING\n";
			if ($handle ne ".") {
			    &sig_error("bad trash pattern $pat");
			}
			exit 1;
		    }
		    opendir DIR, $workdir;
		    while ($file = readdir(DIR)) {
			next if $file eq "." || $file eq "..";
			if ($file =~ /^$pat/) {
			    if (-d "$workdir/$file") {
				remove_tree($file);
			    }
			}
		    }
		    closedir DIR;
		}
		if (&is_dangerous($DEFTRASH)) {
		    print STDERR "smogrify: default trash directory $DEFTRASH points to " .
			"dangerous location\nCheck $DEFTRASH setting in $MYNAME. ABORTING\n";
		    if ($handle ne ".") {
			&sig_error("bad deftrash $DEFTRASH");
		    }
		    exit 1;
		}
		my $deftrashdir = "$workdir/.$DEFTRASH";
		if (! -d $deftrashdir) {
		    make_path $deftrashdir, {mode => 01777};
		}
		remove_tree($deftrashdir, {keep_root => 1});
		if ($handle ne ".") {
		    &sig_complete;
		}
		exit 0;
	    }
	    ## non-general
	    if (!defined($ARGV[3]) || ($op ne "giotrash" && $op ne "delete")) {
		if ($handle ne ".") {
		    &sig_error("Invalid value passed to empty_trash");
		}
		exit 1;
	    }

	    $trashdir = $ARGV[3];

	    if (&is_dangerous($trashdir)) {
		if ($handle ne ".") {
		    &sig_error("bad trashdir $ARGV[3]");
		}
		exit 1;
	    }

	    print "\n\nCommencing disk cleanup operation:\n\n";
	    print STDERR "smogrify: \n\nCommencing disk cleanup operation:\n\n";

	    $trashdir = "$workdir/$trashdir";

	    if (-d $trashdir) {
		opendir DIR, $trashdir;
		while ($file = readdir(DIR)) {
		    next if $file eq "." || $file eq "..";
		    my $origfile = "$workdir/$file";
		    if (-d $origfile) {
			if ($op eq "giotrash") {
			    print "moving directory $workdir/$file to trash bin\n";
			    print STDERR "smogrify: moving directory $workdir/$file to trash bin\n";
			    &smog_system("gio trash $origfile");
			}
			else {
			    print "deleting directory $workdir/$file\n";
			    print STDERR "smogrify: deleting directory $workdir/$file\n";
			    remove_tree($origfile);
			}
		    }
		    else {
			if (-f $origfile) {
			    print "deleting file $workdir/$file\n";
			    print STDERR "smogrify: deleting file $workdir/$file\n";
			    unlink $origfile;
			}
		    }
		}
		closedir DIR;
	    }
	    print "\n\nDisk cleanup completed\n\n";
	    print STDERR "smogrify: \n\nDisk cleanup completed\n\n";
	    if ($handle ne ".") {
		&sig_complete;
	    }
	    exit 0;
	}

	
	if ($command eq "play_opening_preview") {
	    # play_opening_preview is exactly like play
	    # except our audio filename is different
	    $opening_preview = 1;
	    $command = "play";
	}


	if ($command eq "play") {
	    ## now only used for audio playback with "sox"

	    $audio_player = &config_get("audio_player");
	    if ($audio_player ne "sox") {
		unlink $pidfile;
		exit 0;
	    }

	    $endian = &get_endian;

	    $fps = $ARGV[2];
	    if ($fps == 0) {
		$fps = &config_get("default_fps");
	    }

	    $start = $ARGV[3];
	    $end = $ARGV[4];

	    $loop = 0;
	    if ($ARGV[5] != 0) {
		$loop = $ARGV[5];
	    }

	    if (!defined $ARGV[6] || $ARGV[6] == 0) {
		$arate = 44100;
	    } else {
		$arate = $ARGV[6];
	    }
	    if (!defined $ARGV[7] || $ARGV[7] == 0) {
		$achans = 2;
	    } else {
		$achans = $ARGV[7];
	    }

	    if (!defined $ARGV[8] || $ARGV[8] == 0) {
		$asamps = 16;
	    } else {
		$asamps = $ARGV[8];
		if ($asamps < 8) {
		    $asamps *= 8;
		}
	    }

	    if (!defined $ARGV[9]) {
		$signed = 1;
	    } else {
		$signed = int($ARGV[9]);
	    }

	    if (!defined $ARGV[10]) {
		$aendian = $endian;
	    } else {
		$aendian = $ARGV[10];
	    }

	    $aformat = ".raw";

	    if (defined($opening_preview)) {
		$audiofile = "$curworkdir/audiodump.pcm";
	    } else {
		$audiofile = "$curworkdir/audio";
	    }

	    if (-f $audiofile && $arate) {
		$audio_start = ($start - 1) / $fps;
		$audio_end = $end / $fps;

		$mute = "";
		if ($arate < 0) {
		    $mute = " -ao null  ";
		    $arate = -$arate;
		    $mute = " -v 0 ";
		}

		$audio_play_command = &config_get("audio_play_command");

		if ($audio_play_command eq "" or $audio_play_command eq '""') {
		    $time = ($audio_end - $audio_start);
		    select(undef, undef, undef, $time);
		}

		elsif ($audio_player eq "sox") {
		    $audio_effect = &config_get("audio_effect");
		    $trimcom = " trim $audio_start";

		    if (($audio_end > $audio_start) && $end > 0) {
			$trimcom .= " ".($audio_end - $audio_start);
		    } else {
			$trimcom .= " 10000000";
		    }

		    ## get_sox_format -
		    ## call get_sox_signed with guessed format (u for 8, s for 16)
		    ## returns signed e.g -e signed

		    $xsigned = &get_sox_signed($signed);

		    if ($aendian == $endian) {
			$aendian = "";
		    } else {
			$aendian = "-x";
		    }

		    if ($audio_effect eq "flanger1") {
			$audio_effect = "flanger 0.6 0.87 3.0 0.9 0.5 -s";
		    }
		    elsif($audio_effect eq "echo1") {
			$audio_effect = "echo 0.8 0.9 1000.0 0.3";
		    }
		    elsif($audio_effect eq "chorus1") {
			$audio_effect = "chorus 0.6 0.9 50.0 0.4 0.25 2.0 -t 60.0  0.32  0.4 1.3 -s";
		    }
		    elsif($audio_effect eq "reverb1") {
			$audio_effect = "reverb 1.0 600.0 180.0 200.0";
		    }
		    elsif($audio_effect eq "phaser1") {
			$audio_effect = "phaser 0.89 0.85 1.0 0.24 2.0 -t";
		    }
		    else {
			$audio_effect = "";
		    }

		    $sox_version = &get_sox_version;
		    $stype = &get_sox_samps($asamps);

		    ## TODO0
		    if ($sox_version < 13000000) {
			$nodither = "";
		    }
		    else {
			$nodither = "-D";
		    }

		    $syscom2 = "$audio_play_command $mute -c $achans -r $arate -$xsigned $aendian -t $aformat -$stype " .
			"$audiofile $trimcom $audio_effect";
		}
	    }

	    my $showed_err = 0;

	    do {
		if (! -d "$curworkdir" || -f "$curworkdir/.stoploop"
		    || -f "$curworkdir/stoploop" || ! -f $audiofile) {
		    $loop = 0;
		} else {
		    $syscom3 = $syscom2 . ">$nulfile 2>&1";
		    $retval = smog_system("$syscom3");
		    if ($retval > 255) {
			unless ($showed_err) {
			    print STDERR "Error playing audio !\n";
			    print STDERR "Failed command was: $syscom2\n";
			    smog_system("$syscom2");
			    $showed_err = 1;
			}
		    }
		}
	    } while ($loop);
	    unlink "$curworkdir/.stoploop";
	    unlink "$curworkdir/stoploop";
	    &sig_complete_audio;
	    exit 0;
	}

	if ($command eq "open_test") {
	    $mplay_command = $ARGV[2];
	    shift(@ARGV);
	    $command = "open";
	    $opentest = 1;
	}


	if ($command eq "open") {
	    ## this is used now only in 3 circumstances
	    # - the user chooses to disable instant opening
	    # - all of the decoder plugins fail
	    # - opening the audio track
	    #$DEBUG_OPEN = 1;

	    unlink "$curworkdir/pause";

	    my $file = $ARGV[2];
	    my $ss = "";

	    my $withsound = $ARGV[3]; # 0 : video only, 1 : video and audio, -1 : audio only

	    if (defined($ARGV[4])) {
		$img_ext = "." . (split(":", $ARGV[4]))[0];
		$ximg_ext = "." . (split(":", $ARGV[4]))[1];
		if ($ximg_ext eq "") {
		    $ximg_ext = $img_ext;
		}
	    }

	    &smog_chdir($curworkdir);

	    unless ($file =~ /^http/) {
		if (! -f $file && ! -d $file) {
		    &sig_error("$file", "could not be found", "$GUI_NAME was unable to open it.",
			       "Please check the file name and try again.");
		}
	    }

	    ## here we might set $mplay_command, $is_mpv
	    &get_file_info($file, 1, 1);

	    if (!$opentest) {
		$mplay_command = "";
	    }
	    if ($mplay_command eq "") {
		my $mp_command = &config_get("video_open_command");
		if ($mp_command ne "") {
		    my $mpc1 = (split(" ", $mp_command))[0];
		    if ($mpc1 =~ /mpv$/ || $mpc1 =~ /mpv\"$/) {
			$is_mpv = 1;
		    }
		}
		else {
		    # sets $is_mpv
		    $mp_command = "\"" . &get_mplayer_location . "\"";
		}
		if ($mp_command eq "") {
		    $panic = 1;
		}
		# try to force language to English
		$mplay_command = "LANGUAGE=en LANG=en $mp_command";
	    }

	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    if (defined($ARGV[5]) && $ARGV[5] > 0) {
		if (!$is_mpv) {
		    $ss = " -ss $ARGV[5] ";
		} else {
		    $ss = " --start=$ARGV[5] ";
		}
	    }
	    $stframes = "";
	    if (defined($ARGV[6]) && $ARGV[6] > 0) {
		$nframes = $ARGV[6]++;
		if (!$is_mpv) {
		    $stframes = " -frames $ARGV[6] ";
		} else {
		    $stframes = " --frames=$ARGV[6] ";
		}
	    }

	    if (defined($ARGV[7])) {
		$achans = $ARGV[7];
	    }

	    my $alang;
	    my @alanglist; ## TODO: get alanglist from get_file_details

	    $extra_params = $ARGV[8];
	    $band = "";

	    if ($is_mpv || $extra_params eq "nobandwidth") {
		$extra_params = "";
	    } else {
		## TODO: set in front end
		if ($extra_params eq "sendbandwidth") {
		    $bandwidth = &config_get("dl_bandwidth_K");
		    if ($bandwidth eq "") {
			$bandwidth = 10000000;
		    }
		    $band = "-bandwidth $bandwidth" . "000";
		    $extra_params = "";
		}
		if ($extra_params =~ /^alang (.*)/) {
		    if ($is_mpv) {
			$alang = "--alang=$1";
		    }
		    else {
			$alang = "-alang $1";
		    }
		    $extra_params = "";
		}
	    }

	    ## TODO: pass in the compression in the front end
	    $compression = &config_get("open_compression_percent");
	    if ($compression eq "") {
		$compression = 15;
	    }

	    # process video

	    # 3 other files we will use
	    $curtmpfile = "tempresult";
	    $audio_out = "audio";
	    $audio_in = "audiodump.pcm";

	    if ($img_ext eq ".png") {
		$compression = int($compression / 10.001);
	    } else {
		$quality = int(100. - $compression);
	    }

	    my $wavhead = "nowaveheader";
	    if ($is_mpv) {
		$wavhead = "--ao-pcm-waveheader=no";
	    }

	    if ($file =~ /:\/\//) {
		$is_remote = 2;
		$wavhead = ""; # need wav header, as user will probably finish by
		# quitting, thus we won't send full info
	    }

	    $aformnew = "";
	    if ($asamps == 32) {
		# cannot handle this yet, need to resample to s16
		if ($endian == 1) {
		    $aformnew = "-format s16le";
		}
		else {
		    $aformnew = "-format s16be";
		}
		$asamps=16;
	    }

	    if ($is_mpv) {
		$aformnew = "--audio-format=s16";
	    }

	    $threads = &smog_system_direct("$SYSBIN" . "grep processor /proc/cpuinfo | wc -l");
	    $threads = int($threads);
	    if ($threads == 0) {
		$threads = 1;
	    }

	    if ($type eq "jpeg" || $type eq "png") {
		$count = $frames;
		$img_ext = $ximg_ext;
	    }
	    else {
		if (!$achans) {
		    $channs = "";
		}
		else {
		    if ($is_mpv) {
			$channs = "--audio-channels=$achans";
		    }
		    else {
			$channs = "-channels $achans";
		    }
		}
		if ($is_mpv) {
		    if ($img_ext eq ".jpg") {
			$img_fmt="--vo=image --vo-image-format=jpg --vo-image-jpeg-quality=$quality";
		    }
		    else {
			$img_fmt="--vo=image --vo-image-format=png --vo-image-png-compression=$compression";
		    }
		}
		else {
		    if ($img_ext eq ".jpg") {
			$img_fmt = "-vo jpeg:quality=$quality";
		    }
		    else {
			$img_fmt = "-vo png:z=$compression";
		    }
		}

		if (!$is_mpv) {
		    $osd = "-osdlevel 0";
		    $threadopts = "-lavdopts o=threads=$threads";
		    $noframedrop = "-noframedrop";
		    $audcom = "-ao pcm:fast:$wavhead";
		}
		else {
		    $osd = "--osd-level=0";
		    $threadopts = "--vd-lavc-threads=$threads";
		    $noframedrop = "--framedrop=no";
		    $audcom = "--ao=pcm $wavhead";
		}

		my $syscomv = "";
		my $syscoma = "";
		my $has_vid = 0;
		my $has_aud = 0;

		if ($is_remote) {
		    if (!$is_mpv) {
			#video and audio
			$syscomb = "$mplay_command -quiet -noconfig all $band $osd $img_fmt $threadopts $audcom $channs " .
			    "$aformnew \"$file\" $extra_params <$nulfile";
		    } else {
			$syscomb = "$mplay_command --quiet --no-config $osd $img_fmt $threadopts $audcom $channs " .
			    "$aformnew \"$file\" $extra_params";
		    }

		    if ($DEBUG_OPEN) {
			print STDERR "vid open command for $handle is: $syscomb\n";
		    }

		    unless ($opentest == 1) {
			&sig_progress(0);
		    }

		    if (open (my $fh, "$syscomb |")) {
			while (defined(my $line = <$fh>)) {
			    if (!$has_vid && ($line =~ /^VO:/ || $line =~ /^VIDEO:/)) {
				my @info = split /  /, $line;

				$size = $info[2];
				$hsize = (split /x/,$size)[0];
				$vsize = (split /x/,$size)[1];

				if (!$is_mpv) {
				    $xbpp = int($info[3]);
				    $xfps = (split / /,$info[4])[0];
				} else {
				    $xbpp = 0;
				    $xfps = 0.;
				}
				$has_vid = 1;
				next;
			    }

			    if (!$has_aud && ($line =~ /^AO:/ || $line =~ /^AUDIO:/)) {
				my @info2 = split /  /, $line;
				if (!$is_mpv) {
				    $xarate = int($info2[1]);
				    $xachans = int($info2[3]);
				} else {
				    $xarate = int($info2[2]);
				    $xachans = int($info2[4]);
				}
				$has_aud = 1;
				next;
			    }
			}
			close $fh;
			if ($? >> 8) {
			    unlink $pidfile;
			    exit 1;
			}
		    }
		    else {
			unlink $pidfile;
			exit 1;
		    }
		} else {
		    ## not remote
		    if (!$is_mpv) {
			if ($withsound == 0 || $withsound == 1) {
			    #video only
			    $syscomv = "$mplay_command -quiet -noconfig all $osd $img_fmt $threadopts -fps 1000000. $ss " .
				"$stframes $noframedrop -nosound \"$file\" $extra_params <$nulfile";
			}
			if ($withsound != 0) {
			    #audio only
			    $syscoma = "$mplay_command -quiet -noconfig all  -novideo $threadopts -fps 1000000. $ss " .
				"$stframes $alang $audcom $aformnew $channs \"$file\" $extra_params <$nulfile";
			}
		    } else {
			if ($withsound == 0 || $withsound == 1) {
			    $syscomv = "$mplay_command --no-config --quiet $osd $img_fmt $threadopts " .
				"$ss $stframes $noframedrop --untimed --no-audio \"$file\" $extra_params";
			}
			if ($withsound != 0) {
			    $syscoma = "$mplay_command --no-config --quiet --no-video $threadopts $ss $stframes " .
				"$alang $audcom $aformnew $channs \"$file\" $extra_params";
			}
		    }

		    if ($DEBUG_OPEN) {
			print STDERR "vid open command for $handle is: $syscomv\n";
			print STDERR "aud open command for $handle is: $syscoma\n";
		    }

		    unless ($opentest == 1) {
			&sig_progress(0);
		    }

		    if ($syscoma) {
			if (open (my $fh, "$syscoma 2>$nulfile |")) {
			    ## write audio file first for opening preview
			    while (defined(my $line = <$fh>)) {
				if ($line =~ /^AO:/ || $line =~ /^AUDIO:/) {
				    my @info2 = split /  /, $line;
				    if (!$is_mpv) {
					$xarate = int($info2[1]);
					$xachans = int($info2[3]);
				    } else {
					$xarate = int($info2[2]);
					$xachans = int($info2[4]);
				    }
				    $has_aud = 1;
				}
			    }
			    close $fh;
			    if ($? >> 8) {
				unlink $pidfile;
				exit 1;
			    }
			}
		    }

		    if ($syscomv) {
			if (open (my $fh, "$syscomv 2> $nulfile |")) {
			    ## write audio file first for opening preview
			    while (defined(my $line = <$fh>)) {
				if ($line =~ /^VO:/ || $line =~ /^VIDEO:/) {
				    my @info = split /  /, $line;
				    $size = $info[2];
				    $hsize = (split /x/,$size)[0];
				    $vsize = (split /x/,$size)[1];

				    if (!$is_mpv) {
					$xbpp = int($info[3]);
					$xfps = (split / /,$info[4])[0];
				    } else {
					$xbpp = 0;
					$xfps = 0.;
				    }
				    $has_vid = 1;
				}
			    }
			    close $fh;
			    if ($? >> 8) {
				unlink $pidfile;
				exit 1;
			    }
			}
		    }

		    if (!$has_aud && !$has_vid) {
			unlink $pidfile;
			exit 1;
		    }
		}

		if ($xbpp > 0) {
		    $bpp = $xbpp;
		}
		if ($xfps > 0.) {
		    $fps = $xfps;
		}
		if ($xarate > 0) {
		    $arate = $xarate;
		}
		if ($xachans > 0) {
		    $achans = $xachans;
		}

		if (! -d $curworkdir) {
		    #curworkdir can be removed by cancel
		    exit 1;
		}

		if ($withsound eq "-1") {
		    $count = 0;
		}
		else {
		    # double check number of frames
		    $count = &count_frames(1, $img_ext);
		}
	    }

	    # if the last frame has zero size, delete it
	    if ($count > 0) {
		$name = &mkname($count);
		while ($count > 0 && -f "$name$img_ext" && -z "$name$img_ext") {
		    unlink "$name$img_ext";
		    $name = &mkname(--$count);
		}
		if ($count == 0) {
		    if ($img_ext == ".png") {
			&sig_error("Your version of mplayer/ffmpeg may be broken. ",
				   "See http://bugzilla.mplayerhq.hu/show_bug.cgi?id=2071",
				   " ",
				   "You can work around this by switching to jpeg output in Preferences/Decoding.");
		    }
		}

		if (&location("identify") ne "") {
		    # double check image size
		    $name = &mkname(1);
		    $imresact = "none";
		    &get_image_size("$name$img_ext");
		    if ($panic || $hsize==-1) {
			unlink $pidfile;
			&sig_error("This does not appear to be a valid video or image file\n",
				   "The file may be damaged or broken, or in an unrecognised format.\n",
				   "$GUI_NAME was unable to open it.");
		    }
		}
	    }

	    if (-f $audio_in) {
		&smog_rename($audio_in, $audio_out);
		$af_size = -s $audio_out;
	    }

	    if ($img_ext eq ".png" || $type eq "png") {
		$bpp = 32;
	    }

	    if ($count == 0 || $type eq "jpeg" || $type eq "png" || $type eq "image" || $type eq "Audio") {
		# we could have audio or images
		if (-f $audio_out) {
		    # just in case...
		    $type = "Audio";
		}
		if (! -d $curworkdir) {
		    #curworkdir can be removed by cancel
		    exit 1;
		}
		if ($af_size > 0 || $count > 0) {
		    &sig_complete($handle, $count, $type, $hsize, $vsize, $bpp, $fps, $f_size, $arate,
				  $achans, $asamps,
				  $signed, $endian, $af_size, $gamma_type, $title, $author, $comment);
		    exit 0;
		}
		# should have audio then
		if ($type ne "Audio") {
		    if (!defined($DEBUG_OPEN) && $withsound >= 0) {
			print STDERR "\nFailed to open file - I tried:\n\n:";
			if (!$is_remote) {
			    print STDERR "$syscomv;\nSsyscoma;";
			}
			else {
			    print STDERR "Ssyscomb;\n";
			}
			if (!$is_mpv) {
			    print STDERR "\nMaybe you are missing a library in mplayer (or it is not a valid media file) ?\n";
			}
			else {
			    print STDERR "\nMaybe you are missing a library in mpv (or it is not a valid media file) ?\n";
			}
		    }
		    &sig_error("This does not appear to be a valid video or image file",
			       "$GUI_NAME was unable to open it.",
			       "",
			       "Check the terminal window for more details.");
		}
	    }

	    # mplayer seems to sometimes output one extra frame for jpg
	    if ($USE_WARNINGS) {
		no warnings 'once';
	    }
	    if (defined($MPLAYER_EXTRA_OPEN_FRAME_BUG) && ($count == $frames + 1) 
		&& (($frames eq "") || (!($frames eq "") && ($count < $ARGV[4]) || ($ARGV[3] == 0)))) {
		$name = &mkname($count);
		unlink "$name$img_ext";
		$count--;
	    }
	    if ($USE_WARNINGS) {
		use warnings 'once';
	    }
	    unless ($frames eq "") {
		$tfps = $fps;
		if ($fps < 1.0) {
		    $tfps = 1.0;
		}
		if ($ARGV[3] >= (1.0 / $tfps)) {
		    # if we opened a selection, mplayer wrongly outputs frame 1,
		    # so we usually need to delete it

		    #TODO ** - check if this is still the case

		    for ($i=2; $i <= $count; $i++) {
			$from = &mkname($i);
			$to = &mkname($i-1);
			&smog_rename("$from$img_ext", "$to$img_ext");
		    }
		    $count--;
		}
	    }

	    $af_size = -s $audio_out;
	    if (-d $curworkdir) {
		&sig_complete($handle, $count, $type, $hsize, $vsize, $bpp, $fps, $f_size, $arate, $achans,
			      $asamps, $signed, $endian, $af_size, $gamma_type, $title, $author, $comment);
	    }
	    exit 0;
	}


	if ($command eq "open_tv_card") {
	    $chanstr = $ARGV[2];
	    $devstr = $ARGV[3];
	    $fifofile = $ARGV[4];

	    $inputstr = $sizestr = $fpsstr = $driverstr = $outfmt = "";

	    if (defined($ARGV[5])) {
		$inputstr = ":input=$ARGV[5]";
	    }
	    if (defined($ARGV[6]) && defined($ARGV[7])) {
		if ($ARGV[6] > 0 && $ARGV[7] > 0) {
		    $sizestr = ":width=$ARGV[6]:height=$ARGV[7]";
		}
	    }

	    if (defined($ARGV[8])) {
		if ($ARGV[8] > 0.) {
		    $fpsstr = ":fps=$ARGV[8]";
		}
	    }

	    if (defined($ARGV[9])) {
		if ($ARGV[9] ne "autodetect") {
		    $driverstr = ":driver=$ARGV[9]";
		}
	    }

	    if (defined($ARGV[10])) {
		if ($ARGV[10] ne "autodetect") {
		    $outfmt = ":outfmt=$ARGV[10]";
		}
	    }

	    my $mp = "\"". &get_mplayer_location . "\"";

	    ## note does not function with mpv since it cannot convert to yuv4mpeg (complains about missing pixel_fmt)
	    my $com = "$mp -really-quiet tv://$chanstr -tv device=$devstr$inputstr$driverstr$sizestr$outfmt$fpsstr " .
		"-vo yuv4mpeg:file=$fifofile >$nulfile 2>&1 <$nulfile";
	    my $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }
	    exit 0;
	}

	if ($command eq "open_fw_card") {
	    my $cardno = $ARGV[2];
	    #$cache = $ARGV[3];
	    my $fifofile = $ARGV[4];
	    my $mp = "\"". &get_mplayer_location . "\"";
	    ## note does not function with mpv since it cannot convert to yuv4mpeg (complains about missing pixel_fmt)
	    my $com = "dvgrab -s 0 -noavc -card $cardno -o - 2>$nulfile | $mp - -really-quiet -demuxer lavf " .
		"-vo yuv4mpeg:file=$fifofile >$nulfile 2>&1";
	    my $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }
	    exit 0;
	}

	if ($command eq "close") {
	    if (! -d $curworkdir || (! -f "$curworkdir/header.lives"
				     && ! -f "$curworkdir/can_remove.tmp"
				     && !&is_specialdir($handle))) {
		print STDERR "\n\nsmogrify: WARNING ! asked to close dir $curworkdir\n";
		if (! -d $curworkdir) {
		    print STDERR "which does not exist\n\n";
		}
		else {
		    print STDERR "which appears not to belong to us, there is no marker file\n";
		}
		exit 1;
	    }
	    my $method = "delete";
	    if (defined($ARGV[2])) {
		$method = $ARGV[2];
	    }
	    if (defined(open IN, "<", $pidfile)) {
		read IN, $target_pid, 8;
		close IN;
		if ($target_pid) {
		    my $sig = "KILL";
		    # cancel any processing
		    &kill_child_pids($target_pid, $sig);
		}
	    }
	    if ($method eq "giotrash") {
		&smog_system("gio trash \"$curworkdir\"");
	    }
	    else {
		remove_tree($curworkdir);
	    }
	    exit 0;
	}


	if ($command eq "save") {
	    my $cmd = "";

	    unlink "$curworkdir/pause";
	    &smog_chdir($curworkdir);

	    my $plugin;
	    my $is_native = 0;
	    my @plstr = split(/:/, $ARGV[2]);

	    if ($plstr[0] eq "native") {
		$is_native = 1;
		$plugin = $plstr[1];
	    }
	    else {
		$plugin = $plstr[0];
	    }

	    our $fps = $ARGV[3];
	    our $nfile = $ARGV[4];

	    # check the file is writable
	    unless ($nfile eq "" || &is_writeable($nfile, 0)) {
		&sig_error("Unable to open output file !", "$GUI_NAME could not write to $nfile.");
	    }

	    our $start = $ARGV[5];
	    my $is_linked = 0;

	    my $linksdir = "$curworkdir/lives-symlinks/";

	    if ($start == -1) {
		# special value which tells us we are dealing with symlinks
		$start = 1;
		$is_linked = 1;
	    }

	    our $end = $ARGV[6];
	    our $arate = $ARGV[7];
	    our $achans = $ARGV[8];
	    our $asamps = $ARGV[9];

	    my $signed;
	    my $aendian;

	    if (defined($ARGV[10])) {
		$signed = $ARGV[10] & 1;
		$aendian = $ARGV[10] & 2; # value of 2 means bigendian
		$aendian = !$aendian; #endian is revesed here (0 means bigend)
	    }
	    else {
		$signed = 1;
		if ($asamps == 8) {
		    $signed = 0;
		}
		$aendian = &get_endian;
	    }

	    my $sox_version = &get_sox_version;
	    my $asigned = &get_sox_signed(!$signed);
	    my $aud_start;
	    my $aud_end;

	    if (defined($ARGV[11])) {
		$aud_start = $ARGV[11];
	    }
	    else {
		$aud_start = ($start - 1.) / ($fps * 1.);
	    }
	    if (defined($ARGV[12])) {
		$aud_end = $ARGV[12];
	    }
	    else {
		$aud_end = ($end * 1.) / ($fps * 1.);
	    }

	    our $img_ext = &get_img_ext($curworkdir, $start);

	    # get image size ($hsize x $vsize)
	    $imresact = "none";

	    my $firstframe = &mkname($start);

	    &get_image_size("$firstframe$img_ext");

	    if ($panic || $hsize == -1) {
		unlink $pidfile;
		exit 1;
	    }

	    our $otype = &config_get("output_type");
	    my $encoder = &config_get("encoder");

	    $DEBUG_ENCODERS = 1;

	    our $audiofile = "";

	    if ($get_rfx == 0) {
		unlink "audiodump.wav";

		my $areq = &get_form_request($plugin);
		#prepare audio stream if requested
		if (-f "$curworkdir/audio" && $arate > 0) {
		    my $origaudio = $audiofile = my $audio_in = "$curworkdir/audio";
		    if ($areq & 1 || $aud_start != 0.) {
			# encode sound up to the next nearest second
			# seems to be the norm...
			my $aud_length = ($aud_end - $aud_start);
			$aud_length = int($aud_length + 1.0);
			my $aud_end = $aud_start + $aud_length;

			# clip the (raw) audio

			$audiofile = $audio_in = &clip_audio($aud_start, $aud_end);
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}

			#pad to end with silence
			my $desired_length = $aud_length * $arate * $achans * $asamps / 8;
			&append_silence(0, $desired_length, $asamps, $achans, $signed, $aendian,
					$audiofile);
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}

			if ($aud_start != 0. && !($areq & 1)) {
			    if (&config_get("conserve_space") eq "true") {
				unlink $origaudio;
			    }
			    else {
				&smog_rename( $origaudio, "$origaudio.origbak");
			    }
			    &smog_rename($audiofile, $origaudio);
			    $audiofile = $audio_in = $origaudio;
			}}
		    if ($areq & 2) {
			# convert raw audio to wav
			$audio_out = "$curworkdir/audiodump.wav";
			&convert_audio_to_wav;
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}
			unlink $audio_in;
			$audiofile = $audio_out;
		    }}}

	    ## TODO: pass  all as a single array, so it should work with any external encoder

	    # plugins need: $atype ($aq), $nfile,
	    # $exe ? $otype, %img_ext,end, start, fps
	    my $atype = &config_get("encoder_acodec");
	    our $aq = $atype;

	    if (!$is_native) {
		my $extra_opts = "";
		my $com;
		my $fields = "$fps \"$nfile\" $start $end $img_ext $otype $atype $hsize $vsize";
		$fields .= " $DEBUG_ENCODERS";
		$fields .= " $arate $achans $asamps $signed";

		if ($plugin =~ /multi_encoder[0-9]*/) {
		    $extra_opts="-v";
		}

		if ($get_rfx == 0) {
		    $fields .= @ARGV[13 .. -1];
		    $com = "\"$plugin\" $extra_opts encode $fields";
		}
		else {
		    $com = "\"$plugin\" get_rfx $fields";
		}

		$smerr = &smog_system($com);
		&sig_complete;
		exit 0;
	    }

	    if (-f ".comment") {
		open IN, "< .comment";
		read IN, $string, 1040;
		close IN;
		unlink ".comment";
	    }
	    my @tmp = split(/\|\|\%/,$string);

	    our $title = $tmp[0];
	    our $author = $tmp[1];
	    our $comment = $tmp[2];

	    chomp($comment);

	    $command = "encode";

	    #print STDERR "requiring the plugin\n";
	    require $plugin;
	    #print STDERR "requiring the plugin\n";

	    unlink $pidfile;

	    &sig_complete;
	    exit 0; # just in case
	}


	if ($command eq "link_frames") {
	    unlink "$curworkdir/pause";
	    &smog_chdir($curworkdir);

	    $start = $ARGV[2];
	    $end = $ARGV[3];

	    $astart = $ARGV[4];
	    $aend = $ARGV[5];

	    $arate = $ARGV[6];
	    $achans = $ARGV[7];
	    $asamps = $ARGV[8];
	    $asigned = $ARGV[9];
	    $aendian = $ARGV[10];

	    $from_handle = $ARGV[11];

	    my $i;
	    my $audiofile = "audio";

	    if ($from_handle eq "") {
		$linksdir = "$curworkdir/lives-symlinks/";

		if (-d $linksdir) {
		    remove_tree($linksdir);
		}

		make_path $linksdir;
	    }
	    else {
		$linksdir = $curworkdir;
		$handle = $from_handle;
		$curworkdir = "$workdir/$handle/";
	    }

	    # copy a slice of the audio file into our links dir
	    # this allows us to resample it
	    # and also aligns the start of audio with new frame 1

	    unless ($aend == 0.) {
		my $ocurworkdir = $curworkdir;
		my $ofrom_handle = $from_handle;
		my $ostart = $start;
		my $oend = $end;

		$start = $astart;
		$end = $aend;
		$where = 0.;
		$from_handle = $handle;
		$curworkdir = $linksdir;
		$audio_to = "$curworkdir/audio";
		&insert_audio($asamps, $achans, $asigned, $aendian, 0);
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}

		$curworkdir = $ocurworkdir;
		$from_handle = $ofrom_handle;
		$start = $ostart;
		$end = $oend;
	    }

	    $img_ext = &get_img_ext($curworkdir, $start);
	    $fstype = &get_fs_type($curworkdir);

	    for ($i = 1; $i <= ($end - $start + 1); $i++) {
		$name = &mkname($i);
		$from = &mkname($i + $start - 1);
		if (-f "$curworkdir/$from$img_ext") {
		    if ($fstype eq "vfat" || $fstype eq "fat32" || $fstype eq "msdos") {
			&smog_copy("$curworkdir/$from$img_ext", "$linksdir/$name$img_ext");
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}
		    }
		    else {
			$smerr =  &smog_system ("$SYSBIN" . "ln \"$curworkdir/$from$img_ext\" " .
						"\"$linksdir/$name$img_ext\"");
			if ($smerr) {
			    &sig_system_error("Linking \"$curworkdir/$from$img_ext\" to " .
					      "\"$linksdir/$name$img_ext\"", $smerr);
			    exit 1;
			}}}
		&sig_progress($i);
	    }

	    unless ($from_handle eq "") {
		$curworkdir = $linksdir;
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "resize_all") {
	    &smog_chdir($curworkdir);

	    unlink "$curworkdir/pause";
	    $end = $ARGV[2];

	    my $iwidth = $ARGV[3];
	    my $iheight = $ARGV[4];

	    $img_ext = "." . $ARGV[5];
	    $img_prefix = &get_img_prefix($img_ext);
	    $resize_ext = ".mgk";
	    $blankname = "blank.jpg";

	    my $letterbox = 0;

	    if (defined($ARGV[6]) && $ARGV[6]>0) {
		# letterboxen - next values are image-in-frame size
		$letterbox = 1;
		$owidth = $iwidth;
		$oheight = $iheight;
		$iwidth = $ARGV[6];
		$iheight = $ARGV[7];
		$in_ext = $resize_ext;
		$out_ext = $img_ext;
	    }

	    &clean_old($handle);

	    if ($letterbox) {
		# make a background frame
		if (!defined($bgcolour)) {
		    $bgcolour = "#000000";
		}

		$com = "$smog_convert_command -size $owidth"."x$oheight\\! xc:$bgcolour $blankname >$nulfile 2>&1";
		$smerr = &smog_system($com);
		if ($smerr) {
		    &sig_system_error($com, $smerr);
		    unlink $blankname;
		    exit 1;
		}
	    }

	    for ($i = 1; $i <= $end; $i++) {
		$name = &mkname($i);
		&smog_copy("$name$img_ext", "$name.bak");
		if ($panic) {
		    if ($letterbox) {
			&try_to_recover($start, $i - 1);
			unlink $blankname;
		    }
		    if ($DEBUG_SMOGRIFY) {
			print STDERR "smogrify debug - rename failed ($!): " .
			    "\"$curworkdir/$name$img_ext\",\"$curworkdir/$name.bak\"\n";
		    }
		    &sig_system_error("Renaming \"$curworkdir/$name$img_ext\" to " .
				      "\"$curworkdir/$name.bak\"");
		    exit 1;
		}

		if ($iwidth > 0 && $iheight > 0) {
		    &resize_frame($name, $iwidth, $iheight);
		}

		if ($panic) {
		    if ($letterbox) {
			&try_to_recover($start, $i);
			unlink $blankname;
		    }
		    unlink $pidfile;
		    exit 1;
		}

		if ($letterbox) {
		    &letterbox_frame($name, abs($iwidth), abs($iheight), $owidth, $oheight);
		}

		if ($panic) {
		    if ($letterbox) {
			&try_to_recover($start, $i);
			unlink $blankname;
		    }
		    unlink $pidfile;
		    exit 1;
		}

		&sig_progress($i);
	    }

	    $start=1;

	    unless ($letterbox) {
		&mv_mgk;
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
	    }
	    else {
		unlink $blankname;
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "backup") {
	    $withaudio = $ARGV[2];
	    $start = $ARGV[3];
	    $end = $ARGV[4];

	    if ($withaudio == 0) {
		$audio = "";
	    }
	    else {
		$audio = "audio";
	    }

	    $nfile = $ARGV[5];

	    unlink "$curworkdir/pause";
	    &smog_chdir($curworkdir);

	    # check the file is writable
	    unless (&is_writeable($nfile, 0)) {
		&sig_error("Unable to open output file !", "$GUI_NAME could not write to $nfile.");
	    }

	    unlink glob "*.tar";
	    $com="tar --ignore-failed-read -cf header.tar \"$audio\" header* extended* event.* " .
		"subs.* file* 2>$nulfile";
	    &smog_system ($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    &sig_progress(0);

	    $com = "tar -cf temp.tar header.tar 2>.tar_err";
	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    $com = "tar --exclude=*.bak --exclude=*.tar --exclude=./.* --exclude=*.mgk --exclude=*.tmp " .
		"--exclude=audioclip* -rf temp.tar * 2>>.tar_err";
	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    $com = "gzip -S .lv1 temp.tar";

	    $smerr = &smog_system ($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    unlink "temp.tar";

	    move ("temp.tar.lv1", "$nfile") or die $!;

	    unlink <header.tar 0*.tar>;
	    if (-f ".tar_err") {
		open IN, "< .tar_err";
		read IN, $tarerr, 255;
		close IN;
		unless ($tarerr eq "") {
		    &sig_error("Error creating new backup.", $tarerr);
		    unlink glob "*.tar .tar_err";
		    exit 1;
		}
	    }
	    $size = -s $nfile;
	    &sig_complete($size);
	    exit 0;
	}

	if ($command eq "restore") {
	    $nfile = $ARGV[2];

	    &smog_chdir($curworkdir);

	    $com = "tar -zxf \"$nfile\" 2>$nulfile";
	    &smog_system ($com);

	    if ($smerr) {
		&sig_system_error("$com", $smerr);
		exit 1;
	    }

	    unless (-f "header.tar" || -f "header" || -f "header.lives") {
		&sig_error("This does not appear to be a valid backup file","$GUI_NAME was unable to open it.");
	    }

	    opendir DIR, $curworkdir;
	    while ($file = readdir(DIR)) {
		if ($file =~ /.tar$/) {
		    &smog_system("tar -xf $file 2>$nulfile");
		    unlink $file;
		}
	    }
	    closedir DIR;

	    &sig_complete;
	    exit 0;
	}

	if ($command eq "reorder") {
	    unlink "$curworkdir/pause";

	    $img_ext = "." . $ARGV[2];
	    $img_prefix = &get_img_prefix($img_ext);

	    if (!defined($ARGV[3])) {
		$endian = &get_endian;
	    }
	    else {
		$endian = $ARGV[3];
	    }
	    if (defined $ARGV[4]) {
		$newwidth = $ARGV[4];
	    }
	    if (defined $ARGV[5]) {
		$newheight = $ARGV[5];
	    }
	    if (defined $ARGV[6]) {
		if ($ARGV[6] == 1) {
		    $leave_bak = 1;
		}
	    }
	    if (defined $ARGV[7]) {
		$old_end = $ARGV[7];
	    }

	    $letterbox=0;

	    if (defined $ARGV[8] && $ARGV[8] != 0) {
		#letterbox size
		$letterbox = 1;
		$owidth = $newwidth;
		$oheight = $newheight;

		$newwidth = $ARGV[8];
		$newheight = $ARGV[9];
		$in_ext = ".mgk";
		$out_ext = $img_ext;
	    }

	    $newframe = -1;
	    $event_file = "$curworkdir/event.frames";
	    $resize_ext = ".tmp";
	    &smog_chdir($curworkdir);

	    unless ($leave_bak == 1) {
		&clean_old($handle);
	    }

	    $blankname = "blank.jpg";

	    if (defined(open IN, "<", $event_file)) {
		$fcount = 1;
		read IN, $val, 4;
		$pstart = &getint($val);
		$count = $pstart;

		if ($letterbox && !$leave_bak) {
		    # make a background frame
		    if (!defined($bgcolour)) {
			$bgcolour = "#000000";
		    }

		    $com = "$smog_convert_command -size $owidth"."x$oheight\\! xc:$bgcolour $blankname>$nulfile 2>&1";
		    $smerr = &smog_system($com);
		    if ($smerr) {
			&sig_system_error($com,  $smerr);
			exit 1;
		    }
		}

		while ($newframe != 0) {
		    read IN, $val, 4;
		    $newframe = &getint($val);
		    if ($newframe > 0) {
			$from = &mkname($newframe);
			if (-f "$curworkdir/$from$img_ext") {
			    $to = &mkname($count);

			    if ($newwidth > 0 && $newheight > 0) {
				&resize_frame($from,$newwidth,$newheight);
				if ($panic) {
				    if ($letterbox) {
					&try_to_recover($start,$i-1);
					unlink $blankname;
				    }
				    unlink $pidfile;
				    exit 1;
				}

				$smerr=rename "$curworkdir/$from$resize_ext","$curworkdir/$to.mgk";
				if (!$smerr) {
				    if ($letterbox) {
					&try_to_recover($start,$i);
					unlink $blankname;
				    }
				    &sig_system_error("Renaming \"$curworkdir/$from$resize_ext\" " .
						      "to \"$curworkdir/$to.mgk\"", $!);
				    exit 1;
				}
			    }
			    else {
				unless (($from eq $to) || $letterbox) {
				    &smog_copy("$curworkdir/$from$img_ext", "$curworkdir/$to.mgk");
				    if ($panic) {
					unlink $pidfile;
					exit 1;
				    }
				}
			    }

			    #now resized are in .mgk
			    if ($letterbox) {
				if ($leave_bak) {
				    #letterboxing was done with resize
				    unless ($from eq $to) {
					&smog_copy("$curworkdir/$from$img_ext",
						   "$curworkdir/$to$img_ext.2");
					if ($panic) {
					    &try_to_recover($start, $i);
					    unlink $pidfile;
					    exit 1;
					}
					&smog_copy("$curworkdir/$from.mgk", "$curworkdir/$to.mgk.2");
					if ($panic) {
					    &try_to_recover($start, $i);
					    unlink $pidfile;
					    exit 1;
					}
				    }
				}
				else {
				    &smog_copy("$curworkdir/$to$img_ext", "$curworkdir/$to.bak");
				    if ($panic) {
					&try_to_recover($start, $i-1);
					unlink $pidfile;
					unlink $blankname;
					exit 1;
				    }
				    # make $to.mgk $to.img_ext
				    &letterbox_frame($to, abs($newwidth), abs($newheight),
						     $owidth, $oheight);

				    if ($panic) {
					&try_to_recover($start, $i - 1);
					unlink $pidfile;
					unlink $blankname;
					exit 1;
				    }
				    #lb are in .img
				}
			    }
			}
			$count++;
		    }
		    &sig_progress($fcount++);
		}
		close IN;

		if ($letterbox && !$leave_bak) {
		    unlink $blankname;
		}
	    }
	    else {
		&sig_error;
	    }

	    $start = $pstart;
	    $new_count = --$count;
	    $end = $count;

	    unless ($letterbox) {
		# mv mgk -> $img_ext
		&mv_mgk;
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
	    }

	    if ($letterbox && $leave_bak) {
		# because of potential ovewrite we made .mgk2 (resized/no letterbox) and $img_ext.2 (resized)
		# now copy these
		unless (!defined($pidfile) || $pidfile eq "") {
		    # don't want to get killed in this stage...
		    unlink $pidfile;
		}
		if ($smerr) {
		    &sig_system_error($com, $smerr);
		    exit 1;
		}
		for ($i = $start; $i <= $end; $i++) {
		    $name = &mkname($i);
		    if (-f "$curworkdir/$name.mgk.2") {
			if (-f "$curworkdir/$name$img_ext.2") {
			    unlink "$curworkdir/$name$img_ext";
			    $smerr = &smog_rename("$curworkdir/$name$img_ext.2",
						  "$curworkdir/$name$img_ext");
			    if (!$smerr) {
				if ($letterbox) {
				    &try_to_recover($start, $end);
				}
				&sig_system_error("Renaming \"$curworkdir/$name$img_ext.2\" to " .
						  "\"$curworkdir/$name$img_ext\"", $!);
				exit 1;
			    }
			}
			unlink "$curworkdir/$name.mgk";
			$smerr = &smog_rename("$curworkdir/$name.mgk.2", "$curworkdir/$name.mgk");

			if (!$smerr) {
			    if ($letterbox) {
				&try_to_recover($start, $end);
			    }
			    &sig_system_error("Renaming \"$curworkdir/$name.mgk.2\" to " .
					      "\"$curworkdir/$name.mgk\"", $!);
			    exit 1;
			}}}}

	    if ($end < $old_end) {
		# remove excess frames
		for ($i = $end + 1; $i <= $old_end; $i++) {
		    $name = &mkname($i);
		    if (-f "$curworkdir/$name$img_ext") {
			unlink "$curworkdir/$name.bak";
			&smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$name.bak");
		    }
		}
		&sig_progress($i);
	    }

	    &sig_complete($new_count);
	    exit 0;
	}


	if ($command eq "deorder") {
	    $curworkdir = "$workdir/$handle";
	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $frames = $ARGV[4];
	    $img_ext = "." . $ARGV[5];
	    if (defined $ARGV[6]) {
		$leave_bak = $ARGV[6];
	    } else {
		$leave_bak = 0;
	    }

	    &smog_chdir($curworkdir);

	    $oend = $end;

	    if ($end > $frames) {
		$end = $frames;
	    }

	    &undo(!$leave_bak);
	    # will abort on failure

	    if ($frames < $oend) {
		# frames were upsampled
		for ($i = $frames + 1; $i <= $oend; $i++) {
		    $name = &mkname($i);
		    if (!$leave_bak) {
			unlink "$curworkdir/$name$img_ext";
		    } else {
			&smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$name.mgk");
		    }
		}
	    } else {
		# frames were downsampled
		for ($i = $end + 1; $i <= $frames; $i++) {
		    $name = &mkname($i);
		    if (!$leave_bak) {
			if (-f  "$curworkdir/$name.bak") {
			    &smog_rename("$curworkdir/$name.bak", "$curworkdir/$name$img_ext");
			}
		    } else {
			&smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$name.mgk");
		    }
		}
	    }

	    if (!$leave_bak) {
		#remove .mgk files from undo
		&clean_old($handle);
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "cut") {
	    unlink "$curworkdir/pause";
	    &clean_old($handle);

	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $cut_audio = $ARGV[4];
	    $frames = $ARGV[5];
	    $img_ext = "." . $ARGV[6];
	    &cut($start, $end);

	    if ($cut_audio) {
		$fps = $ARGV[7];
		$arate = $ARGV[8];
		$achans = $ARGV[9];
		$asamps = $ARGV[10];
		if ($arate * $asamps * $achans) {
		    $start = ($start - 1) / $fps;
		    $end = $end / $fps;
		    &cut_audio;
		    if ($panic) {
			unlink $pidfile;
			exit 1;
		    }
		}
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "clear_clipboard") {
	    $frames = $ARGV[2];
	    if (int($handle) < 65535 || ! -d $curworkdir) {
		&sig_complete;
		exit 1;
	    }
	    unlink glob "$curworkdir/*.png $curworkdir/*.jpg $curworkdir/audio";

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "reverse") {
	    unlink "$curworkdir/pause";
	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $img_ext = "." . $ARGV[4];
	    &reverse;
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "undo") {
	    unlink "$curworkdir/pause";

	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $img_ext = "." . $ARGV[4];
	    &undo;
	    # will abort on failure
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "redo") {
	    unlink "$curworkdir/pause";

	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $img_ext = "." . $ARGV[4];

	    &mv_mgk(1);
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "fs_preview") {
	    $win = $ARGV[2];
	    $hsize = $ARGV[3];
	    $vsize = $ARGV[4];
	    $start_time = $ARGV[5];
	    $preview_frames = $ARGV[6];
	    $volume = $ARGV[7];
	    $file = $ARGV[8];
	    $extra_params = $ARGV[9];
	    $mplayer_command = "\"". &get_mplayer_location . "\"";
	    $threads = &smog_system_direct("$SYSBIN" . "grep processor /proc/cpuinfo | wc -l");
	    $threads = int($threads);
	    if ($threads == 0) {
		$threads = 1;
	    }
	    if ($preview_frames >= 0) {
		if (!$is_mpv) {
		    if ($preview_frames > 0) {
			$framestr = "-frames $preview_frames";
		    } else {
			$framestr = "";
		    }
		    $threadopts = "-lavdopts o=threads=$threads";
		    $com = "$mplayer_command -vo x11 -zoom -really-quiet -noconfig all -osdlevel 0 -wid $win $threadopts " .
			"-vf dsize=$hsize:$vsize:0 -ss $start_time $framestr \"$file\" -volume $volume $extra_params " .
			">$nulfile 2>&1 <$nulfile";
		} else {
		    if ($preview_frames > 0) {
			$framestr = "--frames=$preview_frames";
		    } else {
			$framestr = "";
		    }
		    $threadopts="--vd-lavc-threads=$threads";
		    $com = "$mplayer_command --no-config --vo=x11 --really-quiet --osd-level=0 --wid=$win $threadopts " .
			"--start=$start_time $framestr \"$file\" --volume=$volume $extra_params >$nulfile 2>&1";
		}
	    } else {
		#audio only
		if (!$is_mpv) {
		    $com = "$mplayer_command -really-quiet -noconfig all -volume $volume -novideo -vo null \"$file\" " .
			"$extra_params >$nulfile 2>&1 <$nulfile";
		} else {
		    $com = "$mplayer_command --really-quiet --no-config --volume=$volume --no-video --vo=null \"$file\" " .
			"$extra_params >$nulfile 2>&1";
		}
	    }
	    #print "COM is $com\n";
	    &smog_system($com);
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "mv_mgk") {
	    unlink "$curworkdir/pause";
	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $img_ext = "." . $ARGV[4];
	    if (defined($ARGV[5])) {
		$leave_bak = $ARGV[5];
	    }

	    &mv_mgk;
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "export_audio") {
	    $audio_start = $ARGV[2];
	    $audio_end = $ARGV[3];
	    $arate = $ARGV[4];
	    $achans = $ARGV[5];
	    $asamps = $ARGV[6];
	    $asigned = $ARGV[7];
	    $nrate = $ARGV[8];
	    $nfile = $ARGV[9];

	    # check the file is writable
	    unless (&is_writeable($nfile, 0)) {
		&sig_error("Unable to open output file !", "$GUI_NAME could not write to $nfile.");
	    }

	    $sox_version = &get_sox_version;
	    $asigned = &get_sox_signed($asigned);

	    if ($audio_end > 0.) {
		$audio_in = &clip_audio($audio_start, $audio_end);
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
	    } else {
		$audio_in = "$curworkdir/audio";
	    }

	    # convert raw audio to wav
	    $audio_out = "$curworkdir/audiodump.wav";
	    &convert_audio_to_wav;
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }
	    if ($audio_end > 0.) {
		unlink $audio_in;
	    }

	    move($audio_out, $nfile) or die $!;
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "append_audio") {
	    # here we end up with (raw) 'audiodump', which will be renamed to 'audio' in commit_audio
	    $endian = &get_endian;

	    $type = $ARGV[2];
	    $nrate = $ARGV[3];
	    $nchans = $ARGV[4];
	    $nsamps = $ARGV[5];
	    $nsigned = $ARGV[6];
	    $nendian = $ARGV[7];
	    $file = $ARGV[8];
	    $audio_in = "audiodump.wav";

	    &smog_chdir($curworkdir);

	    $audiofile = "audio";

	    ## mp3 / ogg try specific openers, if fail, call othera open. which tries with mplayer
	    ## all create pcm wav @ $audio_in. wav_open just copies
	    if ($type eq "mp3") {
		&mp3_open;
	    }
	    elsif($type eq "ogg") {
		&ogg_open;
	    }
	    elsif($type eq "wav") {
		## sets $arate, $achans, $asamps to defaults ifndef, sets asigned, aendian
		# if ! signed, endian, copies $file to $audio_in
		## does no actual conv.
		## sets f_size
		&wav_open;
	    }
	    else {
		&othera_open;
	    }

	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    &smog_system_sync();

	    unless (-f $audio_in) {
		&sig_error("$GUI_NAME was not able to open the file", "$file");
	    }

	    $audio_out = "audio.new";

	    ## TODO: resample in the conversion if possible
	    &convert_audio_to_raw;  ## audiodump.wav -> audio.new
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }
	    unlink $audio_in;

	    $audio_bak = "audio.bak";

	    # resample $audio_out (if required)
	    unless ($arate == $nrate && $achans == $nchans && $asamps == $nsamps && $nsigned == $signed &&
		    $nendian == $endian) {
		$audio_in = $audio_out;
		#audio.new
		$audio_out = $audio_bak;
		#audio.bak
		&resample_audio;
		# audio.new -> audio.bak
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}

		unlink $audio_in;
		#audio.new
		&smog_rename($audio_out, $audio_in);
		#audio.bak -> audio.new
		$audio_out = $audio_in;
		#audio.new
	    }

	    unless(&config_get("conserve_space") eq "true") {
		&smog_rename($audio_out, "keep_$audio_out");
		&backup_audio(0);
		if ($panic) {
		    unlink "keep_$audio_out";
		    unlink $pidfile;
		    exit 1;
		}
		&smog_rename("keep_$audio_out", $audio_out);
	    }

	    # append audio audio.new -> audiodump
	    $fdd_in = "$curworkdir/audio";
	    $fdd_out = "$curworkdir/audiodump";
	    $size = -s $fdd_in;
	    &fast_dd($size, 0, 0);
	    return if ($panic);


	    $fdd_in = "$curworkdir/audio,new";
	    $fdd_out = "$curworkdir/audiodump";
	    $size = -s $fdd_in;
	    &fast_dd($size, 0, -s $fdd_out);
	    return if ($panic);

	    unlink $audio_out;
	    #audio.new

	    $fsize = -s "$curworkdir/audiodump";
	    &sig_complete($fsize);
	    exit 0;
	}


	if ($command eq "trim_audio") {
	    $audio_start = $ARGV[2];
	    $audio_end = $ARGV[3];

	    $arate = $ARGV[4];
	    $achans = $ARGV[5];
	    $asamps = $ARGV[6];
	    $asigned = $ARGV[7];
	    $aendian = $ARGV[8];

	    &smog_chdir($curworkdir);

	    #trim audio to selection

	    # $audio_from is the new clip ("audioclip")
	    $clipaudio = $audio_from = &clip_audio($audio_start, $audio_end);
	    if ($panic) {
		unlink $pidfile;
		unlink $clipaudio;
		exit 1;
	    }

	    $align = $achans * $asamps / 8;
	    $acsize = -s $audio_from;
	    $acsize /= $align * $arate;

	    if (!defined $audio_in) {
		## TODO - was set in clip_audio !!!
		$audio_in = "$curworkdir/audio";
	    }

	    &smog_rename($audio_in, $audio_in . ".bak");

	    $where = $audio_start;
	    $end = $audio_end - $audio_start;
	    $start = 0;

	    if ($end > $acsize) {
		#the part we clipped may have been *shorter* than the selection
		# need to check for this now, as we are more careful with reads and writes
		$end = $acsize;
	    }

	    # insert_audio will insert silence at start, and should recreate "audio" from "audioclip" ($audio_from)
	    $audio_to = $audio_in;
	    &insert_audio($asamps, $achans, $asigned, $aendian, 1);
	    if ($panic) {
		unlink $pidfile;
		unlink $clipaudio;
		exit 1;
	    }

	    #append silence to pad to end
	    #print STDERR "filling audio to $audio_end\n";

	    &append_silence(0, &align($audio_end * $arate * $align), $asamps, $achans, $asigned, $aendian);
	    if ($panic) {
		unlink $pidfile;
		unlink $clipaudio;
		exit 1;
	    }

	    unlink $clipaudio;
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "delete_audio") {
	    $start = $ARGV[2];
	    $end = $ARGV[3];
	    $arate = $ARGV[4];
	    $achans = $ARGV[5];
	    $asamps = $ARGV[6];

	    &cut_audio;
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "resample_audio") {
	    $arate = $ARGV[2];
	    $achans = $ARGV[3];
	    $asamps = $ARGV[4];
	    $asigned = $ARGV[5];
	    $aendian = $ARGV[6];

	    $nrate = $ARGV[7];
	    $nchans = $ARGV[8];
	    $nsamps = $ARGV[9];
	    $nsigned = $ARGV[10];
	    $nendian = $ARGV[11];
	    $audio_in = "$curworkdir/audio.bak";
	    $audio_out = "$curworkdir/audio";

	    if (defined($ARGV[12])) {
		$stretch = $ARGV[12];
		$audio_in = "$curworkdir/audio.orig";
	    }

	    unlink $audio_in;
	    &smog_rename($audio_out, $audio_in);
	    # aborts on failure

	    &smog_system_sync();

	    &resample_audio;
	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    if (&config_get("conserve_space") eq "true" && (-s $audio_out)) {
		unlink $audio_in;
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "get_window_id") {
	    &smog_system("xwininfo > \"$curworkdir/tmpinfo\"");

	    &smog_system("grep \"Window id:\" \"$curworkdir/tmpinfo\" > \"$curworkdir/tmpinfo2\"");
	    if (defined(open IN, "<", "$curworkdir/tmpinfo2")) {
		read IN, $win_id, 128;
		close IN;
	    }
	    @wid = split( / /, $win_id);
	    $win_id = hex($wid[3]);
	    chomp($win_id);

	    &smog_system("grep \"Width:\" \"$curworkdir/tmpinfo\" > \"$curworkdir/tmpinfo2\"");
	    if (defined(open IN, "<", "$curworkdir/tmpinfo2")) {
		read IN, $width, 128;
		close IN;
	    }
	    @widths = split( /Width: /, $width);
	    $width = $widths[1];
	    chomp($width);

	    &smog_system("grep \"Height:\" \"$curworkdir/tmpinfo\" > \"$curworkdir/tmpinfo2\"");
	    if (defined(open IN, "<", "$curworkdir/tmpinfo2")) {
		read IN, $height, 128;
		close IN;
	    }
	    @heights = split( /Height: /, $height);
	    $height = $heights[1];
	    chomp($height);

	    &smog_system("grep \"Depth:\" \"$curworkdir/tmpinfo\" > \"$curworkdir/tmpinfo2\"");
	    if (defined(open IN, "<", "$curworkdir/tmpinfo2")) {
		read IN, $bpp, 128;
		close IN;
	    }
	    @bpps = split( /Depth: /, $bpp);
	    $bpp = $bpps[1];
	    chomp($bpp);

	    &smog_system("grep \"Visual:\" \"$curworkdir/tmpinfo\" > \"$curworkdir/tmpinfo2\"");
	    if (defined(open IN, "<", "$curworkdir/tmpinfo2")) {
		read IN, $visual, 128;
		close IN;
	    }
	    @visuals = split( /Visual: /, $visual);
	    $visual = $visuals[1];
	    chomp($visual);

	    unlink "$curworkdir/tmpinfo";
	    unlink "$curworkdir/tmpinfo2";

	    &sig_complete($win_id, $width, $height, $bpp, $visual);
	    exit 0;
	}


	if ($command eq "fill_and_redo_frames") {
	    # remove any gaps in the play images
	    unlink "$curworkdir/pause";
	    $end = $ARGV[2];
	    $width = $ARGV[3];
	    $height = $ARGV[4];
	    $img_ext = "." . $ARGV[5];
	    $img_prefix = &get_img_prefix($img_ext);
	    $has_audio = 0;
	    $fps = $ARGV[6];
	    $arate = $ARGV[7];
	    $achans = $ARGV[8];
	    $asamps = $ARGV[9];
	    $asigned = $ARGV[10];
	    $aendian = $ARGV[11];

	    if ($achans > 0) {
		$has_audio = 1;
	    }

	    &fill_and_redo_frames;

	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    if ($has_audio) {
		$aud_end = ($end / $fps) * $achans * $arate * ($asamps / 8);
		$audio_in = "$curworkdir/audio";
		$audsize = -s $audio_in;
		if ($audsize > $aud_end) {
		    $audio_out = &clip_audio(0., $end / $fps);
		    if ($panic) {
			unlink $pidfile;
			exit 1;
		    }
		    unlink $audio_in;
		    &smog_rename($audio_out, $audio_in);
		}
		elsif ($audsize < $aud_end) {
		    $audio_out = "$curworkdir/audio.bak";
		    &smog_rename($audio_in, $audio_out);
		    &append_silence(0, $aud_end - $audsize, $asamps, $achans, $asigned, $aendian, $audio_in);
		    if ($panic) {
			unlink $pidfile;
			exit 1;
		    }

		    $fdd_in = $audio_out;
		    $fdd_out = $audio_in;
		    $size = -s $fdd_in;
		    $wpos = -s $fdd_out;
		    &fast_dd($size, 0, $wpos);
		    return if ($panic);

		    unlink $audio_out;
		}
	    }

	    &sig_complete;
	    exit 0;
	}


	if ($command eq "commit_audio") {
	    # commit the audio file in either audiodump, audiodump.pcm  or audiodump.wav as new audio file

	    $allow_nonex = 0;
	    if (defined $ARGV[2]) {
		$allow_nonex = $ARGV[2];
	    }

	    &smog_chdir($curworkdir);

	    $audio_out = "audio";
	    $audio_bak = "audio.bak";

	    my $gotit = 0;

	    $file = $audio_in = "audiodump.wav";

	    my $f_size = -s $audio_in;
	    if ($f_size > 0) {
		# wav format
		unless(&config_get("conserve_space") eq "true") {
		    if (-f $audio_out) {
			$smres = &smog_rename($audio_out, $audio_bak);
		    } else {
			unless (&is_writeable($audio_bak)) {
			    &sig_write_error($audio_bak);
			    exit 1;
			}
		    }
		}
		&get_file_info($file, 2, 0);
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
		&convert_audio_to_raw;
		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
	    } else {
		$audio_in = "audiodump";
		if (! -f $audio_in) {
		    $audio_in = "audiodump.pcm";
		} else {
		    $gotit = 1;
		}
		if ($gotit || -f $audio_in) {
		    # raw format
		    unless(&config_get("conserve_space") eq "true") {
			if (-f $audio_out) {
			    $smres = &smog_rename($audio_out, $audio_bak);
			    #aborts on failure
			} else {
			    unless (&is_writeable($audio_bak, 0)) {
				&sig_write_error($audio_bak);
				exit 1;
			    }
			}
		    }
		    &smog_rename($audio_in, $audio_outs);
		    # dummy values, the GUI should know these
		    $arate = $achans = $asamps = 0;
		    $signed = $endian = 1;
		} else {
		    unless($allow_nonex) {
			&sig_error("$GUI_NAME audio error.");
		    }
		}
	    }
	    unlink glob "audiodump.*";

	    $f_size = -s $audio_out;
	    &sig_complete($arate, $achans, $asamps, $signed, $endian, $f_size);
	    exit 0;
	}


	if ($command eq "cancel_audio") {
	    # remove the audio file in audiodump/audiodump.wav
	    # plus any audio.new files (e.g. from append audio)
	    unlink glob "$curworkdir/audiodump* $curworkdir/audio.new";
	    &sig_complete;
	    exit 0;
	}

	if ($command eq "download_clip") {
	    my $USE_YTDL = 1;
	    my $url = $ARGV[2];
	    my $ddir = $ARGV[3];
	    my $dfile = $ARGV[4];
	    my $formreq = $ARGV[5];
	    my $deswidth = int($ARGV[6]);
	    my $desheight = int($ARGV[7]);
	    my $matchsize = int($ARGV[8]);
	    my $vidchoice = $ARGV[9];
	    my $audchoice = $ARGV[10];
	    my $keep_frags = $ARGV[11];
	    my $do_update = int($ARGV[12]);
	    my $debug = $ARGV[13];

	    if (!$USE_YTDL) {
		my $com = 'vlc "$url" --sout=file/ps:"$ddir/$dfile.$formreq" --play-and-exit --no-interact';
		&smog_system($com);
		my $errnum = ($? >> 8);
		if ($errnum) {
		    if ($debug) {
			print STDERR "vlc failed !\n";
		    }
		    if ($debug) {
			print STDERR "returning ERROR\n\n";
		    }
		    &sig_system_error("A download error occured");
		    exit 1;
		}
		if ($debug) {
		    print STDERR "download complete\n";
		}
		&sig_complete;
		exit 0;
	    }

	    ##print STDERR "DO UPDATE is $do_update\n";
	    my $youtube_dl = "youtube-dl";
	    my $line;

	    my $islocal = 0;
	    my $com;
	    my $smerr;

	    my $home = $ENV {"HOME"};
	    my $local_bindir = "$home/.local/bin";
	    my $prog = "$local_bindir/$youtube_dl";
	    my $localprog = $prog;

	    if (0 && $ARGV[14] eq "forceddownload") {
		## user chose to install local copy
		## this is no longer run
		$islocal = 1;
		if (! -d $local_bindir) {
		    make_path $local_bindir;
		}
		if ($debug) {
		    print STDERR "commencing install of binary $youtube_dl\n";
		}
		$com = "pip install --upgrade '$youtube_dl'";
		$smerr = &smog_system($com);
		if ($debug) {
		    print STDERR "ran command: $com\nresult was $smerr\n";
		}
		if ($smerr || ! -f $prog) {
		    &sig_system_error("Installing $youtube_dl using pip",
				      $smerr);
		    exit 1;
		}
		$com = "chmod u+x '$prog'";
		$smerr = &smog_system($com);
		if ($smerr || ! -x $prog) {
		    &sig_system_error("Changing permissions for $prog", $smerr);
		    exit 1;
		}
		$com = "hash -d $youtube_dl";
		&smog_system($com);
	    }
	    else {
		## if there is a local copy of ytdl we try to use that
		if (-x $prog && ! -z $prog) {
		    if ($debug) {
			print STDERR "found local copy of $youtube_dl at $prog\n";
		    }
		    $islocal = 1;
		}
		else {
		    $prog = &location($youtube_dl);
		    if ($prog eq "") {
			$prog = &location($youtube_dl . "c");
		    }
		}
		if ($prog eq "") {
		    $com = $youtube_dl;
		    open (my $fh, "$com 2>&1 |");
		    if ($fh) {
			$line = <$fh>;
			close $fh;
		    }
		    if ($debug) {
			print STDERR "unable to run: $com,\nerror was $line\n";
			print STDERR "exiting\n\n";
		    }
		    if (!$line) {
			if (!$OK) {
			    $line = "Command not found";
			}
			else {
			    $line = "Unknown error";
			}
		    }
		    my $cmd1 = "";
		    if (!$islocal) {
			$cmd1 = "cp $prog $local_prog\n";
		    }
		    &sig_system_error(
			$com, $line, "_ASKPERM_|COPYLOCAL|youtube-dl|" .
			"14|forceddownload|" . $cmd1 .
			"pip install --upgrade $youtube_dl");
		    exit 1;
		}
	    }

	    ## first of all deal with update

	    if ($do_update && $islocal) {
		$com = "pip install --upgrade '$youtube_dl'";
		#$com = "'$prog' -U";
		if ($debug) {
		    print STDERR "\n\nUPDATING $prog WITH $com\n";
		}
		open (my $fh, "$com 2>&1 |");
		while (defined(my $line = <$fh>)) {
		    if ($debug) {
			print STDERR "got line: $line\n";
		    }
		}
		close $fh;
		if ($debug) {
		    print STDERR "Update of $prog complete, result was $?\n";
		}
		my $errnum = ($? >> 8);
		if ($errnum) {
		    if ($debug) {
			print STDERR "Update failed !\n";
		    }
		    if ($debug) {
			print STDERR "unknown ERROR. Exiting\n";
		    }
		    &sig_error("Unknown error");
		    exit 1;
		}
	    }

	    ## get list of files for selected format type
	    ## also note if there are files in alternate format(s)
	    my $vb = "";
	    if ($debug) {
		print STDERR "Listing files\n";
		$vb = "-v";
	    }
	    my $tdiag = sqrt($deswidth * $deswidth + $desheight * $desheight);
	    if ($vidchoice ne "") {
		$bestvideo = $vidchoice;
	    }
	    else {
		$bestvideo = "";
		$ddelta = -1;
	    }
	    my $bestaudio = "";
	    my $xdfile = "$ddir/$dfile.$formreq";
	    my $noaudio;

	    if ($audchoice ne "0") {
		$bestaudio = $audchoice;
		if ($audchoice eq "-1") {
		    $noaudio = 1;
		}
	    }

	    if ($vidchoice eq "" || $audchoice eq "-1" || $audchoice eq "0") {
		## next, list all the formats and select the we want (or return them)
		my $best = "(best)";
		my $video_only = "video only";
		my @lines;
		my $count = 0;

		if ($debug) {
		    print STDERR "DOING: '$prog' $vb --no-playlist -F '$url'\n";
		}

		my $OK = open(my $fh, "'$prog' $vb --no-playlist -F '$url' |");
		if ($OK) {
		    while (defined($line = <$fh>)) {
			if ($debug) {
			    print STDERR "WE GOT $line.";
			}
			my ($fcode, $ext, $reso, @notes) = split ' ', $line;
			my $note = join(" ", @notes);
			next if ($reso eq "downloading" || $reso eq "Downloading");

			if ($formreq eq "webm" && $ext ne $formreq) {
			    $hasalts = 1;
			}

			if (index($note, $best) != -1) {
			    $isbest = 1;
			}
			else {
			    $isbest = 0;
			}

			## filter by extension, type
			if ($reso eq "audio") {
			    if ($audchoice eq "0") {
				if ($isbest) {
				    $bestaudio = $fcode;
				}
				elsif ($bestaudio eq "") {
				    if ($formreq eq "webm") {
					$bestaudio = "bestaudio[ext=webm]";
				    }
				    else {
					$bestaudio = "bestaudio[ext=m4a]";
				    }}}
			    next;
			}

			if (index($note, $video_only) == -1) {
			    $vidonly = 0;
			}
			else {
			    $vidonly = 1;
			}
			if ($vidchoice eq $fcode && $audchoice eq "0" && !$vidonly) {
			    ## bestaudio doesn't always work, so if the video has an audio track, use that
			    ## to be safe
			    $xbestaudio = "";
			    $bestaudio = $fcode;
			    last;
			}

			next if ($vidchoice ne "");
			next if ($formreq ne "" && $ext ne $formreq);

			$count++;

			if ($reso eq "unknown") {
			    if ($matchsize == 6) {
				push (@lines, $line);
			    }
			    if ($bestvideo eq "") {
				$bestvideo = $fcode;
				if ($matchsize == 4) {
				    $tdiag--;
				}
				elsif ($matchsize == 5) {
				    $tdiag++;
				}
			    }
			    next;
			}

			## then examine the frame size, note which is best fit
			my ($width, $height) = split 'x', $reso;
			my $idiag = sqrt($width * $width + $height * $height);

			if ($matchsize == 1) {
			    ## nearest
			    if ($ddelta == -1 || abs($idiag - $tdiag) <= $ddelta) {
				if ($ddelta == -1 ||abs($idiag - $tdiag) < $ddelta || $isbest) {
				    $ddelta = abs($idiag - $tdiag);
				    $bestvideo = $fcode;
				    if ($isbest && !$vidonly) {
					$xbestaudio = $bestvideo;
				    }
				    else {
					$xbestaudio = "";
				    }}}}

			elsif ($matchsize == 2) {
			    ## atleast
			    if ($idiag >= $tdiag && ($ddelta == -1 || $idiag - $tdiag <= $ddelta)) {
				if ($ddelta == -1 || $idiag - $tdiag < $ddelta || $isbest) {
				    $ddelta = $idiag - $tdiag;
				    $bestvideo = $fcode;
				    if ($isbest && !$vidonly) {
					$xbestaudio = $bestvideo;
				    }
				    else {
					$xbestaudio = "";
				    }}}}

			elsif ($matchsize == 3) {
			    ## atmost
			    if ($idiag <= $tdiag && ($ddelta == -1 || $tdiag - $idiag <= $ddelta)) {
				if ($ddelta == -1 || abs($idiag - $tdiag) < $ddelta || $isbest) {
				    $ddelta = $tdiag - $idiag;
				    $bestvideo = $fcode;
				    if ($isbest && !$vidonly) {
					$xbestaudio = $bestvideo;
				    }
				    else {
					$xbestaudio = "";
				    }}}}

			elsif ($matchsize == 4) {
			    ## largest
			    if ($idiag >= $tdiag) {
				if ($bestvideo eq "" || $idiag > $tdiag || $isbest) {
				    $tdiag = $idiag;
				    $bestvideo = $fcode;
				    if ($isbest && !$vidonly) {
					$xbestaudio = $bestvideo;
				    }
				    else {
					$xbestaudio = "";
				    }}}}

			elsif ($matchsize == 5) {
			    ## smallest
			    if ($idiag <= $tdiag) {
				if ($bestvideo eq "" || $idiag < $tdiag || $isbest) {
				    $tdiag = $idiag;
				    $bestvideo = $fcode;
				    if ($isbest && !$vidonly) {
					$xbestaudio = $bestvideo;
				    }
				    else {
					$xbestaudio = "";
				    }}}}

			elsif ($matchsize == 6) {
			    ## choose
			    push (@lines, $line);
			}
		    }
		    close $fh;
		}

		if ($OK) {
		    $res = $? >> 8;
		}
		else {
		    $res = 0;
		}

		if (!$OK || $res || (!$count && $vidchoice eq "")) {
		    if ($debug) {
			print STDERR "unable to run: command,\nerror was $line\n";
			print STDERR "exiting\n\n";
		    }
		    if (!$line) {
			if (!$OK) {
			    $line = "Command not found";
			}
			else {
			    $line = "Unknown error";
			}
		    }
		    if (!$islocal) {
		    my $cmd1 = "cp $prog $localprog\n";

		    &sig_system_error(
			$com, $line, "_ASKPERM_|COPYLOCAL|youtube-dl|" .
			"14|forceddownload|" . $cmd1 .
			"pip install --upgrade $youtube_dl");
		    }
		    else {
			&sig_system_error($com, $line);
		    }
		    exit 1;
		}

		if ($matchsize == 6 && $count > 0) {
		    if ($debug) {
			print STDERR "returning $count lines\n";
		    }
		    &sig_complete(@lines);
		    exit 0;
		}

		if ($bestvideo eq "" || ($count == 0 && $matchsize != 7)) {
		    if ($hasalts) {
			&sig_complete("altfmts");
			exit 0;
		    }
		    if ($debug) {
			print STDERR "Could not open $url\n\n";
			&sig_error("The requested URL", $url, "could not be opened with the desired format.",
				   "\nPlease try again.");
			exit 1;
		    }

		    if ($audchoice eq "" && $xbestaudio ne "") {
			$bestaudio = $xbestaudio;
		    }

		    if ($vidchoice eq "") {
			if ($debug) {
			    print STDERR "SMOG returning selection\n";
			}
			&sig_complete($bestvideo, $bestaudio);
			exit 0;
		    }
		}
	    }

	    ## perform the actual download
	    
	    if (-f $xdfile) {
		unlink $xdfile;
	    }

	    if (-f $xdfile) {
		print STDERR "Could not remove existing file $xdfile\n";
		&sig_error("Could not remove existing file $xdfile");
		exit 3;
	    }


	    if ($noaudio) {
		$xbestaudeo = "";
	    }

	    else {
		if ($bestaudio eq "") {
		    if ($formreq eq "webm") {
			$xbestaudio = "+bestaudio[ext=webm]";
		    }
		    else {
			$xbestaudio = "+bestaudio";
		    }}
		else {
		    $xbestaudio = "+$bestaudio";
		}
	    }

	    my $kf = "";
	    if ($keep_frags) {
		$kf = "--keep-fragments";
	    }
	    
	    my $extra = "";
	    ## TODO: proxy URl, ipv4 / ipv6 switch

	    ## for now we assume these are set in the config file
	    
	    if ($formreq eq "mp4") {
		#$extra = "--merge-output-format mp4";
		$dfile = $xdfile;
	    } elsif ($formreq eq "webm") {
		if ($bestaudio eq "" || $noaudio) {
		    $dfile = $xdfile;
		}
	    }

	    $com = "'$prog' $vb --audio-quality 0 --no-mark-watched $kf --no-playlist $kf -o \"$xdfile\" $extra " .
		"--no-part --no-cache-dir -f '$bestvideo$xbestaudio' \"$url\"";
	    
	    if ($debug) {
		print STDERR "RUNNING $com\n";
	    }

	    &sig_progress(1);

	    if (-f $xdfile) {
		unlink $xdfile;
	    }

	    if (open (my $fh, "$com |")) {
		while (defined(my $line = <$fh>)) {
		    print STDERR "output: $line\n";
		    if ($line =~ /\ (.*)%\ /) {
			&sig_progress($1);
		    }
		}
		close $fh;
	    }

	    $res = ($? >> 8);

	    if ($res || ! -f $xdfile || -z $xdfile) {
		## if $kf is set, we can leave the fragments for a retry (needs testing)

		if (-f $xdfile) {
		    unlink $xdfile;
		}
		if ($debug) {
		    print STDERR "\nDownload failed ! Error was $res\n\n";
		}
		&sig_system_error($com, $res);
		exit 1;
	    }

	    if ($kf) {
		unlink glob "$ddir/$dfile.*.Frag";
	    }

	    if ($debug) {
		print STDERR "download complete\n";
	    }
	    &sig_complete;
	    exit 0;
	}

	if ($command eq "cdopen") {
	    my $cdda2wav_command = &location("cdda2wav");
	    if ($cdda2wav_command eq "") {
		$cdda2wav_command = &location("icedax");
		if ($cdda2wav_command eq "") {
		    &sig_error("cdda2wav or icedax is required for this function.", "Please install it first.");
		}
	    }
	    $cdplay_device = &config_get("cdplay_device");
	    if ($cdplay_device eq "") {
		&sig_error("You must set the CD device first in Preferences.");
	    }

	    $track = $ARGV[2];

	    $audiofile = $curworkdir . "/audiodump.wav";
	    if (-f $audiofile) {
		unlink $audiofile;
	    }

	    $com = "\"$cdda2wav_command\" -q -x -D \"$cdplay_device\" -t $track \"$audiofile\"| " .
		">$nulfile 2>&1";
	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }
	    $f_size = -s $audiofile;
	    # the '-x' option will force these
	    $arate = 44100;
	    $achans = 2;
	    $asamps = 16;
	    $aendian = &get_endian;
	    $asigned = 1;
	    if ($asamps == 8) {
		$asigned = 0;
	    }
	    &sig_complete($arate, $achans, $asamps, $asigned, $aendian, $f_size);
	    exit 0;
	}


	if ($command eq "audioopen") {
	    # TODO - allow front end to specify rate/channels, etc.
	    $file = $ARGV[2];

	    &smog_chdir($curworkdir);

	    $audio_in = "audiodump.wav";

	    $ext = &get_ext($file);

	    if ($ext eq ".mp3") {
		&mp3_open;
	    }
	    elsif($ext eq ".ogg") {
		&ogg_open;
	    }
	    elsif($ext eq ".wav") {
		## sets $arate, $achans, $asamps to defaults ifndef, sets asigned, aendian if ! signed, endian,
		## copies $file to $audio_in
		## does no actual conv.
		## sets f_size
		&wav_open;
	    }
	    else {
		&othera_open;
	    }

	    if ($panic) {
		unlink $pidfile;
		exit 1;
	    }

	    &sig_complete($arate, $achans, $asamps, $asigned, $aendian, $f_size);
	    exit 0;
	}


	if ($command eq "insert") {
	    # with_audio: 0, no audio, 1 video and audio, 2 ONLY audio
	    # for 0 and 1, start, end, where are in frames; for 2, start,end,where are in seconds
	    # with -ve arate means insert silence [with_audio should be 1 or 2]
	    # with -ve times means undo cut/delete [source is same clip dir]

	    # with -ve end means "allow missing frames" (used for copying to clipboard)

	    unlink "$curworkdir/pause";
	    $img_ext = "." . $ARGV[2];
	    $img_prefix = &get_img_prefix($img_ext);
	    $where = $ARGV[3];
	    $start = $ARGV[4];
	    $end = $ARGV[5];
	    $from_handle = $ARGV[6];
	    $with_audio = $ARGV[7];
	    $num_frames = $ARGV[8];

	    $width = $ARGV[9];
	    $height = $ARGV[10];
	    $times = 1;
	    $undo_cut = 0;

	    $new_frames = $num_frames;

	    &smog_chdir($curworkdir);

	    $allow_missing = 0;
	    if ($end < 0) {
		# insert (copy) from (virtual) clip into clipboard
		$allow_missing = 1;
		$end = -$end;
	    }

	    if (defined($ARGV[17])) {
		$times = $ARGV[17];
		if ($times < 0) {
		    # this indicates undoing a previous cut
		    $times = -$times;
		    $undo_cut = 1;
		    $new_frames = $num_frames + $end - $start + 1;
		} else {
		    if ($start >= 0) {
			&clean_old($handle);
			if ($with_audio) {
			    if (-f "audio") {
				## back this up here for undo
				&smog_rename("audio", "audio.bak");
			    }
			}
		    } else {
			# $start < 0 tells us to leave backups alone
			$start = -$start;
		    }
		    $antialias = &config_get("antialias");
		}
	    }

	    if ($with_audio < 2) {
		if (!$undo_cut) {
		    if (!$allow_missing) {
			$img_ext2 = &get_img_ext("$workdir/$from_handle", $start);
			$img_prefix2 = &get_img_prefix($img_ext2);
		    } else {
			$img_ext2 = $img_ext;
			$img_prefix2 = $img_prefix;
		    }
		}

		&insert;

		if ($panic) {
		    unlink $pidfile;
		    exit 1;
		}
		if ($allow_missing) {
		    &sig_progress($new_frames);
		}
	    }

	    if ($with_audio) {
		$fps = $ARGV[11];
		$arate = $ARGV[12];
		$achans = $ARGV[13];
		$asamps = $ARGV[14];
		$asigned = $ARGV[15];
		$aendian = $ARGV[16];
		my $xarate = $arate;
		if ($arate < 0) {
		    $xarate = -$arate;
		}

		if ($xarate * $achans * $asamps != 0) {
		    if (!$undo_cut) {
			if ($with_audio < 2) {
			    $end /= $fps;
			    $start = ($start - 1.) / $fps;
			    $where /= $fps;
			}
			if (-f "audio.bak") {
			    &smog_copy("audio.bak", "audio");
			}
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}
			&smog_system_sync();
		    } else {
			$end = -s "$curworkdir/audio.bak";
			$end /= $xarate * $achans * $asamps / 8;
			$start = 0.;
			if ($with_audio < 2) {
			    $where /= $fps;
			}
		    }

		    $audio_to = "$curworkdir/audio";
		    &insert_audio($asamps, $achans, $asigned, $aendian, $undo_cut);
		    if (&config_get("conserve_space") eq "true") {
			unlink "audio.bak";
		    }
		    if ($panic) {
			unlink $pidfile;
			exit 1;
		    }
		}
	    }

	    if ($undo_cut) {
		&clean_old($handle);
	    }

	    &sig_complete;
	    exit 0;
	}

	if ($command eq "undo_insert") {
	    #clean up after an interrupted insert
	    #	    $start = $ARGV[2];
	    #	    $end = $ARGV[3];
	    #	    $frames = $ARGV[4];
	    #	    $img_ext = "." . $ARGV[5];

	    # move extra frames back to end
	    #	    &smog_system_sync;
	    #	    &undo_insert;

	    if (-f "$curworkdir/audio.bak") {
		&undo_audio;
	    }
	    # will abort on failure
	    &clean_old($handle);
	    &sig_complete;
	    exit 0;
	}


	# effects

	if ($command =~ /^pfxrender_/) {
	    #fx preview
	    $fx_prev = 1;
	    $command = substr($command, 1);
	    $out_ext = ".pre";
	}


	if ($command =~ /^fxrender_(.*)/) {
	    $curworkdir = "$workdir/$handle";

	    unlink "$curworkdir/pause";
	    $command = $1;
	    $status = $ARGV[2];

	    ## for generators, $start is always 1, $end is target no. of frames to generate
	    $start = $ARGV[3];
	    $end = $ARGV[4];

	    ## for generators, this is the target frame size, for other fx, it is the current size
	    $width = $ARGV[5];
	    $height = $ARGV[6];

	    $img_ext = "." . $ARGV[7];
	    $img_prefix = &get_img_prefix($img_ext);
	    $fps = $ARGV[8];

	    if ($USE_WARNINGS) {
		no warnings 'once';
	    }
	    $convert_command = $smog_convert_command; # set for plugins
	    $composite_command = $smog_composite_command; # set for plugins
	    if ($USE_WARNINGS) {
		use warnings 'once';
	    }
	    # call render plugins in plugins/effects/rendered/$command
	    unless (defined($fx_prev)) {
		$out_ext = ".mgk";
		$out_prefix = $img_prefix;
		&clean_old($handle);
	    }
	    &smog_chdir($curworkdir);
	    if ($status == 0) {
		$plugin_name = $command;
	    }
	    elsif ($status == 1) {
		$plugin_dir = "$lives_config_dir/plugins/effects/rendered/custom";
		$plugin_name = "$plugin_dir/$command";
	    }
	    else {
		$plugin_dir = "$lives_config_dir/plugins/effects/rendered/test";
		$plugin_name = "$plugin_dir/$command";
	    }
	    $smcom = "\"$plugin_name\" get_description";
	    $fx_desc = &smog_system_direct($smcom);
	    if ($?) {
		&sig_system_error($smcom, $?);
		exit 1;
	    }

	    $num_in_channels = (split( /\|/, $fx_desc))[3];

	    if ($num_in_channels == 0) {
		# generators get the out_extension
		$out_ext = $img_ext;
		$out_prefix = &get_img_prefix($img_ext);
	    }

	    if ($num_in_channels == 2) {
		# transitions get some extra params
		$img_ext2 = "." . $ARGV[9];
		$img_prefix2 = &get_img_prefix($img_ext2);
		if ($USE_WARNINGS) {
		    no warnings 'once';
		}
		$start2 = $ARGV[10]; # defined for plugins
		$clipboard = $ARGV[11]; # defined for plugins
		if ($USE_WARNINGS) {
		    use warnings 'once';
		}
	    }
	    $smcom = "\"$plugin_name\" get_capabilities";
	    $fx_caps = &smog_system_direct($smcom);
	    if ($?) {
		&sig_system_error($smcom, $?);
		exit 1;
	    }

	    if ($fx_caps & 0x8000) {
		# is autogenerated
		# we are so nice :-), we set:
		# $handle,$curworkdir,$img_ext,$start,$end,$width,$height
		# and for transitions $where, $chandle
		# remove fxrender $handle $status start end width height img_ext fps (start2 clipboard)
		##splice(@ARGV, 0, 6);
		splice(@ARGV, 0, 8); ## 1 less, since we will replace $ARGV[0]
		if ($num_in_channels == 2) {
		    splice(@ARGV, 0, 3);
		}

		$ARGV[0] = "process";
		if ($status > 1) {
		    $error = "";
		    unless (eval "require (\"$plugin_name\")") {
			$error = $@;
		    }
		    unless ($error eq "") {
			&sig_error("$plugin_name failed:", $error);
		    }
		} else {
		    require($plugin_name);
		}

		if ($num_in_channels > 0) {
		    $end = $frame - 1;
		    $frames = $frame - $start;
		    unless (defined($fx_prev)) {
			&mv_mgk;
			if ($panic) {
			    unlink $pidfile;
			    exit 1;
			}
		    }
		} else {
		    $frames = &count_frames(1, $img_ext);
		}
		## nwidth, nheight set for backwards compat,
		&sig_complete($width, $height, $fps, $frames, $nwidth, $nheight);
		exit 0;
	    } else {
		# other (non-perl) language
		splice(@argv, 0, 3);

		#remove "fxrender_.." and $handle $status
		my $res_file = "$curworkdir/.rfx_result";
		my $err;

		if ($num_in_channels == 2) {
		    $err = &smog_system("\"$plugin_name\" process \"$curworkdir\" $img_prefix2 " .
					"$img_prefix $out_prefix $out_ext " .
					"@ARGV > \"$res_file\"");
		}
		elsif ($num_in_channels == 1) {
		    $err = &smog_system("\"$plugin_name\" process \"$curworkdir\" $img_prefix " .
					"$out_prefix $out_ext @ARGV > \"$res_file\"");
		}
		else {
		    $err = &smog_system("\"$plugin_name\" process \"$curworkdir\" $out_prefix " .
					"$out_ext @ARGV > \"$res_file\"");
		}
		## TODO - use pipe
		if (!$err) {
		    if (defined(open IN, "<", $res_file)) {
			read IN, $size, 128;
			($width, $height) = split(" ", $size);
			close IN;
			unlink $res_file;
		    }
		} else {
		    my (@err) = "";
		    if (defined(open IN, "<", $res_file)) {
			read IN, $errt, 512;
			@err = split("\n", $errt);
			close IN;
			unlink $res_file;
		    }
		    &sig_error(@err);
		    exit 1;
		}
		unless(defined($fx_prev)) {
		    &mv_mgk;
		    if ($panic) {
			unlink $pidfile;
			exit 1;
		    }
		}
		&sig_complete($width, $height);
		exit 0;
	    }
	}


	if ($command eq "import_project") {
	    $proj_file = $ARGV[2];
	    &smog_chdir($workdir);
	    $com = "tar --no-same-owner -xzf \"$proj_file\"";
	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }
	    &sig_complete;
	    exit 0;
	}


	if ($command eq "export_project") {
	    my $set = $ARGV[2];
	    my $proj_file = $ARGV[3];
	    &smog_chdir($workdir);
	    $com = "tar --exclude=*.bak --exclude=.pid* --exclude .status* --exclude=lock.* " .
		"--exclude=*.mgk " .
		"--exclude=*.tmp --exclude=audioclip* -czf \"$proj_file\" $set";
	    my $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error($com, $smerr);
		exit 1;
	    }

	    &sig_complete;
	    exit 0;
	}

	print STDERR "smogrify for $GUI_NAME v$version: unrecognised command $command. " .
	    "Exiting with code 100.\n";
	print STDERR "smogrify for $GUI_NAME v$version: \@ARGV = " . join(" ", @ARGV) . "\n";
	unlink "$pidfile";
	exit 100;
    } ## end if !caller
}

######################################################

#subroutines

sub usage {
    print STDERR "$command must have a parameter\n";
    print STDERR "show usage...\n";
}


sub mkname {
    sprintf("%08d", shift);
}

sub perl_supports {
    my $thing = shift;
    return !system("perl -e 'use $thing;' 2>/dev/null");
}

sub check_for_pause {
    if (-f "$curworkdir/pause") {
	&smog_system_sync();
    }
    while (-f "$curworkdir/pause") {
	sleep 1;
    }
}


sub kill_child_pids {
    my ($target_pid, $sig) = @_;
    my $pid;
    &smog_system("pgrep -P $target_pid > \"$workdir/.pids.$target_pid\" 2>/dev/null");

    if ($sig eq "KILL") {
	&smog_system("kill -$sig $target_pid >/dev/null 2>&1");
    } else {
	&smog_system("pkill -$sig -P $target_pid");
	# must not ! >/dev/null 2>&1
    }

    if (-s "$workdir/.pids.$target_pid" && defined(open IN, "<", "$workdir/.pids.$target_pid")) {
	while (<IN>) {
	    $pid = $_;
	    chomp($pid);
	    &kill_child_pids($pid, $sig);
	}
	close IN;
    }

    unlink "$workdir/.pids.$target_pid";
}


sub is_dangerous {
    my $pathname = shift;
    if (index($pathname, ".") != -1) {
	return 1;
    }
    return 0;
}



sub is_writeable {
    # see if is writable or creatable
    ## if the file does not exist and $leave is nonzero the the file will be created
    ## with mode 644, modified by umask
    my ($file, $leave) = @_;
    return 1 if -f $file && -w $file;
    return 0 if (0 + -e $file);
    my $err = &smog_system("$SYSBIN" . "touch \"$file\"");
    if ($err) {
	unlink $file;
	return 0;
    }
    chmod 0644, $file;
    my $ret = -w $file;
    if (!$leave) {
	unlink $file;
    }
    return $ret;
}


sub get_img_ext {
    my ($ckdir, $imgno) = @_;

    if ($imgno eq "") {
	$imgno = 1;
    }

    my $tfile = &mkname($imgno);

    if (-f "$ckdir/$tfile.png") {
	return ".png";
    }

    unless (-f "$ckdir/$tfile.jpg") {
	print STDERR "Warning ! command $command needs to set image type !! Please report this as a bug.\n";
	print STDERR "details: $ckdir/$tfile.jpg not found\n";
    }

    return ".jpg";
}


sub get_img_prefix {
    my $imgext = shift;

    if ($imgext eq ".png") {
	return "PNG32:";
    }

    return "";
}


sub try_to_recover {
    #something failed badly during processing
    # attempt to recover clip from backup images

    my ($smstart, $smend) = @_;

    if ($DEBUG_SMOGRIFY) {
	print STDERR "Beginning clip recovery process for $curworkdir\n";
    }
    for ($i = $smstart; $i <= $smend; $i++) {
	$name = &mkname($i);
	&smog_rename("$name.bak", "$name$img_ext");
    }
    if ($DEBUG_SMOGRIFY) {
	print STDERR "Clip recovery process succeeded for $curworkdir\n";
    }
}


sub get_sox_version {
    #try to get version of sox

    # parse a line like "sox: SoX v14.3.2"
    # which would return 14.3.2
    # (take third word and strip leading v)

    # final value would be 140003002

    my $soxv = &smog_system_direct("sox -h 2>&1 | grep -i sox:");
    my $soxvv = (split("SoX ", $soxv))[1];

    if (substr($soxvv, 0, 1) eq "v") {
	$soxvv = substr($soxvv, 1, length($soxvv));
    }
    if ($soxvv eq "") {
	# version 12.x.x or lower
	$soxvv = 0;
    }
    else {
	$soxvv = &version_hash($soxvv);
    }

    return $soxvv;
}


sub get_sox_samps {
    my $asamps = shift;

    if (!defined($sox_version)) {
	$sox_version = &get_sox_version;
    }

    if ($sox_version < 13000000) {
	if ($asamps == 8) {
	    return "b";
	}
	else {
	    return "w";
	}
    }
    else {
	if ($sox_version >= 14004001) {
	    return "b $asamps";
	}
	return $asamps;
    }
}


sub get_sox_signed {
    my $signed = shift;
    my $signedstr;

    if (!defined($sox_version)) {
	$sox_version = &get_sox_version;
    }

    if ($sox_version < 14004001) {
	if ($signed == 0) {
	    $signedstr = "-u";
	}
	else {
	    $signedstr = "-s";
	}
    }
    else {
	if ($signed == 0) {
	    $signedstr = "-e unsigned-integer";
	}
	else {
	    $signedstr = "-e signed-integer";
	}
    }
    return $signedstr;
}


sub get_sox_format {
    my $asamps = shift;
    if ($asamps == 8) {
	return &get_sox_signed(0);
    } else {
	return &get_sox_signed(1);
    }
}


sub mv_mgk {
    # pass in first param as 1 to show progress of frames
    # new files are .mgk files, back up old files to .bak

    my $option = shift;
    my ($i, $name);

    unless(!defined($pidfile) || $pidfile eq "") {
	# don't want to get killed in this stage...
	unlink $pidfile;
    }

    for ($i = $start; $i <= $end; $i++) {
	$name = &mkname($i);
	if (-f "$curworkdir/$name.mgk") {
	    if (-f "$curworkdir/$name$img_ext") {
		if ($leave_bak == 1 && -f "$curworkdir/$name.bak") {
		    unlink "$curworkdir/$name$img_ext";
		}
		else {
		    $smres = &smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$name.bak");
		    if (!$smres) {
			&try_to_recover($start, $i - 1);
			if ($DEBUG_SMOGRIFY) {
			    print STDERR "smogrify debug - rename failed ($!): \"$curworkdir/$name$img_ext\", " .
				"\"$curworkdir/$name.bak\"\n";
			}
			&sig_system_error("Renaming \"$curworkdir/$name$img_ext\" to \"$curworkdir/$name.bak\"");
			exit 1;
		    }
		}
	    }
	    $smerr = &smog_rename("$curworkdir/$name.mgk", "$curworkdir/$name$img_ext");
	    if (!$smerr) {
		&try_to_recover($start, $i);
		if ($DEBUG_SMOGRIFY) {
		    print STDERR "smogrify debug - rename failed ($!): \"$curworkdir/$name.mgk\", " .
			"\"$curworkdir/$name$img_ext\"\n";
		}
		&sig_system_error("Renaming \"$curworkdir/$name.mgk\" to \"$curworkdir/$name$img_ext\"");
		exit 1;
	    }
	}
	if ($option == 1) {
	    &sig_progress($i);
	}
    }

    &smog_system_sync();
}


sub mv_pre {
    # new files are .pre files, back up old files to .bak
    my ($i, $name);

    unless (!defined($pidfile) || $pidfile eq "") {
	# don't want to get killed in this stage...
	unlink "$pidfile";
    }

    &smog_chdir($curworkdir);
    unlink glob "*.mgk";

    for ($i = $start; $i <= $end; $i++) {
	$name = &mkname($i);
	if (-f "$name.pre") {
	    if (-f "$name$img_ext") {
		unlink "$name.bak";
		$smerr = &smog_rename("$name$img_ext", "$name.bak");
		if (!$smerr) {
		    &try_to_recover($start, $i - 1);
		    if ($DEBUG_SMOGRIFY) {
			print STDERR "smogrify debug - rename failed ($!): " .
			    "\"$curworkdir/$name$img_ext\",\"$curworkdir/$name.bak\"\n";
		    }
		    &sig_system_error("Renaming \"$curworkdir/$name$img_ext\" to " .
				      "\"$curworkdir/$name.bak\"");
		    exit 1;
		}
	    }
	    $smerr = rename "$name.pre", "$name$img_ext";
	    if (!$smerr) {
		&try_to_recover($start, $i);
		if ($DEBUG_SMOGRIFY) {
		    print STDERR "smogrify debug - rename failed ($!): " .
			"\"$curworkdir/$name.pre\",\"$curworkdir/$name$img_ext\"\n";
		}
		&sig_system_error("Renaming \"$curworkdir/$name.pre\" to " .
				  "\"$curworkdir/$name$img_ext\"");
		exit 1;
	    }
	}
    }
    &smog_system_sync();
}


sub undo {
    # recover .bak files; if param==0 or undefined, move current files to .mgk files
    my $param = shift;

    if ($param eq "") {
	$param = 0;
    }

    my ($i, $name);
    for ($i = $start; $i <= $end; $i++) {
	$name = &mkname($i);
	if (-f "$curworkdir/$name.bak") {
	    if ($param == 0 && -f "$curworkdir/$name$img_ext") {
		# file may not exist if we are undoing resample for example
		&smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$name.mgk");
	    }
	    &smog_rename("$curworkdir/$name.bak", "$curworkdir/$name$img_ext");
	}
	&sig_progress($i);
    }
}


sub undo_audio {
    if (-f "$curworkdir/audio.orig") {
	unlink "$curworkdir/audio";
	&smog_rename("$curworkdir/audio.orig", "$curworkdir/audio");
	unlink "$curworkdir/audio.bak";
    } else {
	unlink "$curworkdir/audio.new";
	if (-f "$curworkdir/audio") {
	    &smog_rename("$curworkdir/audio", "$curworkdir/audio.new");
	}
	if (-f "$curworkdir/audio.bak") {
	    unlink "$curworkdir/audio";
	    &smog_rename("$curworkdir/audio.bak", "$curworkdir/audio");
	}
	if (-f "$curworkdir/audio.new") {
	    unlink "$curworkdir/audio.bak";
	    &smog_rename("$curworkdir/audio.new", "$curworkdir/audio.bak");
	}
    }
}


sub backup_audio {
    my $mstcopy = shift;
    my $audio = "$curworkdir/audio";

    return if (! -f $audio);

    my $audio_bak = "$curworkdir/audio.bak";

    unless ($mstcopy) {
	if (-f $audio_bak) {
	    unlink $audio_bak;
	    if (-f $audio_bak) {
		$panic = &sig_write_error($audio_bak);
		return;
	    }
	}
    }

    if (!&is_writeable($audio_bak)) {
	$panic = 1;
	return;
    }

    my $fsize = -s  $audio;
    unless ($mstcopy) {
	&smog_rename($audio, $audio_bak);
    }
    else {
	&clean_old($handle);
	&smog_copy($audio, $audio_bak);
	return if $panic;
    }

    if (-s $audio_bak != $fsize) {
	$panic = &sig_write_error($audio_bak);
	$panic = 1;
	return;
    }
}


sub cut {
    my ($start, $end) = @_;
    my ($i, $name);

    &smog_chdir($curworkdir);
    $frames_cut = $end - $start + 1;

    for ($i = $start; $i <= $end; $i++) {
	$name = &mkname($i);
	if (-f "$name$img_ext") {
	    &smog_rename("$name$img_ext", "$name.bak");
	}
	&sig_progress($i);
    }

    for ($i = $end + 1; $i <= $frames; $i++) {
	$from = &mkname($i);
	$to = &mkname($i - $frames_cut);
	if (-f "$curworkdir/$from$img_ext") {
	    &smog_rename("$curworkdir/$from$img_ext", "$curworkdir/$to$img_ext");
	}
	&sig_progress($i);
    }
}


sub reverse {
    for ($i = $start; $i < int(($start + $end) / 2 + .5); $i++) {
	$from = &mkname($i);
	$to = &mkname($end - $i + 1);
	$hasfrom = -f "$curworkdir/$from$img_ext";
	$hasto = -f "$curworkdir/$to$img_ext";
	if ($hasfrom) {
	    if ($hasto) {
		&smog_rename("$curworkdir/$from$img_ext", "$curworkdir/$from.revtemp");
		&smog_rename("$curworkdir/$to$img_ext", "$curworkdir/$from$img_ext");
	    }
	    &sig_progress($start + ($i - $start) * 2);
	    if ($hasto) {
		&smog_rename("$curworkdir/$from.revtemp", "$curworkdir/$to$img_ext");
	    } else {
		&smog_rename("$curworkdir/$from$img_ext", "$curworkdir/$to$img_ext");
	    }
	} else {
	    &sig_progress($start + ($i - $start) * 2);
	    if ($hasto) {
		&smog_rename("$curworkdir/$to$img_ext", "$curworkdir/$from$img_ext");
	    }
	}
	&sig_progress($start + ($i - $start) * 2 + 1);
    }
}


sub undo_insert {
    $frames_inserted = $end - $start + 1;

    if ($start <= $frames) {
	## insert was BEFORE

	## $frames is orig frames
	## we inserted $frames_inserted from start to end

	for ($i = $end + 1; $i <= $frames + $frames_inserted; $i++) {
	    # move any shifted frames back
	    $name = &mkname($i);
	    $to = &mkname($i - $frames_inserted);
	    if (-f "$curworkdir/$name$img_ext") {
		unlink "$curworkdir/$to$img_ext";
		&smog_rename("$curworkdir/$name$img_ext", "$curworkdir/$to$img_ext");
	    }
	}

	for ($i = $frames + 1; $i <= $end; $i++) {
	    $name = &mkname($i);
	    unlink "$curworkdir/$name$img_ext";
	}

    } else {
	## insert was AFTER, remove excess frames
	for ($i = $frames + 1; $i <= $frames + $frames_inserted; $i++) {
	    $name = &mkname($i);
	    unlink "$curworkdir/$name$img_ext";
	}}
}


sub smog_system_sync {
    &smog_system("sync") x 3;
}


sub smog_system {
    my ($smcommand) = @_;
    my $smcomret;

    $smcomret = system($smcommand);
    #$DEBUG_SMOGRIFY=1;
    if ($smcomret && $DEBUG_SMOGRIFY) {
	print STDERR "smogrify debug: $smcommand\n";
	print STDERR "smogrify debug - command failed: result was $smcomret\n";
    }
    $smcomret;
}


sub smog_system_direct {
    my ($smcommand) = @_;
    my $smcomret;

    $smcomret = `$smcommand`;

    if ($DEBUG_SMOGRIFY && $? != 0) {
	print STDERR "smogrify debug: command was $smcommand\n";
	print STDERR "smogrify debug: result was $smcomret\n";
	print STDERR "smogrify debug: error was $?\n";
    }

    $smcomret;
}


sub smog_rename {
    # CAUTION: return value of 0 is error here
    # move should be used if there is the possibilty of traversing filesystems
    my ($smfrom, $smto) = @_;
    my $smcomret = rename $smfrom, $smto;

    if ($smcomret == 0) {
	if ($DEBUG_SMOGRIFY) {
	    print STDERR "smogrify debug - rename failed ($!): $smfrom $smto\n";
	}
	&sig_system_error("Renaming \"$smfrom\" to \"$smto\"", $!);
	exit 1;
    }

    $smcomret;
}


sub smog_copy {
    my ($smfrom, $smto) = @_;
    my $smcomret;
    ## cp -f, --force
    ##      if  an  existing destination file cannot be opened, remove it and try again
    ## -b baclup dest files
    ## -n no clobber

    $smcomret = system("$SYSBIN" . "cp -f \"$smfrom\" \"$smto\"");

    if ($smcomret != 0 && -f $smfrom) {
	if ($DEBUG_SMOGRIFY) {
	    print STDERR "smogrify debug - copy failed ($!): $smfrom $smto\n";
	}
	$panic = &sig_system_error("Copying $smcomret \"$smfrom\" to \"$smto\"", $!);
    }

    $smcomret;
}


sub smog_chdir {
    ## return TRUE on success
    my $smdir = shift;
    $smcomret = chdir $smdir;
    if (!$smcomret) {
	if ($DEBUG_SMOGRIFY) {
	    print STDERR "smogrify debug - chdir failed ($!): $smdir\n";
	}
	&sig_system_error("Changing to directory \"$smdir\"", $!);
	exit 1;
    }
    return $smcomret;
}


sub insert {
    my ($from, $to, $frames_to_move);
    $times_inserted = 0;
    $factor = 1;

    my $nend = $end;
    my $nstart = $start;
    my $nwhere = $where;
    my $nfromdir = $fromdir;

    my $quick_copy = -1;

    if (!defined($times)) {
	$times = 1;
    }
    if (!defined($undo_cut)) {
	$undo_cut = 0;
    }

    if (!defined($img_prefix2)) {
	$img_prefix2 = &get_img_prefix($img_ext2);
    }
    if (!defined($img_prefix)) {
	$img_prefix = &get_img_prefix($img_ext);
    }

    $fromdir = "$workdir/$from_handle";
    $frames_to_move = ($nend - $nstart + 1) *$times;

    # make space for new frames
    for ($i = $num_frames; $i > $nwhere; $i--) {
	$from = &mkname($i);
	$to = &mkname($i + $frames_to_move);
	if (-f "$curworkdir/$from$img_ext") {
	    &smog_rename("$curworkdir/$from$img_ext", "$curworkdir/$to$img_ext");
	}
	&sig_progress($new_frames - $i);
    }

    $resize_ext = $img_ext;

    if (!defined($antialias)) {
	$antialias = "false";
    }

    #move from $from_handle
    $j = $start;
    while ($times_inserted < $times) {
	for ($i = $nstart; $i <= $nend; $i++) {
	    $from = &mkname($i);
	    $to = &mkname($nwhere + $i - $nstart + 1);

	    if ($undo_cut == 1) {
		if (-f "$fromdir/$from.bak") {
		    &smog_rename("$fromdir/$from.bak", "$curworkdir/$to$img_ext");
		    return if $panic;
		}
	    } else {
		if (! -f "$fromdir/$from$img_ext2") {
		    next if ($allow_missing);
		    while (! -f "$fromdir/$from$img_ext2") {
			print STDERR "waiting on image $fromdir/$from$img_ext2\n";
			sleep 1;
			&smog_system_sync();
		    }
		}
		if ($quick_copy == -1) {
		    $quick_copy = 0;
		    if ($img_ext eq $img_ext2) {
			if ($height * $width == 0) {
			    $quick_copy = 1;
			} else {
			    $imresact = "none";
			    &get_image_size("$fromdir/$from$img_ext2");
			    return if $panic;

			    if ($hsize == $width && $vsize == $height) {
				$quick_copy = 1;
			    }}}}

		if ($quick_copy == 1) {
		    &smog_copy("$fromdir/$from$img_ext2", "$curworkdir/$to$img_ext");
		    return if $panic;
		} else {
		    if ($antialias eq "false") {
			$com = "$smog_convert_command +antialias -size $width" .
			    "x$height $img_prefix2\"$fromdir/$from$img_ext2\" -scale $width" .
			    "x$height\\! $img_prefix\"$curworkdir/$to$img_ext\" >$nulfile 2>&1";
			$smerr = &smog_system($com);
			if ($smerr) {
			    &sig_system_error($com, $smerr);
			    $panic = 1;
			    return;
			}
		    } else {
			$com = "$smog_convert_command -antialias -size $width" .
			    "x$height $img_prefix2\"$fromdir/$from$img_ext2\" -scale $width" .
			    "x$height\\! $img_prefix\"$curworkdir/$to$img_ext\" >$nulfile 2>&1";
			$smerr = &smog_system($com);
			if ($smerr) {
			    &sig_system_error($com, $smerr);
			    $panic = 1;
			    return;
			}}}}

	    &sig_progress(int($new_frames - $where + $j++ -$start + 1));
	}

	$times_inserted += $factor;
	$inserted = $nend - $nstart + 1;

	if ($times_inserted == 2) {
	    $nend = $nwhere;
	    $nstart = $nwhere - $inserted + 1;
	    $fromdir = $curworkdir;
	}
	$nwhere += $inserted;

	if ($times_inserted > 1) {
	    $nend += $inserted;
	    $factor *= 2;
	}
	while ($times > $times_inserted && $factor > $times - $times_inserted) {
	    $nstart += ($nend - $nstart + 1) / 2;
	    $factor /= 2;
	}
    }
}


sub resize_frame {
    my ($name, $width, $height) = @_;

    if (!defined($input_ext)) {
	$input_ext = $img_ext;
    }
    if (!defined($antialias)) {
	$antialias = "false";
    }

    if (!defined($input_prefix)) {
	$input_prefix = &get_img_prefix($input_ext);
    }
    if (!defined($resize_prefix)) {
	$resize_prefix = &get_img_prefix($resize_ext);
    }

    # TODO ****  !!! provide alternate if imagemagick is not available

    if ($antialias eq "false") {
	$com = "$smog_convert_command +antialias -size $width" . "x$height $input_prefix\"$name$input_ext\" -scale $width"
	    . "x$height\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    } else {
	$com = "$smog_convert_command -size $width" . "x$height $input_prefix\"$name$input_ext\" -resize $width"
	    . "x$height\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    }

    $smerr = &smog_system($com);
    if ($smerr) {
	&sig_system_error($com, $smerr);
	$panic = 1;
    }
}


sub letterbox_frame {
    my ($name, $iwidth, $iheight, $owidth, $oheight) = @_;

    # frame is at size $iwidth x $iheight (if known, or 0)
    # from name is $name$input_ext, to name is $name$output_ext
    # center overlay frame in black rect size $owidth x $oheight

    # TODO ****  !!! provide alternate if imagemagick is not available

    my $from = "$name$in_ext";
    my $to = "$name$out_ext";

    # overlay frame
    $xstart = int(($owidth - $iwidth) / 2);
    $ystart = int(($oheight - $iheight) / 2);

    #must unlink first in case $to is a symlink (when saving selection)
    unlink $to;

    $com = "$smog_composite_command -compose plus -dissolve 100 -geometry $iwidth"
	. "x$iheight\\!+$xstart\\!+$ystart\\! \"$from\" $blankname \"$to\" >$nulfile 2>&1";
    $smerr = &smog_system($com);
    if ($smerr) {
	&sig_system_error($com, $smerr);
	$panic = 1;
	return;
    }
}


sub zoom_frame {
    my ($name, $centre_x, $centre_y, $width, $height, $rscale) = @_;
    # zoom into a frame

    # first we will crop the frame to just slightly larger than
    # our target, then we will resize, then do an exact crop

    # TODO - if we expand an edge a lot, our final crop needs
    # adjusting

    if (!defined($antialias)) {
	$antialias = "false";
    }
    if (!defined($input_ext)) {
	$input_ext = $img_ext;
    }

    if (!defined($input_prefix)) {
	$input_prefix = &get_img_prefix($input_ext);
    }
    if (!defined($resize_prefix)) {
	$resize_prefix = &get_img_prefix($resize_ext);
    }

    my $left = int($centre_x - ($width / (2.*$rscale))) - 1;
    my $top = int($centre_y - ($height / (2.*$rscale))) - 1;
    my $right = int($centre_x + ($width / (2.*$rscale))) + 1;
    my $bottom = int($centre_y + ($height / (2.*$rscale))) + 1;

    if ($left < 0) {
	$right -= $left;
	$left = 0;
    }
    if ($top < 0) {
	$bottom -= $top;
	$top = 0;
    }
    if ($right >= $width) {
	$left -= $right - $width + 1;
	if ($left < 0) {
	    $left = 0;
	}
	$right = $width - 1;
    }
    if ($bottom >= $height) {
	$top -= $bottom - $height + 1;
	if ($top < 0) {
	    $top = 0;
	}
	$bottom = $height - 1;
    }
    my $nwidth = $right - $left + 1;
    my $nheight = $bottom - $top + 1;

    if ($antialias eq "false") {
	$com = "$smog_convert_command -antialias -size $width"."x$height $input_prefix\"$name$input_ext\" -crop $nwidth"
	    ."x$nheight\\!+$left\\!+$top\\! -resize $width"."x$height\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    } else {
	$com = "$smog_convert_command +antialias -size $width"."x$height $input_prefix\"$name$input_ext\" -crop $nwidth"
	    ."x$nheight\\!+$left\\!+$top\\! -resize $width"."x$height\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    }

    $smerror = &smog_system($com);

    if ($smerror) {
	&sig_system_error($com, $smerror);
	$panic = 1;
    }
}


sub trim_frame {
    my ($name, $width, $height, $x, $y, $nwidth, $nheight) = @_;

    if (!defined($input_ext)) {
	$input_ext = $img_ext;
    }
    if (!defined($input_prefix)) {
	$input_prefix = &get_img_prefix($input_ext);
    }
    if (!defined($resize_prefix)) {
	$resize_prefix = &get_img_prefix($resize_ext);
    }
    if (!defined($antialias)) {
	$antialias = "false";
    }
    if ($antialias eq "false") {
	$com = "$smog_convert_command +antialias -size $width"."x$height $input_prefix\"$name$input_ext\" -crop $nwidth"
	    ."x$nheight\\!+$x\\!+$y\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    } else {
	$com = "$smog_convert_command -antialias -size $width"."x$height $input_prefix\"$name$input_ext\" -crop $nwidth"
	    ."x$nheight\\!+$x\\!+$y\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";
    }

    $smerror = &smog_system($com);

    if ($smerror) {
	&sig_system_error($com, $smerror);
	$panic = 1;
    }
}


sub trim_center {
    my ($name, $width, $height) = @_;

    if (!defined($input_ext)) {
	$input_ext = $img_ext;
    }

    $imresact = "none";
    &get_image_size("$name$input_ext");
    return if ($panic || $hsize == -1);

    if (!defined($input_prefix)) {
	$input_prefix = &get_img_prefix($input_ext);
    }

    if (!defined($resize_prefix)) {
	$resize_prefix = &get_img_prefix($resize_ext);
    }

    unless ($hsize >= $width &&$vsize >= $height) {
	#composite over a large enough frame
	$size = $hsize;
	if ($vsize > $size) {
	    $size = $vsize;
	}

	$xstart = int(($size - $hsize) / 2);
	$ystart = int(($size - $vsize) / 2);

	# make a background frame
	if (!defined($bgcolour)) {
	    $bgcolour = "#000000";
	}

	$com = "$smog_convert_command -size $size" . "x$size\\! xc:$bgcolour $input_prefix" .
	    "blank$input_ext >$nulfile 2>&1";

	$smerror = &smog_system($com);

	if ($smerror) {
	    &sig_system_error($com, $smerror);
	    $panic = 1;
	    return;
	}

	$com = "$smog_composite_command -compose plus -dissolve 100 -geometry $hsize" .
	    "x$vsize\\!+$xstart\\!+$ystart\\! "
	    ."$input_prefix\"$name$input_ext\" $input_prefix".
	    "blank$input_ext $input_prefix\"$name$input_ext\" >$nulfile 2>&1";

	$smerror = &smog_system($com);

	if ($smerror) {
	    &sig_system_error($com, $smerror);
	    $panic = 1;
	    return;
	}

	$hsize = $vsize = $size;
    }

    $x = int(($hsize - $width) / 2);
    $y = int(($vsize - $height) / 2);

    $com = "$smog_convert_command $input_prefix\"$name$input_ext\" -crop $width" .
	"x$height\\!+$x\\!$y\\! $resize_prefix\"$name$resize_ext\" >$nulfile 2>&1";

    $smerror = &smog_system($com);

    if ($smerror) {
	&sig_system_error($com, $smerror);
	$panic = 1;
	return;
    }
}


sub get_mpv_info {
    ## we will set the following if possible:
    # $count, $bpp, $type, $hsize, $vsize, $signed, $f_size, $arate, $achans, $asamps, $endian, $abitrate
    # $length, $id_vid_form, $id_aud_form
    # $comment, $title, $author
    my ($file, $VERBOSE) = @_;

    my @allprops = split("\n",
			 "filename
			 path
			 stream-start
			 stream-end
			 stream-length

			 demuxer

			 length
			 duration
			 chapters
			 editions
			 titles
			 metadata

			 audio
			 audio-bitrate
			 audio-codec
			 audio-codec-name
			 audio-format
			 audio-params/channel-count
			 audio-channels
			 audio-params/samplerate
			 audio-samplerate

			 video
			 angle
			 video-bitrate
			 video-codec
			 video-format
			 video-aspect
			 fps
			 width
			 height
			 dwidth
			 dheight
			 gamma
			 seekable

			 sub");

    my $propstr = "";
    my $com;

    foreach my $line(@allprops) {
	$line =~ s/^\s+//;
	$propstr .= "XDATA:$line=\\\${=$line}\n";
    }

    if (defined($is_remote) && ($is_remote > 0)) {
	$com = "$mplay_command --term-playing-msg=\"$propstr\" --vo=null --ao=null --frames=1 --quiet --cache=32 " .
	    "--no-config \"$file\" 2>$nulfile";
    } else {
	$com = "$mplay_command --term-playing-msg=\"$propstr\" --vo=null --ao=null --frames=1 --quiet --no-cache " .
	    "--no-config \"$file\"  2>$nulfile";
    }

    if ($VERBOSE > 1) {
	print STDERR "EXECUTING: $com\n";
    }

    open (my $fh, "$com |");
    while (defined(my $line = <$fh>)) {
	if ($line =~ /^XDATA:duration=(.*)/ || $line =~ /^XDATA:length=(.*)/) {
	    next if ($lenght);
	    next if ($1 eq "");
	    $length = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got length $length\n";
	    }

	    if ($length eq "0" && !(defined($is_remote) && ($is_remote > 0)) && !$retried) {
		close $fh;
		$com =
		    "$mplay_command -noconfig all -nocache -demuxer lavf -identify -vo null -ao null -frames 0 \"$file\" " .
		    "<$nulfile";
		open ($fh, "$com |");
		$retried = 1;
		if ($VERBOSE > 1) {
		    print STDERR "retrying with demuxer lavf\n";
		}
	    }
	    next;
	}

	if ($line =~ /^XDATA:video-format=(.*)/) {
	    next if ($id_vid_form);
	    next if ($1 eq "");
	    $id_vid_form = $1;
	    $type = $id_vid_form;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got id_vid_form $id_vid_form\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:audio-format=(.*)/ || $line =~ /^XDATA:audio-codec-name=(.*)/) {
	    next if ($id_aud_form);
	    next if ($1 eq "");
	    $id_aud_form = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got id_aud_form $id_aud_form\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:audio-samplerate=(.*)/ || $line =~ /^XDATA:audio-params\/samplerate=(.*)/) {
	    next if ($arate);
	    next if ($1 eq "");
	    $arate = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got arate $arate\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:audio-channels=(.*)/ || $line =~ /^XDATA:audio-params\/channel-count=(.*)/) {
	    next if ($achans);
	    next if ($1 eq "");
	    $achans = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got achans $achans\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:audio-bitrate=(.*)/) {
	    next if ($abitrate);
	    next if ($1 eq "");
	    $abitrate = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got abitrate $abitrate\n";
	    }
	    next;
	}

	if ($line =~ /^AO: \[null\] (.*)/) {
	    next if ($audio);
	    next if ($1 eq "");
	    $audio = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got audio $audio\n";
	    }

	    $samps = (split(/ /, $audio))[3];
	    chomp($samps);
	    if ($VERBOSE > 1) {
		print STDERR "mpv got asamps $asamps\n";
	    }
	    if ($samps =~ /^f/) {
		$asamps = 32;
		$signed = 1;
	    }
	    elsif ($samps =~ /^s/) {
		$asamps = substr($asamps, 1, -1);
		$signed = 1;
	    }
	    elsif($samps =~ /^u/) {
		$asamps = substr($asamps, 1, -1);
		$signed = 0;
	    }
	    $audend = substr($samps, -2, 2);
	    if ($audend eq "le") {
		# le
		$endian = 1;
	    }
	    elsif ($audend eq "be") {
		$endian = 0;
	    }
	    if ($VERBOSE > 1) {
		print STDERR "mpv got asamps $asamps, signed $signed, endian $endian\n";
		if ($asamps == 32) {
		    print STDERR "32 bit audio input not yet supported, will downsample to 16 bit (patchme)\n";
		}
	    }
	    next;
	}

	if ($line =~ /^XDATA:width=(.*)/) {
	    next if ($hsize);
	    next if ($1 eq "");
	    $hsize = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got hsize $hsize\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:height=(.*)/) {
	    next if ($vsize);
	    next if ($1 eq "");
	    $vsize = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got vsize $vsize\n";
	    }
	    next;
	}

	if ($line =~ /^XDATA:fps=(.*)/) {
	    next if ($fps);
	    next if ($1 eq "");
	    $fps = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got fps $fps\n";
	    }
	    next;
	}

	if ($line =~ /^frames total (.*)/) {
	    next if ($count);
	    next if ($1 eq "");
	    $count = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got frames $count\n";
	    }
	    next;
	}

	if ($line =~ /^Comment: (.*)/) {
	    next if ($1 eq "");
	    if ($VERBOSE > 1) {
		print STDERR "mpv got comment $1\n";
	    }
	    $comment .= $1;
	    next;
	}

	if ($line =~ /^Title: (.*)/) {
	    next if ($title);
	    next if ($1 eq "");
	    $title = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got title $title\n";
	    }
	    next;
	}

	if ($line =~ /^Author: (.*)/) {
	    next if ($author);
	    next if ($1 eq "");
	    $author = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mpv got author $author\n";
	    }
	    next;
	}
	if ($VERBOSE > 1) {
	    print STDERR "mpv: unmatched line $line\n";
	}
    }

    if (!$id_vid_form) {
	if ($id_aud_form) {
	    $id_vid_form = "Audio";
	}
	if ($VERBOSE > 1) {
	    print STDERR "Video id not found, assuming Audio\n";
	}
    }

    if (!$count) {
	$count = int($fps * $length + .5);
	if ($VERBOSE > 1) {
	    print STDERR "Frames not found, calculating from length and fps ($count)\n";
	}
    }

    if (!$asamps && $arate * $achans > 0) {
	$asamps = $abitrate / $arate / $achans;
	if ($VERBOSE > 1) {
	    print STDERR "Audio sample size not found, calculating from abitrate, arate and achans ($asamps)\n";
	}
    }
    if (!$asamps) {
	$asamps = 16;
	if ($VERBOSE > 1) {
	    print STDERR "forced asamps to $asamps\n";
	}
    }
    if ($achans > 2) {
	if ($VERBOSE > 1) {
	    print STDERR "$achans audio chans not yet supported, will downmix to 2\n";
	}
	$achans = 2;
    }

    $f_size = -s $file;
    if ($VERBOSE > 1) {
	print STDERR "file size is $f_size\n";
    }
}


sub get_ffprobe_info {
    ## we will set the following if possible:
    # $count, $hsize, $vsize, $signed, $f_size, $arate, $achans, $asamps, $endian, $abitrate
    # $length, $id_vid_form, $id_aud_form
    # $comment, $title, $author

    my ($file, $VERBOSE) = @_;
    ##my $com = "ffprobe -hide_banner -show_streams -count_frames \"$file\"";

    my $com;
    my $is_video = 0;
    my $is_audio = 0;

    if ($VERBOSE > 1) {
	print STDERR "Running: $com\n";
    }

    my @streams = ("V", "a");
    foreach (@streams) {
	$com = "ffprobe -loglevel 24 -show_format -hide_banner -show_streams -select_streams $_ \"$file\" 2>$nulfile";
	if ($VERBOSE > 1) {
	    print STDERR "running $com\n";
	}
	open (my $fh, "$com |");
	while (defined(my $line = <$fh>)) {
	    $line =~ s/\r\n//g;
	    if ($line =~ /^\[FORMAT\]/) {
		$is_format = 1;
		$is_video = 0;
		$is_audio = 0;
		next;
	    }
	    if ($line =~ /^\[\/FORMAT\]/) {
		$is_format = 0;
		next;
	    }
	    if ($is_video && $line =~ /^avg_frame_rate=(.*)/) {
		next if ($fps);
		next if ($1 eq "N/A");
		$frate = $1;
		@rfps = (split /\//, $frate);
		if ($rfps[1] > 0) {
		    $fps = $rfps[0] / $rfps[1];
		}
		else {
		    $fps = $rfps[0];
		}
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got fps $fps\n";
		}
		next;
	    }
	    if ($is_audio && $line =~ /^bitrate=(.*)/) {
		next if ($bitrate);
		next if ($1 eq "N/A");
		$abitrate = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got abitrate $abitrate\n";
		}
		next;
	    }
	    if ($line =~ /^title=(.*)/) {
		next if ($1 eq "N/A");
		$title = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got title $title\n";
		}
		next;
	    }
	    if ($is_audio && $line =~ /^sample_fmt=(.*)/) {
		next if ($asamps && $signed);
		next if ($1 eq "N/A");
		my $samps = $1;
		if ($samps =~ /^f/) {
		    $asamps = 32;
		    $signed = 1;
		}
		elsif ($samps =~ /^s/) {
		    $asamps = substr($asamps, 1, -1);
		    $signed = 1;
		}
		elsif($samps =~ /^u/) {
		    $asamps = substr($asamps, 1, -1);
		    $signed = 0;
		}
		$audend = substr($samps, -2, 2);
		if ($audend eq "le") {
		    $endian = 1;
		}
		elsif ($audend eq "be") {
		    $endian = 0;
		}
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got fltp\n";
		}
		next;
	    }
	    if ($line =~ /^author=(.*)/) {
		next if ($1 eq "N/A");
		$author = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got author $author\n";
		}
		next;
	    }
	    if ($line =~ /^channels=(.*)/) {
		next if ($achans);
		next if ($1 eq "N/A");
		$achans = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got achans $achans\n";
		}
		next;
	    }
	    if ($line =~ /^sample_rate=(.*)/) {
		next if ($arate);
		next if ($1 eq "N/A");
		$arate = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got arate $arate\n";
		}
		next;
	    }
	    if ($is_video && $line =~ /^nb_frames=(.*)/) {
		next if ($count);
		next if ($1 eq "N/A");
		$count = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got frames $count\n";
		}
		next;
	    }
	    if ($line =~ /^width=(.*)/) {
		next if ($hsize);
		next if ($1 eq "N/A");
		$hsize = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got width $hsize\n";
		}
		next;
	    }
	    if ($line =~ /^height=(.*)/) {
		next if ($vsize);
		next if ($1 eq "N/A");
		$vsize = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got height $vsize\n";
		}
		next;
	    }
	    if ($is_format && $line =~ /^size=(.*)/) {
		next if ($f_size);
		next if ($1 eq "N/A");
		$f_size = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got f_size $f_size\n";
		}
		next;
	    }
	    if ($line =~ /^duration=(.*)/) {
		next if ($length && !$is_format);
		next if ($1 eq "N/A");
		$length = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got length .$length.\n";
		}
		next;
	    }
	    if ($line =~ /^codec_name=(.*)/) {
		next if ($1 eq "N/A");
		$codec = $1;
		## continue and get codec_type
	    }
	    if ($line =~ /^color_transfer=(.*)/) {
		next if ($gamma_type);
		next if ($1 eq "N/A");
		$gamma_type = $1;
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got gamma_type $gamma_type\n";
		}
		next;
	    }
	    if ($line =~ /^codec_type=(.*)/) {
		next if ($1 eq "N/A");
		$is_video = $is_audio = $is_format = 0;
		if ($1 eq "video") {
		    if ($id_vid_form) {
			print STDERR "Warning, found multiple video streams in container. " .
			    "Only the first will be imported.\n";
			next;
		    }
		    $id_vid_form = $codec;
		    $is_video = 1;
		    if ($VERBOSE > 1) {
			print STDERR "ffprobe got id_vid_form $id_vid_form\n";
		    }
		    next;
		}
		if ($1 eq "audio") {
		    if ($id_aud_form) {
			print STDERR "Found multiple audio streams in container.\n" .
			    "aid select not yet implemented. Only the first will be imported. (FIXME)\n";
			next;
		    }
		    $id_aud_form = $codec;
		    $is_audio = 1;
		    if ($VERBOSE > 1) {
			print STDERR "ffprobe got id_aud_form $id_aud_form\n";
		    }
		    next;
		}
		if ($1 eq "subtitle") {
		    $subs++;
		}
		if ($VERBOSE > 1) {
		    print STDERR "ffprobe got codec_type $1\n";
		}
		next;
	    }
	}
    }
}
if (!$count) {
    $count = int($fps * $length + .5);
}
if ($VERBOSE > 1) {
    print STDERR "ffprobe got revised frames $count\n";
}
if (!$asamps && $arate * $achans > 0) {
    $asamps = $abitrate / $arate / $achans;
    if ($VERBOSE > 1) {
	print STDERR "ffprobe guessed asamps $asamps\n";
    }
}
if (!$asamps) {
    $asamps = 16;
    if ($VERBOSE > 1) {
	print STDERR "ffprobe forced asamps to $asamps\n";
    }
}

if ($achans > 2) {
    $achans = 2;
    if ($VERBOSE > 1) {
	print STDERR "ffprobe forced achans to $achans\n";
    }
}
if ($subs && $VERBOSE > 0 ) {
    print STDERR "clip has $subs subtitles available, these will not be imported (PATCHME).\n";
}

if (!$id_vid_form  && $codec) {
    $id_vid_form = $codec;
}

if (!$id_vid_form || $id_vid_form eq "Unknown") {
    if ($id_aud_form) {
	$id_vid_form = $id_aud_form;
	$type = "Audio";
    }
    if ($VERBOSE > 1) {
	print STDERR "Video id not found, assuming Audio\n";
    }
}



sub get_mplayer_info {
    ## we will set the following if possible:
    # $count, $bpp, $type, $hsize, $vsize, $signed, $f_size, $arate, $achans, $asamps, $endian, $abitrate
    # $length, $id_vid_form, $id_aud_form
    # $comment, $title, $author

    my ($file, $VERBOSE) = @_;
    my $com;
    my $retried = 0;

    if (defined($is_remote) && ($is_remote > 0)) {
	# remote files might be streams, so we need to cache a bit before we can identify them
	$com = "$mplay_command -noconfig all -identify -vo null -ao null -frames 0 -cache 32 \"$file\" <$nulfile 2>$nulfile";
    } else {
	$com = "$mplay_command -noconfig all -identify -vo null -ao null -frames 0 -nocache \"$file\" <$nulfile 2>$nulfile";
    }

    if ($VERBOSE > 1) {
	print STDERR "EXECUTING: $com\n";
    }

    open (my $fh, "$com |");
    while (defined(my $line = <$fh>)) {
	if ($line =~ /^ID_LENGTH=(.*)/) {
	    next if ($lenght);
	    next if ($1 eq "");
	    $length = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got length $length\n";
	    }

	    if ($length eq "0" && !(defined($is_remote) && ($is_remote > 0)) && !$retried) {
		close $fh;
		$com = "$mplay_command -noconfig all -nocache -demuxer lavf -identify -vo null -ao null -frames 0 \"$file\" " .
		    "<$nulfile";
		open ($fh, "$com |");
		$retried = 1;
		if ($VERBOSE > 1) {
		    print STDERR "retrying with demuxer lavf\n";
		}
	    }
	    next;
	}

	if ($line =~ /^ID_VIDEO_FORMAT=(.*)/) {
	    next if ($id_vid_form);
	    next if ($1 eq "");
	    $id_vid_form = $1;
	    $type = $id_vid_form;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got id_vid_form $id_vid_form\n";
	    }
	    next;
	}

	if ($line =~ /^ID_AUDIO_FORMAT=(.*)/) {
	    next if ($id_aud_form);
	    next if ($1 eq "");
	    $id_aud_form = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got id_aud_form $id_aud_form\n";
	    }
	    next;
	}

	if ($line =~ /^ID_AUDIO_RATE=(.*)/) {
	    next if ($arate);
	    next if ($1 eq "");
	    $arate = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got arate $arate\n";
	    }
	    next;
	}

	if ($line =~ /^ID_AUDIO_NCH=(.*)/) {
	    next if ($achans);
	    next if ($1 eq "");
	    $achans = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got achans $achans\n";
	    }
	    next;
	}

	if ($line =~ /^ID_AUDIO_BITRATE=(.*)/) {
	    next if ($abitrate);
	    next if ($1 eq "");
	    $abitrate = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got abitrate $abitrate\n";
	    }
	    next;
	}

	if ($line =~ /^AUDIO: (.*)/) {
	    next if ($audio);
	    next if ($1 eq "");
	    $audio = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got audio $audio\n";
	    }

	    $samps = (split(/\, /, $audio))[2];
	    chomp($samps);
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got asamps $asamps\n";
	    }
	    if ($samps =~ /^f/) {
		$asamps = 32;
		$signed = 1;
	    }
	    elsif ($samps =~ /^s/) {
		$asamps = substr($asamps, 1, -1);
		$signed = 1;
	    }
	    elsif($samps =~ /^u/) {
		$asamps = substr($asamps, 1, -1);
		$signed = 0;
	    }
	    $audend = substr($samps, -2, 2);
	    if ($audend eq "le") {
		# le
		$endian = 1;
	    }
	    elsif ($audend eq "be") {
		$endian = 0;
	    }
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got asamps $asamps, signed $signed, endian $endian\n";
		if ($asamps == 32) {
		    print STDERR "32 bit audio input not yet supported, will downsample to 16 bit (patchme)\n";
		}
	    }
	    next;
	}

	if ($line =~ /^ID_VIDEO_WIDTH=(.*)/) {
	    next if ($hsize);
	    next if ($1 eq "");
	    $hsize = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got hsize $hsize\n";
	    }
	    next;
	}

	if ($line =~ /^ID_VIDEO_HEIGHT=(.*)/) {
	    next if ($vsize);
	    next if ($1 eq "");
	    $vsize = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got vsize $vsize\n";
	    }
	    next;
	}

	if ($line =~ /^ID_VIDEO_FPS=(.*)/) {
	    next if ($fps);
	    next if ($1 eq "");
	    $fps = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got fps $fps\n";
	    }
	    next;
	}

	if ($line =~ /^frames total (.*)/) {
	    next if ($count);
	    next if ($1 eq "");
	    $count = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got frames $count\n";
	    }
	    next;
	}

	if ($line =~ /^Comments: (.*)/) {
	    next if ($1 eq "");
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got comment $1\n";
	    }
	    $comment .= $1;
	    next;
	}

	if ($line =~ /^Title: (.*)/) {
	    next if ($title);
	    next if ($1 eq "");
	    $title = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got title $title\n";
	    }
	    next;
	}

	if ($line =~ /^Author: (.*)/) {
	    next if ($author);
	    next if ($1 eq "");
	    $author = $1;
	    if ($VERBOSE > 1) {
		print STDERR "mplayer got author $author\n";
	    }
	    next;
	}
    }

    if (!$id_vid_form) {
	if ($id_aud_form) {
	    $id_vid_form = "Audio";
	}
	if ($VERBOSE > 1) {
	    print STDERR "Video id not found, assuming Audio\n";
	}
    }

    if (!$count) {
	$count = int($fps * $length + .5);
	if ($VERBOSE > 1) {
	    print STDERR "Frames not found, calculating from length and fps ($count)\n";
	}
    }

    if (!$asamps && $arate * $achans > 0) {
	$asamps = $abitrate / $arate / $achans;
	if ($VERBOSE > 1) {
	    print STDERR "Audio sample size not found, calculating from abitrate, arate and achans ($asamps)\n";
	}
    }
    if (!$asamps) {
	$asamps = 16;
	if ($VERBOSE > 1) {
	    print STDERR "forced asamps to $asamps\n";
	}
    }
    if ($achans > 2) {
	if ($VERBOSE > 1) {
	    print STDERR "$achans audio chans not yet supported, will downmix to 2\n";
	}
	$achans = 2;
    }

    $f_size = -s $file;
    if ($VERBOSE > 1) {
	print STDERR "file size is $f_size\n";
    }
}


sub trim {
    ## trim whitespace from beginning and end
    my $str = shift;
    $str =~ s/^\s+|\s+$//g;
    $str;
}


sub get_file_info {
    ## TODO: use 'file' to get the filetype and check for common video, audio, image types
    # e.g file -ib foo

    ## we will set the following if possible:
    # $count, $bpp, $type, $hsize, $vsize, $signed, $f_size, $arate, $achans, $asamps, $endian, $abitrate
    # $length, $id_vid_form, $id_aud_form
    # $comment, $title, $author

    # $type may be a video format ($id_vid_form) or "Audio" or an image format ("jpeg", "png", etc).
    my ($file, $scantype, $VERBOSE) = @_; ## 0 = scan_all, 1 = scan_for_open, 2 = scan_audio, 3 = scan_remote, 4 = scan_img
    my $is_remote = 0;
    if ($scantype == 3) {
	$is_remote = 1;
    }
    my $P_IMAGE = 1;
    my $P_AUDIO = 2;
    my $P_VIDEO = 4;

    my $ptypes = $P_AUDIO | $P_VIDEO;
    if ($scantype == 0) {
	$ptypes |= $P_IMAGE;
    }
    elsif ($scantype == 4) {
	$ptypes = $P_IMAGE;
    }
    $count = 0;
    $bpp = 24;
    # default if none is found
    $fps = 0;
    # let the front-end handle this if we can't get it ;-)
    $type ="Unknown";
    $hsize = 0;
    $vsize = 0;
    $signed = -1;
    $f_size = 0;
    $arate = $asamps = $achans = 0;
    $frames = 0;
    $asamps = 0;
    $length = 0;
    $id_vid_form = "";
    $endian="";
    $gamma_type = "";
    $author = "";
    $title = "";
    $comment = "";

    $is_mpv = 0;

    $DEBUG_FILE_ID = 0;
    if ($DEBUG_FILE_ID) {
	print STDERR "IDing $file, scan type $scantype\n";
    }

    if (!defined($ximg_ext)) {
	$ximg_ext = $img_ext;
    }

    if ($scantype == 2) {
	$type = "Audio";
	$ptypes = $P_AUDIO;
    }

    my $guess_type = "";

    if ($scantype == 0) {
	if (!defined($file_exec)) {
	    $file_exec = &location("file");
	}
	if ($file_exec) {
	    $guess_type = &file_examine($file);
	    if ($DEBUG_FILE_ID) {
		print STDERR "guessed as $guess_type\n";
	    }
	    if ($guess_type eq "audio") {
		$ptypes = $P_AUDIO;
		$type = "Audio";
	    }
	    if ($guess_type eq "image") {
		$ptypes = $P_IMAGE;
	    }
	    if ($guess_type eq "application" || $guess_type eq "text") {
		$ptypes = $P_AUDIO | $P_VIDEO;
	    }
	}
    }

    while (1) {
	if (($ptypes & $P_VIDEO) || ($ptypes & $P_AUDIO)) {
	    my $ffprobe = &location("ffprobe");
	    if ($ffprobe ne "") {
		&get_ffprobe_info($file, $VERBOSE);
	    }

	    if ($id_vid_form eq "") {
		if ($mplay_command eq "") {
		    my $mp_command = &config_get("video_open_command");
		    if ($mp_command ne "") {
			my $mpc1 = (split(" ", $mp_command))[0];
			if ($mpc1 =~ /mpv$/ || $mpc1 =~ /mpv\"$/) {
			    $is_mpv = 1;
			}
		    }
		    else {
			# sets $is_mpv
			$mp_command = "\"" . &get_mplayer_location . "\"";
		    }

		    if ($mp_command eq "") {
			$panic = 1;
			return;
		    }
		    # try to force language to English
		    $mplay_command = "LANGUAGE=en LANG=en $mp_command";
		}

		# if mplayer supports the -identify command, use that
		# the format changed for 1.0pre1 so now we use -vo null -ao null -frames 0

		unless ($mplay_command eq "") {
		    if ($is_mpv) {
			&get_mpv_info($file, $VERBOSE);
		    }
		    else {
			&get_mplayer_info($file, $VERBOSE);
		    }
		}
	    }
	    if ($id_vid_form && $id_vid_form ne "Unknown") {
		$type = $id_vid_form;
		last;
	    }
	    last if $type eq "Audio";
	}

	$fwtype = (split(" ", $type))[0];
	#print ("IMG FILT .$count. .$type. .$length. .$fps.\n");

	if (($ptypes & $P_IMAGE) && $guess_type ne "video"
	    && !($type eq "Audio" && $achans >0 ) && (!$hsize || !$vsize || !$count
						      ## test for various image types...
						      || (($type eq "ffpng" || $fwtype eq "PNG"
							   || (($type eq "mjpeg" || $type=="png")
							       && $length eq "" && int($fps)==1)
							   || $type eq "gif") && !$count)
						      || (($type eq "ffmjpeg" || $fwtype eq "MJPEG")
							  && $count == 1)
						      || ($fps == 1. && $count == 1 && $id_aud_form eq ""
							  && (int($length) == 1 || !$length)))) {

	    $origcount = $count;
	    $name = &mkname(1);

	    unless (-f "$curworkdir/$name") {
		# see if it is image(s)
		&open_images(1, $file);
		if ($hsize == -1) {
		    $hsize = $vsize = 0;
		}

		opendir DIR, $curworkdir;
		while ($file2 = readdir(DIR)) {
		    if ($file2 =~ /$ximg_ext$/) {
			$count++;
			$count -= $origcount;
			$origcount = 0;
			$imresact = "none";
			if ($only_first) {
			    unlink glob "$curworkdir/*$ximg_ext";
			    last;
			}
			else {
			    $f_size += -s $file2;
			}
		    }
		    else {
			unless ($file2 eq $audio_in) {
			    unlink $file2;
			}
		    }
		}
		closedir DIR;

		if ($count) {
		    # got image(s)
		    $frames = $count;
		    # got image(s)
		    if ($ximg_ext eq ".jpg") {
			$type = "jpeg";
		    }
		    else {
			$type = "png";
		    }

		    $count = 0;
		    $name = &mkname(1);
		    last;
		}
		else {
		    if ($ptypes == $P_IMAGE) {
			$ptypes = $P_VIDEO | $P_AUDIO;
		    }
		    else {
			last;
		    }
		}
	    }
	}
	else {
	    last;
	}
    }

    if ($signed == -1) {
	if ($asamps == 8) {
	    $signed = 0;
	}
	else {
	    $signed = 1;
	}
    }

    if ($endian eq "") {
	$endian = &get_endian; # assume audio endian matches machine endian
    }

    # get file size
    if ($f_size == 0) {
	$f_size = -s $file;
    }

    if ($asamps < 5 && $asamps > 0) {
	$asamps = 16;
    }

    if ($type =~ m/^\[/) {
	$type = substr($type, 1, -1);
    }
    if ($type eq " Unknown") {
	if ($count) {
	    $type = "video";
	}
	elsif ($achans > 0) {
	    $type = "Audio";
	}
    }
}


sub convert_audio_to_raw {
    #convert .wav to raw pcm using sox or mplayer
    ## NEEDS $arate, $asamps, $achans, $audio_in, $audio_out ($nodither, $signed, $endian)
    ## for sox needs sox_version

    #TODO: my ($arate, ...) = @_;
    my $smcom;

    if (&location("sox") ne "") {
	if (!defined($sox_version)) {
	    $sox_version = &get_sox_version;
	}
	if (!defined($axsigned)) {
	    ## call get_sox_signed with guessed format (u for 8, s for 16)
	    ## returns signed e.g -e signed
	    $axsigned = &get_sox_format($asamps);
	}
	if (!$axsamps) {
	    ## returns eg -b 16
	    $axsamps = &get_sox_samps($asamps);
	}

	## only needed for resampling I think
	if ($sox_version < 13000000) {
	    $nodither = "";
	}
	else {
	    $nodither = "-D";
	}

	$smcom = "sox $nodither -t .wav  \"$audio_in\" -t .raw -r $arate " .
	    "-c $achans $axsigned -$axsamps \"$audio_out\" >$nulfile 2>&1";
	if ($DEBUG_SMOGRIFY) {
	    print STDERR "COM is $smcom\n";
	}
    }
    else {
	my $threads = &smog_system_direct("$SYSBIN" . "grep processor /proc/cpuinfo | wc -l");
	$threads = int($threads);
	if ($threads == 0) {
	    $threads = 1;
	}
	$mp_com = "\"". &get_mplayer_location . "\"";
	$format = &get_mplayer_format;

	if ($is_mpv) {
	    if ($format eq "") {
		# e.g. s8, u16, also no endian handling
		&sig_system_error("mpv cannot fully resample");
		$panic = 1;
		return;
	    }
	    my $threadopts = "--vd-lavc-threads=$threads";
	    $smcom = "$mp_com --really-quiet --no-config --vo=null --no-video --ao=pcm --ao-pcm-waveheader=no " .
		"--ao-pcm-file=\"$audio_out\" $format";
	}
	else {
	    my $threadopts = "-lavdopts o=threads=$threads";
	    $smcom = "$mp_com -really-quiet -noconfig all -vo null -vc null -benchmark-novideo -ao pcm:fast:nowaveheader:" .
		"file=\"$audio_out\" $format <$nulfile";
	}
    }

    my $smerr = &smog_system($smcom);

    if ($smerr) {
	&sig_system_error($smcom, $smerr);
	$panic = 1;
	return;
    }

    &smog_system_sync();
}


sub convert_audio_to_wav {
    #convert raw to .wav
    my $smcom;

    &smog_chdir($curworkdir);
    if (&location("sox") eq "") {
	my $aasamps = $asamps / 8;
	my $mp_com = "\"". &get_mplayer_location . "\"";
	my $format = &get_mplayer_format;
	my $threads = &smog_system_direct("$SYSBIN" . "grep processor /proc/cpuinfo | wc -l");
	$threads = int($threads);
	if ($threads == 0) {
	    $threads = 1;
	}

	if ($is_mpv) {
	    my $threadopts = "--vd-lavc-threads=$threads";
	    $smcom = "$mp_com --no-config --really-quiet --demuxer=rawaudio --demuxer-rawaudio-rate=$arate " .
		"--demuxer-rawaudio-format=$format --demuxer-rawaudio-channels=$achans --vo=null --no-video " .
		"--ao=pcm:waveheader \"$audio_in\"";
	}
	else {
	    my $threadopts = "-lavdopts o=threads=$threads";
	    $smcom = "$mp_com -noconfig all -really-quiet -demuxer rawaudio -rawaudio " .
		"rate=$arate:channels=$achans:samplesize=$aasamps $format -vo null -novideo " .
		"-ao pcm:fast:waveheader:$format \"$audio_in\" >$nulfile 2>&1 <$nulfile";
	}
    }
    else {
	if (!defined($sox_version)) {
	    $sox_version = &get_sox_version;
	}

	$sasamps = &get_sox_samps($asamps);

	if (!defined($nrate)) {
	    $nrate = $arate;
	}

	if (!defined($nchans)) {
	    $nchans = $achans;
	}

	if (!defined($nasamps)) {
	    $nasamps = $asamps;
	}

	if (!defined($asigned)) {
	    ## call get_sox_signed with guessed format (u for 8, s for 16)
	    ## returns signed e.g -e signed
	    $asigned = &get_sox_format($asamps);
	}

	if (!defined($nsigned)) {
	    ## call get_sox_signed with guessed format (u for 8, s for 16)
	    ## returns signed e.g -e signed
	    $nsigned = &get_sox_format($nasamps);
	}

	$nnasamps = &get_sox_samps($nasamps);

	## only needed for resampling I think
	if ($sox_version < 13000000) {
	    $nodither = "";
	}
	else {
	    $nodither = "-D";
	}

	$smcom = "sox $nodither -t .raw -r $arate $asigned -$sasamps -c $achans \"$audio_in\" -t .wav -r $nrate " .
	    "-c $nchans $nsigned -$nnasamps \"$curworkdir/audiodump.wav\" >$nulfile 2>&1";
    }

    if ($DEBUG_SMOGRIFY) {
	print STDERR "com is $smcom\n";
    }
    my $smerr = &smog_system($smcom);
    if ($smerr) {
	&sig_system_error($smcom, $smerr);
	$panic = 1;
	return;
    }

    &smog_system_sync();
}


#clip (actually, trim) the audio from $start seconds to $end seconds

sub clip_audio {
    ## needs global: arate, achans, asamps, start, end, curworkdir

    my ($start, $end) = @_;

    if ($achans == 0) {
	return;
    }

    $audio_in = "$curworkdir/audio";
    $audio_out = "$curworkdir/audioclip";

    if (-s $audio_out) {
	unlink $audio_out;
	&smog_system_sync();
	if (-s $audio_out) {
	    $panic = &sig_write_error($audio_out);
	    return;
	}
    }
    unless (&is_writeable($audio_out, 1)) {
	$panic = &sig_write_error($audio_out);
	return;
    }

    my $align = $achans * $asamps / 8;

    my $spos = &align($arate * $align * $start);
    my $epos = &align($arate * $align * $end);

    my $fsize = -s $audio_in;

    if ($epos > $fsize) {
	$epos = $fsize;
    }
    if ($spos > $epos) {
	$spos = $epos;
    }

    my $size = ($epos - $spos);

    if ($size > 0) {
	$fdd_in = $audio_in;
	$fdd_out = $audio_out;
	&fast_dd($size, $spos, 0);
	return if ($panic);
	if (-s $audio_out != $size) {
	    $panic = &sig_write_error($audio_out);
	    return;
	}
    }

    $audio_out;
}


sub resample_audio {
    #WARNING - $asamps and $nasamps are in bits

    $endian = &get_endian;

    if (!defined($sox_version)) {
	$sox_version = &get_sox_version;
    }

    if ($sox_version < 13000000) {
	$nodither = "";
    }
    else {
	$nodither = "-D";
    }

    $osamps = &get_sox_samps($asamps);
    $nsamps = &get_sox_samps($nsamps);

    $osigned = &get_sox_signed($asigned);
    $nsigned = &get_sox_signed($nsigned);

    if ($aendian == $endian) {
	$oendian = "";
    }
    else {
	$oendian = "-x";
    }

    if ($nendian == $endian) {
	$nendian = "";
    }
    else {
	$nendian = "-x";
    }

    if (defined($stretch)) {
	$com = "sox $nodither -t .raw -r $arate -c $achans $osigned -$osamps $oendian \"$audio_in\" -t .raw -r $nrate "
	    ."-c $nchans $nsigned $nendian -$nsamps \"$audio_out\" stretch $stretch>$nulfile 2>&1";
    }
    else {
	$com = "sox $nodither -t .raw -r $arate -c $achans $osigned -$osamps $oendian \"$audio_in\" -t .raw -r $nrate "
	    ."-c $nchans $nsigned $nendian -$nsamps \"$audio_out\">$nulfile 2>&1";
    }
    $smerr = &smog_system($com);
    if ($smerr) {
	&sig_system_error($com, $smerr);
	$panic = 1;
    }
}


sub get_ext {
    my $fname = shift;
    my $ext = (split(/\./,$fname))[-1];
    if ($ext =~ /(.*)\"$/) {
	$ext = $1;
    }

    return "." . $ext;
}


sub insert_audio {
    ## called from: lin_frames. trim_audio, insert_audio

    #what we are going to do:
    # 1) copy the end of the audio (after insertion) to a new file [$where to $to_end]
    # 2) insert silence up to $where (if necessary)
    # 3) insert (append / overwrite) the new section [$start to $end at $where]
    # 4) insert silence up to $where + ($end - $tart) (if necessary)
    # 5) then put the end back

    ## optimisations:
    # if start is 0, then we rename instead of copy for step 1
    # when performing multple insertions, after two insertions we insert from the output file
    #
    ## if insert gap is >= eof - end, then we can do step 1 inplace and skip step 5
    #

    #input params:
    # $audio_to = name of file to insert into
    # $where - insertion pt - in seconds
    # $start, $end - section of from_file to insert - both in seconds
    # $from_handle - handle of from file
    # $times - number of times to insert
    # $arate

    #if $arate < 0, we will insert silence from $where to $where + $start - $end
    # if $undo_cut is non-zero then we are reversing a previous cut. In this case we will insert all of audio.bak
    # which should contain the cut section

    my ($xxsamps, $xxchans, $xxsigned, $xxendian, $undo_cut) = @_;

    if ($achans == 0) {
	return;
    }

    unless (&is_writeable($audio_to, 0)) {
	$panic = &sig_write_error($audio_to);
	return;
    }

    if (!defined($times)) {
	$times = 1;
    }

    my $audio_from = $audio_from;

    my $audio_to = "$curworkdir/audio";
    if (defined($from_handle)) {
	if ($undo_cut) {
	    $audio_from = "$workdir/$from_handle/audio.bak";
	}
	else {
	    $audio_from = "$workdir/$from_handle/audio";
	}
    }
    my $audio_temp = "$curworkdir/audio.temp";

    if (-s $audio_temp) {
	unlink $audio_temp;
	if (-s $audio_temp) {
	    $panic = &sig_write_error($audio_temp);
	    return;
	}
    }

    my $silence = 0;
    if ($arate < 0) {
	$silence = 1;
	$arate = -$arate;
    }

    $align = $achans * $asamps / 8;

    my $ospos = $spos = &align($arate * $align * $start);
    my $oepos = $epos = &align($arate * $align * $end);
    my $owpos = $wpos = &align($arate * $align * $where);
    my $inplace = 0;
    my $fsize = -s $audio_to;
    my $needstemp = 1;

    if ($wpos >= $fsize) {
	$needstemp = 0;
    }
    else {
	if ($epos - $spos >= $fsize - $wpos) {
	    $inplace = 1;
	}
    }

    if ($needstemp) {
	if ($where > 0) {
	    # step 1 - copy end to temp
	    my $size = $fsize - $wpos;
	    if ($inplace) {
		my $isize = $epos - $spos;
		$fdd_in = $audio_to;
		$fdd_out = $audio_to;
		&fast_dd($size, $wpos, $wpos + $isize);
		return if $panic;
		$fsize += $size;
		if (-s $audio_to != $fsize) {
		    $panic = &sig_write_error($audio_to);
		    return;
		}
	    }
	    else {
		unless (&is_writeable($audio_temp, 1)) {
		    $panic = &sig_write_error($audio_temp);
		    return;
		}
		$fdd_in = $audio_to;
		$fdd_out = $audio_temp;
		&fast_dd($size, $wpos, 0);
		return if ($panic);
		if (-s $audio_temp != $size) {
		    $panic = &sig_write_error($audio_temp);
		    unlink $audio_temp;
		    return;
		}}
	}
	else {
	    if ($fsize) {
		&smog_rename($audio_to, $audio_temp);
		&smog_system_sync();
		if (-s $audio_temp != $fsize) {
		    $panic = &sig_write_error($audio_temp);
		    return;
		}
	    }
	}
    }

    if ($wpos > $fsize) {
	# step 2 - pad with silence to insertion point (if necessary)
	&append_silence(0, $wpos, $xxsamps, $xxchans, $xxsigned, $xxendian, $audio_to);
    }

    ####################################################
    # step 3 - insert new section (possibly multiple times)

    my $nepos = $epos;
    my $nspos = $spos;
    my $nwpos = $wpos;

    $times_inserted = 0;
    $factor = 1;
    $silence_remembered = 0;

    my $xaudio_from = $audio_from;

    while ($times_inserted < $times) {
	$size = $nepos - $nspos;
	if ($size > 0) {
	    if ($silence) {
		my $offset = $fsize - $nwpos;
		if ($inplace) {
		    ## because $fsize grew
		    $offset -= ($epos - $spos);
		}
		&append_silence($offset, $nwpos + $size, $xxsamps, $xxchans, $xxsigned, $xxendian, $audio_to);
		return if $panic;
	    }
	    else {
		$fdd_in = $xaudio_from;
		$fdd_out = $audio_to;
		my $afsize = -s $fdd_in;
		if ($size + $nspos > $afsize) {
		    $size = $afsize - $nspos;
		}
		&fast_dd($size, $nspos, $nwpos);
		return if $panic;
	    }
	}

	$times_inserted += $factor;
	# step 4, pad with silence if necessary
	if (!$silence && (-s $audio_temp || $times_inserted < $times)) {
	    $inserted = $oepos - $nspos;
	    $fsize = -s $audio_to;
	    &append_silence(0, $nwpos + $inserted, $xxsamps, $xxchans, $xxsigned, $xxendian, $audio_to);
	    return if $panic;
	    unless ($silence_remembered > 0) {
		$silence_remembered = $oepos - $nepos;
	    }
	}
	else {
	    $inserted = $nepos - $nspos;
	}

	if ($times_inserted == 2) {
	    $oepos = $nepos = $nwpos;
	    $nspos = $nwpos - $inserted;
	    $xaudio_from = $audio_to;
	}

	$nwpos += $inserted;

	if ($times_inserted > 1) {
	    $nepos += $inserted;
	    $oepos += $inserted;
	    $factor *= 2;
	}
	while ($times > $times_inserted && $factor > $times - $times_inserted) {
	    $nspos += ($oepos - $nspos) / 2;
	    $factor /= 2;
	}

	if ($factor + $times_inserted == $times) {
	    # this will be our last insertion...
	    if (-s $audio_temp == 0) {
		$nepos -= $silence_remembered;
	    }}}

    ####################################################

    #step 5 - copy end back after insertion
    if ($needstemp && !$inplace) {
	$size = &align(-s $audio_temp);
	if ($size > 0) {
	    $fdd_in = $audio_temp;
	    $fdd_out = $audio_to;

	    &fast_dd($size, 0, $nwpos);
	    return if $panic;
	}
    }

    unlink $audio_temp;
    &smog_system_sync();
}


sub cut_audio {
    #what we are going to do:
    # a)
    ## copy from $start to $end to temp
    ## copy (inline) in original, removing (overwriting) the deleted section
    ## truncate original
    #
    ## on success, rename temp to bak
    ## bytes copied: $start to eof
    ##
    ## optimisations:
    # if $start == 0, end == 0
    # rename orig to backup
    #
    # $start == 0, $end > 0
    # rename orig to temp
    # copy from end to eof to orig
    # truncate temp
    #
    # if end == fsize
    # copy start to eof to temp
    # truncate orig

    return if !$achans;

    my $audio_in = "$curworkdir/audio";
    my $audio_bak = "$audio_in.bak";
    my $audio_temp = "$audio_in.temp";
    $align = $achans * $asamps / 8;
    my $fsize = &align(-s $audio_in);

    $spos = &align($arate * $align * $start);
    $epos = &align($arate * $align * $end);

    if ($epos == 0 || $epos > $fsize) {
	$epos = $fsize;
    }

    if ($spos >= $epos || ($spos == 0 && $epos == $fsize)) {
	if (-s $audio_bak) {
	    unlink $audio_bak;
	    if (-s $audio_bak) {
		$panic = &sig_write_error($audio_bak);
		return;
	    }
	}
	if ($spos == 0 && $epos == $fsize) {
	    &smog_rename($audio_in, $audio_bak);
	}
	else {
	    &smog_copy($audio_in, $audio_bak);
	    return if $panic;
	}

	&smog_system_sync();
	if (-s $audio_bak != $fsize) {
	    $panic = &sig_write_error($audio_bak);
	}
	return;
    }

    if (-s $audio_temp) {
	unlink $audio_temp;
	&smog_system_sync();
	if (-s $audio_temp) {
	    $panic = &sig_write_error($audio_temp);
	    return;
	}
    }

    if ($spos == 0) {
	&smog_rename($audio_in, $audio_temp);
	&smog_system_sync();
	if (-s $audio_temp != $fsize) {
	    $panic = &sig_write_error($audio_temp);
	}

	unless (&is_writeable($audio_in, 1)) {
	    $panic = &sig_write_error($audio_in);
	    return;
	}

	$fsize -= $epos;
	$fdd_in = $audio_temp;
	$fdd_out = $audio_in;
	&fast_dd($fsize, $epos, 0);
	&smog_system_sync();
	if (-s $audio_in != $fsize) {
	    $panic = &sig_write_error($audio_in);
	    return;
	}
	open my $fh, ">>", $audio_temp or $panic = &sig_write_error($audio_temp);
	return if $panic;
	truncate $fh, $epos;
	close $fh;
	&smog_system_sync();
	if (-s $audio_temp != $epos) {
	    $panic = &sig_write_error($audio_temp);
	    return;
	}
    }
    else {
	# step 1
	unless (&is_writeable($audio_temp, 1)) {
	    $panic = &sig_write_error($audio_temp);
	    return;
	}
	$fdd_in = $audio_in;
	$fdd_out = $audio_temp;
	&fast_dd($epos - $spos, $spos, 0);
	return if $panic;
	&smog_system_sync();
	if (-s $audio_temp != $epos - $spos) {
	    $panic = &sig_write_error($audio_temp);
	    return;
	}

	#step 2

	if ($epos < $fsize) {
	    $fdd_in = $audio_in;
	    $fdd_out = $audio_in;
	    &fast_dd($fsize - $epos, $epos, $spos);
	    return if $panic;
	    &smog_system_sync();
	    if (-s $audio_in != $fsize) {
		$panic = &sig_write_error($audio_in);
		return;
	    }
	}

	open my $fh, ">>", $audio_in or $panic = &sig_write_error($audio_in);
	return if $panic;
	$fsize -= $epos - $spos;
	truncate $fh, $fsize;
	close $fh;
	&smog_system_sync();
	if (-s $audio_in != $fsize) {
	    $panic = &sig_write_error($audio_in);
	    return;
	}
    }

    if (-s $audio_bak) {
	unlink $audio_bak;
	&smog_system_sync();
	if (-s $audio_bak) {
	    $panic = &sig_write_error($audio_bak);
	    return;
	}
    }
    $fsize = -s $audio_temp;
    &smog_rename($audio_temp, $audio_bak);
    &smog_system_sync();
    if (-s $audio_bak != $fsize) {
	$panic = &sig_write_error($audio_bak);
    }
}


sub align {
    ##TODO: requires global $align or global $achans, $asamps
    my $guess = shift;
    # align our audio cuts so we don't end halfway through a sample / channel
    if (!defined($align)) {
	$align = $achans * $asamps / 8;
    }
    $guess = int($guess / $align + .5) * $align;
    return $guess;
}


sub append_silence {
    ## neds global curworkdir
    #pad from eof - $offset of $audio_file to byte $end-1 with zeros
    ## pass in $audio_file or it will use "audio"

    my ($offset, $end, $asamps, $achans, $asigned, $aendian, $audio_file) = @_;
    if (!$audio_file) {
	$audio_file = "$curworkdir/audio";
    }

    $end = &align($end);
    my $fsize = -s $audio_file;
    $fsize = &align($fsize - $offset);
    my $size = &align($end - $fsize);

    return if !$size;

    unless (-f $audio_file) {
	unless (&is_writeable($audio_file, 1)) {
	    $panic = &sig_write_error($audio_file);
	    return;
	}
    }

    open AUD, "+<", $audio_file or $panic = &sig_write_error($audio_file);
    return if $panic;

    binmode AUD or $panic = &sig_read_error($fdd_in);
    return if $panic;

    if ($fsize > 0) {
	seek AUD, $fsize, SEEK_SET or $panic = &sig_write_error($audio_file);
	return if $panic;
    }

    #insert at $fsize - $offset
    #insert to $end

    my $bs = (stat AUD)[11] || 8192; # block size
    $bs = &align($bs);

    if ($size < $bs) {
	$bs = $size;
    }

    #create 1 block's worth of silence
    my $p1 = 0;
    my $p2 = 0;

    if ($asigned == 0) {
	$p1 = $p2 = 128;
	if ($asamps == 16) {
	    if ($aendian) {
		$p1 = 0;
	    } else {
		$p2 = 0;
	    }
	}
    }

    my $buff = pack("(C2)*", ($p1, $p2) x ($bs / 2));

    # now write the buffer n times
    while ($size > 0) {
	if ($size < $bs) {
	    $bs = $size;
	    $buff = substr($buff, 0, $bs);
	}
	print AUD $buff;
	$size -= $bs;
    }
    close AUD;
}


sub open_images {
    # set $only_first to 1 to just open the first image in a directory
    # set $height $width to force a particular size, otherwise $height=$width=0 to open all to the first image size
    # if first image size cannot be obtained $dwidth and $dheight are used

    my ($i, $file) = @_;
    my $orig = $i;

    if (-d $file) {
	# is a directory

	$dir = $file;
	opendir DIR, $dir;
	my @files = readdir(DIR);
	closedir DIR;

	unless ($only_first) {
	    # open in numeric order
	    @files = sort {byfile($a, $b)}(@files);
	    if (!defined($antialias)) {
		$antialias = &config_get("antialias");
	    }
	}

	foreach (@files) {
	    $file = "$dir/$_";
	    unless($_ =~ /^\./ || (! -f $file) || (-z $file)) {
		$nframes = &open_single_image($i, $file);
		return if $panic;
		if (-s "$curworkdir/$name$img_ext") {
		    if ($only_first) {
			$i++;
			last;
		    } else {
			$i += $nframes;
		    }
		} else {
		    unlink "$curworkdir/$name$img_ext";
		}}}
    } else {
	&open_single_image($i, $file);
	return if $panic;
	unless (-s "$curworkdir/$name$img_ext") {
	    unlink "$curworkdir/$name$img_ext";
	}
    }
    return $i - $orig;
}


sub byfile {
    # sort files in numerical-alpha order

    #thanks to dominus !
    my @a = split /(\d+)/, $a;
    my @b = split /(\d+) /, $b;
    my $M = @a > @b ? @a : @b;
    my $res = 0;
    for (my $i = 0; $i < $M; $i++) {
	return -1 if !defined $a[$i];
	return 1 if !defined $b[$i];
	if ($a[$i] =~ /\d/) {
	    $res = $a[$i] <=> $b[$i];
	} else {
	    $res = $a[$i] cmp $b[$i];
	}
	last if $res;
    }
    $res;
}


sub open_single_image {
    # set $hsize and $vsize to force the image size, set to 0 to autoresize
    #identify hangs if the file extension is ".avit" or ".mp4" or ".mpg"

    my ($i, $file) = @_;
    my $j;
    my $nframes = 0;
    $name = &mkname($i);

    if ($file =~ /:\/\//) {
	return 0;
    }

    if (!defined($file_exec)) {
	$file_exec = &location("file");
    }

    if ($file_exec) {
	if (&file_examine($file) eq "video") {
	    $height = $width = $bpp = 0;
	    return;
	}
    }

    if (!defined($file_ext)) {
	$file_ext = &get_ext($file);
    }

    if ($file_ext eq ".avi" || $file_ext eq ".mp4" || $file_ext eq ".mpg" || $file_ext eq ".ogg" || $file_ext eq ".mov" ||
	$file_ext eq ".ogv" ||
	$file_ext eq ".webm" || $file_ext eq ".mkv" || $file_ext eq ".asf" || $file_ext eq ".wmv" || $file_ext eq ".flv" || $file_ext eq ".mng" ||
	$file_ext eq ".dv" || $file_ext eq ".mp3" || $file_ext eq ".mpeg") {
	# need to set $name before we return
	return 0;
    }

    if ($hsize * $vsize == 0) {
	&get_image_size($file);
	return 0 if ($panic || $hsize == -1);
    }

    if ($hsize * $vsize == 0) {
	return 0;
    }

    &smog_system_sync();

    if (!defined($ximg_ext)) {
	$ximg_ext = $img_ext;
	$ximg_prefix = $img_prefix;
    }
    elsif(!defined($ximg_prefix)) {
	$ximg_prefix = &get_img_prefix($img_ext);
    }

    if ($antialias eq "false") {
	$com = "$smog_convert_command +antialias \"$file\" -scale $hsize" . "x" .
	    "$vsize\\! $ximg_prefix\"$curworkdir/$name$ximg_ext\" > $nulfile 2>&1";
    } else {
	$com = "$smog_convert_command \"$file\" -resize $hsize" . "x" .
	    "$vsize\\! $ximg_prefix\"$curworkdir/$name$ximg_ext\" >$nulfile 2>&1";
    }

    $smerr = &smog_system($com);

    if ($smerr) {
	&sig_system_error($com, $smerr);
	$panic = 1;
	return 0;
    }

    #convert gives multiple frames for e.g. animated gif
    $found = 1;
    for ($j = 0; $found == 1; $j++) {
	if (-f "$curworkdir/$name-$j$ximg_ext") {
	    $newname = &mkname($i + $j);
	    &smog_rename("$curworkdir/$name-$j$ximg_ext", "$curworkdir/$newname$ximg_ext");
	    $nframes++;
	} else {
	    $found = 0;
	}
    }

    if ($hsize * $vsize == 0) {
	&get_image_size("$curworkdir/$name$ximg_ext");
    }

    if ($hsize * $vsize > 0 && $nframes == 0) {
	$nframes = 1;
    }

    return $nframes;
}


sub file_examine {
    my $target = shift;
    if (!defined($file_exec)) {
	$file_exec = &location("file");
    }
    return "" if $file_exec eq "";
    return "" if  ! -f $target;
    my $cmd = "$file_exec -bi \"$target\"";
    my $res = &smog_system_direct($cmd);
    $res = (split /\//, (split /\;/, $res)[0])[0];
    $res;
}


sub get_image_size {
    ## neds global $hsize, $vsize, $bpp, $inresact, $dheight ?, $dwidth ?, $ file_exec ?
    ## needs $configfile if $imresact not defined

    # returns $hsize,$vsize and $bpp for $1
    my $file = shift;
    my $i;
    my $shrink;

    if (!defined($file_exec)) {
	$file_exec = &location("file");
    }
    if ($file_exec) {
	if (&file_examine($file) eq "video") {
	    $height = $width = $bpp = 0;
	    return;
	}
    }
    else {
	#identify hangs if the file extension is ".avi" or ".mp4"...maybe more...
	my $file_ext = &get_ext("$file");

	if ($file_ext eq ".avi" || $file_ext eq ".mp4" || $file_ext eq ".mpg" || $file_ext eq ".mpeg") {
	    $height = $width = $bpp = 0;
	    return;
	}
    }

    $bpp = 8; # default for images
    if (!defined($imresact)) {
	$imresact = &config_get("image_resize_action");
    }

    # imresact can be:
    # default - resize all images to default
    # bound - use as max size but keep aspect (e.g. for thumbnails)
    # none - return actual size

    my $id_cmd = &location("identify");

    unless ($id_cmd eq "") {
	my $com = "\"$id_cmd\" \"$file\" 2>$nulfile";
	my @info = split / /, &smog_system_direct($com);

	if ($?) {
	    # probably not an image then
	    $hsize = $vsize = $bpp = -1;
	    return;
	}

	my $file2 = $file;
	my $space_count = ($file2 =~ tr/ //);
	for ($i = 0; $i < $space_count; $i++) {
	    shift(@info);
	}
	my $bppstr = $info[4];
	my @bpp = split /-/, $bppstr;
	$bpp = $bpp[0];
	my @sizestr = split /\+/, $info[2];
	my @size = split /x/, $sizestr[0];

	$hsize = $size[0];
	$vsize = $size[1];

	if ($imresact eq "bound") {
	    if ($hsize > $dwidth) {
		$shrink = $hsize / $dwidth;
		$hsize /= $shrink;
		$vsize /= $shrink;
	    }
	    if ($vsize > $dheight) {
		$shrink = $vsize / $dheight;
		$hsize /= $shrink;
		$vsize /= $shrink;
	    }
	    $hsize = int($hsize);
	    $vsize = int($vsize);
	}
	elsif($imresact eq "default") {
	    $hsize = $vsize = "";
	}
    }
    if (!defined($hsize) || $hsize eq "") {
	$hsize = $dwidth;
    }
    if (!defined($vsize) || $vsize eq "") {
	$vsize = $dheight;
    }
}


sub fill_and_redo_frames {
    # resample and fill gaps in frames
    ## needs global $curworkdir, $img_ext,  $last ???
    my $next = 0;

    &smog_chdir($curworkdir);

    for ($i = 1; $i <= $end; $i++) {
	my $name = &mkname($i);

	if ($next < $i && $next > -1) {
	    $last = $next;
	    $next = &get_next_frame;
	    return if $panic;
	}
	if ($last > 0 && ($i - $last) < ($next - $i)) {
	    $fromname = &mkname($last);
	} else {
	    $fromname = &mkname($next);
	}

	unless($fromname eq $name) {
	    &smog_copy("$curworkdir/$fromname$img_ext", "$curworkdir/$name$img_ext");
	    return if $panic;
	}
	&sig_progress($i);
    }
}


sub get_next_frame {
    my $j;
    my $xname;
    $imresact = "none";

    for ($j = $i; $j <= $end; $j++) {
	$xname = &mkname($j);
	if (-f "$xname$img_ext") {
	    # found next frame, resize it
	    &get_image_size("$xname$img_ext");
	    return if ($panic || $hsize == -1);

	    if ($hsize < $width || $vsize < $height) {
		$com = "$smog_convert_command -resize $width" .
		    "x$height\\!+0\\!+0\\! $img_prefix$xname$img_ext $img_prefix$xname.mgk >$nulfile 2>&1";
	    } else {
		$com = "$smog_convert_command -crop $width" .
		    "x$height\\! $img_prefix$xname$img_ext $img_prefix$xname.mgk >$nulfile 2>&1";
	    }
	    $smerr = &smog_system($com);
	    if ($smerr) {
		&sig_system_error("$com", $smerr);
		$panic = 1;
		return;
	    }
	    &smog_rename("$xname.mgk", "$xname$img_ext");
	    return $j;
	}
    }
    # no next frame found
    return -1;
}


sub mp3_open {
    if (-f $audio_in) {
	unlink "$audio_in";
    }
    $f_size = 0;

    # prefer mpg321 if it's available
    unless (&location("mpg321") eq "") {
	&smog_system("mpg321 -w \"$audio_in\" --rate 44100 --stereo \"$file\" >$nulfile 2>&1");
	$f_size = -s $audio_in;
    }
    if ($f_size == 0) {
	unless (&location("mpg123") eq "") {
	    &smog_system("mpg123 -w \"$audio_in\" --rate 44100 --stereo \"$file\" >$nulfile 2>&1");
	    $f_size = -s $audio_in;
	}
    }
    if ($f_size == 0) {
	&othera_open;
	return;
    }

    &get_file_info($audio_in, 2, 0);
    return if $panic;

    $asigned = $signed;
    $aendian = $endian;

    if ($arate == 0 && $achans == 0 && $asamps == 0) {
	# TODO - find way to read these
	# have to assume these for now
	$arate = 44100;
	$achans = 2;
	$asamps = 16;

	$asigned = 1;
	if ($asamps == 8) {
	    $asigned = 0;
	}
	$aendian = &get_endian;
    }

    if ($f_size == 0) {
	$f_size = -s $audio_in;
    }
}


sub ogg_open {
    $f_size = 0;

    if (-f $audio_in) {
	unlink "$audio_in";
    }

    unless (&location("ogg123") eq "") {
	smog_system("ogg123 -d wav -f \"$audio_in\" \"$file\" >$nulfile 2>&1");
	$f_size= -s $audio_in;
    }

    if ($f_size == 0) {
	&othera_open;
	return;
    }

    &get_file_info($audio_in, 2, 0);
    return if $panic;

    $asigned = $signed;
    $aendian = $endian;

    if ($arate == 0 && $achans == 0 && $asamps == 0) {
	# TODO - find way to read these
	# have to assume these for now
	$arate = 44100;
	$achans = 2;
	$asamps = 16;

	$asigned = 1;
	if ($asamps == 8) {
	    $asigned = 0;
	}
	$aendian = &get_endian;
    }

    if ($f_size==0) {
	$f_size= -s $audio_in;
    }
}


sub wav_open {
    $curtmpfile = "$curworkdir/.temp";

    if (-f $audio_in) {
	unlink "$audio_in";
    }

    &smog_copy($file, $audio_in);
    return if $panic;

    &get_file_info($audio_in, 2, 0);
    return if $panic;

    $asigned = $signed;
    $aendian = $endian;

    if ($arate == 0 && $achans == 0 && $asamps == 0) {
	# TODO - find way to read these
	# have to assume these for now
	$arate = 44100;
	$achans = 2;
	$asamps = 16;

	$asigned = 1;
	if ($asamps == 8) {
	    $asigned = 0;
	}
	$aendian = &get_endian;
    }

    if ($f_size==0) {
	$f_size= -s $audio_in;
    }
}


sub othera_open {
    $arate = 44100;
    $achans = 2;
    $asamps = 16;
    $aendian = &get_endian;
    $asigned = 1;

    $mplayer_command = "\"". &get_mplayer_location . "\"";
    $format = &get_mplayer_format;

    if ($is_mpv && $format eq "") {
	&sig_system_error("mpv cannot fully resample");
	$panic = 1;
	return;
    }

    if (!$is_mpv) {
	$com = "$mplayer_command -really-quiet \"$file\" -ao pcm:fast:waveheader:file=\"$audio_in\" $format -channels " .
	    "$achans <$nulfile";
    }
    else {
	$com = "$mplayer_command --really-quiet \"$file\" --ao=pcm:waveheader:file=\"$audio_in\" $format " .
	    "--audio-channels=$achans";
    }
    my $smerr = &smog_system($com);

    if ($smerr) {
	&sig_system_error($com, $smerr);
	$panic = 1;
	return;
    }

    &get_file_info($audio_in, 2, 0);
    return if $panic;

    $asigned = $signed;
    $aendian = $endian;

    if ($arate == 0 && $achans == 0 && $asamps == 0) {
	# TODO - find way to read these
	# have to assume these for now
	$arate = 44100;
	$achans = 2;
	$asamps = 16;

	$asigned = 1;
	if ($asamps == 8) {
	    $asigned = 0;
	}
	$aendian = &get_endian;
    }

    if ($f_size == 0) {
	$f_size= -s $audio_in;
    }
}

################################################
# utility subroutines

sub flush {
    my $fh = shift;
    my $ofh = select $fh;
    $| = 1;                    # Make $fh hot
    print $fh "";              # print nothing
    $| = 0;                    # Make $fh buffered
    select $ofh;
}


sub get_current_datetime {
    ($Second, $Minute, $Hour, $Day, $Month, $Year,,,) = localtime(time);
    my $RealMonth = $Month + 1; # Months of the year are not zero-based

    if ($RealMonth < 10) {
	$RealMonth = "0$RealMonth"; # add a leading zero to one-digit months
    }
    if ($Day < 10){
	$Day = "0$Day"; # add a leading zero to one-digit days
    }
    if ($Hour < 10){
	$Hour = "0$Hour"; # add a leading zero to one-digit days
    }
    if ($Minute < 10){
	$Minute = "0$Minute"; # add a leading zero to one-digit days
    }
    if ($Second < 10){
	$Second = "0$Second"; # add a leading zero to one-digit days
    }

    $Fixed_Year = $Year + 1900;

    return "$Fixed_Year-$RealMonth-$Day at $Hour:$Minute:$Second";
}


sub getint {
    # get an int from a file in $endian format
    my $string = shift;
    my (@val) = unpack("CCCC", $string); #unpack $string to 4 chars in @val array

    # TODO - this needs checking !

    if (!defined($endian) || $endian == 1) {
	#little endian
	return ((($val[3] * 256 + $val[2]) * 256 + $val[1]) * 256) + $val[0];
    }
    else {
	return ((($val[0] * 256 + $val[1]) * 256 + $val[2]) * 256) + $val[3];
    }
}


sub get_endian {
    # 0 == big-endian
    # 1 == little-endian
    return unpack("h*", pack("s", 1)) =~ /^1/;
}


sub get_mplayer_format {
    #TODO: my ($arate, ...) = @_;

    if (!defined($nrate)) {
	$nrate = $arate;
    }
    if (!defined($nchans)) {
	$nchans = $achans;
    }
    if (!defined($nasamps)) {
	$nasamps = $asamps;
    }
    if (!defined($asigned)) {
	$asigned = $signed;
    }
    if (!defined($aendian)) {
	$aendian = $endian;
    }
    if (!defined($nsigned)) {
	$nsigned = $asigned;
    }
    if (!defined($nendian)) {
	$nendian = $aendian;
    }

    if ($is_mpv) {
	if ($nsamps == 8) {
	    if ($nsigned == 0) {
		return "--audio-format=u8";
	    }
	}
	if ($nsamps == 16) {
	    if ($nsigned == 1) {
		return "--audio-format=s16";
	    }
	}
	return "";
    }

    if ($nasamps == 8) {
	if ($nsigned == 0) {
	    return "-format u8";
	}
	return "-format s8";
    }
    if ($nsigned == 0) {
	if ($nendian == 0) {
	    return "-format u16be";
	}
	return "-format u16le";
    }
    if ($nendian == 0) {
	return "-format s16be";
    }
    return "-format s16le";
}


sub clean_old {
    # clear away any old backup files, etc.
    my ($handle, $movetotrash, $log) = @_;
    if ($movetotrash && &is_dangerous($DEFTRASH)) {
	exit 1;
    }
    return if (! -d "$workdir/$handle" || ! -f "$workdir/$handle/header.lives");
    my $dir = cwd();
    &smog_chdir("$workdir/$handle");
    if ($log) {
	print "smogrify: removing *.mgk *.bak *.pre *.tmp" .
	    " pause audio.* audiodump* audioclip\n";
	print "smogrify: from directory $workdir/$handle\n";

	print STDERR  "smogrify: removing *.mgk *.bak *.pre *.tmp" .
	    " pause audio.*audiodump* audioclip\n";
	print STDERR "smogrify: from directory $workdir/$handle\n";
    }

    my $use_trash = 0;
    my @files = glob ("*.mgk *.bak *.pre *.tmp pause audio.* audiodump* audioclip");
    if ($movetotrash) {
	my $deftrashdir = "$workdir/.$DEFTRASH";
	if (! -d $deftrashdir) {
	    make_path $deftrashdir, {mode => 01777};
	}
	if (-d $deftrashdir && -w $deftrashdir) {
	    $use_trash = 1;
	}
    }
    foreach my $xfile (@files) {
	if ($use_trash) {
	    ## concat $handle and $file without dirsep, just so we have a unique filename
	    &smog_rename($xfile, "$deftrashdir/$handle$file");
	}
	else {
	    unlink $xfile;
	}
    }
    &smog_chdir($dir);
}


sub sig_complete {
    return if (! -d $curworkdir);
    return if ($handle eq '.');

    if (defined($statusfile)) {
	if (-d $curworkdir) {
	    my $status = "completed|" . join("|", @_);
	    open OUT, ">", $statusfile;
	    select((select(OUT), $| = 1)[0]);
	    print OUT $status;
	    close OUT;
	}
    }
    if (defined($pidfile)) {
	unlink $pidfile;
    }
}


sub sig_complete_stdout {
    my $status = "completed|" . join("|", @_);
    print $status;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
}


sub sig_killed {
    return if (! -d $curworkdir);
    return if (-f $statusfile);
    my $status = "killed";

    open OUT, ">", $statusfile;
    select((select(OUT), $| = 1)[0]);
    print OUT $status;
    close OUT;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
}


sub sig_complete_audio {
    return if (! -d "$curworkdir");
    my $status = "audio_ended|" . join("|", @_);

    open OUT, ">", "$statusfile.play";
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $status;
    close OUT;

    unless ($opening_preview == 1) {
	if (defined($pidfile)) {
	    unlink $pidfile;
	}
    }
}


sub sig_error {
    # WARNING: error strings MUST NOT contain newlines,
    #instead you can pass up to 3 lines of text as params
    # write to backend -> frontend $statusfile

    return if (! -d $curworkdir);
    return if ($handle eq '.');

    my $status = "error";
    my $count = 0;
    foreach (@_) {
	$status .= "|" . $_;
	$count++;
    }
    if ($count < 4) {
	$status .= "|||ERROR|";
    }

    open OUT, ">", $statusfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $status;
    close OUT;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
    exit 1;
}


sub sig_write_error {
    # got an error writing to a file. pass in filename as 1st param

    # write to backend -> frontend $statusfile

    # in params: 1 - filename, 2 - optional additional info (1 line)

    return 1 if (! -d $curworkdir);

    my ($smfile, $smaddinfo) = @_;
    my $smstatus = "error|write|$smfile";

    if ($smaddinfo ne "") {
	$smstatus .= "|$smaddinfo";
    }

    open OUT, ">", $statusfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $smstatus;
    close OUT;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
    return 1;
}


sub sig_read_error {
    # got an error reading from a file. pass in filename as 1st param

    # write to backend -> frontend $statusfile

    # in params: 1 - filename, 2 - optional additional info (1 line)

    return 1 if (! -d $curworkdir);
    my ($smfile, $smaddinfo) = @_;
    my $smstatus = "error|read|$smfile";

    if ($smaddinfo ne "") {
	$smstatus .= "|$smaddinfo";
    }

    open OUT, ">", $statusfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $smstatus;
    close OUT;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
    return 1;
}


sub sig_system_error {
    # got an error running a system command. pass in command as 1st param
    # error code is second param
    # use only for commands that really really must never fail

    # preference is to use read/write errors as this is clearer to the user what failed

    # write to backend -> frontend $statusfile

    #
    # in params: 1 - command which failed or description of command, 2 - error value returned,
    # 3 - optional additional info (1 line)

    return 1 if (! -d $curworkdir);

    my ($smcom, $smerrno, $smaddinfo) = @_;
    my $smstatus = "error|system|$smcom|$smerrno";

    if ($smaddinfo ne "") {
	$smstatus .= "|$smaddinfo";
    }

    open OUT, ">", $statusfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $smstatus;
    close OUT;
    if (defined($pidfile)) {
	unlink $pidfile;
    }
    return 1;
}


sub sig_pid {
    # write our pid in case we need to be cancelled
    open OUT, ">", $pidfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $$;   # process ID
    close OUT;
}


sub sig_progress {
    # report progress of frame processing
    ###
    ###
    # write to backend -> frontend $statusfile
    my $status = "";
    foreach (@_) {
	if ($status eq "") {
	    $status = $_;
	}
	else {
	    $status .= "|" . $_;
	}
    }
    &check_for_pause;
    open OUT, ">", $statusfile;
    # autoflush output
    select((select(OUT), $| = 1)[0]);
    print OUT $status;
    close OUT;
}


sub sig_clear {
    &check_for_pause;
    if (defined($statusfile)) {
	unlink $statusfile;
    }
}


sub location {
    # return the location of an executable
    my $command = shift;
    my $location = &smog_system_direct("which \"$command\" 2>$nulfile");
    chomp($location);
    $location;
}


sub get_mplayer_location {
    ## global $is_mpv
    $is_mpv = 0;
    my $mpc = &location("mplayer");
    if ($mpc eq "") {
	$mpc = &location("mplayer2");
    }
    if ($mpc eq "") {
	$is_mpv = 1;
	$mpc = &location("mpv");
    }
    return $mpc;
}


sub listemall {
    my @needc = @_;
    my %hash;
    my %subs;
    my %subsneed;
    my $usesrc = 0;
    my $lastline = "";
    open (PS, $MYNAME);
    while (<PS>) {
	my $xline = $_;
	if ($xline =~ /\$command eq \"/) {
	    my $line = (split("\"", (split(" eq \"", $xline))[1]))[0];
	    if ($line) {
		if ($lastline) {
		    $hash{$lastline} = $usesrc;
		}
		$lastline = $line;
		$usesrc = 0;
	    }
	    next;
	}
	if ($xline =~ /^sub (.*) \{/) {
	    if ($lastline) {
		$hash{$lastline} = $usesrc;
		$lastline = "";
	    }
	    $sub = $1;
	    if ($lastsub) {
		if ($lastsub =~ /config_get/ || $lastsub =~ /config_set/) {
		    $usesrc = 0;
		}
		else {
		    $subs{$lastsub} = $usesrc;
		}
	    }
	    $lastsub = $sub;
	    $usesrc = 0;
	    next;
	}
	if (($lastline || $lastsub) && ($xline =~ /config_get/ || $xline =~ /config_set/)) {
	    $usesconfig = 1;
	}
    }
    close PS;
    $subs{$lastsub} = $usesrc;
    print "smogrify command summary:\n";
    foreach my $cmd (keys %hash) {
	print "$cmd";
	if (grep(/^$cmd$/, @needc)) {
	    print " (needs configfile)";
	}
	else {
	    if ($hash{$cmd}) {
		print " WARNING: MAY USE CONFIGFILE !\n";
	    }
	}
	print "\nreferenced from:\n";
	print `grep -no "%s $cmd " src/*.c`;
	print "\n";
    }

    return;

    print "\nsmogrify subroutine summary:\n";
    foreach my $sub (keys %subs) {
	print "$sub";
	if ($subs{$sub}) {
	    print " (MAY USE CONFIGFILE)\n";
	}
	print "\nreferenced from:\n";
	print `grep -no "$sub" ./$MYNAME`;
	print "\n";
    }

    print "\n\nThe following subroutines may need config:\n";
    foreach my $sub (keys %subs) {
	if ($subs{$sub} && $sub ne "listemall") {
	    print "$sub\n";
	    $subsneed{$sub} = "";
	}
    }
    print "\n\nRecursively checking subroutines:\n";
    my $size = keys %subsneed;
    while (1) {
	foreach my $sub (keys %subsneed) {
	    print "\n\nscanning for $sub\n";
	    $lastsub = "";
	    $usesrc = 0;
	    open (PS, $MYNAME);
	    while (<PS>) {
		my $xline = $_;
		if ($xline =~ /^sub (.*) \{/) {
		    $xsub = $1;
		    if ($lastsub) {
			if (!($lastsub =~ /config_get/ || $lastsub =~ /config_set/)) {
			    if ($usesrc) {
				$subsneed{$lastsub} = $sub;
				print "$lastsub calls $sub, adding it.\n";
			    }
			}
		    }
		    $lastsub = $xsub;
		    $usesrc = 0;
		    next;
		}
		if ($lastsub && $xline =~ /$sub/) {
		    $usesrc = 1;
		}
	    }
	    close PS;
	}
	print "old size is $size, ";
	last if $size == keys %subsneed;
	$size = keys %subsneed;
	print "new size is $size\n";
    }
    print "\n\nResults of cross-ref:\n";
    foreach my $sub (keys %subsneed) {
	while (1) {
	    last if $sub eq $subsneed{$sub};
	    if (!$subsneed{$sub}) {
		print "$sub needs config file.\n";
		last;
	    }
	    print "$sub calls $subsneed{$sub}, ";
	    $sub = $subsneed{$sub};
	}
	print "\n";
    }
    print "\n\nRechecking commands:\n";
    foreach my $sub (keys %subsneed) {
	print "\n\nscanning for $sub\n";
	$lastline = "";
	$usesrc = 0;
	open (PS, $MYNAME);
	while (<PS>) {
	    my $xline = $_;
	    if ($xline =~ /\$command eq \"/) {
		my $line = (split("\"", (split(" eq \"", $xline))[1]))[0];
		if ($line) {
		    if ($lastline) {
			if ($usesrc) {
			    $hash{$lastline} = $sub;
			}
			else {
			    $hash{$lastline} = "";
			}
		    }
		    $lastline = $line;
		    $usesrc = 0;
		}
		next;
	    }
	    if ($lastline && $xline =~ /$sub/) {
		$usesrc = 1;
	    }
	}
	close PS;
    }
    print "\n\nFindings from cross ref check:\n";
    foreach my $cmd (keys %hash) {
	next if grep(/^$cmd$/, @needc);
	if ($hash{$cmd}) {
	    print " $cmd calls sub $hash{$cmd}, ";
	    $sub = $hash{$cmd};
	    while (1) {
		if (!$subsneed{$sub} || $subsneed{$sub} eq $sub) {
		    print "$sub needs config file.\n\n";
		    last;
		}
		last if $sub eq $subsneed{$sub};
		print "$sub calls $subsneed{$sub}, ";
		$sub = $subsneed{$sub};
	    }
	}
    }
    print "\n\n";
}


sub config_set_if_not_set {
    my ($key, $value) = @_;
    $noreplace = 1;
    return &config_set($key, $value);
}


sub config_set_priority {
    my ($key, $value) = @_;
    $priority = 1;
    return &config_set($key, $value);
}


sub config_delete {
    my $key = shift;
    $delpref = 1;
    &config_set($key, "");
}


sub config_deprecate {
    my ($key, $newkey, $dversion) = @_;
    $deprecate = 1;
    &config_set($key, $newkey, $dversion);
}


sub config_find {
    my $key = shift;
    $find = 1;
    return &config_get($key);
}


sub printlen {
    # print a string and return its length (or -1 on failure)
    my ($fh, $string) = @_;
    print $fh $string or return -1;
    length($string);
}


sub config_set {
    # set/modify/delete a value in the .config file
    my ($key, $value, $dversion) = @_;

    my $xconfigfile = $configfile;

    if (defined($prefs_file)) {
	$xconfigfile = $prefs_file;
    }

    if ($xconfigfile eq "") {
	&startup_error(4, "config_set", "defined_xconfigfile0", "$command:$configfile:$prefs_file:$key:\@:");
    }

    if ($xconfigfile ne $configfile && $key eq "") {
	return 0;
    }

    my $oconfigsize = -s $xconfigfile;

    if ($key eq "" && $oconfigsize > 0) {
	return 0;
    }

    unlink $newconfigfile;
    if (0 + -e $newconfigfile) {
	&startup_error(4, "config_set_new", "remove_new0", "$newconfigfile:\@:");
    }
    if (!defined(open OUT, ">", $newconfigfile)) {
	&startup_error(4, "config_set_new", "open_new0", "$newconfigfile:\@:");
    }

    # autoflush output
    select((select(OUT), $| = 1)[0]);

    if ($value ne "" && substr($value, -1, 1) eq "\"" && substr($value, 0, 1) ne "\"") {
	# strip trailing unmatched quotes
	$value = substr($value, 0, length($value) - 1);
    }
    my $found_deprecate = 1;
    my $configfilesize = 0;
    my $addsize = 0;

    if (!defined($delpref)) {
	# delete a value
	$delpref = 0;
    }
    if (!defined($noreplace)) {
	# add value only if not already there
	$noreplace = 0;
    }
    if (!defined($deprecate)) {
	# add a # deprecated <$key> comment
	$deprecate = 0;
    }

    if ($deprecate) {
	$found_deprecate = 0;
    }

    ## the process here is that we read in the $configfile a line at a time, constructing a new copy of it.
    ## if we find <key> in the old file, we stop copying until we reach the next non-blankspace line after the next </key>
    ## then insert <key>, value, </key>, newline
    ## then continue to the end of the file

    ## if $noreplace is 1, we return if we find <key> followed by </key>
    ## if $delpref is 1, we skip writing the key, and just insert a newline
    ## some keys are "priority" keys and get inserted at the start

    ## as we create the new file, we record the exact number of bytes written.
    ## If the filesize of the new file is not what we expect then we abort the operation

    ## otherwise, we copy the old file to a recovery backup, then copy new file to the old file

    ## after copying we check again. If the overwritten file has the wrong size, we delete it and abort.

    my $stage = -1;
    my $extrablanks = 0;
    my $current = "";
    my $lastline = "";

    if ($key eq "") {
	## if $key is "" then we just write comments at the start (we already checked to make sure the file doesn't exist)
	$date = &get_current_datetime;
	$addsize = &printlen(OUT, "# This file was autogenerated by $GUI_NAME version $version on $date\n");
	if ($addsize == -1) {
	    close OUT;
	    unlink $newconfigfile;
	    &startup_error(4, "config_set_new", "write_new_header0", "$newconfigfile:\@:");
	}
	$configfilesize += $addsize;
	$addsize = &printlen(OUT, "# You are STRONGLY advised to change the values only through the GUI ($GUI_NAME) ",
			     "unless otherwise directed.\n");
	if ($addsize == -1) {
	    close OUT;
	    unlink $newconfigfile;
	    &startup_error(4, "config_set_new", "write_new_header1", "$newconfigfile:\@:");
	}
	$configfilesize += $addsize;
    }
    else {
	if ($oconfigsize) {
	    if (!defined(open IN, "<", $xconfigfile) && (0 + -e $xconfigfile)) {
		close OUT;
		unlink $newconfigfile;
		&startup_error(3, "config_get", "config_set_open_file", "$xconfigfile:\@:");
	    }

	    ## $stage defines the action applied to the line:
	    # first we remove the newline from the end, then:
	    #
	    ## stage -1 = scan mode:
	    # - if it's a whitespace line, we increment $extrablanks and skip it
	    # - if it's a ^#comment and not a ^#deprecated comment, we insert $extrablanks whitespace lines,
	    #  then reset $extrablanks
	    # - copy the #comment
	    #
	    # otherwise, we insert a single newline, then proceed to stage 0
	    # or for a priority pref we go to straight to stage 2
	    #
	    ## stage 0 = copy mode:
	    # - we just copy the lines verbatim, re-adding the newline, until:
	    # if we match ^<$key>, then we proceed to stage 1
	    # - if $deprecate is 1 and the last line wasnt a ^#deprecated comment, we insert the #deprecated comment
	    #
	    ## stage 1 = look for end:
	    # - in deprecate mode we output the line, newline
	    # - in delpref mode, we output nothing
	    # - otherwise, we output only #comments
	    # - if we match </key>$ then we proceed to stage 2
	    #   - in deprecate mode, we reset $deprecate, set $delpref = 1 and $stage = 0
	    #
	    ## stage 2 = skip mode:
	    # - skip whitespace
	    #
	    # otherwise:
	    # - if we are in $noreplace mode, and it wasn't EOF, we exit the subroutine after deleting $newconfigfile;
	    # 	otherwise:
	    # - if we are in delpref mode, we write the line, newline and go back to stage 0; otherwise:
	    # - we output <$key>, newline, value, newline, </$key>, newline
	    # - we set $delpref to 1
	    # - we set $priority to 0
	    # - if the line ends with </$key> we skip it, go to stage 0
	    # - otherwise, if the line starts with <$key> we skip it and go back to stage 1 (now with $delpref set)
	    # - otherwise, we ouptut a newline, the line, newline, and go back to stage 0

	    while (<IN>) {
		$current = $_;
		$current =~ s/\n//g;

		if ($stage != 0 && ($current eq "" || $current =~ /^\s+$/)) {
		    # skip whitespace, unless stage is 0
		    if ($stage == -1) {
			$extrablanks++;
		    }
		    next;
		}

		if ($stage < 2 && $current =~ /^#/ && !($delpref && $stage > 0)
		    && !($stage == -1 && $current =~ /^#deprecated/)) {
		    # handle comments
		    next if (($delpref || $deprecate) && $current =~ /^#deprecated <$key>/);
		    if ($stage == -1) {
			for ($i = 0; $i < $extrablanks; $i++) {
			    # add back skipped whitespace
			    $addsize = &printlen(OUT, "\n");
			    if ($addsize == -1) {
				close OUT;
				unlink $newconfigfile;
				&startup_error(4, "config_set_new", "write_new0",
					       "$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:\@:");
			    }
			    $configfilesize += $addsize;
			}
			$extrablanks = 0;
		    }

		    $addsize = &printlen(OUT, "$current\n");
		    if ($addsize == -1) {
			close OUT;
			unlink $newconfigfile;
			&startup_error(4, "config_set_new", "write_new1",
				       "$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:\@:");
		    }
		    $configfilesize += $addsize;
		    next;
		}

		if ($stage == -1) {
		    # the first non-comment, non whitespace line
		    #insert a single newline because we skipped all the whitespace
		    $addsize = &printlen(OUT, "\n");
		    if ($addsize == -1) {
			close OUT;
			unlink $newconfigfile;
			&startup_error(4, "config_set_new", "write_new2",
				       "$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:\@:");
		    }
		    $configfilesize += $addsize;
		    if (defined($priority) && $priority == 1) {
			# insert the key now, we already skipped excess whitespace
			$stage = 2;
		    }
		    else {
			$stage = 0;
		    }
		}

		if (!$priority && $current =~ /^<$key>/) {
		    # we found <key>
		    next if $stage > 0; # if we already found key, ignore the duplicate
		    if ($deprecate) {
			$found_deprecate = 1;
			$deprecate = 0;
			if ($value eq "") {
			    $addsize = &printlen(OUT, "#deprecated <$key> as of $GUI_NAME version $dversion\n");
			}
			else {
			    $addsize = &printlen(OUT, "#deprecated <$key> as of $GUI_NAME version $dversion, " .
						 "use <$value> instead\n");
			}
			if ($addsize == -1) {
			    close OUT;
			    unlink $newconfigfile;
			    &startup_error(4, "config_set_new",
					   "write_new3", "$newconfigfile:$key:$value:$priority:$delpref:" .
					   "$deprecate:$dversion:\@:");
			}
			$configfilesize += $addsize;
			$delpref = 1; # should be safe as we are in stage 0, but it stops us re-adding the key at the end
		    } else {
			$stage = 1;
		    }
		}

		if ($stage == 1) {
		    if ($current =~ /<\/$key>$/) {
			if ($noreplace) {
			    close IN;
			    close OUT;
			    unlink "$newconfigfile";
			    if (0 + -e $newconfigfile) {
				close OUT;
				&startup_error(4, "config_set_new", "remove_new1", "$newconfigfile:\@:");
			    }
			    $noreplace = 0;
			    return 1;
			}
			$stage = 2;
		    }
		    next;
		}

		if ($stage == 2) {
		    # skipped whitespace after the </key>
		    if (!$delpref) {
			# inject the key / value, and add a newline
			$addsize = &printlen(OUT, "<$key>\n$value\n</$key>\n\n");
			if ($addsize == -1) {
			    close OUT;
			    unlink $newconfigfile;
			    &startup_error(4, "config_set_new",
					   "write_new4", "$newconfigfile:$key:$value:$priority:$delpref:" .
					   "$deprecate:$dversion:\@:");
			}
			$configfilesize += $addsize;
		    }
		    $priority = 0;
		    $delpref = 1;

		    if ($current =~ /<\/$key>$/) {
			# we should skip whitespace
			$stage = 2;
			next;
		    }

		    if ($current =~ /^<$key>/) {
			# we should write nothing until we reach the key end
			$stage = 1;
			next;
		    }

		    $stage = 0;
		}

		# $stage should always be 0 if we reach here, but just in case
		next if ($stage != 0);

		$addsize = &printlen(OUT, "$current\n");
		if ($addsize == -1) {
		    close OUT;
		    unlink $newconfigfile;
		    &startup_error(4, "config_set_new", "write_new6",
				   "$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:\@:");
		}
		$configfilesize += $addsize;
	    }
	}
	close IN;
    }

    if (!$found_deprecate) {
	close OUT;
	if (-s $newconfigfile != $configfilesize) {
	    unlink $newconfigfile;
	}
	return 0;
    }

    if ($key ne "") {
	if ($stage == 0 && !($current eq "" || $current =~ /^\s+$/)) {
	    $addsize = &printlen(OUT, "\n");
	    if ($addsize == -1) {
		close OUT;
		unlink $newconfigfile;
		&startup_error(4, "config_set_new", "write_new7", "$newconfigfile:$key:$value:$priority:" .
			       "$delpref:$deprecate:$dversion:\@:");
	    }
	    $configfilesize += $addsize;
	}
	if (!$delpref) {
	    $addsize = &printlen(OUT, "<$key>\n$value\n</$key>\n\n");
	    if ($addsize == -1) {
		close OUT;
		unlink $newconfigfile;
		&startup_error(4, "config_set_new", "write_new8", "$newconfigfile:$key:$value:$priority:" .
			       "$delpref:$deprecate:$dversion:\@:");
	    }
	    $configfilesize += $addsize;
	}
    }

    close OUT;

    if (-s $newconfigfile != $configfilesize) {
	unlink $newconfigfile;
	&startup_error(4, "config_set_new", "check_new_size0",
		       "$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:" .
		       -s $newconfigfile . ":$configfilesize:\@:");
    }

    if ($xconfigfile eq $configfile) {
	# attempt to backup $configfile before replacement
	unlink $recconfigfile;
	if (0 + -e $recconfigfile) {
	    &startup_error(4, "config_set_rec", "remove_recovery1", "$recconfigfile:\@:");
	}
	if (-s $configfile) {
	    $panic = 0;
	    &smog_copy($configfile, $recconfigfile);
	    if ($panic || -s $recconfigfile != $oconfigsize) {
		unlink $recconfigfile;
		if (-s $configfile != $oconfigsize) {
		    unlink $configfile;
		    if (-s $newconfigfile == $configfilesize) {
			$panic = 0;
			&smog_copy($newconfigfile, $configfile);
			if ($panic || -s $configfile != $configfilesize) {
			    unlink $configfile;
			    if (-s $newconfigfile != $configfilesize) {
				unlink $newconfigfile;
			    }
			}
		    }
		}
		&startup_error(4, "config_set_rec", "check_size_rec0", "$configfile:$recconfigfile:" .
			       -s $recconfigfile . ":$oconfigsize:\@:");
	    }
	}
    }

    ## copy new file here ###
    &smog_copy($newconfigfile, $xconfigfile);
    ##################3

    my $configsize = -s $xconfigfile;

    if ($panic || $configsize != $configfilesize) {
	# copy failed
	if ($configsize != $oconfigsize && $xconfigfile eq $configfile) {
	    unlink $configfile;
	}
	&startup_error(5, "config_set", "check_size0",
		       "$xconfigfile:$newconfigfile:$key:$value:$priority:$delpref:$deprecate:$dversion:" .
		       "$configsize:$oconfigsize:$configfilesize:\@:");
    }

    $delpref = $noreplace = 0;

    unlink $newconfigfile;
    if (0 + -e $newconfigfile) {
	&startup_error(4, "config_set_new", "remove_new2", "$newconfigfile:\@:");
    }

    return 0;
}


sub startup_error {
    my ($err, $func, $detail, $text) = @_;
    if ($command eq "report") {
	if ($gui_bootstrap_file ne "" && $gui_bootstrap_file ne "-") {
	    # backwards compatibility only
	    if (defined(open OUT, ">", $gui_bootstrap_file)) {
		print OUT "$version|/tmp|||||";
		close OUT;
	    }
	    exit $err;
	}
    }
    if (!caller) {
	print STDERR "smogrify for $GUI_NAME v$version: Error. Exiting with code $err\n";
	print STDERR "smogrify: Error in subroutine $func, action = $detail, text = $text\n";
	unless ($text =~ /:\@:$/) {
	    print STDERR "smogrify: (Message appears to be incomplete)\n";
	}
	if ($bad_configfile) {
	    print STDERR "smogrify: $configfile is damaged and could not be removed.\n";
	    print STDERR "smogrify: File MUST contain at least a <version> key.\n";
	}
	if (! (0 + -e $configfile)) {
	    print STDERR "smogrify: $configfile not present.\n";
	    if (-s $recconfigfile) {
		print STDERR "smogrify: Recovery may be possible from $recconfigfile\n";
	    }
	    elsif (-s $newconfigfile > 0) {
		print STDERR "smogrify: Recovery may be possible from $newconfigfile\n";
	    }
	}
	if (-s $recconfigfile_damaged > 0) {
	    print STDERR "smogrify: Damaged file may have been saved as $recconfigfile_damaged.\n";
	    print STDERR "smogrify: File MUST contain at least a <version> key.\n";
	}
	if (-s $recconfigfile_tried > 0) {
	    print STDERR "smogrify: Unsuccesfull recovery appears to have been attempted from $recconfigfile_tried\n";
	    print STDERR "smogrify: File MUST contain at least a <version> key.\n";
	}
	if (-s $recconfigfile_tried > 0) {
	    print STDERR "smogrify: Unsuccesfull recovery appears to have been attempted from $recconfigfile_tried\n";
	}
	if (-s $recconfigfile_succeeded > 0) {
	    print STDERR "smogrify: Succesfull recovery appears to have been nade from $recconfigfile_succeeded\n";
	}
	print STDERR "smogrify: \@ARGV = " . join(" ", @ARGV) . "\n";
    }

    print STDOUT "smogrify::error|$func|$detail|$text|\n";
    exit $err;
}


sub recover_configfile {
    ## attempt a one-time recovery of $configfile from $configfile.recovery
    #inputs: $configfile, $recconfigfile, $recconfigfile_tried

    return "" if (0 + -e $configfile);

    my $xrecconfigfile = $recconfigfile;
    if (! -s $recconfigfile) {
	unlink $recconfigfile;
	if (-s $newconfigfile) {
	    $xrecconfigfile = $newconfigfile;
	}
    }

    my $size = -s $xrecconfigfile;

    if ($size == 0) {
	unlink $xrecconfigfile;
	return "";
    }

    # try to back up the recovery file if we can
    if (0 + -e $recconfigfile_tried) {
	unlink $recconfigfile_tried;
    }

    $panic = 0;
    if (! (0 + -e $recconfigfile_tried)) {
	&smog_copy($xrecconfigfile, $recconfigfile_tried);
	if (!$panic && -s $recconfigfile_tried == $size) {
	    # $recconfigfile succesfully renamed as $recconfigfile_tried
	    # good. This is intented to be a one-time recovery
	    # now copy it to $configfile
	    unlink $xrecconfigfile;
	    $xrecconfigfile = $recconfigfile_tried;
	}
	else {
	    # copy failed...
	    $panic = 0;
	    unlink $recconfigfile_tried;
	    if (-s $xrecconfigfile != $size) {
		# ...recovery file damaged in the process, try to remove it; no recovery
		unlink $xrecconfigfile;
		return "";
	    }
	    # ...try with the original
	}
    }
    #print STDERR "Copy $xrecconfigfile to $configfile\n";
    &smog_copy($xrecconfigfile, $configfile);

    if ($panic || -s $configfile != $size) {
	# copy failed !
	if ($xrecconfigfile ne $recconfigfile_tried && -s $xrecconfigfile != $size) {
	    # recovery file, damaged, try to remove it unless it was a backup
	    unlink $xrecconfigfile;
	}
	# try to remove configfile; no recovery
	$panic = 0;
	unlink $configfile;
	return "";
    }

    # success !
    if ($xrecconfigfile != $recconfigfile_tried) {
	unlink $xrecconfigfile;
    }
    return $xrecconfigfile;
}


sub config_get {
    # return a value from our .config file
    my $key = shift;
    my $xconfigfile = $configfile;
    my $string = "";

    if (defined($prefs_file)) {
	$xconfigfile = $prefs_file;
    }

    if ($xconfigfile eq "") {
	&startup_error(3, "config_get", "defined_xconfigfile0", "$command:$configfile:$prefs_file:$key:\@:");
    }

    if (! -s $xconfigfile) {
	return $string;
    }

    unless (defined(open IN, "<", $xconfigfile)) {
	&startup_error(3, "config_get", "open_file0", "$xconfigfile:$key:\@:");
    }

    my $stage = 0;
    while (<IN>) {
	my $line = $_;
	next if $line =~ /^#/;
	$line =~ s/\n//g;
	if ($stage == 0 && $line =~ /^<$key>(.*)/) {
	    if (!$find) {
		$string = $1;
	    }
	    $stage = 1;
	}

	if ($stage > 0) {
	    if ($line =~ /<\/$key>$/) {
		close IN;
		if ($find) {
		    $find = 0;
		    return 1;
		}
		return $string;
	    }
	    if (!$find && $stage == 2) {
		if ($string eq "") {
		    $string = $line;
		}
		else {
		    $string .= "\n" . $line;
		}
	    }
	    $stage = 2;
	}
    }
    close IN;
    if ($find) {
	$find = 0;
	return 0;
    }
    return "";
}


sub fast_dd {
    ## needs global $fdd_in, $fdd_out

    # copy size bytes from $fdd_in to $fdd_out - skip $skip bytes in input and $seek bytes in output
    # no truncation is done
    my ($size, $skip, $seek) = @_;
    my $nobs = 1;
    if ($size < 0) {
	$size = -$size;
	$nobs = 0;
    }
    return if ($size <= 0);

    my ($data, $i);
    my $smres;
    my ($len, $wlen);

    my $sizecheck = -s $fdd_in;
    if ($size + $skip > $sizecheck) {
	if ($DEBUG_SMOGRIFY) {
	    print STDERR "smogrify debug - fast_dd was asked to read $size bytes at offset $skip from a $sizecheck byte file\n" .
		"$fdd_in\n";
	}
	$size = $sizecheck - $skip;
	if ($skip >= $sizecheck) {
	    $panic = &sig_read_error($fdd_in);
	    return;
	}
    }

    if (! open FFIN, "<", $fdd_in) {
	if (-f $fdd_in) {
	    $panic = &sig_read_error($fdd_in);
	}
	return;
    }

    binmode FFIN or $panic = &sig_read_error($fdd_in);
    return if $panic;

    my $blocksize = my $bs = (stat FFIN)[11];
    my $use_sys = 0;

    if ($size < $bs) {
	$use_sys = 1;
    }
    else {
	if ($nobs) {
	    ## faster to use fixed bs or read whole data ?
	    $bs = $blocksize = $size;
	}
    }

    if ($skip > 0) {
	if ($use_sys) {
	    sysseek FFIN, $skip, SEEK_START or $panic = &sig_read_error($fdd_in);
	}
	else {
	    seek FFIN, $skip, SEEK_START or $panic = &sig_read_error($fdd_in);
	}
	if ($panic) {
	    close FFIN;
	    return;
	}
    }

    unless (&is_writeable($fdd_out, 1)) {
	$panic = &sig_write_error($fdd_out);
	if (defined FFIN) {
	    close FFIN;
	}
	return;
    }

    open FFOUT, "+<", $fdd_out or $panic = &sig_write_error($fdd_out);
    binmode FFOUT or do {
	$panic = &sig_write_error($fdd_out);
	if (defined FFIN) {
	    close FFIN;
	}
	close FFOUT;
	return;
    };

    if ($seek > 0) {
	if ($use_sys) {
	    sysseek FFOUT, $seek, SEEK_START or $panic = 1;
	}
	else {
	    seek FFOUT, $seek, SEEK_START or $panic = 1;
	}
	if ($panic) {
	    $panic = &sig_write_error($fdd_out);
	    if (defined FFIN) {
		close FFIN;
	    }
	    close FFOUT;
	    return;
	}
    }
    while ($size > 0) {
	$bs = $blocksize;
	if ($size < $bs) {
	    $bs = $size;
	}

	if ($use_sys) {
	    $len = sysread FFIN, $data, $size;
	}
	else {
	    $len = read FFIN, $data, $bs;
	}
	if (!defined $len) {
	    next if $! =~ /^Interrupted/;       # ^Z and fg
	    $panic = &sig_read_error($fdd_in);
	    last;
	}

	$size -= $len;

	while ($len > 0) {
	    if ($len < $bs) {
		$bs = $len;
	    }

	    if ($use_sys) {
		$wlen = syswrite FFOUT, $data, $len;
		if (!defined $wlen) {
		    next if $! =~ /^Interrupted/;       # ^Z and fg
		    $panic = &sig_write_error($fdd_out);
		    last;
		}
		$len -= $wlen;
	    }
	    else {
		print FFOUT $data;
		$len = 0;
		&flush(FFOUT);
	    }
	}
	last if $panic;
    }

    if (defined FFIN) {
	close FFIN;
    }
    close FFOUT;
}


sub count_frames {
    #count number of frames using a binary search
    my ($count, $img_ext) = @_;
    $count--;
    my $gap = 1;

    if (!defined $handle) {
	$handle = $ARGV[1];
	$curworkdir = "$workdir/$handle";
    }

    $name = &mkname(1);

    unless (-f "$curworkdir/$name$img_ext") {
	return 0;
    }

    while (1) {
	$name = &mkname($count + $gap);
	if (-f "$curworkdir/$name$img_ext") {
	    $count += $gap;
	    $gap *= 2;
	}
	else {
	    if ($gap == 1) {
		last;
	    }
	    $gap /= 2;
	}
    }
    return $count;
}


sub my_basename {
    my $exe = shift;
    return "" if ($exe eq "");
    my @parts = split(/\//, $exe);
    return $parts[-1];
}


sub fndset {
    # check if a directory is in a set (old style)
    # but useful for frontend marking the currently opened clips,
    # so we do not prune them by accident

    /^set\./ or return; ###/ fix formatting for EMACS...
	$in_set = 1;
}


# get default values

sub config_get_default {
    # return a value from our .config file
    my $key = shift;
    my $ret = "";

    if ($key eq "mplayer_play_video_command") {
	$ret = "\"" . &get_mplayer_location . "\"";
	if (!($ret eq "")) {
	    $ret .= " -double";
	}
    }

    elsif ($key eq "sox_command") {
	$ret = "\"" . &location("play") . "\"";
    }

    elsif ($key eq "mplayer_audio_command") {
	$ret = "\"". &get_mplayer_location . "\"";
    }

    $ret;
}


sub get_fs_type {
    my $dir = shift;
    my @res = split /\n/, &smog_system_direct("df -T");

    my $xfstype = "Unknown";

    my (@pieces, $mp, $mplen);
    my $maxlen = 0;

    foreach (@res) {
	@pieces = split /\s+/, $_;
	$mp = $pieces[6];
	$mplen = length($mp);
	$fstype = $pieces[1];
	if ($dir =~ /$mp/ && $mplen > $maxlen) {
	    $maxlen = $mplen;
	    $xfstype = $fstype;
	}
    }
    $xfstype;
}


sub version_check_and_upgrade {
    # various things that should happen on update

    # mainly for very very old versions

    my ($upd, $old_version_hash, $old_version) = @_;

    if ($upd) {
	if ($old_version_hash < 8005) {
	    # not used now
	    &config_delete("frontend");
	    &config_delete("mog_auto_bak");
	    &config_delete("plugin_dir");
	}
    }
    if ($old_version_hash < 9001) {
	if ($upd) {
	    # convert_version is used instead now
	    &config_deprecate("mogrify_version", "convert_version", "0.9.1");
	    &config_delete("effects_command");

	    &config_delete("no_fast_keys");

	    # changed to "open_compression_percent"
	    &config_delete("open_quality");

	    &config_delete("kbd_rpt_state");
	    &config_delete("kbd_rpt_delay");
	    &config_delete("kbd_rpt_rate");

	    #encoder_command not used any more
	    &config_delete("encoder_command");

	    #encoder_acodec used instead
	    &config_deprecate("audio_encode_quality", "encoder_acodec", "0.9.1");
	}
	#encoder plugin names changed
	$msg .= "Encoder names have changed since version $old_version.\n" .
	    "Please make sure you delete all the old encoder plugins and re-install the new ones.\n";
    }

    if ($upd) {
	if ($old_version_hash < 9006) {
	    &config_delete("video_player");
	    &config_delete("video_play_command");
	    &config_delete("PAL/NTSC");
	    &config_delete("osc_opts");
	}

	if ($old_version_hash < 1001005) {
	    &config_delete("debug_encoders");
	}

	if ($old_version_hash < 1002000) {
	    my $wmask = &config_get("lives_warning_mask");
	    $wmask |= 8192;
	    &config_set("lives_warning_mask", $wmask);
	}

	if ($old_version_hash < 1003011) {
	    &config_set("osc_port", 49999);
	    $msg = "Default OSC port has been changed to 49999.\n" . $msg;
	}

	if ($old_version_hash < 3000003) {
	    &config_deprecate("current_autotrans", "active_autotrans", "3.0.3");
	    &config_deprecate("def_autotrans", "", "3.0.3");
	    &config_deprecate("session_tempdir", "", "3.0.3");
	    &config_deprecate("tempdir", "workdir", "3.0.3");
	}

	unless (&location("build-lives-plugin-multi") eq "") {
	    &smog_system("build-lives-plugin-multi custom");
	    &smog_system("build-lives-plugin-multi test");
	}
    }
}


sub version_hash {
    # turn a version like
    # a.b.c into an integer
    # a * 1,000,000 plus b * 1,000 plus c
    # eg. 1.4.6 becomes 10004006

    my $string = shift;
    if ($string eq "") {
	return 0;
    }
    my ($ver_major, $ver_minor, $ver_micro) = split(/[.]/, $string, 3);
    if ($USE_WARNINGS) {
	no warnings 'numeric';
    }
    $ver_micro = $ver_micro + 0;
    if ($USE_WARNINGS) {
	use warnings 'numeric';
    }
    my $ver_hash = ($ver_major * 1000 + $ver_minor) * 1000 + $ver_micro;
    return $ver_hash;
}


## EXTERNAL PLUGIN UTILLITY
sub RGB24_to_string {
    # convert input like 255,255,255 to #FFFFFFFF (final FF is always alpha opaque)
    my ($red, $green, $blue) = @_;
    sprintf("\"#%02X%02X%02XFF\"", int($red), int($green), int($blue));
}


## EXTERNAL PLUGIN UTILLITY
sub parse_font_string {
    ## TODO: stretch, weight, style, size
    ## returns ($fam, $stretch, $style, $weight)
    my $fstring = shift;
    my @parts = split / /, $fstring;
    my $fam;
    my $weight = "Normal";
    my $style = "Normal";
    my $stretch = "Normal";
    my $fontpart = 1;
    foreach my $word(@parts) {
	if ($word eq "Condensed" || $word eq "Expanded" || $word eq "ExtraCondensed"
	    || $word eq "ExtraExpanded" || $word eq "SemiCondensed" || $word eq "SemiExpanded"
	    || $word eq "UltraCondensed"
	    || $word eq "UltraExpanded") {
	    $stretch = $word;
	    $fontpart = 0;
	    next;
	}
	if ($word eq "Thin" || $word eq "ExraLight" || $word eq "UltraLight" || $word eq "Light"
	    || $word eq "Normal" || $word eq "Regular"
	    || $word eq "Medium" || $word eq "DemiBold" || $word  eq "SemiBold" || $word eq "Bold"
	    || $word eq "ExtraBold" || $word eq "UltraBold" || $word eq "Black"
	    || $word eq "Heavy") {
	    $weight = $word;
	    $fontpart = 0;
	    next;
	}
	if ($word eq "Italic" || $word eq "Oblique") {
	    $style = $word;
	    $fontpart = 0;
	    next;
	}
	if ($fontpart == 1 && !int($word)) {
	    if (!$fam) {
		$fam = $word;
	    }
	    else {
		$fam .= " $word";
	    }
	}
    }
    return ($fam, $stretch, $style, $weight);
}


sub get_form_request {
    # get format request from a (encoder) plugin
    # part of the encoding process
    my $plugin = shift;
    return &smog_system_direct("\"$plugin\" get_format_request");
}


sub get_convert_version {
    my $real_convert_command = (split(" ", $smog_convert_command))[0];
    my $convert_version = &smog_system_direct("$real_convert_command 2>$nulfile | grep -i version");
    $convert_version = (split(" ", $convert_version))[2];
    return $convert_version;
}


sub get_convert_version_hash {
    ## needed for RFX plugins, e.g. colorize
    &version_hash(&get_convert_version);
}


sub is_specialdir {
    my $dirname = shift;

    if (-d "$workdir/$dirname") {
	if ($dirname =~ /^_/) {
	    if ($dirname =~ /^_scrap/ || $dirname =~ /^_ascrap/ || $dirname =~ /^_thm/
		|| $dirname =~ /^_fsp/) {
		return 1;
	    }
	}
	elsif ($dirname =~ /^scrap[0-9]+$/ || $dirname =~ /^ascrap[0-9]+$/|| $dirname =~ /^thm[0-9]+$/
	    || $dirname =~ /^fsp[0-9]+$/) {
	    return 1;
	}
    }
    return 0;
}

sub is_special_file {
    my $filename = shift;

    if ($filename =~ /^recover/ || $filename =~ /^layout/ || $filename =~ /^record/) {
	return 1;
    }
    return 0;
}


sub clean_workdir {
    # here is where we clear up diskspace
    ### - actual deletion is no longer done. Instead this is a kind of dry run.
    ### a zero length file of the same name is created in the trash dir
    ### and the user can choose whether to delete the originals or cancel
    ### a commentary is produced both on STDERR and STDOUT. The latter would be picked up
    ### using popen from the front end.

    my ($opts, $trashsubdir, $new_work) = @_;

    &smog_chdir($workdir);

    # opts: - if unset bit 0 (1)  delete clips not in any set and not marked as "in-use"
    # (protected with set.* file)
    #      - if unset bit 1 (2)  prune (clean up) any non "in-use" clips (remove backup
    # files etc.) (prot. w. noprune)
    #	   - if set   bit 2 (4)  remove any sets (directories) with layouts but no
    # clips (subdirectories)
    #      - if unset bit 3 (8)  remove marker files
    #      - if unset bit 4 (16) remove misc. files
    #      - if set bit 5 (32) recover clips
    #      - if set bit 6 (64) remove stale recovery files
    #      - if set bit 7 (128) remove empty dirs

    my $file;
    my @left;

    my $nitems = 0;
    my $index;

    my $trashdir = "$workdir/$trashsubdir";
    my $remdir = "$trashdir/remove";   ## names of things to be removed by default
    my $recdir = "$trashdir/recover";  ## names of things to recover
    my $leavedir = "$trashdir/leave";  ## names of dubious things...

    my $deftrashdir = "$workdir/.$DEFTRASH";

    make_path ($trashdir, $remdir, $recdir, $leavedir); ## $trashdir is redundant, but why not

    if (!$new_work) {
	print "\n\n\n\nsmogrify: running diskspace analysis in $workdir\n\n";
    }
    else {
	print STDERR "\n\n\n\nsmogrify: migrating contents of $workdir to $new_work\n";
	make_path $new_work;
    }

    #print STDERR "smogrify: size of directory at start is $start_total bytes\n";
    opendir DIR, $workdir;
    while ($file = readdir(DIR)) {
	next if $file eq "." || $file eq ".." || $file eq ".$DEFTRASH" || $file eq $trashsubdir;
	print STDERR "\nFile entry: $file:\n";
	print "\nFile entry: $file:\n";
	if (-d "$workdir/$file") {
	    print STDERR "is a directory...";
	    print "is a directory...";
	    my $norem = "$file/.noremove";
	    if (-f $norem) {
		print STDERR "\nsmogrify: $file is marked for manual removal only\n";
		print "\n$file is marked for manual removal only\n";
		if ($new_work) {
		    print STDERR "smogrify: moving contents of $workdir/$file\nto\n$new_work/$file\n";
		    # we are to move this set
		    if (-d "$new_work/$file") {
			opendir DIR2, "$workdir/$file";
			while (my $file2 = readdir(DIR2)) {
			    if (-e "$new_work/$file/$file2" && $file2 ne ".noremove") {
				&smog_rename("$new_work/$file/$file2", "$new_work/$file/$file2.bak");
				unless ($file2 eq ".noremove" && -e "$new_work/$file/$file2") {
				    &smog_rename("$workdir/$file/$file2", "$new_work/$file/$file2");
				}
			    }
			}
			closedir DIR2;
			remove_tree("$workdir/$file");
		    }
		    else {
			&smog_rename("$workdir/$file", "$new_work/$file");
		    }
		}
		else {
		    &smog_system("$SYSBIN" . "touch $leavedir/$file");
		}

		$nitems++;
		push (@left, $file);
		next;
	    }
	    my $ignfile = "$file/.inuse";
	    if (-f $ignfile && -s $ignfile) {
		if (defined(open IN, "<", $ignfile)) {
		    my $chval;
		    read IN, $chval, 256;
		    close IN;
		    if ("$workdir/$chval" eq "$trashdir\n") {
			print STDERR "\nsmogrify: $file is in use:\n";
			print "\n$file is in use:\n";
			unless ($opts & 0x02) {
			    unless (-f "$file/.noclean") {
				print STDERR "smogrify: doing some careful cleaning " .
				    "of directory\n";
				print STDERR "\nsmogrify: (can be overridden with option 0x02)\n";
				print "doing some careful cleaning " .
				    "of directory\n";
				print "(can be overridden by disabling option " .
				    "'Clear Useless Backup Files'\n";
				&prune($file, 0);
			    }
			}
			next;
		    }
		    else {
			print STDERR "smogrify: $file has old ignval, hmmm...what happened\n";
			print "$file has old ignval, hmmm...wonder what happened there\n";
		    }
		}
	    }
	    my $is_empty = 0;
	    if (!($opts & 0x80)) {
		$is_empty = !(&is_non_empty_dir("$workdir/$file"));
	    }
	    # remove a set unlesss it has clips OR (layouts AND NOT (opts & 0x04))
	    if (!$is_empty && &is_non_empty_dir("$workdir/$file/clips")
		|| (&is_non_empty_dir("$workdir/$file/layouts") && !($opts & 0x04))) {
		# new style sets, check clips and optionally layouts
		if ($new_work) {
		    print STDERR "smogrify: moving $workdir/$file\nto\n$new_work/$file\n";
		    # we are to move this set
		    if (-d "$new_work/$file") {
			print STDERR "smogrify: backing up $new_work/$file to " .
			    "$new_work/$file.bak first\n";
			&smog_rename("$new_work/$file", "$new_work/$file.bak");
		    }
		    unless ($opts & 0x02) {
			unless (-f "$file/.noclean") {
			    print STDERR "smogrify: removing unnecessary files from " .
				"$workdir/$file\n";
			    print STDERR "\nsmogrify: (can be overridden with option 0x02)\n";
			    print "removing unnecessary files from " .
				"$workdir/$file\n";
			    print "(can be overridden by disabling option " .
				"'Clear Useless Backup Files'\n";
			    &prune($file, 0);
			}
		    }
		    unless ($opts & 0x08) {
			print STDERR "smogrify: removing lock.* files from " .
			    "$workdir/$file\n";
			print STDERR "\nsmogrify: (can be overridden with option 0x08)\n";
			print "removing lock.* files from " .
			    "$workdir/$file\n";
			print "(can be overridden by disabling option 'Remove Lock Files')\n";
			unlink glob "$file/lock.*";
		    }
		    move($file, $new_work) or die $!;
		    print STDERR "smogrify: moved $workdir/$file to $new_work/$file\n";
		}
		else {
		    print STDERR "smogrify: directory contains clips or layouts, skipping it\n";
		    print "directory contains clips or layouts, skipping it\n";
		}
	    }
	    else {
		if ($is_empty) {
		    print STDERR "smogrify: directory is empty and option 0x80 " .
			"is not set\n";
		    print STDERR "directory will be removed.\n";
		    print "directory is empty and option 'Remove Empty Directories' " .
			"is set\n";
		    print "directory will be removed.\n";
		    &smog_system("$SYSBIN" . "touch $remdir/$file");
		    $nitems++;
		    next;
		}
		print STDERR "with no clips...\n";
		print "with no clips...\n";
		$in_set = 0;
		find(\&fndset, $file);
		print STDERR "smogrify: checking $workdir/$file\n";
		print "checking $workdir/$file\n";
		if (!$in_set) {
		    print STDERR "smogrify: file does not contain set.* file, " .
			"assuming it is not in a set\n";
		    print  "file does not contain set.* file, " .
			"assuming it is not in a set\n";
		    if (!($opts & 0x01)) {
			print STDERR "\nsmogrify: option 0x01 is not set, so\n";
			print STDERR "considering $workdir/$file for removal\n";
			print "option 'Delete Orphan Clips' is set, so\n";
			print "considering $workdir/$file for removal\n";
			my $has_hdrs = 0;
			unless ($opts & 0x20) {
			    &smog_chdir("$workdir/$file");
			    @files = glob("header*");
			    if (@files) {
				$has_hdrs = 1;
			    }
			    &smog_chdir("$workdir");
			}
			if (!$has_hdrs && (-f "$file/order"
					   || (-d "$file/layouts" && ($opts & 0x04))
					   || (int($file) > 65535 && $file =~ /^[0-9]{5,}$/)
					   || &is_specialdir($file))) {
			    print STDERR "Will remove $workdir/$file\n";
			    print "Will remove $workdir/$file\n";
			    &smog_system("$SYSBIN" . "touch $remdir/$file");
			    $nitems++;
			}
			else {
			    print STDERR "leaving it because: \n";
			    print "leaving it because: \n";
			    if ($has_hdrs) {
				print STDERR "   - $workdir/$file/ contains header* and we " .
				    "need to check with the user first\n";
				print "   - $workdir/$file/ contains header* and we " .
				    "need to check with the user first\n";
				&smog_system("$SYSBIN" . "touch $recdir/$file");
				$nitems++;
				next;
			    }
			    else {
				if (! -f "$workdir/$file/order") {
				    print STDERR "   - $workdir/$file/order does not exist\n";
				    print "   - $workdir/$file/order does not exist\n";
				}
				unless (int($file) > 65535 && $file =~ /^[0-9]{5,}$/) {
				    unless (int($file > 0)) {
					print STDERR "   - directory name contains non digit\n";
					print "   - directory name contains non digit\n";
				    }
				    else {
					unless ($file =~ /^[0-9]{5,}$/) {
					    print STDERR "   - directory name $file contains " .
						"fewer than 5 digits\n";
					    print "   - directory name $file contains " .
						"fewer than 5 digits\n";
					}
					else {
					    unless (int($file) > 65535) {
						print STDERR "   - directory name < 65536\n";
						print "   - directory name < 65536\n";
					    }
					    else {
						print STDERR "   - unspecified reason ???!!?\n";
						print "   - unspecified reason ???!!?\n";
					    }}}}}

			    unless (&is_specialdir($file)) {
				print STDERR "   - directory name does not begin with scrap, " .
				    "ascrap or fsp followed by digits.\n";
				print "   - directory name does not begin with scrap, " .
				    "ascrap or fsp followed by digits.\n";
			    }
			    print STDERR "smogrify: manual cleanup may be required if this " .
				"directory is no longer needed\n";
			    print "smogrify: manual cleanup may be required if this " .
				"directory is no longer needed\n";
			    &smog_system("$SYSBIN" . "touch $leavedir/$file");
			    $nitems++;
			    push (@left, $file);
			}}}
		else {
		    unless ($opts & 0x02) {
			print STDERR "smogrify: $file contains set.* file, " .
			    "assuming it is in a set\n";
			print "smogrify: $file contains set.* file, " .
			    "assuming it is in a set\n";
			unless (-f "$file/.noclean") {
			    print STDERR "smogrify: removing unnecessary files " .
				"from directory\n";
			    print STDERR "\nsmogrify: (can be overridden with option 0x02)\n";
			    print "removing unnecessary files " .
				"from directory\n";
			    print "(can be overridden by disabling option " .
				"'Remove Useless Backup files')\n";
			    #print "removing unnecessary files " .
			    #	"from directory\n";
			    #   print "(can be overridden with option 0x02)\n";
			    &prune($file, 0);
			}
		    }
		    if ($new_work) {
			# workdir was changed, so we move all sets (subdirs)
			# from old workdir to new
			# [TODO - check for set name collision !]
			if (-d "$workdir/$file") {
			    print STDERR "smogrify: moving $workdir/$file to $new_work/$file\n";
			    if (-d "$new_work/$file") {
				print STDERR "smogrify: first I will backup " .
				    "$new_work/$file to $new_work/$file.bak\n";
				&smog_rename("$new_work/$file", "$new_work/$file.bak");
			    }
			    move($file, $new_work) or die $!;
			    print STDERR "smogrify: moved $workdir/$file to $new_work/$file\n";
			}}}}

	    unless ($opts & 0x08) {
		print STDERR "smogrify: removing temporary marker file " .
		    ".noclean from $workdir/$file\n";
		print STDERR "\nsmogrify: (can be overridden with option 0x08)\n";
		print "removing temporary marker file " .
		    ".noclean from $workdir/$file\n";
		print "(can be overridden by disabling option 'Remove Misc Files')\n";
		unlink "$workdir/$file/.noclean";
	    }
	}
	else {
	    print STDERR "is a file...";
	    print "is a file...";
	    # move crash recovery files
	    if ($new_work) {
		if ($file =~ /^recovery/ || $file =~ /^layout/) {
		    print STDERR "smogrify: moving recovery files recovery* and layout* " .
			"from $workdir to $new_work\n";
		    move($file, $new_work) or die $!;
		}}
	    else {
		if (&is_special_file($file)) {
		    print STDERR "...with known type. Ignoring\n";
		    print "...with known type. Ignoring\n";
		}
		else {
		    print STDERR "...with unknown type. Consider for manual removal\n";
		    print "...with unknown type. Consider for manual removal\n";
		    &smog_system("$SYSBIN" . "touch $leavedir/$file");
		    $nitems++;
		    push (@left, $file);
		}
	    }
	}
	print STDERR "analysed.\n";
	if (!$new_work) {
	    print "analysed.\n";
	}
    }

    closedir DIR;

    unless ($opts & 0x10) {
	# clean up a few extra remnants
	if ($new_work) {
	    if (&is_dangerous($DEFTRASH)) {
		if ($log) {
		    print "default trash directory $DEFTRASH points to dangerous location\n" .
			"Check $DEFTRASH setting in $MYNAME. ABORTING\n";
		    print STDERR "smogrify: default trash directory $DEFTRASH points to " .
			"dangerous location\nCheck $DEFTRASH setting in $MYNAME. ABORTING\n";
		}
		&sig_error("Bad default trashdir");
	    }
	}
	print STDERR "\nsmogrify: removing rfx.* *" .
	    "and keep_layout* from $workdir";
	print STDERR "\nsmogrify: (can be overridden with option 0x10)\n";
	print "\nremoving rfx.* keep_layout* layout_numbering.* from $workdir";
	print "\n(can be overridden by disabling 'Remove Misc Files')\n";
	if ($new_work) {
	    if (! -d $deftrashdir) {
		make_path $deftrashdir, {mode => 01777};
	    }
	}
	for $filerem(glob "rfx.* keep_layout* layout_numbering.*") {
	    if ($new_work) {
		&smog_rename($filerem, $deftrashdir);
	    }
	    else {
		if (-f "$leavedir/$filerem") {
		    unlink "$leavedir/$filerem";
		    $index = 0;
		    $index++ until $left[$index] eq "$leavedir/$filerem" || $index == $nitems;
		    splice(@left, $index, 1);
		    $nitems--;
		}
		&smog_system("$SYSBIN" . "touch $remdir/$filerem");
		$nitems++;
	    }
	}
	if ($new_work) {
	    print STDERR "smogrify: removing firew* encoder_log_* and stream.* from $workdir\n";
	    for $filerem(glob "firew.* encoder_log_* stream.*") {
		&smog_rename($filerem, $deftrashdir);
	    }
	}
    }
    unless ($opts & 0x40) {
	if ($new_work) {
	    if (&is_dangerous($DEFTRASH)) {
		if ($log) {
		    print "default trash directory $DEFTRASH points to dangerous location\n" .
			"Check $DEFTRASH setting in $MYNAME. ABORTING\n";
		    print STDERR "smogrify: default trash directory $DEFTRASH points to " .
			"dangerous location\nCheck $DEFTRASH setting in $MYNAME. ABORTING\n";
		}
		&sig_error("Bad default trashdir");
	    }
	}
	print STDERR "\nsmogrify: removing stale recovery files recorded-layout* from $workdir\n";
	#print STDERR "\nsmogrify: (can be overridden with option 0x40)\n";
	print "\nremoving stale recovery files recorded-layout* from $workdir\n";
	#print "\n(can be overridden with option 0x40)\n";
	if ($new_work) {
	    if (! -d $deftrashdir) {
		make_path $deftrashdir, {mode => 01777};
	    }
	}
	for $filerem(glob "recorded-layout*") {
	    if ($new_work) {
		&smog_rename($filerem, $deftrashdir);
	    }
	    else {
		if (-f "$leavedir/$filerem") {
		    unlink "$leavedir/$filerem";
		    $index = 0;
		    $index++ until $left[$index] eq "$leavedir/$filerem" || $index == $nitems;
		    splice(@left, $index, 1);
		    $nitems--;
		}
		&smog_system("$SYSBIN" . "touch $remdir/$filerem");
		$nitems++;
	    }
	}
    }

    if (@left) {
	print STDERR "leaving the following files for manual removal\n";
	print STDERR join("\n", @left);
	print "\n";
    }
    print STDERR "\n";
    if (!$new_work) {
	print "\n\n";
    }
    else {
	remove_tree($deftrashdir);
    }
    return $nitems;
}


sub prune {
    # clean up old backup images etc in $workdir/$file
    # part of the diskspace recovery process
    my ($file, $log) = @_;
    &clean_old($file, 0, $log);
}


sub clear_symlinks {
    # clear symlinks directory - called after encoding a selection (part of a clip which gets symlinked)

    # this is for "safe" symlinks
    # we say that symlinks may be created only within <workdir>/handle/lives-symlinks on whatever system

    # this allows e.g dynebolic to use non-Linux disk as working directory

    my $curworkdir = "$workdir/$handle";
    my $linksdir = "$curworkdir/lives-symlinks/";
    exit 1 if (&is_dangerous($linksdir));

    &smog_chdir($curworkdir);

    if (-d $linksdir) {
	# remove directory + contents
	remove_tree($linksdir);
    }

    #create dir so next step works
    make_path $linksdir;

    # remove dir + any empty parents
    &smog_system("$SYSBIN" . "rmdir -p \"$linksdir\" 2>$nulfile");
}


sub is_non_empty_dir {
    #return 1 if "the parameter" is the name of a directory containing at least one non-dotted file
    my $dirname = shift;
    opendir (my $dh, $dirname) or return 0;
    return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) != 0;
}
