#!/usr/bin/perl # 2010 Z. Cliffe Schreuders # free software: GPL v3 or later # # sorts tv shows into tvshow/series directories # if the dirs don't exist they are created # updates xbmc via the web interface # unsorted files are moved to a dir if specifed # # other contributers: # salithus - xbmc forum # schmoko - xbmc forum # # Please goto the xbmc forum to discuss SortTV: # http://forum.xbmc.org/showthread.php?t=75949 # # Get the latest version from here: # http://sourceforge.net/projects/sorttv/files/ # # Cliffe's website: # http://schreuders.org/ # # Please consider a $5 donation if you find this program helpful. use File::Copy::Recursive "dirmove", "dircopy"; use File::Copy; use File::Glob ':glob'; use LWP::Simple; use File::Spec::Functions "rel2abs"; use File::Basename; use TVDB::API; use File::Find; use FileHandle; use warnings; use strict; my ($sortdir, $tvdir, $nonepisodedir, $xbmcwebserver, $matchtype); my ($showname, $series, $episode, $pureshowname) = ""; my ($newshows, $new, $log); my $REDO_FILE = my $moveseasons = "TRUE"; my $usedots = my $rename = my $logfile = my $verbose = my $seasondoubledigit = my $removesymlinks = my $needshowexist = my $windowsnames = 0; my $seasontitle = "Season "; my $sortby = "MOVE"; my $renameformat = "[SHOW_NAME] - [EP1][EP_NAME1]"; my $treatdir = "RECURSIVELY_SORT_CONTENTS"; my $fetchimages = "NEW_SHOWS"; my $imagesformat = "POSTER"; my @showrenames; my $scriptpath = dirname(rel2abs($0)); my $tvdblanguage = "en"; print "Got torough the declirations"; out("std", "SortTV\n", "~" x 6,"\n"); get_config_from_file("$scriptpath/sorttv.conf"); process_args(@ARGV); if(!defined($sortdir) || !defined($tvdir)) { out("warn", "Incorrect usage or configuration (missing sort or sort-to directories)\n"); out("warn", "run 'perl sorttv.pl --help' for more information about how to use SortTV"); exit; } print "Got torough the first if"; my $TVDBAPIKEY = "FDDBDB916D936956"; my $tvdb = TVDB::API::new($TVDBAPIKEY); # if uses thetvdb, set it up if($renameformat =~ /\[EP_NAME\d]/i || $fetchimages ne "FALSE") { $tvdb->setLang($tvdblanguage); my $hashref = $tvdb->getAvailableMirrors(); $tvdb->setMirrors($hashref); $tvdb->chooseMirrors(); unless (-e "$scriptpath/.cache" || mkdir "$scriptpath/.cache") { out("warn", "WARN: Could not create cache dir: $scriptpath/cache $!\n"); exit; } $tvdb->setCacheDB("$scriptpath/.cache/.tvdb.db"); $tvdb->setUserAgent("SortTV"); $tvdb->setBannerPath("$scriptpath/.cache/"); } print "Got torough the second if"; $log = FileHandle->new("$logfile", "a") or out("warn", "WARN: Could not open log file $logfile: $!\n") if $logfile; display_info(); sort_directory($sortdir); if($xbmcwebserver && $newshows) { sleep(4); get "http://$xbmcwebserver/xbmcCmds/xbmcHttp?command=ExecBuiltIn(Notification(,NEW EPISODES NOW AVAILABLE TO WATCH\n$newshows, 7000))"; } $log->close if(defined $log); exit; print "Got torough the third if"; sub sort_directory { my ($sortd) = @_; FILE: foreach my $file (bsd_glob($sortd.'*')) { $showname = ""; # Regex for tv show season directory if(-l $file) { if($removesymlinks eq "TRUE") { out("std", "DELETE: Removing symlink: $file\n"); unlink($file) or out("warn", "WARN: Could not delete symlink $file: $!\n"); } # otherwise file is a symlink, ignore } elsif(-d $file && $treatdir eq "IGNORE") { # ignore directories } elsif(-d $file && $treatdir eq "RECURSIVELY_SORT_CONTENTS") { sort_directory("$file/"); # removes any empty directories from the to-sort directory and sub-directories finddepth(sub{rmdir},"$sortd"); } elsif(-d $file && $file =~ /.*\/(.*)(?:Season|Series|$seasontitle)\D?0*(\d+).*/i && $1) { $pureshowname = $1; if($seasondoubledigit eq "TRUE") { $series = sprintf("%02d", $2); } else { $series = $2; } $showname = fixtitle($pureshowname); if(move_series($pureshowname, $showname, $series, $file) eq $REDO_FILE) { redo FILE; } # Regex for tv show episode: S01E01 or 1x1 or 1 x 1 etc } elsif(filename($file) =~ /(.*)(?:\.|\s)[Ss]0*(\d+)\s*[Ee]0*(\d+).*/ || filename($file) =~ /(.*)(?:\.|\s)0*(\d+)\s*[xX]\s*0*(\d+).*/ || ($matchtype eq "LIBERAL" && filename($file) =~ /(.*)(?:\.|\s)0*(\d+)\D*0*(\d+).*/)) { $pureshowname = $1; $showname = fixtitle($pureshowname); if($seasondoubledigit eq "TRUE") { $series = sprintf("%02d", $2); } else { $series = $2; } $episode = $3; if($showname ne "") { if(move_episode($pureshowname, $showname, $series, $episode, $file) eq $REDO_FILE) { redo FILE; } } } elsif(defined $nonepisodedir) { my $newname = $file; $newname =~ s/$sortdir//; $newname = escape_myfilename($newname); out("std", "MOVING NON-EPISODE: $file to $nonepisodedir$newname\n"); if(-d $file) { dirmove($file, $nonepisodedir . $newname) or out("warn", "WARN: File $file cannot be copied to $nonepisodedir. : $!"); } else { move($file, $nonepisodedir . $newname) or out("warn", "WARN: File $file cannot be copied to $nonepisodedir. : $!"); } } } } print "Got torough the main sort"; sub process_args { foreach my $arg (@_) { if($arg =~ /^--non-episode-dir:(.*)/ || $arg =~ /^-ne:(.*)/) { if(-e $1) { $nonepisodedir = $1; # append a trailing / if it's not there $nonepisodedir .= '/' if($nonepisodedir !~ /\/$/); } else { out("warn", "WARN: Non-episode directory does not exist ($1)\n"); } } elsif($arg =~ /^--xbmc-web-server:(.*)/ || $arg =~ /^-xs:(.*)/) { $xbmcwebserver = $1; } elsif($arg =~ /^--match-type:(.*)/ || $arg =~ /^-mt:(.*)/) { $matchtype = $1; } elsif($arg =~ /^--treat-directories:(.*)/ || $arg =~ /^-td:(.*)/) { $treatdir = $1; } elsif($arg =~ /^--show-name-substitute:(.*-->.*)/ || $arg =~ /^-sub:(.*-->.*)/) { push @showrenames, $1; } elsif($arg =~ /^--log-file:(.*)/ || $arg =~ /^-o:(.*)/) { $logfile = $1; } elsif($arg =~ /^--rename-episodes:(.*)/ || $arg =~ /^-rn:(.*)/) { $rename = $1; } elsif($arg =~ /^--lookup-language:(.*)/ || $arg =~ /^-lang:(.*)/) { $tvdblanguage = $1; } elsif($arg =~ /^--fetch-images:(.*)/ || $arg =~ /^-fi:(.*)/) { $fetchimages = $1; } elsif($arg =~ /^--images-format:(.*)/ || $arg =~ /^-if:(.*)/) { $imagesformat = $1; } elsif($arg =~ /^--require-show-directories-already-exist:(.*)/ || $arg =~ /^-rs:(.*)/) { $needshowexist = $1; } elsif($arg =~ /^--force-windows-compatible-filenames:(.*)/ || $arg =~ /^-fw:(.*)/) { $windowsnames = $1; } elsif($arg =~ /^--rename-format:(.*)/ || $arg =~ /^-rf:(.*)/) { $renameformat = $1; } elsif($arg =~ /^--remove-symlinks:(.*)/ || $arg =~ /^-rs:(.*)/) { $removesymlinks = $1; } elsif($arg =~ /^--use-dots-instead-of-spaces:(.*)/ || $arg =~ /^-dots:(.*)/) { $usedots = $1; } elsif($arg =~ /^--season-title:(.*)/ || $arg =~ /^-st:(.*)/) { $seasontitle = $1; } elsif($arg =~ /^--sort-by:(.*)/ || $arg =~ /^-by:(.*)/) { $sortby = $1; } elsif($arg =~ /^--season-double-digits:(.*)/ || $arg =~ /^-sd:(.*)/) { $seasondoubledigit = $1; } elsif($arg =~ /^--verbose:(.*)/ || $arg =~ /^-v:(.*)/) { $verbose = $1; } elsif($arg =~ /^--read-config-file:(.*)/ || $arg =~ /^-conf:(.*)/) { get_config_from_file($1); } elsif($arg =~ /^--directory-to-sort:(.*)/ || $arg =~ /^-sort:(.*)/) { if(-e $1) { $sortdir = $1; # append a trailing / if it's not there $sortdir .= '/' if($sortdir !~ /\/$/); } else { out("warn", "WARN: Directory to sort does not exist ($1)\n"); } } elsif($arg =~ /^--directory-to-sort-into:(.*)/ || $arg =~ /^-sortto:(.*)/) { if(-e $1) { $tvdir = $1; # append a trailing / if it's not there $tvdir .= '/' if($tvdir !~ /\/$/); } else { out("warn", "WARN: Directory to sort into does not exist ($1)\n"); } } elsif($arg eq "--help" || $arg eq "-h") { showhelp(); } elsif(!defined($sortdir)) { if(-e $arg) { $sortdir = $arg; # append a trailing / if it's not there $sortdir .= '/' if($sortdir !~ /\/$/); } else { out("warn", "WARN: Directory to sort does not exist ($arg)\n"); } } elsif(!defined($tvdir)) { if(-e $arg) { $tvdir = $arg; # append a trailing / if it's not there $tvdir .= '/' if($tvdir !~ /\/$/); } else { out("warn", "Directory to sort into does not exist ($arg)\n"); } } else { out("warn", "WARN: Incorrect usage (invalid option): $arg\n"); out("warn", "INFO: run 'perl sorttv.pl --help' for more information about how to use SortTV"); } } } print "Got torough the aug"; sub get_config_from_file { my ($filename) = @_; my @arraytoconvert; if(open (IN, $filename)) { out("verbose", "INFO: Reading configuration settings from '$filename'\n"); while(my $in = ) { chomp($in); if($in =~ /^\s*#/ || $in =~ /^\s*$/) { # ignores comments and whitespace } elsif($in =~ /(.+):(.+)/) { process_args("--$1:$2"); } else { out("warn", "WARN: this line does not match expected format: '$in'\n"); } } close (IN); } else { out("warn", "WARN: Couldn't open config file '$filename': $!\n"); out("warn", "INFO: An example config file is available and can make using SortTV easier\n"); } } print "Got torough the config file"; sub showhelp { my $heredoc = < "Season 1", "Series "->"Series 1", "Season."->"Season.1") If not specified, "Season " --season-double-digits:[TRUE|FALSE] Season format padded to double digits (eg "Season 01" rather than "Season 1") If not specified, FALSE --match-type:[NORMAL|LIBERAL] Match type. LIBERAL assumes all files are episodes and tries to extract season and episode number any way possible. If not specified, NORMAL --sort-by:[MOVE|COPY|MOVE-AND-LEAVE-SYMLINK-BEHIND|LEAVE-AND-PLACE-SYMLINK] Sort by moving or copying the file. If the file already exists because it was already copied it is silently skipped. The MOVE-AND-LEAVE-SYMLINK-BEHIND option may be handy if you want to continue to seed after sorting, this leaves a symlink in place of the newly moved file. PLACE-SYMLINK does not move the original file, but places a symlink in the sort-to directory (probably not what you want) If not specified, MOVE --treat-directories:[AS_FILES_TO_SORT|RECURSIVELY_SORT_CONTENTS|IGNORE] How to treat directories. AS_FILES_TO_SORT - sorts directories, moving entire directories that represents an episode, also detects and moves directories of entire seasons RECURSIVELY_SORT_CONTENTS - doesn't move directories, just their contents, including subdirectories IGNORE - ignores directories If not specified, RECURSIVELY_SORT_CONTENTS --require-show-directories-already-exist:[TRUE|FALSE] Only sort into show directories that already exist This may be helpful if you have multiple destination directories. Just set up all the other details in the conf file, and specify the destination directory when invoking the script. Only episodes that match existing directories in the destination will be moved. If this is false, then new directories are created for shows that dont have a directory. If not specified, FALSE --remove-symlinks:[TRUE|FALSE] Deletes symlinks from the directory to sort while sorting. This may be helpful if you want to remove all the symlinks you previously left behind using --sort-by:MOVE-AND-LEAVE-SYMLINK-BEHIND You could schedule "perl sorttv.pl --remove-symlinks:TRUE" to remove these once a week/month If this option is enabled and used at the same time as --sort-by:MOVE-AND-LEAVE-SYMLINK-BEHIND, then only the previous links will be removed, and new ones may also be created If not specified, FALSE --show-name-substitute:NAME1-->NAME2 Substitutes files equal to NAME1 for NAME2 This argument can be repeated to add multiple rules for substitution --force-windows-compatible-filenames:[TRUE|FALSE] Forces MSWindows compatible file names, even when run on other platforms such as Linux This may be helpful if you are writing to a Windows share from a Linux system If not specified, FALSE --lookup-language:[en|...] Set language for thetvdb lookups, this effects episode titles etc Valid values include: it, zh, es, hu, nl, pl, sl, da, de, el, he, sv, eng, fi, no, fr, ru, cs, en, ja, hr, tr, ko, pt If not specified, en (English) --fetch-images:[NEW_SHOWS|FALSE] Download images for shows, seasons, and episodes from thetvdb Downloaded images are copied into the sort-to (destination) directory. NEW_SHOWS - When new shows, seasons, or episodes are created the associated images are downloaded FALSE - No images are downloaded if not specified, NEW_SHOWS --images-format:POSTER Sets the image format to use, poster or banner. POSTER/BANNER if not specified, POSTER EXAMPLES: Does a sort, as configured in sorttv.conf: perl sorttv.pl The directory-to-sort and directory-to-sort-to can be supplied directly: To sort a Downloads directory contents into a TV directory perl sorttv.pl /home/me/Downloads /home/me/Videos/TV Alternatively: perl sorttv.pl --directory-to-sort:/home/me/Downloads --directory-to-sort-into:/home/me/Videos/TV To move non-episode files in a separate directory: perl sorttv.pl --directory-to-sort:/home/me/Downloads --directory-to-sort-into:/home/me/Videos/TV --non-episode-dir:/home/me/Videos/Non-episodes To integrate with xbmc (notification and automatic library update): perl sorttv.pl --directory-to-sort:/home/me/Downloads --directory-to-sort-into:/home/me/Videos/TV --xbmc-webserver:localhost:8080 And so on... FURTHER INFORMATION: Please goto the xbmc forum to discuss SortTV: http://forum.xbmc.org/showthread.php?t=75949 Get the latest version from here: http://sourceforge.net/projects/sorttv/files/ Cliffe's website: http://schreuders.org/ Please consider a \$5 paypal donation if you find this program helpful. END out("std", $heredoc); exit; } sub displayandupdateinfo { my ($show, $xbmcwebserver) = @_; # update xbmc video library get "http://$xbmcwebserver/xbmcCmds/xbmcHttp?command=ExecBuiltIn(updatelibrary(video))"; # pop up a notification on xbmc # xbmc.executebuiltin('XBMC.Notification(New content found, ' + filename + ', 2000)') get "http://$xbmcwebserver/xbmcCmds/xbmcHttp?command=ExecBuiltIn(Notification(NEW EPISODE, $show, 7000))"; } # replaces ".", "_" and removes "the" and "," # removes numbers and spaces # removes the dir path sub fixtitle { my ($title) = @_; $title = substitute_name($title); $title =~ s/,|\.the\.|\bthe\b//ig; $title =~ s/\.and\.|\band\b//ig; $title =~ s/&|\+//ig; $title =~ s/(.*\/)(.*)/$2/; $title = remdot($title); $title =~ s/\d|\s|\(|\)//ig; return $title; } # substitutes show names as configured sub substitute_name { my ($from) = @_; foreach my $substitute (@showrenames) { if($substitute =~ /(.*)-->(.*)/) { if($from eq $1) { return $2; } } } # if no matches, returns unchanged return $from; } # removes dots and underscores for creating dirs sub remdot { my ($title) = @_; $title =~ s/\./ /ig; $title =~ s/_/ /ig; $title =~ s/-//ig; $title =~ s/'//ig; # don't start or end on whitespace $title =~ s/\s$//ig; $title =~ s/^\s//ig; return $title; } # removes path sub filename { my ($title) = @_; $title =~ s/(.*\/)(.*)/$2/; return $title; } sub escape_myfilename { my ($name) = @_; if($^O =~ /MSWin/ || $windowsnames eq "TRUE") { $name =~ s/[\\\/:*?\"<>|]/-/g; } else { $name =~ s/[\\\/\"<>|]/-/g; } return $name; } sub display_info { my ($second, $minute, $hour, $dayofmonth, $month, $yearoffset) = localtime(); my $year = 1900 + $yearoffset; my $thetime = "$hour:$minute:$second, $dayofmonth-$month-$year"; out("std", "$thetime\n"); out("std", "Sorting $sortdir into $tvdir\n"); } sub move_episode { my ($pureshowname, $showname, $series, $episode, $file) = @_; out("verbose", "INFO: trying to move $pureshowname season $series episode $episode\n"); SHOW: foreach my $show (bsd_glob($tvdir.'*')) { my $subshowname = fixtitle(escape_myfilename(substitute_name($showname))); if(fixtitle($show) =~ /^\Q$showname\E$/i || fixtitle(escape_myfilename(substitute_name(filename($show)))) =~ /^\Q$subshowname\E$/i) { out("verbose", "INFO: found a matching show:\n\t$show\n"); my $s = $show.'/*'; my @g=bsd_glob($show); foreach my $season (bsd_glob($show.'/*')) { if(-d $season.'/' && $season =~ /(?:Season|Series|$seasontitle)?\s?0*(\d+)$/i && $1 == $series) { out("verbose", "INFO: found a matching season:\n\t$season\n"); move_an_ep($file, $season, $show, $series, $episode); if($xbmcwebserver) { $new = "$showname season $series episode $episode"; displayandupdateinfo($new, $xbmcwebserver); $newshows .= "$new\n"; } # next FILE; return 0; } } # didn't find a matching season, make DIR out("std", "INFO: making season directory: $show/$seasontitle$series\n"); my $newpath = "$show/$seasontitle$series"; if(mkdir($newpath, 0777)) { fetchseasonimages(substitute_name(remdot($pureshowname)), $show, $series, $newpath) if $fetchimages ne "FALSE"; redo SHOW; # try again now that the dir exists } else { out("warn", "WARN: Could not create season dir: $!\n"); # next FILE; return 0; } } } if($needshowexist ne "TRUE") { # if we are here then we couldn't find a matching show, make DIR my $newshowdir = $tvdir . escape_myfilename(substitute_name(remdot($pureshowname))); out("std", "INFO: making show directory: $newshowdir\n"); if(mkdir($newshowdir, 0777)) { fetchshowimages(substitute_name(remdot($pureshowname)), $newshowdir) if $fetchimages ne "FALSE"; # try again now that the dir exists # redo FILE; return $REDO_FILE; } else { out("warn", "WARN: Could not create show dir: $newshowdir:$!\n"); # next FILE; return 0; } } else { out("verbose", "SKIP: Show directory does not exist: " . $tvdir . escape_myfilename(substitute_name(remdot($pureshowname)))."\n"); # next FILE; return 0; } } sub fetchshowimages { my ($fetchname, $newshowdir) = @_; out("std", "DOWNLOAD: downloading images for $fetchname\n"); my $banner = $tvdb->getSeriesBanner($fetchname); my $fanart = $tvdb->getSeriesFanart($fetchname); my $poster = $tvdb->getSeriesPoster($fetchname); copy ("$scriptpath/.cache/$fanart", "$newshowdir/fanart.jpg") if $fanart && -e "$scriptpath/.cache/$fanart"; copy ("$scriptpath/.cache/$banner", "$newshowdir/banner.jpg") if $banner && -e "$scriptpath/.cache/$banner"; copy ("$scriptpath/.cache/$poster", "$newshowdir/poster.jpg") if $poster && -e "$scriptpath/.cache/$poster"; my $ok = eval{symlink "$newshowdir/poster.jpg", "$newshowdir/folder.jpg" if $poster && -e "$scriptpath/.cache/$poster" && $imagesformat eq "POSTER";}; if(!defined $ok) {copy "$newshowdir/poster.jpg", "$newshowdir/folder.jpg" if $poster && -e "$scriptpath/.cache/$poster" && $imagesformat eq "POSTER";}; $ok = eval{symlink "$newshowdir/banner.jpg", "$newshowdir/folder.jpg" if $banner && -e "$scriptpath/.cache/$banner" && $imagesformat eq "BANNER";}; if(!defined $ok) {copy "$newshowdir/banner.jpg", "$newshowdir/folder.jpg" if $banner && -e "$scriptpath/.cache/$banner" && $imagesformat eq "BANNER";}; } sub fetchseasonimages { my ($fetchname, $newshowdir, $season, $seasondir) = @_; out("std", "DOWNLOAD: downloading season image for $fetchname\n"); my $banner = $tvdb->getSeasonBanner($fetchname, $season); my $bannerwide = $tvdb->getSeasonBannerWide($fetchname, $season); my $snum = sprintf("%02d", $season); copy ("$scriptpath/.cache/$banner", "$newshowdir/season${snum}.jpg") if $banner && -e "$scriptpath/.cache/$banner" && $imagesformat eq "POSTER"; copy ("$scriptpath/.cache/$bannerwide", "$newshowdir/season${snum}.jpg") if $bannerwide && -e "$scriptpath/.cache/$bannerwide" && $imagesformat eq "BANNER"; my $ok = eval{symlink "$newshowdir/season$snum.jpg", "$seasondir/folder.jpg" if -e "$newshowdir/season$snum.jpg";}; if(!defined $ok) {copy "$newshowdir/season$snum.jpg", "$seasondir/folder.jpg" if -e "$newshowdir/season$snum.jpg";}; } sub fetchepisodeimage { my ($fetchname, $newshowdir, $season, $seasondir, $episode, $newfilename) = @_; # if the episode was moved (or already existed) if(-e "$seasondir/$newfilename") { my $epimage = $tvdb->getEpisodeBanner($fetchname, $season, $episode); my $newimagepath = "$seasondir/$newfilename"; $newimagepath =~ s/(.*)(\..*)/$1.tbn/; copy ("$scriptpath/.cache/$epimage", $newimagepath) if $epimage && -e "$scriptpath/.cache/$epimage"; } } sub move_an_ep { my($file, $season, $show, $series, $episode) = @_; my $newfilename = filename($file); my $newpath; if($rename eq "TRUE") { my $ext = my $eptitle = ""; unless(-d $file) { $ext = $file; $ext =~ s/(.*\.)(.*)/\.$2/; } if($renameformat =~ /\[EP_NAME(\d)]/i) { out("verbose", "INFO: Fetching episode title for ", substitute_name(remdot($pureshowname)), " Season $series Episode $episode.\n"); my $name = $tvdb->getEpisodeName(substitute_name(remdot($pureshowname)), $series, $episode); if($name) { $eptitle = " - $name" if $1 == 1; $eptitle = ".$name" if $1 == 2; } else { out("warn", "WARN: Could not get episode title for ", substitute_name(remdot($pureshowname)), " Season $series Episode $episode.\n"); } } my $sname = substitute_name(remdot($pureshowname)); my $ep1 = sprintf("S%02dE%02d", $series, $episode); my $ep2 = sprintf("%dx%d", $series, $episode); my $ep3 = sprintf("%dx%02d", $series, $episode); # create the new file name $newfilename = $renameformat; $newfilename =~ s/\[SHOW_NAME]/$sname/ig; $newfilename =~ s/\[EP1]/$ep1/ig; $newfilename =~ s/\[EP2]/$ep2/ig; $newfilename =~ s/\[EP3]/$ep3/ig; $newfilename =~ s/\[EP_NAME\d]/$eptitle/ig; $newfilename .= $ext; # make sure it is filesystem friendly: $newfilename = escape_myfilename($newfilename, "-"); } if($usedots eq "TRUE") { $newfilename =~ s/\s/./ig; } $newpath = $season . '/' . $newfilename; if(-e $newpath) { if(filename($file) =~ /repack|proper/i) { out("warn", "OVERWRITE: Repack/proper version.\n"); } else { out("warn", "SKIP: File $newpath already exists, skipping.\n") unless($sortby eq "COPY" || $sortby eq "PLACE-SYMLINK"); return; } } out("std", "$sortby: sorting $file to ", $newpath, "\n"); if($sortby eq "MOVE" || $sortby eq "MOVE-AND-LEAVE-SYMLINK-BEHIND") { if(-d $file) { dirmove($file, $newpath) or out("warn", "File $show cannot be moved to $season. : $!"); } else { move($file, $newpath) or out("warn", "File $show cannot be moved to $season. : $!"); } } elsif($sortby eq "COPY") { if(-d $file) { dircopy($file, $newpath) or out("warn", "File $show cannot be copied to $season. : $!"); } else { copy($file, $newpath) or out("warn", "File $show cannot be copied to $season. : $!"); } } elsif($sortby eq "PLACE-SYMLINK") { symlink($file, $newpath) or out("warn", "File $file cannot be symlinked to $newpath. : $!"); } # have moved now link if($sortby eq "MOVE-AND-LEAVE-SYMLINK-BEHIND") { symlink($newpath, $file) or out("warn", "File $newpath cannot be symlinked to $file. : $!"); } fetchepisodeimage(substitute_name(remdot($pureshowname)), $show, $series, $season, $episode, $newfilename) if $fetchimages ne "FALSE"; } sub move_a_season { my($file, $show, $series) = @_; my $newpath = $show."/".escape_myfilename("$seasontitle$series", "-"); if(-e $newpath) { out("warn", "SKIP: File $newpath already exists, skipping.\n") unless($sortby eq "COPY" || $sortby eq "PLACE-SYMLINK"); return; } print "$sortby SEASON: $file to $newpath\n"; out("verbose", "$sortby: sorting directory to: $newpath\n"); if($sortby eq "MOVE" || $sortby eq "MOVE-AND-LEAVE-SYMLINK-BEHIND") { dirmove($file, "$newpath") or out("warn", "$show cannot be moved to $show/$seasontitle$series: $!"); } elsif($sortby eq "COPY") { dircopy($file, "$newpath") or out("warn", "$show cannot be copied to $show/$seasontitle$series: $!"); } elsif($sortby eq "PLACE-SYMLINK") { symlink($file, $newpath) or out("warn", "File $file cannot be symlinked to $newpath. : $!"); } if($sortby eq "MOVE-AND-LEAVE-SYMLINK-BEHIND") { symlink($newpath, $file) or out("warn", "File $newpath cannot be symlinked to $file. : $!"); } } # move a new Season x directory sub move_series { my ($pureshowname, $showname, $series, $file) = @_; out("verbose", "INFO: trying to move $pureshowname season $series directory\n"); SHOW: foreach my $show (bsd_glob($tvdir.'*')) { if(fixtitle($show) =~ /^\Q$showname\E$/i) { out("verbose", "INFO: found a matching show:\n\t$show\n"); my $s = $show.'/*'; my @g=bsd_glob($show); foreach my $season (bsd_glob($show.'/*')) { if(-d $season.'/' && $season =~ /(?:Season|Series|$seasontitle)?\s?0*(\d)$/i && $1 == $series) { out("warn", "SKIP: Cannot move season directory: found a matching season already existing:\n\t$season\n"); return 0; } } # didn't find a matching season, move DIR move_a_season($file, $show, $series); if($xbmcwebserver) { $new = "$showname Season $series directory"; displayandupdateinfo($new, $xbmcwebserver); $newshows .= "$new\n"; } return 0; } } if($needshowexist ne "TRUE") { # if we are here then we couldn't find a matching show, make DIR my $newshowdir = $tvdir . escape_myfilename(substitute_name(remdot($pureshowname))); out("std", "INFO: making show directory: $newshowdir\n"); if(mkdir($newshowdir, 0777)) { fetchshowimages(substitute_name(remdot($pureshowname)), $newshowdir) if $fetchimages ne "FALSE"; # try again now that the dir exists # redo FILE; return $REDO_FILE; } else { out("warn", "WARN: Could not create show dir: $newshowdir:$!\n"); # next FILE; return 0; } } else { out("verbose", "SKIP: Show directory does not exist: " . $tvdir . escape_myfilename(substitute_name(remdot($pureshowname)))."\n"); # next FILE; return 0; } } sub out { my ($type, @msg) = @_; if($type eq "verbose") { return if $verbose ne "TRUE"; print @msg; print $log @msg if(defined $log); }elsif($type eq "std") { print @msg; print $log @msg if(defined $log); } elsif($type eq "warn") { warn @msg; print $log @msg if(defined $log); } }