#!/usr/bin/env perl # # MPlayer wrapper to select and play random video file(s) # # TODO: incorporate history-cleanse ? use strict; use warnings; use File::Find; use File::stat; #-------------------------------------------------------------------------------------------------------# ## BEGIN CONFIGURABLE OPTIONS : #-------------------------------------------------------------------------------------------------------# # Configure your video 'collection' locations here. This is probably the only thing you need to change. our @Collections=( "/Volumes/ENCRYPTED", #"/mnt/pr0n", ); # Specify Alternate image viewer, it will take one argument - related filename(s) our $image_viewer=""; # Otherwise the defaults: our $def_image_viewer_osx="open"; our $def_image_viewer_linux="eog"; # Autoplay the files, or give a y/n prompt? 1 = auto, 0 = prompt our $autoplay="0"; # Path to mplayer, and any default options you want to pass along our $mplayer="mplayer"; our $mplayer_opts=""; #our $mplayer_opts="-display :0"; # Supported file types mplayer can handle - if it's not in this list, it won't be played, even though mplayer probably supports it our @Supported_videos=("mpg","mpeg","avi","wmv","asf","mov","mp4","mkv","m4v","rmvb","flv"); # Cover images - some filetypes that may exist in your collection that mplayer should ignore / will not play our @Supported_images=(".jpg",".png",".gif",".bmp"); # Volume Suppression. use Negative integer to decrease mplayer's volume. our $volume_suppression="-25"; our $volume_mods=""; #our $volume_mods="-af volume=$volume_suppression:0"; # Set some default behavior switches; alter for desired behavior my $version="3.1"; # Current Version. our $file_info_banner="1"; # Display info about the selected file ? our $use_colors="1"; # Colorize output ? our $verbose="0"; # Verbose by default ? our $very_verbose="0"; # Even more info ? our $fullscreen="0"; # Fullscreen mplayer ? our $sort_order="0"; # Sort by mtime (1), vs alphabetical (0)? our $cover="0"; # Open related images ? our $nosound="0"; # Nosound (unrecoverable stream) ? our $search="0"; # Jump to search prompt ? our $test_exists="0"; # Simple test for existance of file our $listing="0"; # List files by default ? our $list_everything="0"; # List videos, images, and unrelated files ? our $list_full_paths="0"; # Display full paths for files ? our $mplayer_output="0"; # Attach to mplayer output ? our $search_char_min="2"; # Minimum search characters ? our $specific_result="0"; # Selected file number to play ? our $debug = "0"; # Debug info by default ? our $pretend="0"; # No Exec ? our $auto_sort="1"; # 1 implies -o with -t our $fork_bomb_protect="1"; # Prevent fork bombing the system with mplayer calls our $do_exec="0"; # Probably never need to change this. our $search_char="0"; # Probably never need to change this. our $custom_dir="0"; # Probably never need to change this. our $total_videos="0"; # Probably never need to change this. our $total_images="0"; # Probably never need to change this. our $total_rejected="0"; # Probably never need to change this. our $search_term=""; # Probably never need to change this. our $search_input; # Probably never need to change this. our $search_request; # Probably never need to change this. our $limit_recent="0"; # Probably never need to change this. our $limit_time="0"; # Probably never need to change this. our $old_or_new; # Probably never need to change this. our $repeat="0"; # Repeat / Range our $default_repeat="5"; # Default repeat range our $repeat_range="0"; our $range_request; our $repetition; our $mplayer_max="10"; # Maximum amount of mplayer windows before limiting spawn our $video_width="0"; our $video_height="0"; our $target_x="0"; our $target_y="0"; our $screen_res_x="0"; our $screen_res_y="0"; our $resolution=""; our $mplayer_opts_geo=""; our $kill_players="0"; our $kill_signal="hup"; our $user_ok=""; our $null_term=""; our $exclude_term=""; # Define highlight color codes my $color_bold = "\033[0;1m"; my $color_white = "\033[0m"; my $color_black = "\033[1;30m"; my $color_red = "\033[1;31m"; my $color_green = "\033[1;32m"; my $color_blue = "\033[1;34m"; my $color_yellow = "\033[1;33m"; my $color_cyan = "\033[1;36m"; my $color_purple = "\033[1;35m"; my $color_dark_blue = "\033[0;34m"; my $color_dark_red = "\033[0;31m"; my $color_dark_green = "\033[0;32m"; my $color_dark_yellow = "\033[0;33m"; my $color_dark_cyan = "\033[0;36m"; my $color_dark_purple = "\033[0;35m"; my $color_dark_black = "\033[0;30m"; # Set which color to actually use for various 'tags' our $colortag_name = $color_red; our $colortag_search = $color_cyan; our $colortag_number = $color_dark_yellow; our $colortag_mtime = $color_dark_blue; our $colortag_default = $color_white; our $colortag_online = $color_bold; our $colortag_offline = $color_bold; our $colortag_warning = $color_bold; our $colortag_error = $color_bold; our $colortag_pretend = $color_dark_purple; our $colortag_exec = $color_bold; our $colortag_vidlabel = $color_bold; our $colortag_abort = $color_yellow; our $colortag_category = $color_blue; our $colortag_imgname = $color_purple; our $colortag_selection = $color_dark_red; # Text used for the static variable tags; our $online_label = "AVAILABLE"; our $offline_label = "UNAVAILABLE"; our $warning_label = "WARNING"; our $error_label = "ERROR"; our $pretend_label = "PRETEND"; our $exists_label = "FOUND"; our $exec_label = "EXEC"; our $abort_label = "ABORT"; our $yes_label = "y"; our $no_label = "n"; our $category_label = "CATEGORY"; our $vid_label = "VID FILE"; # Initial self awareness for this script my @ScriptPath=split(/\//,$0); our $script_name=$ScriptPath[$#ScriptPath]; # Required commands used by this script our @Required=("$mplayer","grep"); #-------------------------------------------------------------------------------------------------------# &Init; ## END CONFIGURABLE OPTIONS : YOU SHOULDN'T NEED TO EDIT ANYTHING BELOW THIS POINT #-------------------------------------------------------------------------------------------------------# sub GlobalVars { if ($debug == 1) { print qq| GlobalVars\n|; } our $mplayer = $main::mplayer; our $mplayer_opts = $main::mplayer_opts; our $volume_suppression = $main::volume_suppression; our $volume_mods = $main::volume_mods; our $image_viewer = $main::image_viewer; our $version = $main::version; our @Collections = @main::Collections; our @Videos = @main::Videos; our @Images = @main::Images; our @Rejected = @main::Rejected; our @Supported_videos = @main::Supported_videos; our @Supported_images = @main::Supported_images; our @AlreadyPicked=@main::AlreadyPicked; } sub Init { # Set & Get some initial requirements &GlobalVars; print qq|\n|; if (@ARGV) { &CheckArgs; } if ($debug == 1) { print qq| Init\n|; } # swap out colors if ($use_colors == 1) { $online_label = $colortag_online . "$online_label" . $colortag_default; $offline_label = $colortag_offline . "$offline_label" . $colortag_default; $warning_label = $colortag_error . "$warning_label" . $colortag_default; $error_label = $colortag_error . "$error_label" . $colortag_default; $pretend_label = $colortag_pretend . "$pretend_label" . $colortag_default; $exists_label = $colortag_pretend . "$exists_label" . $colortag_default; $exec_label = $colortag_exec . "$exec_label" . $colortag_default; $abort_label = $colortag_abort . "$abort_label" . $colortag_default; $yes_label = $color_bold . "$yes_label" . $colortag_default; $no_label = $color_bold . "$no_label" . $colortag_default; $vid_label = $colortag_vidlabel . $vid_label . $colortag_default; $script_name = $color_bold . $script_name . $colortag_default; #$category_label = $colortag_category . "$category_label" . $colortag_default; } if ($autoplay == 1) { our $do_exec = 1; } # Check some command dependancies &DepCheck; # If one of the args is a directory, play file from requested dir if ($custom_dir == 1) { our $dir=$main::mydir; my $dirlabel = $color_bold . "USING DIR" . $colortag_default; print qq|[$dirlabel]: $dir\n\n|; if ($search == 1) { &Search; } else { #Draw random file from user dir (will inherit dir) &BeginFlow; } } else { # Default Program flow; Play random file from collections &BeginFlow; } } sub CheckArgs { # Process arguments before action if ($debug == 1) { print qq| CheckArgs\n|; } my $args=0; my $pass_opt_check=0; my $next; foreach my $arg (@ARGV) { if ($arg =~/^\-q$/) { $nosound = 1; } elsif ($arg =~/^\-i$/) { our $cover = 1; } elsif ($arg =~/^\-h$|^-{1,2}help$/) { &Usage; } elsif ($arg =~/^\-v$/) { our $verbose = 1; } elsif ($arg =~/^-vv$/) { our $verbose = 1; our $very_verbose = 1; } elsif ($arg =~/^-{1,2}debug$/) { our $debug = 1; } elsif ($arg =~/^\-a$/) { our $autoplay = 1; our $do_exec = 1; } elsif ($arg =~/^\-l$/) { our $listing = 1; } elsif ($arg =~/^\-z$/) { our $list_everything = 1; our $listing = 1; } elsif ($arg =~/^\-c$/) { if ($use_colors == 1) { our $use_colors = 0; } else { our $use_colors = 1; } } elsif ($arg =~/^\-o$/) { if ($sort_order == 1) { our $sort_order = 0; } else { our $sort_order = 1; } } elsif ($arg =~/^\-k$/) { our $kill_players = 1; chomp(my $mplayer_pid=`pidof mplayer`); if ($mplayer_pid) { system("kill -s $kill_signal $mplayer_pid"); } } elsif ($arg =~/^\-p$/) { our $pretend = 1; } elsif ($arg =~ /^\-fs$/) { our $fullscreen = 1; } elsif ($arg =~ /^\-m$/) { our $mplayer_output = 1; } elsif ($arg =~/^\-t$/) { # Limit results by time logic; our $limit_recent = 1; if ($auto_sort == 1) { our $sort_order = 1; } my $next=$ARGV[$args+1]; unless ($next) { print qq|[$error_label]: With -t, You must specify a time period.\n\n|; sleep 1; &Usage; } unless ($next=~/^-(\w+)/) { $limit_time = $next; } if ($limit_time=~/^(\+?)(\d+)(\w?)$/) { $pass_opt_check=1; our $old_or_new = $1; my $number = $2; my $amount = $3; if ($amount) { if ($amount eq 'd') { if ($old_or_new) { $number = $number - 1; } $limit_time = $number * 1; } elsif ($amount eq 'w') { $limit_time = $number * 7; } elsif ($amount eq 'm') { $limit_time = $number * 31; } elsif ($amount eq 'y') { $limit_time = $number * 365; } else { print qq|Recent Limit: $limit_time - "$amount" is not a valid time period... must be either d,w,m,y\n|; exit; } } } } elsif (($arg =~ /^\-s$/) || ($arg=~ /^-e$/)) { # grab search term if ($arg =~/^\-s$/) { our $search = 1; } elsif ($arg =~/^\-e$/) { our $search = 1; our $listing = 1; our $test_exists = 1; } my $next=$ARGV[$args+1]; if ($next) { unless ($next=~/^-(\w+)/) { $search_term = $next; } if ($search_term=~/^((\w+)|(\*)|(\.))/g) { $pass_opt_check=1; } } else { $search_term = ""; } } elsif ($arg =~ /^\-x$/) { our $null_term = 1; my $next=$ARGV[$args+1]; if ($next) { unless ($next=~/^-(\w+)/) { $exclude_term = $next; } if ($exclude_term=~/^((\w+)|(\*)|(\.))/g) { $pass_opt_check=1; } } else { $exclude_term = ""; } } elsif ($arg =~ /^\-n$/) { our $specific_result = 1; my $wrong_number="[$error_label]: With -n, You must specify the number of a search result to play.\n"; my $next = $ARGV[$args+1]; if ($next) { unless ($next=~/^(\d+)$/) { print qq|$wrong_number\n|; sleep 1; &Usage; } else { $search_request = $next; } if ($search_request=~/^(\d+)/) { $pass_opt_check=1; } } else { print qq|$wrong_number\n|; sleep 1; &Usage; } } elsif ($arg =~ /^\-r$/) { our $repeat = 1; $repeat_range = "$default_repeat"; my $next=$ARGV[$args+1]; if ($next) { # unless next var is another option, assign it as a var unless ($next=~/^-(\w+)/) { $repeat_range = $next; } if ($repeat_range=~/^((\d+)|all)$/g) { my $range_limit=$mplayer_max + $default_repeat; if (($repeat_range=~/^\d+/) && ($repeat_range > $range_limit)) { unless ($fork_bomb_protect == 1) { &PromptUser("WARNING: $repeat_range is over $range_limit processes. Continue?"); } } $pass_opt_check=1; } } $range_request = $repeat_range; } elsif ((-d "$arg")||(-l "$arg")) { our $custom_dir = 1; our $mydir=$arg; $mydir=~s/\/$//; } else { # if an option has been validated, don't complain unless ($pass_opt_check == 1) { print qq|[$error_label]: Unknown Argument: "$arg"\n\n|; sleep 1; &Usage; exit; } $pass_opt_check=0; } $args++; } return; } sub BeginFlow { # Silence errors of subcommands, unless ... unless (($very_verbose == 1) || ($debug == 1)) { open(STDERR, ">/dev/null") or die "Can't redirect stderr: $!"; } if ($debug == 1) { print qq| BeginFlow\n|; } &CheckStorage; &GetSysInfo; if (($search == 1) || ($test_exists == 1)) { &Search; } else { &SelectFile; } print qq|OOPS! You should not be here.\n|; exit; } sub GetSysInfo { # Alter commands per OS; my $this_os="$^O"; # MAC OSX if ($this_os =~/^(darwin)$/i) { # grab default for image viewer unless ($image_viewer) { $image_viewer = $def_image_viewer_osx; } # If repeating, and resolution hasn't been grabbed yet... we need it to determine window layouts if ($repeat == 1) { unless ($resolution) { my $getrez_cmd="system_profiler SPDisplaysDataType | grep Resolution"; chomp($resolution=`$getrez_cmd`); $resolution=~s/^\s+//g; $resolution=~s/Resolution: //g; # Pull x & Y variables from result ($screen_res_x,$screen_res_y)=split(/\ x\ /,$resolution); } } } elsif ($this_os =~/^(linux)$/i) { # grab default for image viewer unless ($image_viewer) { $image_viewer = $def_image_viewer_linux; } # Same process as above.. if ($repeat == 1) { unless($resolution) { my $getrez_cmd="xdpyinfo | grep dimensions | awk '{print \$2}' | awk -Fx '{print \$1, \$2}'"; chomp($resolution=`$getrez_cmd`); # Pull x & Y variables from result ($screen_res_x,$screen_res_y)=split(/\ /,$resolution); } } } else { print qq|Unsupported OS: some things may not work as expected.\n|; } if ($debug == 1) { print qq| Screen Resolution: "$resolution"\n|; }; } sub CheckStorage { # Consider multiple sources, check for availability if ($debug == 1) { print qq| CheckStorage\n|; } our @Available; our @Unavailable; my $count; # Check storage for availability foreach my $csellection (@Collections) { $count++; chomp $csellection; if (-d "$csellection") { push(@Available,$csellection); } else { push(@Unavailable,$csellection); } } # if no collections available, print error -- unless user specified a directory to use my $X; my $Y; my $storage_info=""; unless ($#Available == -1) { if ($very_verbose == 1) { $storage_info .= qq| [COLLECTIONS]:\n\n|; foreach my $avail (@Available) { $storage_info .= qq| [$online_label]: $avail\n|; } foreach my $unavail (@Unavailable) { $storage_info .= qq| [$offline_label]: $unavail\n|; } print qq|$storage_info\n|; } else { $Y=0; foreach my $unavail (@Unavailable) { if ($verbose == 1) { print qq|[$warning_label]: $unavail is currently unavailable.\n\n|; if ($Y == $#Unavailable) { print qq| Consider editing the \@Collections array.\n\n|; } } $Y++; } unless ($#Unavailable < 0) { #print qq|\n|; } } } else { unless ($custom_dir == 1) { # Display this message if no available collections $storage_info .= qq|[$error_label]: No collections currently available?\n|; $X=0; foreach my $unavail (@Unavailable) { $X++; $storage_info .= qq|[$offline_label]: $unavail\n|; } $storage_info .= qq|\n You may need to edit the list of \@Collections in this script,\n|; $storage_info .= qq| or otherwise specify a directory to select a file from:\n|; $storage_info .= qq|\n $script_name ~/path/to/videos\n|; print qq|$storage_info\n|; exit; } } return; } sub DepCheck { # Ensure required commands exist if ($debug == 1) { print qq| DepCheck\n|; } foreach my $command (@Required) { chomp(my $which=`which $command`); unless (-e "$which") { print qq|[$error_label]: "$command" command not found!\n\n|; if ($command =~/mplayer$/) { print qq|This script is just a wrapper for command line mplayer.\n|; print qq|Please ensure mplayer is compiled & installed properly.\n|; print qq|http://www.mplayerhq.hu/design7/dload.html\n\n|; } exit; } } } sub BuildLists { # Filter unsupported file types and build list of available files. if ($debug == 1) { print qq| BuildLists\n|; } our $location; my $Y=0; my $mtime; my %mtime; my $supported_videos; my $supported_images; my @AllFiles; my @FileList; # Build List from a collection directory, or user-specified path if ($custom_dir == 1) { $location = &PrepPath("$main::mydir"); @main::Available = $main::mydir; } else { $location=""; foreach my $item (@main::Available) { chomp $item; $location .= &PrepPath("$item"); if ($#main::Available > 0) { $location = $location . " "; } } } if ($debug == 1) { print qq| \n|; } # FILE FIND: IF sorting by oder, find files in all available paths & sort by mtime (-l -o) if ($sort_order == 1 && $limit_recent == 0) { find(sub {$mtime{$File::Find::name} = -M _ if -f;}, @main::Available); @AllFiles = sort {$mtime{$b} <=> $mtime{$a}} keys %mtime; } # FILE FIND: (-l -o -t [x]): selective population of the $mtime{} hash for sorting, based on mtime vs $limit_time elsif ($sort_order == 1 && $limit_recent == 1) { unless ($repetition) { unless ($old_or_new eq '+') { $old_or_new = "-"; } $limit_time = $old_or_new . $limit_time; } # File::Find in action; find( sub { if ($old_or_new eq '+') { if (-f && -M >= $limit_time) { $mtime{$File::Find::name} = -M _; } } elsif ($old_or_new eq '-') { $limit_time=~s/\-//; if (-f && $limit_time >= -M) { $mtime{$File::Find::name} = -M _; } } }, @main::Available); @AllFiles = sort { $mtime{$b} <=> $mtime{$a} } keys %mtime; } # FIND FILE: Limit file list to time period (-l -t [x]) elsif ($sort_order == 0 && $limit_recent == 1) { # THIS CONDITION IS UNREACHABLE IF $sort_order =1 is defined under -t option -- or $auto_sort = 1|; #@AllFiles=`find $location -type f -mtime $limit_time`; unless ($repetition) { unless ($old_or_new eq '+') { $old_or_new = "-"; } $limit_time = $old_or_new . $limit_time; } find( sub { if ($old_or_new eq '+') { if (-f && -M >= $limit_time) { push(@AllFiles, $File::Find::name); ### FIX THIS SHIT CONDITIONAL } } elsif ($old_or_new eq '-') { $limit_time=~s/\-//; if (-f && $limit_time >= -M) { push(@AllFiles, $File::Find::name); } } }, @main::Available); } # IF NOT sorting, no need for mtime check (just -l), slight speedup else { #@AllFiles=`find $location -type f`; find(sub { push(@AllFiles, $File::Find::name) if -f }, @main::Available); } # SEARCH FILTER: Now we'll use a secondary list for parsed search results if ($search == 1) { my @search_words=split(/\,/,$search_term); foreach my $search_word (@search_words) { $search_word=~s/\,\ /\,/g; $search_word=~s/\./\(\\W+\)/g; #$search_word=~s/\./\(\(\\W+\)|\(\\S+\)\)/g; # Must-have for simple search $search_word=~s/\ /\./g; #$search_word=~s/\ /\(\\W+\)/g; # Expand search if wildcard, else this will not split and var is still useable my @sub_words=(split(/\*/,"$search_word")); # Now, actually populate @FileList if file matches search terms foreach my $sub_word (@sub_words) { $sub_word=~s/^\.//g; foreach my $item (@AllFiles) { chomp $item; # .. unless it matches an -x exclusion_term, put it in the list unless (($null_term == 1 )&& ($exclude_term ne "") && ($item=~/$exclude_term/ig)) { if ($item=~/$search_word/ig) { push(@FileList,$item); } if ($item=~/$sub_word/ig) { push(@FileList,$item); } } } } } } else { # If not searching, use the complete list @FileList = @AllFiles; } # Determine Supported file types by creating a filter $Y=0; foreach my $type (@Supported_videos) { $supported_videos .= "\.$type\$"; unless ($Y == $#Supported_videos) { $supported_videos .= "|"; } $Y++; } # Same style filter image types $Y=0; foreach my $type (@Supported_images) { $supported_images .= "\.$type\$"; unless ($Y == $#Supported_images) { $supported_images .= "|"; } $Y++; } # Isolate specific file types into arrays for later access foreach my $item (@FileList) { chomp $item; if ($item =~/$supported_videos/i) { push (@main::Videos,$item); our $total_videos++; } elsif ($item=~/$supported_images/i) { push (@main::Images,$item); our $total_images++; } else { # Store all other files as rejected push (@main::Rejected,$item); our $total_rejected++; } } # Strip out duplicates. my %unique; undef %unique; @unique{@main::Videos} = (); my @Videos = keys %unique; unless ($sort_order == 1) { #NOTE: Don't sort this list, it should already be sorted @main::Videos=sort(@Videos); } else { # Strip duplicates; @main::Videos = grep(!$unique{$_}++, @main::Videos); } undef %unique; @unique{@main::Images} = (); my @Images = keys %unique; @main::Images = sort(@Images); undef %unique; @unique{@main::Rejected} = (); my @Rejected = keys %unique; @main::Rejected = sort(@Rejected); my $zero; if ($total_videos == 0) { if ($use_colors == 1) { $zero = $color_red . "0" . $colortag_default; } else { $zero = "0"; } if ($search == 1 && $limit_recent == 0) { print qq|[$error_label]: $zero results found for "$main::search_input" in: $location\n\n|; } elsif ($search == 1 && $limit_recent == 1) { my $result_text=""; if ($limit_time =~/^\-/) { $result_text = "results NEWER than $limit_time days"; } elsif ($limit_time =~/^\+/) { $result_text = "results OLDER than $limit_time days"; } print qq|[$error_label]: $zero $result_text found for "$main::search_input" in: $location\n\n|; } else { print qq|[$error_label:] $zero results found in: $location\n\n|; } exit; } return; } sub Search { if ($debug == 1) { print qq| Search: \"$main::search_term\"\n|; } # Gather Search input if not specified on the command line unless ($main::search_term) { print qq|[SEARCH]: |; chomp($search_input=); print qq|\n|; } else { unless (-d "$main::search_term") { $search_input=$main::search_term; } else { if ($debug == 1) { print qq|Search $main::search_term is a dir.\n|; } $main::search_term = ""; print qq|[SEARCH]: |; chomp($search_input=); print qq|\n|; } } my $search_char=length($search_input); # if empty search input, default to random selection, else format a regex based on input if ($search_char == 0) { print qq|[NOTICE]: No Search terms specified, selecting random file...\n\n|; $main::search = 0; &SelectFile; exit; } elsif ($search_char < $main::search_char_min ) { print qq|[$error_label]: $search_input is too short ($search_char of $main::search_char_min min).\n\n|; exit; } else { # Now that input is validated, Search $search_term="$search_input"; &SelectFile; exit; } } sub SelectFile { if ($debug == 1) { print qq| SelectFile\n|; } # Only build the list once unless ($repetition) { &BuildLists; } my $count; my $display_name; my $selected; my $selected_movie_raw_path; my $unique_videos=$#main::Videos+1; # If the range is 'all', play in order (avoid dupes) by dropping to 1st request, and jumping back to top of &SelectFile for full increment if (($repeat == 1) && ($range_request =~/^all$/)) { $specific_result = 1; $search_request = 1; $range_request = $total_videos; $repeat_range = $range_request; # Reset counter $total_videos = ""; # Start over; &SelectFile; } # If a user specifies the file from a list, we already know it's number. Else draw randomly. if ($specific_result == 1) { my $result_summary; my $req_orig = $search_request; $selected = $search_request; # Alter user request to align with array unless ($search_request == 0) { $search_request = ($search_request - 1); } if ($search_request > $#main::Videos) { if ($total_videos == 1) { $result_summary = qq|only 1 result for "$search_term" ... |; } else { $result_summary = qq|only $unique_videos Videos found |; if ($search == 1) { $result_summary .= qq|for "$search_term" |; } $result_summary .= qq |...|; } print qq|[$error_label]: $req_orig is out of bounds, $result_summary\n\n|; exit; } chomp($selected_movie_raw_path=$main::Videos[$search_request]); } else { # THE WHEEL OF FATE SPINS! my $rand=int rand ($#main::Videos+1); $selected = $rand; chomp($selected_movie_raw_path=$main::Videos[$rand]); unless ($selected == 0) { $selected = ($selected + 1); } # ..but try to avoid dupes if ($repeat == 1) { foreach my $past_select (@main::AlreadyPicked) { # If this selection has been drawn before... if ($past_select == $selected) { if ($debug == 1) { print qq| PICKED $selected but ALREADY PLAYED $#main::AlreadyPicked $#main::Videos\n|; } # exit if we've gone through the full list, else roll again if ($#main::AlreadyPicked >= $#main::Videos) { exit; } else { &SelectFile; } } } # Keep track of drawn numbers push (@main::AlreadyPicked,$selected); } } if (($selected)&&($debug == 1)) { print qq| "$selected"\n|; print qq| 0) { print qq| [VIDEO FILES]:|; if ($search == 1) { $total_videos = $unique_videos; print qq| $total_videos results for "$search_input" ... \n\n|; } elsif ($limit_recent == 1) { my $result_text=""; if ($limit_time =~/^\-/) { $result_text = "results NEWER than $limit_time days"; } elsif ($limit_time =~/^\+/) { $result_text = "results OLDER than $limit_time days"; } print qq| $total_videos $result_text\n\n|; } else { print qq| $total_videos videos available.\n\n|; } $count=0; # Loop through and print list foreach my $item (@main::Videos) { $count++; $display_name=$item; # Strip source name from file to help categorize unless ($list_full_paths == 1) { foreach my $avail (@main::Available) { $display_name=~s/^$avail\///g; } } # Colorize search-term hits if (($search == 1) && ($use_colors == 1)) { my $search_word; # Split seperated terms my @search_breakdown2=split(/\,/,$search_term); foreach my $seperate_term (@search_breakdown2) { #$search_term=~s/\,\ /\,/g; my @search_breakdown=split(/\ |\,|\.|\*/,$search_term); # Break the search phrase down into individual words for matching results foreach my $search_word (@search_breakdown) { my @matches = $display_name =~/($search_word)/ig; foreach my $match (@matches) { my $highlighted=$colortag_search . "$match" . $colortag_default; $display_name=~s/$match/$highlighted/g; } } } } # Include mtime info ? my $mtime=""; if (($verbose == 1) || ($limit_recent == 1) || ($sort_order == 1)) { $mtime = scalar localtime stat("$item")->mtime; # format only useful data $mtime =~s/\ \ /\ /g; (my $day,my $month,my $date,my $time,my $year)=split(/\ /,$mtime); $mtime = "$year $month $date"; $mtime = " ($mtime)"; if ($use_colors == 1) { $mtime = $colortag_mtime . $mtime . $colortag_default; } } my $number; if ($use_colors == 1) { $number = $colortag_number . $count . $colortag_default; } else { $number = $count; } # Finally display list item print qq| [$number]:$mtime $display_name \n|; } print qq|\n|; } $count=0; } if ($list_everything == 1) { print qq| [IMAGES]:\n\n|; foreach my $item (@main::Images) { $count++; print qq| [$count]: $item\n|; } print qq|\n|; $count=0; print qq| [OTHER FILES]:\n\n|; foreach my $item (@main::Rejected) { $count++; print qq| [$count]: $item\n|; } print qq|\n|; $count=0; print qq| [VIDEOS]: $total_videos\n|; print qq| [IMAGES]: $total_images\n|; print qq| [OTHER]: $total_rejected\n|; print qq|\n|; exit; } if ($test_exists == 1) { $pretend = 1; if ($total_videos > 1) { print qq| [$exists_label] $total_videos matches for "$search_term".\n\n|; } elsif ($total_videos == 1) { print qq| [$exists_label] File "$file_name" matches.\n\n|; } exit; } # If repeating, report itteration if ($repeat == 1) { #keep track of repetitions (after earlier reset) $repetition++; # If we've opened too many windows, bind to terminal to prevent fork bombing the system if ($fork_bomb_protect == 1) { if ($repetition > $mplayer_max) { $target_x = 0; $target_y = 0; $mplayer_output = 1; } } # Turn off listing for repititions $listing = 0; #if user requests 4, but there's only 3 vids, drop range to match total_videos if ($range_request >= $total_videos) { $range_request = $total_videos; } print qq| [INSTANCE]: $repetition of $range_request\n|; } # Determine selection # of total if ($selected == 0) { $selected = 1; } my $file_info=""; my $selected_label = $selected; if ($use_colors == 1) { $selected_label = $colortag_selection . $selected_label . $colortag_default; } $file_info .= qq| [SELECTED]: File \# $selected_label of $unique_videos\n|; # Display some useful info about the file if verbose if ($#Collections >= 1) { foreach my $item (@main::Available) { if ($selected_movie_raw_path=~/^$item/) { my $csellection = $item; if ($use_colors == 1) { #$csellection .= $colortag_category . $csellection . $colortag_default; } $file_info .= qq| [SOURCE]: $csellection\n|; } } } # Unless custom dir, list some path info as 'category' - it's up to you to organize your files though unless (($custom_dir == 1) || ($#folders == 1)) { $file_info .= qq| [$category_label]: |; if ($use_colors == 1) { $file_info .= $colortag_category; } for (my $i=0; $i < $#folders; $i++) { unless ($folders[$i] =~"^\/") { $file_info .= qq|$folders[$i]|; unless ($i == 0) { $file_info .= qq|/ |; } } } if ($use_colors == 1) { $file_info .= $colortag_default; } $file_info .= qq|\n|; } if ($verbose == 1) { # Additional date my $mtime = scalar localtime stat("$selected_movie_raw_path")->mtime; # format only useful data $mtime =~s/\ \ /\ /g; (my $day,my $month,my $date,my $time,my $year)=split(/\ /,$mtime); $mtime = "$month $date $year"; if ($use_colors == 1) { $mtime = $colortag_mtime . $mtime . $colortag_default; } $file_info .= qq| [VID DATE]: $mtime\n|; } # Color label for filename? my $filename_label = $file_name; my $orig_total = $total_videos; if ($use_colors == 1) { $filename_label = $colortag_name . $file_name . $colortag_default; $total_videos = $color_red . $total_videos . $colortag_default; } else { # Bold if no color $filename_label = $color_bold . $file_name . $colortag_default; $total_videos = $color_bold . $total_videos . $colortag_default; } $file_info .= qq| [$vid_label]: $filename_label\n\n|; # Finally print the $file_info we've formatted if (($file_info_banner == 1) || ($verbose == 1)) { unless (($#ARGV == 0)&&($listing == 1)) { print qq|$file_info|; } else { # Exit after -l is given print qq| $total_videos videos in your collection.\n\n|; exit; } } $total_videos = $orig_total; # Handle repitition requests if ($repeat == 1) { # Determine geometries for multiple window layouts, unless remaining attached unless ($mplayer_output == 1) { my $last_vid_x = $target_x; my $last_vid_y = $target_y; # Only move across if user has played previous file, or autoplay unless ($user_ok eq "n") { $target_x=$target_x + $video_width; } #$target_y= $target_y + $video_height; # Get video resolution my $mplayer_info_cmd="mplayer -vo null -ao null -frames 0 -identify $playable_path 2>/dev/null | grep ^ID_VIDEO"; my @vid_info=`$mplayer_info_cmd`; foreach my $info_line (@vid_info) { chomp $info_line; if ($info_line =~/^ID_VIDEO_WIDTH=/) { $video_width=$info_line; $video_width=~s/^ID_VIDEO_WIDTH=//; } if ($info_line =~/^ID_VIDEO_HEIGHT=/) { $video_height=$info_line; $video_height=~s/^ID_VIDEO_HEIGHT=//; } }; # Determine next geometry, and if breaching far edge, drop down to new row. my $next_x = $target_x + $video_width; my $next_y = $target_y + $video_height; if ($next_x >= $screen_res_x) { $target_x = 0; $target_y= $target_y + $video_height; } if ($next_y >= $screen_res_y) { #$target_y = 0; } if ($debug == 1) { print qq| vidX: $video_width : $target_x of $screen_res_x : $next_x ($resolution)\n|; print qq| vidY: $video_height : $target_y of $screen_res_y : $next_y ($resolution)\n|; } # Set next windows' mplayer geometry $mplayer_opts_geo = " -geometry $target_x:$target_y"; } # Return to center if we've stacked too many if ($repetition > $mplayer_max) { $mplayer_opts_geo = ""; } ## IF -n $search_request, array needs alignment elsewhere (-1), so to increment the range to +1, we must actually +2 if ($search_request) { $search_request = $search_request +2; } # If the file #1 is requested, var was = 0, so previous test fails, so we need to bump to 2 to equal back to 0 to allow file +1 else { $search_request = 2; } # if repeat range has more cycles, if ($repeat_range > 1) { # remove a cycle from requested repetitions, $repeat_range--; # and unless this is the last cycle, spin another video unless ($repetition >= $total_videos) { #sleep 1; &PlayFile("$playable_path"); &SelectFile; } } } # Under regular operation, finally open file; &PlayFile("$playable_path"); if (($verbose == 1) || ($debug == 1)) { &GetStats; } exit; } sub PlayFile { chomp(my $play_file="$_[0]"); if ($debug == 1) { print qq| PlayFile $play_file\n|; } # Form the actual mplayer command my $cmd = "$mplayer $mplayer_opts -title \"$main::file_name\""; my $silent_output="1>/dev/null 2>&1"; # Append mplayer options before command is formed if ($mplayer_opts_geo) { $cmd .= " $mplayer_opts_geo"; } if ($fullscreen == 1) { $cmd .=" -fs"; } if ($nosound == 1) { $cmd .=" -nosound"; } else { $cmd .= " $volume_mods"; } # Feed file to command $cmd = $cmd . " $play_file"; # Unless attaching, silence output unless ($mplayer_output == 1) { $cmd="$cmd $silent_output"; } # Added Ability to open an image, even if -p if ($cover == 1 && $pretend == 1) { &GetRelatedImages; if ($#main::Related_images >= 1) { &PromptUser("Open image only?"); foreach my $item (@main::Related_images) { # Possible to be called and return nothing, so only act on tangible items unless ($item =~/^$/) { &OpenImage("$item"); } } } } unless ($pretend == 1) { # Build a List if ($cover == 1) { &GetRelatedImages; } unless ($do_exec == 1) { # SAFETY; confirmation prompt before playing the video; print qq|[$warning_label]: Always wise to double-check before exec ... ('-a' to override)\n\n|; &PromptUser("Play file?"); } # Now that a user will have confirmed, open all related images if ($cover == 1) { if ($do_exec == 1) { foreach my $item (@main::Related_images) { # Possible to be called and return nothing, so only act on tangible items unless ($item =~/^$/) { &OpenImage("$item"); } } } } # NOW we can finally launch mplayer unless ($mplayer_output == 1) { if ($do_exec == 1) { system("$cmd \&"); # Unless auto = 1, reset so it prompts again unless ($autoplay == 1) { $do_exec = 0; } } } else { print qq|Mplayer output:\n--\n|; system("$cmd"); } } else { print qq| [$pretend_label]: no exec ...\n\n|; unless ($repeat == 1) { exit; } } if ($very_verbose == 1) { print qq|\n [$exec_label]: $cmd\n\n|; } return; } sub GetRelatedImages { # Find Associated Images; if ($debug == 1) { print qq| GetRelatedImages for "$main::generic_name" in $main::location\n|; } # Reset; our @Related_images=""; our $path_to_image; my $image; my $image_file=""; my $images_found=""; my $generic_name=$main::generic_name; # strip non-word characters into regex, strip "part X" for multi-part video labels $generic_name =~s/part((\s+)*)(\d+)/\.\*/ig; $generic_name =~s/(\W+)/\.\*/g; # Go through all images we found and find matches for the selected video, set them aside foreach my $item (@main::Images) { chomp $item; if ($item=~/$generic_name/g) { push (@Related_images,$item); } } # If we have some related image results, make use of them - but don't pass to OpenImage yet unless ($#Related_images == -1) { my $X; foreach my $image (@Related_images) { $X++; chomp $image; my @image_path=split(/\//,$image); $image_file=$image_path[$#image_path]; unless ($image =~/^$/) { if ($use_colors == 1) { $image_file = $colortag_imgname . $image_file . $colortag_default; } print qq| [IMAGE]: $image_file\n|; # Format image file list correct, by adding space if it's the last one if ($#Related_images < $X) { print qq|\n|; } } } } else { print qq| [NOTICE]: No images found for "$generic_name"\n\n|; } return; } sub OpenImage { # Open Associated Image(s) chomp(my $imagepath=$_[0]); $imagepath=&PrepPath("$imagepath"); my $image_command = "$image_viewer $imagepath 1>/dev/null 2>&1"; my @image_path=split(/\//,$imagepath); my $image_name=$image_path[$#image_path]; if ($debug == 1) { print qq| : "$image_command"\n\n|; } unless (($main::user_ok eq 'n') || ($imagepath =~/^$/)) { system("$image_command \&"); } return; } sub PromptUser { # Unless otherwise stated, prompt before blatantly opening a video. if ($debug == 1) { print qq| \n|; } my $prompt_msg = "$_[0]"; unless ($autoplay == 1) { print qq|$prompt_msg [$yes_label/$no_label|; if ($repeat == 1) { print $color_bold . qq|/q| . $colortag_default; } print qq|] ($yes_label)|; chomp($main::user_ok=); if ($user_ok =~/^y$/i) { $do_exec = 1; #return; } elsif ($user_ok eq "") { $do_exec = 1; #return; } elsif ($user_ok =~/^n$/i) { print qq|\n [$abort_label]: Not Launching.\n\n|; #exit; } elsif ($user_ok =~/^q$|^quit$|^exit$/) { print qq|\n [$abort_label]: Aborted.\n\n|; exit; } else { &PromptUser("$prompt_msg"); } print qq|\n|; } } sub PrepPath { # Sanitize path for command executions if ($debug == 1) { print qq| "$_[0]"\n|; } my $esc_path; chomp($esc_path=$_[0]); $esc_path=~s/\ /\\\ /g; $esc_path=~s/\&/\\&/g; $esc_path=~s/\[/\\[/g; $esc_path=~s/\]/\\]/g; $esc_path=~s/\(/\\(/g; $esc_path=~s/\)/\\)/g; $esc_path=~s/\'/\\'/g; $esc_path=~s/\!/\\!/g; $esc_path=~s/\;/\\;/g; return $esc_path; } sub GetStats { # Time this scripts execution time if (($verbose == 1)||($debug == 1)) { my $time = time - $^T; my $decimal=$time/60; my $minutes; my $fraction; my $time_report; ($minutes,$fraction)=split(/\./,$decimal); # Square off time into minutes, subtract from overall time to get additional seconds my $min_seconds=($minutes*60); my $seconds=($time-$min_seconds); # If over an hour, break down into additional minutes if ($minutes >= 60) { my $decimal2 = $minutes/60; my $hours; ($hours,$fraction)=split(/\./,$decimal2); my $hour_minutes=($hours*60); my $extramins=($minutes - $hour_minutes); $time_report = qq|$hours hours $extramins minutes $seconds seconds.\n|; } elsif ($minutes >= 1) { $time_report = qq|$minutes minutes $seconds seconds.\n|; } else { $time_report = qq|$seconds seconds.\n|; } print qq|$script_name runtime: $time_report|; } } sub Usage { if ($debug == 1) { print qq| Usage\n|; } if ($use_colors == 1) { $script_name = $color_bold . $script_name . $colortag_default; } print qq| $script_name [options] [dir]\n (v.$version)\n\n|; print qq| -h\t\tThis Help. \n|; print qq| -p\t\tPretend Only. No execution.\n|; print qq| -q\t\tNo Volume. Play Muted.\n|; print qq| -a\t\tAutoplay. Bypass confirmation prompt.\n|; print qq| -l\t\tList available videos.\n|; print qq| -o\t\tSort listing by file modification times.\n|; print qq| -n [#]\tPlay Nth numbered result. (As numbered by the "-l" option)\n|; print qq| -e [TERM]\tQuick check against collection and exit. Shorter than '-s -p'\n|; print qq| -s [TERM]\tSearch files using keywords.\n\t\t ("." = wildcard, "," = expanded search terms)\n|; print qq| -x [TERM]\tExclude a term from search results.\n|; print qq| -t [#x]\tOnly list files newer than [Xx]. +X for 'older than' X.\n\t\t x = (d/w/m/y). Ex: 5d / 2w / 1m / 1y\n|; print qq| -r [#][all]\tRepeat selection process X amount of times. default: $default_repeat\n\t\t Will increment to n+X if starting point specified with -n\n|; print qq| -i\t\tOpen any images related to selection, if found.\n|; print qq| -fs\t\tFull Screen.\n|; print qq| -m\t\tAttach MPlayer output, do not fork to background.\n|; print qq| -k\t\tKill any existing mplayer instances before starting new ones.\n|; print qq| -c\t\tToggle \$use_colors on/off.\n|; print qq| -v\t\tVerbose information about the selected file. (file date)\n|; print qq| -vv\t\tVery verbose: even more info.\n|; print qq| --debug\tInclude script execution debug info.\n|; #print qq| -z\t\tList Every file parsed from storage locations\n|; print qq|\n\n Example Usages:\n\n|; print qq| Play a random file from the locations set in \@Collections array:\n|; print qq| (\@Collections = "@Collections")\n|; print qq|\t$script_name\n\n|; print qq| List files and play a random file from another directory:\n|; print qq|\t$script_name -l ~/Downloads\n\n|; print qq| Order listing by mtime, select 3rd video from list:\n|; print qq|\t$script_name -l -o -n 3\n\n|; print qq| Open multiple videos, no prompt. If not given, \$default_repeat = $default_repeat:\n|; print qq|\t$script_name -r 3 -a\n\n|; print qq| List and play $default_repeat videos from the past month, muted, kill any already running:\n|; print qq|\t$script_name -q -l -t 1m -r -k\n\n|; print qq| Starting at first vid, sequential play all videos that match search term:\n|; print qq|\t$script_name -l -o -s "star wars" -n 1 -r all\n\n|; print qq|\n|; exit; }