Gfy

pyTVMove 0.4

Gfy
Aug 9th, 2011
179
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2. '''
  3. Show the possible parameters and usage:
  4.    python TVMove.py --help
  5.  
  6. Created on 25-jul-2009
  7.  
  8. Tested on Windows with cygwin.
  9.  
  10. What does it do? It changes this:
  11.    .
  12.    |-- 24.S07E01.DVDRip.XviD-TOPAZ
  13.    |-- 24.S07E02.DVDRip.XviD-TOPAZ
  14.    |-- 24.S07E03.DVDRip.XviD-TOPAZ
  15.    |-- tpz-24701-sample.avi
  16.    |-- tpz-24701.nfo
  17.    |-- tpz-24702-sample.avi
  18.    |-- tpz-24702.nfo
  19.    |-- tpz-24703-sample.avi
  20.    `-- tpz-24703.nfo
  21.  
  22. to this:
  23.    .
  24.    |-- 24.S07E01.DVDRip.XviD-TOPAZ
  25.    |   |-- Sample
  26.    |   |   `-- tpz-24701-sample.avi
  27.    |   `-- tpz-24701.nfo
  28.    |-- 24.S07E02.DVDRip.XviD-TOPAZ
  29.    |   |-- Sample
  30.    |   |   `-- tpz-24702-sample.avi
  31.    |   `-- tpz-24702.nfo
  32.    `-- 24.S07E03.DVDRip.XviD-TOPAZ
  33.        |-- Sample
  34.        |   `-- tpz-24703-sample.avi
  35.        `-- tpz-24703.nfo
  36.  
  37. This program is useful for:
  38.  - moving srt subtitles into their corresponding directory.
  39.  - moving downloaded television series to their correct scene folder.
  40.    (if your usenet client doesn't do this already for you, like Pan)
  41.    You must create the folders separately. e.g. by a batch script:
  42.       mkdir folder_name1
  43.       mkdir ...
  44.    Save it as make_dirs.bat.
  45.    Execute it: ./make_dirs.bat and you have your dirs.
  46.    
  47.    Do it fast with vim, based on nzb/par2 file names:
  48.        ls -1 *.nzb > mkdir.bat
  49.        vim mkdir
  50.            q a i mkdir <ESC> j 0 q    record a macro
  51.            10@a                       execute it 10 times
  52.            :%s/.nzb//g                remove the extensions
  53.            :wq                        save and quit
  54.    
  55.  - learning Python. This is my very first Python program!
  56.  
  57. Example usage: ./TVMove.py -s .
  58. If you get     bash: ./TVMove.py: Permission denied
  59. do             chmod u+x TVMove.py
  60.  
  61. Place this in your bin dir to use your own name and options:
  62.    #!/bin/bash
  63.    exec python /home/you/bin/TVMove.py -s . $@
  64. Name the file 'tvmove' and you can use 'tvmove' like it would be any other command.
  65. Make it executable:
  66.    chmod u+x tvmove
  67.  
  68. To create your own bin dir for executable files:
  69. * Create the dir:
  70.    mkdir ~/bin
  71. * To add a directory to your PATH, add
  72.    #my own executable files
  73.    PATH=$PATH:$HOME/bin
  74.  to your .bashrc file
  75.  
  76. http://motechnet.com/~gfy/tvmove/
  77.  
  78. 0.4: extra's, files like fs1001uc.xvid-siso.nfo work (2009-10-15)
  79.     wat-himym-s02e01-sample.avi gets recognized now
  80. 0.3: support for subpacks (2009-08-21)
  81. 0.2: refactorings, additional comments (2009-07-27)
  82. 0.1: initial release (2009-07-26)
  83.  
  84. @author: Gfy
  85. '''
  86.  
  87. import os, sys, glob, re, optparse
  88.  
  89. __VERSION__ = "0.4"
  90.  
  91. #a dictionary with key/value pairs
  92. folders = {}
  93.  
  94. def send_error(message):
  95.     print "%s Exiting..." % message
  96.     sys.exit(1)
  97.  
  98. #adds a release directory to the folders variable
  99. def append_folder(key,value):
  100.     global folders
  101.     folders[key] = value
  102.    
  103. def get_key(name):
  104.     '''
  105.    This function tries to generate a unique identifying key for a given
  106.    releasename or filename.
  107.    '''
  108.    
  109.     # for folders
  110.     patternRegular = ".*[\.-]?[sS]([0-9]?[0-9])[\.]?[eE]([0-9]{1,3})[\.-]?[eE]?([0-9]{2,3})?.*"
  111.     patternFoV = ".*\.?([0-9]{1,2})[x]([0-9]{2,3})([\._-]([0-9]{1,2}[x])?)?([0-9]{2,3})?[\._]?.*"
  112.     patternRETRO = ".*\.[eE][pP]([0-9][0-9]).*"
  113.    
  114.     # for files like:
  115.     #     tpz-24724.nfo 24.S07E24.PREAIR.DVDRip.XviD-TOPAZ
  116.     #     fs1001uc.xvid-siso.nfo Friends.S10E01.UNCUT.DVDRip.XviD-SiSO
  117.     #     leverage.113.dvdrip.xvid-saints.nfo
  118.     patternFile = ".*(?:(?:[0-9][0-9])([0-9]+)|[^0-9]([0-9]{1,2}))([0-9]{2})\.?.*"
  119.    
  120.     # test if it's a release folder
  121.     regular_match = re.match(patternRegular, name)
  122.     fov_match = re.match(patternFoV, name)
  123.     retro_match = re.match(patternRETRO, name)
  124.    
  125.     # test for the TOPAZ file format
  126.     file_match = re.match(patternFile, name)
  127.    
  128.     subpack_match = re.search("subpack", name, re.IGNORECASE)
  129.     extras_match = re.search("extras", name, re.IGNORECASE)
  130.     match = False
  131.    
  132.     if regular_match:
  133.         match = True
  134.         season = int(regular_match.group(1))
  135.         episode = int(regular_match.group(2))
  136.     elif fov_match:
  137.         match = True
  138.         season = int(fov_match.group(1))
  139.         episode = int(fov_match.group(2))
  140.     elif file_match:
  141.         match = True
  142.         if file_match.group(1) == None:
  143.             season = int(file_match.group(2))
  144.         else:
  145.             season = int(file_match.group(1))
  146.         episode = int(file_match.group(3))
  147.     elif not match and retro_match:
  148.         match = True
  149.         season = 001
  150.         episode = int(file_match.group(1))
  151.     elif not match and subpack_match:
  152.         match = True
  153.         season = 999
  154.         episode = 999
  155.     elif not match and extras_match:
  156.         match = True
  157.         season = 999
  158.         episode = 998
  159.        
  160.     if match:
  161.         # construct a key: ssseee
  162.         return '%(season)03d%(episode)03d' % {'season': season, "episode": episode}
  163.    
  164.     return None
  165.  
  166. def check_new_folder(foldername):
  167.     key = get_key(foldername)
  168.     if key is not None:
  169.         if options.verbose > 1:
  170.             print "Release folder found: " + str(foldername.strip(os.sep))
  171.             print "    Season: " + str(int(key[:3]))    # The first three characters
  172.             print "    Episode: " + str(int(key[3:]))   # All but the first three characters
  173.             print "    Generated key: " + key
  174.            
  175.         #add to the list of folders
  176.         append_folder(key, foldername)
  177.  
  178. def check_for_subdir(file):
  179.     extra_subdir = ""
  180.    
  181.     # does it needs to be moved to a Sample/Subs dir?
  182.     if options.samples_subs:
  183.         if re.search("sample", file, re.IGNORECASE):
  184.             extra_subdir = "Sample"
  185.         elif re.search("subs|vobsub", file, re.IGNORECASE):
  186.             extra_subdir = "Subs"
  187.        
  188.         if options.verbose > 1 and extra_subdir != "":
  189.             print "Will be moved to additional subdir %s." % extra_subdir
  190.    
  191.     return extra_subdir
  192.    
  193. def move_file(move_to_folder, file):
  194.     extra_subdir = check_for_subdir(file)
  195.    
  196.     if options.verbose > 0:
  197.         print "Moving... %(file)s to\n          %(dir)s" % \
  198.             {'file': os.path.basename(file),
  199.              'dir': move_to_folder + extra_subdir}
  200.    
  201.     if not options.dry_run:
  202.         if options.verbose > 1:
  203.             print "Actually moving the file."
  204.        
  205.         try:
  206.             move_to_folder = os.path.realpath(move_to_folder + os.sep +
  207.                                               extra_subdir + os.sep + file)
  208.             file = os.path.realpath(file)
  209.             os.renames(file, move_to_folder)
  210.         except (IOError, os.error), err:
  211.             send_error(err)
  212.    
  213. def main(options, args):
  214.     count_directories = 0
  215.     count_files = 0
  216.    
  217.     # iterate the directories to process
  218.     for path in args:
  219.         try:
  220.             os.chdir(path)
  221.         except:
  222.             send_error("Can't enter %s." % path)
  223.            
  224.         if options.verbose > 0:
  225.             print "### Processing %s ###" % os.path.realpath(os.path.basename(path))
  226.            
  227.         # clean dictionary
  228.         global folders
  229.         folders = {}
  230.        
  231.         # iterate through all the folders in given directory
  232.         for folder in glob.glob("*" + os.sep):
  233.             # process the folder
  234.             check_new_folder(folder)
  235.         if options.verbose > 0:
  236.             print "### %d episode folders found. ###" % len(folders)
  237.         elif options.verbose > 1:
  238.             for (key, value) in folders.items():
  239.                 print key + " " + value.rstrip(os.sep)
  240.  
  241.         # iterate through all the files in given directory
  242.         for file in os.listdir(os.curdir):
  243.             # only process files, not directories
  244.             if os.path.isfile(file):
  245.                 # retrieve episode and season
  246.                 key = get_key(file)
  247.                
  248.                 if key is not None:
  249.                     if options.verbose > 1:
  250.                         print "Key found for file %(file)s: %(key)s" \
  251.                             % {'file': file, "key": key}
  252.                
  253.                     #check if there is a folder to move to
  254.                     if folders.has_key(key):
  255.                         count_files += 1
  256.                         move_file(folders[key], file)
  257.                            
  258.                     else:
  259.                         if options.verbose > 0:
  260.                             print "No folder found for %s" % file
  261.                
  262.                 # season and episode can't be detected
  263.                 else:
  264.                     if options.verbose > 1:
  265.                         print "Failed recognition for %(file)s" % {'file': file}
  266.          
  267.         count_directories += 1
  268.     return (count_directories, count_files)
  269.  
  270. if __name__ == '__main__':
  271.     parser = optparse.OptionParser(
  272.         usage="Usage: %prog [options] dir1 ...",
  273.         version="%prog " + __VERSION__) # --help, --version
  274.    
  275.     bold = "\033[1m"
  276.     reset = "\033[0;0m"
  277.    
  278.     output = optparse.OptionGroup(parser, "Feedback options")
  279.     parser.add_option_group(output)
  280.  
  281.     output.add_option("-q", "--quiet",
  282.                       action="store_const", const=0, dest="verbose",
  283.                       help="don't print status messages to stdout")
  284.     output.add_option("-v", "--verbose",
  285.                       action="store_const", const=1, dest="verbose", default=1,
  286.                       help="print feedback (default)")
  287.     output.add_option("-y", "--noisy",
  288.                       action="store_const", const=2, dest="verbose",
  289.                       help="print internal workings too")
  290.    
  291.     parser.add_option("-n", "--dry-run",
  292.                       action="store_true", dest="dry_run", default=False,
  293.                       help="do no harm")
  294.     parser.add_option("-s", "--sample-subs",
  295.                       action="store_true", dest="samples_subs", default=False,  
  296.                       help="moves the detected " + bold + "sample" + reset +
  297.                       " and " + bold + "subs" + reset +
  298.                       " files respectively to ./Sample and ./Subs subdirs")
  299.  
  300.     # no arguments given
  301.     if len(sys.argv) < 2:
  302.         print parser.format_help()
  303.     else:      
  304.         (options, args) = parser.parse_args()
  305.        
  306.         if options.verbose > 1:
  307.             print "options: " + str(options)
  308.             print "folders: " + str(args)
  309.        
  310.         (nb_dirs, nb_files) = main(options,args)
  311.        
  312.         if options.verbose > 0:
  313.             print "All done."
  314.             print "Moved %d files when processing %s director%s" % \
  315.                     (nb_files, nb_dirs, "y." if (nb_dirs == 1) else "ies.")
RAW Paste Data