Assembla home | Assembla project page
 

root/trunk/main/sabnzbd/tvsort.py

Revision 3260, 35.4 kB (checked in by shypike, 2 weeks ago)

Implement call method for class Option so that every option.get() can be replaced by option().

Line 
1 #!/usr/bin/python -OO
2 # Copyright 2008-2009 The SABnzbd-Team <team@sabnzbd.org>
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17
18 """
19 sabnzbd.tvsort - Sorting Functions
20 Series Sorting - Sorting downloads into seasons & episodes
21 Date sorting - Sorting downloads by a custom date matching
22 Generic Sorting - Sorting large files by a custom matching
23 """
24
25 import os
26 import logging
27 import re
28
29 import sabnzbd
30 from sabnzbd.misc import move_to_path, cleanup_empty_directories, \
31                          get_unique_filename, get_ext, renamer, remove_dir
32 from sabnzbd.constants import series_match, date_match, year_match, sample_match
33 import sabnzbd.cfg as cfg
34 from sabnzbd.codecs import titler
35 from sabnzbd.lang import Ta
36
37 RE_SAMPLE = re.compile(sample_match, re.I)
38 # Do not rename .vob files as they are usually DVD's
39 EXCLUDED_FILE_EXTS = ('.vob', '.bin')
40
41 LOWERCASE = ('the','of','and','at','vs','a','an','but','nor','for','on',\
42                          'so','yet')
43 UPPERCASE = ('III', 'II', 'IV')
44
45 replace_prev = {'\\':'/'}
46 replace_after = {
47     '()': '',
48     '..': '.',
49     '__': '_',
50     '  ': ' ',
51     '//': '/',
52     ' .%ext': '.%ext'
53 }
54
55 # Title() function messes up country names, so need to replace them instead
56 COUNTRY_REP = ('(US)', '(UK)', '(EU)', '(CA)', '(YU)', '(VE)', '(TR)', '(CH)', \
57                '(SE)', '(ES)', '(KR)', '(ZA)', '(SK)', '(SG)', '(RU)', '(RO)', \
58                '(PR)', '(PT)', '(PL)', '(PH)', '(PK)', '(NO)', '(NG)', '(NZ)', \
59                '(NL)', '(MX)', '(MY)', '(MK)', '(KZ)', '(JP)', '(JM)', '(IT)', \
60                '(IL)', '(IE)', '(IN)', '(IS)', '(HU)', '(HK)', '(HN)', '(GR)', \
61                '(GH)', '(DE)', '(FR)', '(FI)', '(DK)', '(CZ)', '(HR)', '(CR)', \
62                '(CO)', '(CN)', '(CL)', '(BG)', '(BR)', '(BE)', '(AT)', '(AU)', \
63                '(AW)', '(AR)', '(AL)', '(AF)')
64
65 _RE_ENDEXT = re.compile(r'\.%ext[{}]*$', re.I)
66
67 def endswith_ext(path):
68     m = _RE_ENDEXT.search(path)
69     return m is not None
70
71
72 def move_to_parent_folder(workdir):
73     """ Move content of 'workdir' to 'workdir/..' possibly skipping some files
74         If afterwards the directory is not empty, rename it to _JUNK_folder, else remove it.
75     """
76     skipped = False # Keep track of any skipped files
77     path1 = os.path.abspath(os.path.join(workdir, '..')) #move things to the folder below
78
79     for root, dirs, files in os.walk(workdir):
80         for _file in files:
81             path = os.path.join(root, _file)
82             new_path = path.replace(workdir, path1)
83             path, new_path = get_unique_filename(path,new_path)
84             move_to_path(path, new_path, False)
85
86     cleanup_empty_directories(workdir)
87     try:
88         remove_dir(workdir)
89     except:
90         pass
91
92     return path1
93
94
95 class Sorter:
96     def __init__(self, cat):
97         self.sorter = None
98         self.type = None
99         self.sort_file = False
100         self.cat = cat
101
102     def detect(self, dirname, complete_dir):
103         self.sorter = SeriesSorter(dirname, complete_dir, self.cat)
104         if self.sorter.is_match():
105             complete_dir = self.sorter.get_final_path()
106             self.type = 'tv'
107             self.sort_file = True
108             return complete_dir
109
110         self.sorter = DateSorter(dirname, complete_dir, self.cat)
111         if self.sorter.is_match():
112             complete_dir = self.sorter.get_final_path()
113             self.type = 'date'
114             self.sort_file = True
115             return complete_dir
116
117         self.sorter = GenericSorter(dirname, complete_dir, self.cat)
118         if self.sorter.is_match():
119             complete_dir = self.sorter.get_final_path()
120             self.type = 'movie'
121             self.sort_file = True
122             return complete_dir
123
124         self.sort_file = False
125         return complete_dir
126
127     def rename(self, newfiles, workdir_complete):
128         if self.sorter.should_rename():
129             self.sorter.rename(newfiles, workdir_complete)
130
131     def move(self, workdir_complete):
132         if self.type == 'movie':
133             move_to_parent = True
134             # check if we should leave the files inside an extra folder
135             if cfg.MOVIE_EXTRA_FOLDER():
136                 #if there is a folder in the download, leave it in an extra folder
137                 move_to_parent = not check_for_folder(workdir_complete)
138             if move_to_parent:
139                 workdir_complete = move_to_parent_folder(workdir_complete)
140             return workdir_complete
141         else:
142             return move_to_parent_folder(workdir_complete)
143
144     def is_sortfile(self):
145         return self.sort_file
146
147 class SeriesSorter:
148     def __init__(self, dirname, path, cat):
149         self.matched = False
150
151         self.original_dirname = dirname
152         self.original_path = path
153         self.cat = cat
154         self.sort_string = cfg.TV_SORT_STRING()
155         self.cats = cfg.TV_CATEGORIES()
156         self.filename_set = ''
157
158         self.match_obj = None
159         self.extras = None
160         self.descmatch = None
161
162         self.rename_or_not = False
163
164         self.show_info = {}
165
166         #Check if it is a TV show on init()
167         self.match()
168
169
170     def match(self):
171         ''' Checks the regex for a match, if so set self.match to true '''
172         if cfg.ENABLE_TV_SORTING() and cfg.TV_SORT_STRING():
173             if (self.cat and self.cat.lower() in self.cats) or (not self.cat and 'None' in self.cats):
174                 #First check if the show matches TV episode regular expressions. Returns regex match object
175                 self.match_obj, self.extras = check_regexs(self.original_dirname, series_match, double=True)
176                 if self.match_obj:
177                     logging.debug("Found TV Show - Starting folder sort (%s)", self.original_dirname)
178                     self.matched = True
179
180
181     def is_match(self):
182         ''' Returns whether there was a match or not '''
183         return self.matched
184
185
186     def get_final_path(self):
187         # Collect and construct all the variables such as episode name, show names
188         if self.get_values():
189             # Get the final path
190             path = self.construct_path()
191             self.final_path = os.path.join(self.original_path, path)
192             return self.final_path
193         else:
194             # Error Sorting
195             return os.path.join(self.original_path, self.original_dirname)
196
197
198     def get_multi_ep_naming(self, one, two, extras):
199         ''' Returns a list of unique values joined into a string and seperated by - (ex:01-02-03-04) '''
200         extra_list = [one]
201         extra2_list = [two]
202         for extra in extras:
203             if extra not in (extra_list, extra2_list):
204                 ep_no2 = extra.rjust(2,'0')
205                 extra_list.append(extra)
206                 extra2_list.append(ep_no2)
207
208         one = '-'.join(extra_list)
209         two = '-'.join(extra2_list)
210         return (one, two)
211
212     def get_shownames(self):
213         ''' Get the show name from the match object and format it '''
214         # Get the formatted title and alternate title formats
215         self.show_info['show_name'], self.show_info['show_name_two'], self.show_info['show_name_three'] = getTitles(self.match_obj, self.original_dirname)
216
217
218     def get_seasons(self):
219         ''' Get the season number from the match object and format it '''
220         season = self.match_obj.group(1).strip('_') # season number
221
222         # Provide alternatve formatting (0 padding)
223         if season.lower() == 's':
224             season2 = season
225         else:
226             try:
227                 season = str(int(season))
228             except:
229                 pass
230             season2 = season.rjust(2,'0')
231
232         self.show_info['season_num'] = season
233         self.show_info['season_num_alt'] = season2
234
235
236     def get_episodes(self):
237         ''' Get the episode numbers from the match object, format and join them '''
238         ep_no = self.match_obj.group(2) # episode number
239         # Store the original episode number
240
241         # Provide alternatve formatting (0 padding)
242         ep_no2 = ep_no.rjust(2,'0')
243         try:
244             ep_no = str(int(ep_no))
245         except:
246             pass
247
248         # Dual episode support
249         if self.extras:
250             ep_no,  ep_no2 = self.get_multi_ep_naming(ep_no,  ep_no2, self.extras)
251
252         self.show_info['episode_num'] = ep_no
253         self.show_info['episode_num_alt'] = ep_no2
254
255
256     def get_showdescriptions(self):
257         ''' Get the show descriptions from the match object and format them '''
258         self.show_info['ep_name'], self.show_info['ep_name_two'], self.show_info['ep_name_three'] = getDescriptions(self.match_obj, self.original_dirname)
259
260
261     def get_values(self):
262         """ Collect and construct all the values needed for path replacement """
263         try:
264             ## - Show Name
265             self.get_shownames()
266
267             ## - Season
268             self.get_seasons()
269
270             ## - Episode Number
271             self.get_episodes()
272
273             ## - Episode Name
274             self.get_showdescriptions()
275
276             return True
277
278         except:
279             logging.error(Ta('error-tvInfo@1'), self.original_dirname)
280             logging.debug("Traceback: ", exc_info = True)
281             return False
282
283
284     def construct_path(self):
285         ''' Replaces the sort string with real values such as Show Name and Episode Number '''
286
287         path = self.sort_string
288
289         if endswith_ext(path):
290             extension = True
291             path = path.replace(".%ext", '')
292         else:
293             extension = False
294
295         for key, name in replace_prev.iteritems():
296             path = path.replace(key, name)
297
298         path = path.replace('%sn', self.show_info['show_name'])
299         path = path.replace('%s.n', self.show_info['show_name_two'])
300         path = path.replace('%s_n', self.show_info['show_name_three'])
301
302         # Replace season number
303         path = path.replace('%s', self.show_info['season_num'])
304         path = path.replace('%0s', self.show_info['season_num_alt'])
305
306         # Replace episode names
307         if self.show_info['ep_name']:
308             path = path.replace('%en', self.show_info['ep_name'])
309             path = path.replace('%e.n', self.show_info['ep_name_two'])
310             path = path.replace('%e_n', self.show_info['ep_name_three'])
311
312         # If no descriptions were found we need to replace %en and eat up surrounding characters
313         path = removeDescription(path, '%e[\._]?n')
314
315         # Replace episode number
316         path = path.replace('%e', self.show_info['episode_num'])
317         path = path.replace('%0e', self.show_info['episode_num_alt'])
318
319
320         for key, name in replace_after.iteritems():
321             path = path.replace(key, name)
322
323         # Lowercase all characters encased in {}
324         path = toLowercase(path)
325
326         # Split the last part of the path up for the renamer
327         if extension:
328             head, tail = os.path.split(path)
329             self.filename_set = tail
330             self.rename_or_not = True
331         else:
332             head = path
333
334
335         return head
336
337     def should_rename(self):
338         return self.rename_or_not
339
340     def rename(self, files, current_path):
341         logging.debug("Renaming Series")
342         renamed = None
343         largest = (None, None, 0)
344
345         def to_filepath(f, current_path):
346             if is_full_path(f):
347                 filepath = f.replace('_UNPACK_', '')
348             else:
349                 filepath = os.path.join(current_path, f)
350             return filepath
351
352         # Create a generator of filepaths, ignore sample files and excluded files (vobs ect)
353         filepaths = ((file, to_filepath(file, current_path)) for file in files if not RE_SAMPLE.search(file) \
354                      and get_ext(file) not in EXCLUDED_FILE_EXTS)
355
356         # Find the largest existing file
357         for file, fp in filepaths:
358             # If for some reason the file no longer exists, skip
359             if not os.path.exists(fp):
360                 continue
361
362             size = os.stat(fp).st_size
363             f_file, f_fp, f_size = largest
364             if size > f_size:
365                 largest = (file, fp, size)
366
367         file, filepath, size = largest
368         # >20MB
369         if filepath and size > 20971520:
370             tmp, ext = os.path.splitext(file)
371             newname = "%s%s" % (self.filename_set,ext)
372             # Replace %fn with the original filename
373             newname = newname.replace('%fn',tmp)
374             newpath = os.path.join(current_path, newname)
375             if not os.path.exists(newpath):
376                 try:
377                     logging.debug("Rename: %s to %s", filepath,newpath)
378                     renamer(filepath,newpath)
379                 except:
380                     logging.error("Failed to rename: %s to %s", current_path, newpath)
381                     logging.debug("Traceback: ", exc_info = True)
382                 rename_similar(current_path, file, self.filename_set)
383             else:
384                 logging.debug('Current path already exists, skipping rename, %s', newpath)
385         else:
386             logging.debug('Nothing to rename, %s', files)
387
388
389 _RE_MULTIPLE = ( \
390     re.compile(r'cd\W?(\d+)\W?', re.I),        # .cd1.avi
391     re.compile(r'\w\W?([\w\d])[{}]*$', re.I),  # blah1.avi blaha.avi
392     re.compile(r'\w\W([\w\d])\W', re.I)        # blah-1-ok.avi blah-a-ok.avi
393 )
394 def check_for_multiple(files):
395     for regex in _RE_MULTIPLE:
396         matched_files = check_for_sequence(regex, files)
397         if matched_files:
398             return matched_files
399     return ''
400
401
402 def check_for_sequence(regex, files):
403     matches = {}
404     prefix = None
405     # Build up a dictionary of matches
406     # The key is based off the match, ie {1:'blah-part1.avi'}
407     for _file in files:
408         name, ext = os.path.splitext(_file)
409         match1 = regex.search(name)
410         if match1:
411             if not prefix or prefix == name[:match1.start()]:
412                 matches[match1.group(1)] = name+ext
413                 prefix = name[:match1.start()]
414
415     # Don't do anything if only one or no files matched
416     if len(matches.keys()) < 2:
417         return {}
418
419     key_prev = 0
420     passed = True
421     alphabet = 'abcdefghijklmnopqrstuvwxyz'
422
423     # Check the dictionary to see if the keys are in a numeric or alphabetic sequence
424     for akey in sorted(matches.keys()):
425         if akey.isdigit():
426             key = int(akey)
427         elif akey in alphabet:
428             key = alphabet.find(akey) + 1
429         else:
430             passed = False
431
432         if passed:
433             if not key_prev:
434                 key_prev = key
435             else:
436                 if key_prev + 1 == key:
437                     key_prev = key
438                 else:
439                     passed = False
440         if passed:
441             # convert {'b':'filename-b.avi'} to {'2', 'filename-b.avi'}
442             item = matches.pop(akey)
443             matches[str(key)] = item
444
445     if passed:
446         return matches
447     else:
448         return {}
449
450
451
452
453 class GenericSorter:
454     def __init__(self, dirname, path, cat):
455         self.matched = False
456
457         self.original_dirname = dirname
458         self.original_path = path
459         self.sort_string = cfg.MOVIE_SORT_STRING()
460         self.extra = cfg.MOVIE_SORT_EXTRA()
461         self.cats = cfg.MOVIE_CATEGORIES()
462         self.cat = cat
463         self.filename_set = ''
464
465         self.match_obj = None
466
467         self.rename_or_not = False
468
469         self.movie_info = {}
470
471         # Check if we match the category in init()
472         self.match()
473
474
475     def match(self):
476         ''' Checks the category for a match, if so set self.match to true '''
477         if cfg.ENABLE_MOVIE_SORTING() and self.sort_string:
478             #First check if the show matches TV episode regular expressions. Returns regex match object
479             if (self.cat and self.cat.lower() in self.cats) or (not self.cat and 'None' in self.cats):
480                 logging.debug("Movie Sorting - Starting folder sort (%s)", self.original_dirname)
481                 self.matched = True
482
483
484     def is_match(self):
485         ''' Returns whether there was a match or not '''
486         return self.matched
487
488
489
490     def get_final_path(self):
491         # Collect and construct all the variables such as episode name, show names
492         if self.get_values():
493             # Get the final path
494             path = self.construct_path()
495             self.final_path = os.path.join(self.original_path, path)
496             return self.final_path
497         else:
498             # Error Sorting
499             return os.path.join(self.original_path, self.original_dirname)
500
501     def get_values(self):
502         """ Collect and construct all the values needed for path replacement """
503
504         ## - Get Year
505         RE_YEAR = re.compile(year_match, re.I)
506         year_m = RE_YEAR.search(self.original_dirname)
507         if year_m:
508             # Find the last matched date
509             # Keep year_m to use in getTitles
510             year = RE_YEAR.findall(self.original_dirname)[-1][0]
511             self.movie_info['year'] = year
512         else:
513             self.movie_info['year'] = ''
514
515         ## - Get Decades
516         self.movie_info['decade'], self.movie_info['decade_two'] = getDecades(self.movie_info['year'])
517
518         ## - Get Title
519         self.movie_info['title'], self.movie_info['title_two'], self.movie_info['title_three'] = getTitles(year_m, self.original_dirname)
520
521         return True
522
523
524     def construct_path(self):
525
526         path = self.sort_string
527
528         if endswith_ext(path):
529             extension = True
530             path = path.replace(".%ext", '')
531         else:
532             extension = False
533
534         for key, name in replace_prev.iteritems():
535             path = path.replace(key, name)
536
537         # Replace title
538         path = path.replace('%title', self.movie_info['title'])
539         path = path.replace('%.title', self.movie_info['title_two'])
540         path = path.replace('%_title', self.movie_info['title_three'])
541
542         path = path.replace('%t', self.movie_info['title'])
543         path = path.replace('%.t', self.movie_info['title_two'])
544         path = path.replace('%_t', self.movie_info['title_three'])
545
546         # Replace year
547         path = path.replace('%y', self.movie_info['year'])
548
549         # Replace decades
550         path = path.replace('%decade', self.movie_info['decade'])
551         path = path.replace('%0decade', self.movie_info['decade_two'])
552
553
554
555         for key, name in replace_after.iteritems():
556             path = path.replace(key, name)
557
558
559         # Lowercase all characters encased in {}
560         path = toLowercase(path)
561
562         # Strip any extra ' ' '.' or '_' around foldernames
563         path = stripFolders(path)
564
565         # Split the last part of the path up for the renamer
566         if extension:
567             head, tail = os.path.split(path)
568             self.filename_set = tail
569             self.rename_or_not = True
570         else:
571             head = path
572
573         return head
574
575     def should_rename(self):
576         return self.rename_or_not
577
578     def rename(self, _files, current_path):
579         logging.debug("Renaming Generic file")
580         def filter_files(_file, current_path):
581             if is_full_path(_file):
582                 filepath = _file.replace('_UNPACK_', '')
583             else:
584                 filepath = os.path.join(current_path, _file)
585             if os.path.exists(filepath):
586                 size = os.stat(filepath).st_size
587                 if size > 314572800 and not RE_SAMPLE.search(_file) \
588                    and get_ext(_file) not in EXCLUDED_FILE_EXTS:
589                     return True
590             return False
591
592         renamed = False
593         # remove any files below 300MB from this list
594         files = [_file for _file in _files if filter_files(_file, current_path)]
595
596         length = len(files)
597         ## Single File Handling
598         if length == 1:
599             file = files[0]
600             if is_full_path(file):
601                 filepath = file.replace('_UNPACK_', '')
602             else:
603                 filepath = os.path.join(current_path, file)
604             if os.path.exists(filepath):
605                 tmp, ext = os.path.splitext(file)
606                 newname = "%s%s" % (self.filename_set,ext)
607                 newname = newname.replace('%fn',tmp)
608                 newpath = os.path.join(current_path, newname)
609                 try:
610                     logging.debug("Rename: %s to %s", filepath,newpath)
611                     renamer(filepath,newpath)
612                 except:
613                     logging.error(Ta('error-tvRename@2'), filepath, newpath)
614                     logging.debug("Traceback: ", exc_info = True)
615                 rename_similar(current_path, file, self.filename_set)
616
617         ## Sequence File Handling
618         # if there is more than one extracted file check for CD1/1/A in the title
619         elif self.extra:
620             matched_files = check_for_multiple(files)
621             # rename files marked as in a set
622             if matched_files:
623                 logging.debug("Renaming a series of generic files (%s)", matched_files)
624                 for index, file in matched_files.iteritems():
625                     filepath = os.path.join(current_path, file)
626                     tmp, ext = os.path.splitext(file)
627                     name = '%s%s' % (self.filename_set, self.extra)
628                     name = name.replace('%1', str(index)).replace('%fn',tmp)
629                     name = name + ext
630                     newpath = os.path.join(current_path, name)
631                     try:
632                         logging.debug("Rename: %s to %s", filepath,newpath)
633                         renamer(filepath,newpath)
634                     except:
635                         logging.error(Ta('error-tvRename@2'), filepath, newpath)
636                         logging.debug("Traceback: ", exc_info = True)
637                     rename_similar(current_path, file, self.filename_set)
638             else:
639                 logging.debug("Movie files not in sequence %s", _files)
640
641
642 class DateSorter:
643     def __init__(self, dirname, path, cat):
644         self.matched = False
645
646         self.original_dirname = dirname
647         self.original_path = path
648         self.sort_string = cfg.DATE_SORT_STRING()
649         self.cats = cfg.DATE_CATEGORIES()
650         self.cat = cat
651         self.filename_set = ''
652
653         self.match_obj = None
654
655         self.rename_or_not = False
656         self.date_type = None
657
658         self.date_info = {}
659
660         # Check if we match the category in init()
661         self.match()
662
663
664     def match(self):
665         ''' Checks the category for a match, if so set self.matched to true '''
666         if cfg.ENABLE_DATE_SORTING() and self.sort_string:
667             #First check if the show matches TV episode regular expressions. Returns regex match object
668             if (self.cat and self.cat.lower() in self.cats) or (not self.cat and 'None' in self.cats):
669                 self.match_obj, self.date_type = checkForDate(self.original_dirname, date_match)
670                 if self.match_obj:
671                     logging.debug("Date Sorting - Starting folder sort (%s)", self.original_dirname)
672                     self.matched = True
673
674
675     def is_match(self):
676         ''' Returns whether there was a match or not '''
677         return self.matched
678
679
680     def get_final_path(self):
681         # Collect and construct all the variables such as episode name, show names
682         if self.get_values():
683             # Get the final path
684             path = self.construct_path()
685             self.final_path = os.path.join(self.original_path, path)
686             return self.final_path
687         else:
688             # Error Sorting
689             return os.path.join(self.original_path, self.original_dirname)
690
691     def get_values(self):
692         """ Collect and construct all the values needed for path replacement """
693
694         if self.date_type == 1: #2008-10-16
695             self.date_info['year'] = self.match_obj.group(1)
696             self.date_info['month'] = self.match_obj.group(2)
697             self.date_info['date'] =  self.match_obj.group(3)
698         else:                       #10.16.2008
699             self.date_info['year'] = self.match_obj.group(3)
700             self.date_info['month'] = self.match_obj.group(1)
701             self.date_info['date'] =  self.match_obj.group(2)
702
703         self.date_info['month_two'] = self.date_info['month'].rjust(2,'0')
704         self.date_info['date_two'] = self.date_info['date'].rjust(2,'0')
705
706         ## - Get Decades
707         self.date_info['decade'], self.date_info['decade_two'] = getDecades(self.date_info['year'])
708
709         ## - Get Title
710         self.date_info['title'], self.date_info['title_two'], self.date_info['title_three'] = getTitles(self.match_obj, self.original_dirname)
711
712         self.date_info['ep_name'], self.date_info['ep_name_two'], self.date_info['ep_name_three'] = getDescriptions(self.match_obj, self.original_dirname)
713
714         return True
715
716
717     def construct_path(self):
718
719         path = self.sort_string
720
721         if endswith_ext(path):
722             extension = True
723             path = path.replace(".%ext", '')
724         else:
725             extension = False
726
727         for key, name in replace_prev.iteritems():
728             path = path.replace(key, name)
729
730         # Replace title
731         path = path.replace('%title', self.date_info['title'])
732         path = path.replace('%.title', self.date_info['title_two'])
733         path = path.replace('%_title', self.date_info['title_three'])
734
735         path = path.replace('%t', self.date_info['title'])
736         path = path.replace('%.t', self.date_info['title_two'])
737         path = path.replace('%_t', self.date_info['title_three'])
738
739         path = path.replace('%sn', self.date_info['title'])
740         path = path.replace('%s.n', self.date_info['title_two'])
741         path = path.replace('%s_n', self.date_info['title_three'])
742
743         # Replace year
744         path = path.replace('%year', self.date_info['year'])
745         path = path.replace('%y', self.date_info['year'])
746
747         if self.date_info['ep_name']:
748             path = path.replace('%desc', self.date_info['ep_name'])
749             path = path.replace('%.desc', self.date_info['ep_name_two'])
750             path = path.replace('%_desc', self.date_info['ep_name_three'])
751
752         # Replace decades
753         path = path.replace('%decade', self.date_info['decade'])
754         path = path.replace('%0decade', self.date_info['decade_two'])
755
756         # Replace month
757         path = path.replace('%m', self.date_info['month'])
758         path = path.replace('%0m', self.date_info['month_two'])
759
760         # Replace date
761         path = path.replace('%d', self.date_info['date'])
762         path = path.replace('%0d', self.date_info['date_two'])
763
764         for key, name in replace_after.iteritems():
765             path = path.replace(key, name)
766
767         # Lowercase all characters encased in {}
768         path = toLowercase(path)
769
770         path = removeDescription(path, '%[\._]?desc')
771
772         # Strip any extra ' ' '.' or '_' around foldernames
773         path = stripFolders(path)
774
775         # Split the last part of the path up for the renamer
776         if extension:
777             head, tail = os.path.split(path)
778             self.filename_set = tail
779             self.rename_or_not = True
780         else:
781             head = path
782
783         return head
784
785     def should_rename(self):
786         return self.rename_or_not
787
788     def rename(self, files, current_path):
789         logging.debug("Renaming Date file")
790         renamed = None
791         #find the master file to rename
792         for file in files:
793             if is_full_path(file):
794                 filepath = file.replace('_UNPACK_', '')
795             else:
796                 filepath = os.path.join(current_path, file)
797
798             if os.path.exists(filepath):
799                 size = os.stat(filepath).st_size
800                 if size > 130000000:
801                     if 'sample' not in file:
802                         tmp, ext = os.path.splitext(file)
803                         newname = "%s%s" % (self.filename_set,ext)
804                         newname = newname.replace('%fn',tmp)
805                         newpath = os.path.join(current_path, newname)
806                         if not os.path.exists(newpath):
807                             try:
808                                 logging.debug("Rename: %s to %s", filepath,newpath)
809                                 renamer(filepath,newpath)
810                             except:
811                                 logging.error(Ta('error-tvRename@2'), current_path, newpath)
812                                 logging.debug("Traceback: ", exc_info = True)
813                             rename_similar(current_path, file, self.filename_set)
814                             break
815
816
817
818 def getTitles(match, name):
819     '''
820     The title will be the part before the match
821     Clean it up and title() it
822
823     ''.title() isn't very good under python so this contains
824     a lot of little hacks to make it better and for more control
825     '''
826     if match:
827         name = name[:match.start()]
828
829     # Replace .US. with (US)
830     if cfg.TV_SORT_COUNTRIES() == 1:
831         for rep in COUNTRY_REP:
832             # (us) > (US)
833             name = replace_word(name, rep.lower(), rep)
834             # (Us) > (US)
835             name = replace_word(name, titler(rep), rep)
836             # .US. > (US)
837             dotted_country = '.%s.' % (rep.strip('()'))
838             name = replace_word(name, dotted_country, rep)
839     # Remove .US. and (US)
840     elif cfg.TV_SORT_COUNTRIES() == 2:
841         for rep in COUNTRY_REP:
842             # Remove (US)
843             name = replace_word(name, rep, '')
844             dotted_country = '.%s.' % (rep.strip('()'))
845             # Remove .US.
846             name = replace_word(name, dotted_country, '.')
847
848     title = name.replace('.', ' ').replace('_', ' ')
849     title = title.strip().strip('(').strip('_').strip('-').strip().strip('_')
850
851     title = titler(title) # title the show name so it is in a consistant letter case
852
853     #title applied uppercase to 's Python bug?
854     title = title.replace("'S", "'s")
855
856     # Replace titled country names, (Us) with (US) and so on
857     if cfg.TV_SORT_COUNTRIES() == 1:
858         for rep in COUNTRY_REP:
859             title = title.replace(titler(rep), rep)
860     # Remove country names, ie (Us)
861     elif cfg.TV_SORT_COUNTRIES() == 2:
862         for rep in COUNTRY_REP:
863             title = title.replace(titler(rep), '').strip()
864
865     # Make sure some words such as 'and' or 'of' stay lowercased.
866     for x in LOWERCASE:
867         xtitled = titler(x)
868         title = replace_word(title, xtitled, x)
869
870     # Make sure some words such as 'III' or 'IV' stay uppercased.
871     for x in UPPERCASE:
872         xtitled = titler(x)
873         title = replace_word(title, xtitled, x)
874
875     # The title with spaces replaced by dots
876     dots = title.replace(" - ", "-").replace(' ','.').replace('_','.')
877     dots = dots.replace('(', '.').replace(')','.').replace('..','.').rstrip('.')
878
879     # The title with spaces replaced by underscores
880     underscores = title.replace(' ','_').replace('.','_').replace('__','_').rstrip('_')
881
882     return title, dots, underscores
883
884 def replace_word(input, one, two):
885     ''' Regex replace on just words '''
886     regex = re.compile('\W(%s)(\W|$)' % one, re.I)
887     matches = regex.findall(input)
888     if matches:
889         for m in matches:
890             input = input.replace(one, two)
891     return input
892
893 def getDescriptions(match, name):
894     '''
895     If present, get a description from the nzb name.
896     A description has to be after the matched item, seperated either
897     like ' - Description' or '_-_Description'
898     '''
899     if match:
900         ep_name = name[match.end():] # Need to improve for multi ep support
901     else:
902         ep_name = name
903     RE_EPNAME = re.compile('_?-[_\W]', re.I)
904     m = RE_EPNAME.search(ep_name)
905     if m:
906         ep_name = ep_name[m.end():].strip('_').strip().strip('_').replace('.', ' ').replace('_', ' ')
907         ep_name2 = ep_name.replace(" - ", "-").replace(" ", ".")
908         ep_name3 = ep_name.replace(" ", "_")
909         return ep_name, ep_name2, ep_name3
910     else:
911         return '', '', ''
912
913 def removeDescription(path, desc_token):
914     regex_string = '(\W*)(token)(\s?)'.replace('token', desc_token)
915     epname_re = re.compile(regex_string)
916     path = epname_re.sub('', path)
917     return path
918
919 def getDecades(year):
920     if year:
921         try:
922             decade = year[2:3]+'0'
923             decade2 = year[:3]+'0'
924         except:
925             decade = ''
926             decade2 = ''
927     else:
928         decade = ''
929         decade2 = ''
930     return decade, decade2
931
932 def check_for_folder(path):
933     for root, dirs, files in os.walk(path):
934         if dirs:
935             return True
936     return False
937
938 _RE_LOWERCASE = re.compile('{([^{]*)}')
939 def toLowercase(path):
940     ''' Lowercases any characters enclosed in {} '''
941     while True:
942         m = _RE_LOWERCASE.search(path)
943         if not m:
944             break
945         path = path[:m.start()] + m.group(1).lower() + path[m.end():]
946
947     # just incase
948     path = path.replace('{', '')
949     path = path.replace('}', '')
950     return path
951
952 def stripFolders(folders):
953     f = folders.strip('/').split('/')
954
955     def strip_all(x):
956         x = x.strip().strip('_')
957         if sabnzbd.WIN32:
958             # Don't want to strip . from folders such as /.sabnzbd/
959             x = x.strip('.')
960         x = x.strip()
961         return x
962
963     return '/'.join([strip_all(x) for x in f])
964
965
966 def rename_similar(path, file, name):
967     logging.debug('Renaming files similar to: %s to %s', file, name)
968     file_prefix, ext = os.path.splitext(file)
969     for root, dirs, files in os.walk(path):
970         for _file in files:
971             fpath = os.path.join(root, _file)
972             tmp, ext = os.path.splitext(_file)
973             if tmp == file_prefix:
974                 newname = "%s%s" % (name,ext)
975                 newname = newname.replace('%fn',tmp)
976                 newpath = os.path.join(path, newname)
977                 if not os.path.exists(newpath):
978                     try:
979                         logging.debug("Rename: %s to %s", fpath,newpath)
980                         renamer(fpath,newpath)
981                     except:
982                         logging.error(Ta('error-tvSimRename@2'), path, newpath)
983                         logging.debug("Traceback: ", exc_info = True)
984
985
986
987
988 def check_regexs(filename, matchers, double=False):
989     """
990     Regular Expression match for a list of regexes
991     Returns the MatchObject if a match is made
992     This version checks for an additional match
993     """
994     '''if double:
995         matcher, extramatchers = matchers
996     else:
997         matcher = matchers
998         extramatchers = []'''
999     extras = []
1000     for expressions in matchers:
1001         expression, extramatchers = expressions
1002         regex = re.compile(expression)
1003         match1 = regex.search(filename)
1004         if match1:
1005             for m in extramatchers:
1006                 regex = re.compile(m)
1007                 match2 = regex.findall(filename,match1.end())
1008                 if match2:
1009                     for match in match2:
1010                         if type(match) == type(()) and len(match) > 1:
1011                             extras.append(match[1])
1012                         else:
1013                             extras.append(match)
1014                     break
1015             return match1, extras
1016     return None, None
1017
1018
1019 def checkForDate(filename, matcher):
1020     """
1021     Regular Expression match for date based files
1022     Returns the MatchObject if a match is made
1023     """
1024     match2 = None
1025     x = 0
1026     if matcher:
1027         for expression in matcher:
1028             regex = re.compile(expression)
1029             match1 = regex.search(filename)
1030             x += 1
1031             if match1:
1032                 return match1, x
1033     return None, 0
1034
1035 def is_full_path(file):
1036     if file.startswith('\\') or file.startswith('/'):
1037         return True
1038     try:
1039         if file[1:3] == ':\\':
1040             return True
1041     except:
1042         pass
1043     return False
Note: See TracBrowser for help on using the browser.