Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- diff --git a/app/config/configApp.py b/app/config/configApp.py
- index ff37120..6ebda3c 100644
- --- a/app/config/configApp.py
- +++ b/app/config/configApp.py
- @@ -45,6 +45,7 @@ class configApp():
- self.setDefault('global', 'launchbrowser', True)
- self.setDefault('global', 'urlBase', '')
- self.setDefault('global', 'ignoreWords', '')
- + self.setDefault('global', 'preferredWords', '')
- self.addSection('Renamer')
- self.setDefault('Renamer', 'enabled', False)
- diff --git a/app/config/updater.py b/app/config/updater.py
- index 2ff5267..58c028a 100644
- --- a/app/config/updater.py
- +++ b/app/config/updater.py
- @@ -5,6 +5,7 @@ from imdb.parser.http.bsouplxml._bsoup import SoupStrainer, BeautifulSoup
- from urllib2 import URLError
- import cherrypy
- import os
- +import subprocess
- import tarfile
- import time
- import urllib2
- @@ -33,6 +34,7 @@ class Updater(SimplePlugin):
- self.debug = cherrypy.config['debug']
- self.updatePath = os.path.join(self.cachePath, 'updates')
- self.historyFile = os.path.join(self.updatePath, 'history.txt')
- + self.tryGit = os.path.isdir(os.path.join(self.runPath, '.git'))
- if not os.path.isdir(self.updatePath):
- os.mkdir(self.updatePath)
- @@ -56,7 +58,7 @@ class Updater(SimplePlugin):
- log.info("Updating")
- self.running = True
- - result = self.doUpdateUnix()
- + result = self.doUpdate()
- time.sleep(1)
- cherrypy.engine.restart()
- @@ -70,31 +72,47 @@ class Updater(SimplePlugin):
- if self.isFrozen:
- self.version = 'Windows build r%d' % version.windows
- else:
- - handle = open(self.historyFile, "r")
- - lineList = handle.readlines()
- - handle.close()
- - self.version = lineList[-1].replace('RuudBurger-CouchPotato-', '').replace('.tar.gz', '')
- + if self.tryGit:
- + try:
- + p = subprocess.Popen('git rev-parse HEAD', stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell = True, cwd = os.getcwd())
- + output, err = p.communicate()
- + self.version = output[:7]
- + except:
- + log.error('Failed using GIT, falling back on normal version check.')
- + self.tryGit = False
- + return self.getVersion(force)
- + else:
- + handle = open(self.historyFile, "r")
- + lineList = handle.readlines()
- + handle.close()
- + self.version = lineList[-1].replace('RuudBurger-CouchPotato-', '').replace('.tar.gz', '')
- return self.version
- def checkForUpdate(self):
- - if self.debug:
- - return
- +# if self.debug:
- +# return
- +
- + if not self.version:
- + self.getVersion()
- if self.isFrozen:
- self.updateAvailable = self.checkForUpdateWindows()
- else:
- - update = self.checkForUpdateUnix()
- + update = self.checkGitHubForUpdate()
- if update:
- history = open(self.historyFile, 'r').read()
- - self.updateAvailable = update.get('name').replace('.tar.gz', '') not in history
- + if self.tryGit:
- + self.updateAvailable = self.version not in update.get('name').replace('.tar.gz', '')
- + else:
- + self.updateAvailable = update.get('name').replace('.tar.gz', '') not in history
- self.availableString = 'Update available' if self.updateAvailable else 'No update available'
- self.lastCheck = time.time()
- log.info(self.availableString)
- - def checkForUpdateUnix(self):
- + def checkGitHubForUpdate(self):
- try:
- data = urllib2.urlopen(self.url, timeout = self.timeout)
- except (IOError, URLError):
- @@ -136,13 +154,33 @@ class Updater(SimplePlugin):
- return False
- - def doUpdateUnix(self):
- - update = self.checkForUpdateUnix()
- + def doUpdate(self):
- + update = self.checkGitHubForUpdate()
- if not update:
- return False
- name = update.get('name')
- data = update.get('data')
- +
- + # Try git first
- + if self.tryGit:
- + try:
- + p = subprocess.Popen('git pull', stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell = True, cwd = os.getcwd())
- + output, err = p.communicate()
- +
- + if 'Aborting.' in output:
- + log.error("Couldn't update via git.")
- + self.tryGit = False
- + return False
- + elif 'Already up-to-date.' in output:
- + log.info('No need to update, already using latest version')
- + return True
- +
- + log.info('Update to %s successful, using GIT.' % name)
- + return True
- + except:
- + pass
- +
- destination = os.path.join(self.updatePath, update.get('name'))
- if not os.path.isfile(destination):
- diff --git a/app/lib/cron/renamer.py b/app/lib/cron/renamer.py
- index dd19cd0..240d79b 100644
- --- a/app/lib/cron/renamer.py
- +++ b/app/lib/cron/renamer.py
- @@ -2,6 +2,7 @@ from app import latinToAscii
- from app.config.cplog import CPLog
- from app.config.db import Movie, RenameHistory, Session as Db, MovieQueue
- from app.lib.cron.base import cronBase
- +from app.lib.library import Library
- from app.lib.qualities import Qualities
- import cherrypy
- import fnmatch
- @@ -9,11 +10,10 @@ import os
- import re
- import shutil
- import time
- -import traceback
- log = CPLog(__name__)
- -class RenamerCron(cronBase):
- +class RenamerCron(cronBase, Library):
- ''' Cronjob for renaming movies '''
- @@ -21,16 +21,16 @@ class RenamerCron(cronBase):
- interval = 1 #minutes
- intervalSec = 10
- config = {}
- - trailer = {}
- - minimalFileSize = 1024 * 1024 * 10 # 10MB
- - ignoredInPath = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '/._'] #unpacking, smb-crap
- + #trailer = {}
- + #minimalFileSize = 1024 * 1024 * 10 # 10MB
- + #ignoredInPath = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '/._'] #unpacking, smb-crap
- # Filetypes
- - movieExt = ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso', '*.img']
- - nfoExt = ['*.nfo']
- - audioCodecs = ['DTS', 'AC3', 'AC3D', 'MP3']
- - videoCodecs = ['x264', 'DivX', 'XViD']
- - subExt = ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass']
- + #movieExt = ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso', '*.img']
- + #nfoExt = ['*.nfo']
- + #audioCodecs = ['DTS', 'AC3', 'AC3D', 'MP3']
- + #videoCodecs = ['x264', 'DivX', 'XViD']
- + #subExt = ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass']
- def conf(self, option):
- return self.config.get('Renamer', option)
- @@ -72,46 +72,28 @@ class RenamerCron(cronBase):
- if self.isDisabled():
- return
- - allFiles = self.findFiles()
- + allMovies = self.getMovies(self.conf('download'))
- - if allFiles:
- + if allMovies:
- log.debug("Ready to rename some files.")
- - for files in allFiles:
- -
- - # See if imdb link is in nfo file
- - nfo = files.get('nfo')
- - movie = {}
- - if nfo:
- - nfoFile = open(os.path.join(nfo.get('path'), nfo.get('filename')), 'r').read()
- - imdbId = self.getImdb(nfoFile)
- - if imdbId:
- - log.info('Found movie via nfo file.')
- - movie = {
- - 'movie': self.getMovie(imdbId),
- - 'queue': None
- - }
- - # Try other methods
- - if not movie:
- - movie = self.determineMovie(files)
- -
- - if movie and movie.get('movie'):
- - finalDestination = self.renameFiles(files, movie['movie'], movie['queue'])
- - if self.config.get('Trailer', 'quality'):
- - self.trailerQueue.put({'movieId': movie['movie'].id, 'destination':finalDestination})
- -
- - # Search for subtitles
- + for movie in allMovies:
- +
- + if movie.get('match'):
- + finalDestination = self.renameFiles(movie)
- +
- + # Search for trailer & subtitles
- + cherrypy.config['cron']['trailer'].forDirectory(finalDestination['directory'])
- cherrypy.config['cron']['subtitle'].forDirectory(finalDestination['directory'])
- else:
- try:
- - file = files['files'][0]
- - path = file['path'].split(os.sep)
- + path = movie['path'].split(os.sep)
- path.extend(['_UNKNOWN_' + path.pop()])
- - shutil.move(file['path'], os.sep.join(path))
- + shutil.move(movie['path'], os.sep.join(path))
- except IOError:
- pass
- - log.info('No Match found for: %s' % str(files['files']))
- + log.info('No Match found for: %s' % str(movie['info']['name']))
- # Cleanup
- if self.conf('cleanup'):
- @@ -125,8 +107,9 @@ class RenamerCron(cronBase):
- log.debug(subfiles)
- # Stop if something is unpacking
- - if '_unpack' in root.lower() or '_failed_' in root.lower() or '_unknown_' in root.lower():
- - break
- + for ignore in self.ignoredInPath:
- + if ignore in root.lower():
- + break
- for filename in filenames:
- fullFilePath = os.path.join(root, filename)
- @@ -146,26 +129,13 @@ class RenamerCron(cronBase):
- except OSError:
- log.error('Tried to clean-up download folder, but "%s" isn\'t empty.' % root)
- - def getQueue(self, movie):
- -
- - log.info('Finding quality for %s.' % movie.name)
- -
- - try:
- - # Assuming quality is the top most, as that should be the last downloaded..
- - for queue in movie.queue:
- - if queue.name:
- - return queue
- -
- - except TypeError:
- - return None
- -
- - def renameFiles(self, files, movie, queue = None):
- + def renameFiles(self, movie):
- '''
- rename files based on movie data & conf
- '''
- multiple = False
- - if len(files['files']) > 1:
- + if len(movie['files']) > 1:
- multiple = True
- destination = self.conf('destination')
- @@ -173,53 +143,35 @@ class RenamerCron(cronBase):
- fileNaming = self.conf('filenaming')
- # Remove weird chars from moviename
- - moviename = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie.name)
- + moviename = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie['info'].get('name'))
- # Put 'The' at the end
- namethe = moviename
- if moviename[:3].lower() == 'the':
- namethe = moviename[3:] + ', The'
- - #quality
- - if not queue:
- - queue = self.getQueue(movie)
- -
- - if not queue:
- - quality = Qualities().guess(files['files'])
- - queueId = 0
- - else:
- - quality = Qualities.types[queue.qualityType]['label']
- - queueId = queue.id
- -
- replacements = {
- 'cd': '',
- 'cdNr': '',
- 'ext': '.mkv',
- 'namethe': namethe.strip(),
- 'thename': moviename.strip(),
- - 'year': movie.year,
- + 'year': movie['info']['year'],
- 'first': namethe[0].upper(),
- - 'quality': quality,
- + 'quality': movie['info']['quality'],
- + 'video': movie['info']['codec']['video'],
- + 'audio': movie['info']['codec']['audio'],
- + 'group': movie['info']['group']
- }
- +
- if multiple:
- cd = 1
- justAdded = []
- -
- - totalSize = 0
- - for file in files['files']:
- - fullPath = os.path.join(file['path'], file['filename'])
- - totalSize += os.path.getsize(fullPath)
- -
- - # Do something with ISO, as they should be between DVDRip and BRRIP
- - ext = os.path.splitext(file['filename'])[1].lower()[1:]
- - if ext == 'iso':
- - totalSize -= (os.path.getsize(fullPath) / 1.6)
- - log.info('Total size of new files is %s.' % int(totalSize / 1024 / 1024))
- -
- finalDestination = None
- finalFilename = self.doReplace(fileNaming, replacements)
- - for file in sorted(files['files']):
- +
- + for file in movie['files']:
- log.info('Trying to find a home for: %s' % latinToAscii(file['filename']))
- replacements['ext'] = file['ext']
- @@ -228,34 +180,26 @@ class RenamerCron(cronBase):
- replacements['cd'] = ' cd' + str(cd)
- replacements['cdNr'] = ' ' + str(cd)
- - replacements['original'] = file['root']
- - replacements['video'] = self.getCodec(file['filename'], RenamerCron.videoCodecs)
- - replacements['audio'] = self.getCodec(file['filename'], RenamerCron.audioCodecs)
- - replacements['group'] = self.getGroup(file['root'])
- + replacements['original'] = file['filename']
- folder = self.doReplace(folderNaming, replacements)
- filename = self.doReplace(fileNaming, replacements)
- - old = os.path.join(file['path'], file['filename'])
- + old = os.path.join(movie['path'], file['filename'])
- dest = os.path.join(destination, folder, filename)
- finalDestination = os.path.dirname(dest)
- if not os.path.isdir(finalDestination):
- - # Use same permissions as conf('destination') folder
- try:
- - #mode = os.stat(destination)
- - #chmod = mode[ST_MODE] & 07777
- log.info('Creating directory %s' % finalDestination)
- os.makedirs(finalDestination)
- shutil.copymode(destination, finalDestination)
- - #os.chmod(finalDestination, chmod)
- except OSError:
- - log.error('Failed setting permissions for %s' % finalDestination)
- - os.makedirs(finalDestination)
- + log.error('Failed changing permissions %s' % finalDestination)
- # Remove old if better quality
- - removed = self.removeOld(os.path.join(destination, folder), justAdded, totalSize)
- + removed = self.removeOld(os.path.join(destination, folder), justAdded, movie['info']['size'])
- if not os.path.isfile(dest) and removed:
- log.info('Moving file "%s" to %s.' % (latinToAscii(old), dest))
- @@ -272,17 +216,21 @@ class RenamerCron(cronBase):
- break
- #get subtitle if any & move
- - if len(files['subtitles']) > 0:
- - log.info('Moving matching subtitle.')
- - subtitle = files['subtitles'].pop(0)
- - replacements['ext'] = subtitle['ext']
- - subFilename = self.doReplace(fileNaming, replacements)
- - shutil.move(os.path.join(subtitle['path'], subtitle['filename']), os.path.join(destination, folder, subFilename))
- + for type in movie['subtitles']:
- + if len(movie['subtitles'][type]) > 0:
- + log.info('Moving matching subtitle.')
- +
- + subtitle = movie['subtitles'][type].pop(0)
- + replacements['ext'] = subtitle['ext']
- + subDest = os.path.join(destination, folder, self.doReplace(fileNaming, replacements))
- +
- + shutil.move(os.path.join(movie['path'], subtitle['filename']), subDest)
- + justAdded.append(subDest) # Add to ignore list when removing stuff.
- # Add to renaming history
- h = RenameHistory()
- - h.movieId = movie.id
- - h.movieQueue = queueId
- + h.movieId = movie['movie'].id
- + h.movieQueue = movie['history'].movieQueue if movie['history'] else 0
- h.old = unicode(old.decode('utf-8'))
- h.new = unicode(dest.decode('utf-8'))
- Db.add(h)
- @@ -292,11 +240,11 @@ class RenamerCron(cronBase):
- cd += 1
- # Mark movie downloaded
- - if queueId > 0:
- - if queue.markComplete:
- + if movie['queue'] and movie['queue'].id > 0:
- + if movie['queue'].markComplete:
- movie.status = u'downloaded'
- - queue.completed = True
- + movie['queue'].completed = True
- Db.flush()
- return {
- @@ -326,7 +274,7 @@ class RenamerCron(cronBase):
- if ('*.' + ext in self.movieExt or '*.' + ext in self.subExt) and not '-trailer' in filename:
- files.append(fullPath)
- - log.info('Quality Old: %s, New %s.' % (int(oldSize / 1024 / 1024), int(newSize / 1024 / 1024)))
- + log.info('Quality Old: %d, New %d.' % (int(oldSize) / 1024 / 1024, int(newSize) / 1024 / 1024))
- if oldSize < newSize:
- for file in files:
- try:
- @@ -357,159 +305,6 @@ class RenamerCron(cronBase):
- def replaceDoubles(self, string):
- return string.replace(' ', ' ').replace(' .', '.')
- - def getMovie(self, imdbId):
- - '''
- - Get movie based on IMDB id.
- - If not in local DB, go fetch it from theMovieDb
- - '''
- -
- - movie = Db.query(Movie).filter_by(imdb = imdbId).first()
- -
- - if not movie:
- - movie = self.searcher.get('movie').findByImdbId(imdbId)
- -
- - return movie
- -
- - def determineMovie(self, files):
- - '''
- - Try find movie based on folder names and MovieQueue table
- - '''
- -
- - for file in files['files']:
- - dirnames = file['path'].split(os.path.sep)
- - dirnames.append(file['filename'])
- - dirnames.reverse()
- -
- - for dir in dirnames:
- - dir = latinToAscii(dir)
- -
- - # check and see if name is in queue
- - queue = Db.query(MovieQueue).filter_by(name = dir).first()
- - if queue:
- - log.info('Found movie via MovieQueue.')
- - return {
- - 'queue': queue,
- - 'movie': queue.Movie
- - }
- -
- - # last resort, match every word in path to db
- - lastResort = {}
- - dirSplit = re.split('\W+', dir.lower())
- - for s in dirSplit:
- - if s:
- - results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).all()
- - for r in results:
- - lastResort[r.id] = r
- -
- - for l in lastResort.itervalues():
- - wordCount = 0
- - words = re.split('\W+', l.name.lower())
- - for word in words:
- - if word in dir.lower():
- - wordCount += 1
- -
- - if wordCount == len(words) and len(words) > 0 and str(l.year) in dir:
- - log.info('Found via last resort searching.')
- - return {
- - 'queue': None,
- - 'movie': l
- - }
- - # Try finding movie on theMovieDB
- - # more checking here..
- -
- - return False
- -
- - def getImdb(self, txt):
- -
- - try:
- - m = re.search('imdb.com/title/(?P<id>tt[0-9]+)', txt)
- - id = m.group('id')
- - if id:
- - return id
- - except AttributeError:
- - return False
- -
- - return False
- -
- - def getCodec(self, filename, codecs):
- - codecs = map(re.escape, codecs)
- - try:
- - codec = re.search('[^A-Z0-9](?P<codec>' + '|'.join(codecs) + ')[^A-Z0-9]', filename, re.I)
- - return (codec and codec.group('codec')) or 'unknown'
- - return 'unknown'
- - except:
- - log.info('Renaming: ' + traceback.format_exc())
- - return 'Exception'
- -
- - def getGroup(self, filename):
- - try:
- - group = re.search('-(?P<group>[A-Z0-9]+)$', filename, re.I)
- - return (group and group.group('group')) or 'unknown'
- - except:
- - log.info('Renaming: ' + traceback.format_exc())
- - return 'Exception'
- -
- - def findFiles(self):
- -
- - files = []
- -
- - path = self.conf('download')
- - for dir in os.listdir(path):
- - fullDirPath = os.path.join(path, dir)
- -
- - for root, subfiles, filenames in os.walk(fullDirPath):
- -
- - subfiles = {'nfo':{}, 'files':[], 'subtitles':[]}
- -
- - patterns = []
- - patterns.extend(self.movieExt)
- - patterns.extend(self.nfoExt)
- - patterns.extend(self.subExt)
- -
- - for pattern in patterns:
- - for filename in fnmatch.filter(filenames, pattern):
- - new = {
- - 'path': root,
- - 'filename': filename,
- - 'root' : os.path.splitext(filename)[0],
- - 'ext': os.path.splitext(filename)[1].lower()[1:], #[1:]to remove . from extension
- - }
- -
- - #nfo file
- - if('*.' + new.get('ext') in self.nfoExt):
- - subfiles['nfo'] = new
- - #subtitle file
- - elif('*.' + new.get('ext') in self.subExt):
- - subfiles['subtitles'].append(new)
- - else:
- - #ignore movies files / or not
- - if not self.ignoreFile(os.path.join(root, filename)):
- - subfiles['files'].append(new)
- -
- - if subfiles['files']:
- - files.append(subfiles)
- -
- - return files
- -
- - def ignoreFile(self, file):
- -
- - if re.search('(^|[\W_])sample\d*[\W_]', file.lower()):
- - return True
- -
- - # minimal size
- - if os.path.getsize(file) < self.minimalFileSize:
- - log.info('File to small.')
- - return True
- -
- - # ignoredpaths
- - for i in self.ignoredInPath:
- - if i in file.lower():
- - log.debug('File still unpacking.')
- - return True
- -
- - # All is OK
- - return False
- -
- def startRenamerCron(config, searcher, debug):
- cron = RenamerCron()
- diff --git a/app/lib/cron/subtitle.py b/app/lib/cron/subtitle.py
- index 634892c..ad1db61 100644
- --- a/app/lib/cron/subtitle.py
- +++ b/app/lib/cron/subtitle.py
- @@ -97,7 +97,7 @@ class SubtitleCron(rss, cronBase, Library):
- movies = self.getMovies(directory)
- for movie in movies:
- - if not movie.get('subtitles'):
- + if not movie['subtitles']['files']:
- subtitleQueue.put(movie)
- log.info('Done adding movies to subtitle search.')
- diff --git a/app/lib/cron/trailer.py b/app/lib/cron/trailer.py
- index 2c7ff41..6f38e45 100644
- --- a/app/lib/cron/trailer.py
- +++ b/app/lib/cron/trailer.py
- @@ -69,7 +69,7 @@ class TrailerCron(rss, cronBase, Library):
- elif not self.isEnabled():
- return
- - log.info('Adding movies to trailer search.')
- + if not directory: log.info('Adding movies to trailer search.')
- self.searchingExisting = time.time()
- movies = self.getMovies(directory)
- @@ -77,14 +77,7 @@ class TrailerCron(rss, cronBase, Library):
- if not movie.get('trailer') or force:
- trailerQueue.put(movie)
- - log.info('Done adding movies to trailer search.')
- -
- - def findYear(self, text):
- - matches = re.search('(?P<year>[0-9]{4})', text)
- - if matches:
- - return matches.group('year')
- -
- - return None
- + if not directory: log.info('Done adding movies to trailer search.')
- def search(self, movie):
- log.info('Search for trailer for: %s' % movie['folder'])
- diff --git a/app/lib/library.py b/app/lib/library.py
- index 9bf371e..7a09c7b 100644
- --- a/app/lib/library.py
- +++ b/app/lib/library.py
- @@ -2,6 +2,7 @@ from app import latinToAscii
- from app.config.cplog import CPLog
- from app.config.db import Movie, Session as Db, MovieQueue
- from app.lib import hashFile
- +from app.lib.qualities import Qualities
- import cherrypy
- import fnmatch
- import os
- @@ -19,10 +20,17 @@ class Library:
- extensions = {
- 'movie': ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso'],
- 'nfo': ['*.nfo'],
- - 'subtitle': ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass'],
- + 'subtitle': ['*.sub', '*.srt', '*.ssa', '*.ass'],
- + 'subtitleExtras': ['*.idx'],
- 'trailer': ['*.mov', '*.mp4', '*.flv']
- }
- + codecs = {
- + 'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
- + 'video': ['x264', 'divx', 'xvid']
- + }
- +
- # From Plex/XBMC
- + clean = '(?i)[^\s](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])[^\s]*'
- multipartRegEx = [
- '[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
- '[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
- @@ -37,8 +45,9 @@ class Library:
- def getMovies(self, folder = None):
- movies = []
- + qualities = Qualities()
- - movieFolder = folder if folder else self.config.get('Renamer', 'destination')
- + movieFolder = unicode(folder if folder else self.config.get('Renamer', 'destination'))
- if not os.path.isdir(movieFolder):
- log.error('Can\'t find directory: %s' % movieFolder)
- return movies
- @@ -48,10 +57,27 @@ class Library:
- movie = {
- 'movie': None,
- + 'queue': 0,
- + 'match': False,
- + 'info': {
- + 'name': None,
- + 'year': None,
- + 'quality': '',
- + 'size': 0,
- + 'codec': {
- + 'video': '',
- + 'audio': ''
- + },
- + 'group': ''
- + },
- 'history': None,
- 'path': root,
- 'folder': root.split(os.path.sep)[-1:].pop(),
- - 'nfo':[], 'files':[], 'subtitles':[], 'trailer':[]
- + 'nfo':[], 'files':[], 'trailer':[],
- + 'subtitles':{
- + 'files': [],
- + 'extras': []
- + }
- }
- patterns = []
- @@ -59,7 +85,7 @@ class Library:
- patterns.extend(extType)
- for pattern in patterns:
- - for filename in fnmatch.filter(filenames, pattern):
- + for filename in fnmatch.filter(sorted(filenames), pattern):
- fullFilePath = os.path.join(root, filename)
- new = {
- 'filename': filename,
- @@ -71,23 +97,22 @@ class Library:
- movie['nfo'].append(filename)
- #subtitle file
- elif('*.' + new.get('ext') in self.extensions['subtitle']):
- - movie['subtitles'].append(new)
- + movie['subtitles']['files'].append(new)
- + #idx files
- + elif('*.' + new.get('ext') in self.extensions['subtitleExtras']):
- + movie['subtitles']['extras'].append(new)
- #trailer file
- elif re.search('(^|[\W_])trailer\d*[\W_]', filename.lower()) and self.filesizeBetween(fullFilePath, 2, 250):
- movie['trailer'].append(new)
- else:
- #ignore movies files / or not
- if self.keepFile(fullFilePath):
- + new['hash'] = hashFile(fullFilePath) # Add movie hash
- + new['size'] = os.path.getsize(fullFilePath) # File size
- movie['files'].append(new)
- if movie['files']:
- - # Check hash
- -# fullPath = os.path.join(movie['path'], movie['files'][0]['filename'])
- -# hash = hashFile(fullPath)
- -# bytesize = os.path.getsize(fullPath)
- -# results = cherrypy.config['searchers']['movie'].findByHash(hash, bytesize)
- -
- # Find movie by nfo
- if movie['nfo']:
- for nfo in movie['nfo']:
- @@ -103,6 +128,22 @@ class Library:
- if movie['movie']:
- movie['history'] = self.getHistory(movie['movie'])
- + movie['match'] = True
- + movie['info']['name'] = movie['movie'].name
- + movie['info']['year'] = movie['movie'].year
- + try:
- + movie['info']['quality'] = movie['history'].movieQueue.qualityType
- + except:
- + movie['info']['quality'] = qualities.guess([os.path.join(movie['path'], file['filename']) for file in movie['files']])
- +
- + for file in movie['files']:
- + movie['info']['size'] += file['size']
- +
- + movie['info']['size'] = str(movie['info']['size'])
- + movie['info']['group'] = self.getGroup(movie['folder'])
- + movie['info']['codec']['video'] = self.getCodec(movie['folder'], self.codecs['video'])
- + movie['info']['codec']['audio'] = self.getCodec(movie['folder'], self.codecs['audio'])
- +
- # Create filename without cd1/cd2 etc
- movie['filename'] = self.removeMultipart(os.path.splitext(movie['files'][0]['filename'])[0])
- @@ -126,7 +167,6 @@ class Library:
- return name
- def getHistory(self, movie):
- -
- for queue in movie.queue:
- if queue.renamehistory and queue.renamehistory[0]:
- return queue.renamehistory
- @@ -135,41 +175,96 @@ class Library:
- def determineMovie(self, movie):
- + movieName = self.cleanName(movie['folder'])
- + movieYear = self.findYear(movie['folder'])
- +
- + # check and see if name is in queue
- + queue = Db.query(MovieQueue).filter_by(name = movieName).first()
- + if queue:
- + log.info('Found movie via MovieQueue.')
- + return queue.Movie
- +
- for file in movie['files']:
- dirnames = movie['path'].split(os.path.sep)
- dirnames.append(file['filename'])
- dirnames.reverse()
- for dir in dirnames:
- - dir = latinToAscii(dir)
- -
- - # check and see if name is in queue
- - queue = Db.query(MovieQueue).filter_by(name = dir).first()
- - if queue:
- - log.info('Found movie via MovieQueue.')
- - return queue.Movie
- + dir = self.cleanName(dir)
- # last resort, match every word in path to db
- lastResort = {}
- dirSplit = re.split('\W+', dir.lower())
- for s in dirSplit:
- if s:
- - results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).all()
- + results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).filter_by(year = movieYear).all()
- for r in results:
- lastResort[r.id] = r
- for l in lastResort.itervalues():
- wordCount = 0
- - words = re.split('\W+', l.name.lower())
- - for word in words:
- - if word in dir.lower():
- + for word in dirSplit:
- + if word in l.name.lower():
- wordCount += 1
- - if wordCount == len(words) and len(words) > 0 and str(l.year) in dir:
- + if wordCount == len(dirSplit) and len(dirSplit) > 0:
- return l
- + # Search tMDB
- + if movieName:
- + log.info('Searching for "%s".' % movie['folder'])
- + result = cherrypy.config['searchers']['movie'].find(movieName + ' ' + movieYear, limit = 1)
- + if result:
- + movie = self.getMovieByIMDB(result.imdb)
- +
- + if not movie:
- + new = Movie()
- + Db.add(new)
- +
- + try:
- + # Add found movie as downloaded
- + new.status = u'downloaded'
- + new.name = result.name
- + new.imdb = result.imdb
- + new.movieDb = result.id
- + new.year = result.year
- + Db.flush()
- +
- + return new
- + except Exception, e:
- + log.error('Movie could not be added to database %s. %s' % (result, e))
- + else:
- + return movie
- +
- return None
- + def cleanName(self, text):
- + cleaned = ' '.join(re.split('\W+', latinToAscii(text).lower()))
- + cleaned = re.sub(self.clean, ' ', cleaned)
- + year = self.findYear(cleaned)
- +
- + if year: # Split name on year
- + try:
- + movieName = cleaned.split(year).pop(0).strip()
- + return movieName
- + except:
- + pass
- + else: # Split name on multiple spaces
- + try:
- + movieName = cleaned.split(' ').pop(0).strip()
- + return movieName
- + except:
- + pass
- +
- + return None
- +
- + def findYear(self, text):
- + matches = re.search('(?P<year>[0-9]{4})', text)
- + if matches:
- + return matches.group('year')
- +
- + return ''
- +
- def getImdb(self, txt):
- try:
- m = re.search('(?P<id>tt[0-9{7}]+)', txt)
- @@ -189,6 +284,21 @@ class Library:
- return Db.query(Movie).filter_by(imdb = imdbId).first()
- + def getCodec(self, filename, codecs):
- + codecs = map(re.escape, codecs)
- + try:
- + codec = re.search('[^A-Z0-9](?P<codec>' + '|'.join(codecs) + ')[^A-Z0-9]', filename, re.I)
- + return (codec and codec.group('codec')) or ''
- + except:
- + return ''
- +
- + def getGroup(self, filename):
- + try:
- + group = re.search('-(?P<group>[A-Z0-9]+)$', filename, re.I)
- + return (group and group.group('group')) or ''
- + except:
- + return ''
- +
- def filesizeBetween(self, file, min = 0, max = 100000):
- try:
- return (min * 1048576) < os.path.getsize(file) < (max * 1048576)
- diff --git a/app/lib/provider/movie/search.py b/app/lib/provider/movie/search.py
- index 57ec717..a6d8f9b 100644
- --- a/app/lib/provider/movie/search.py
- +++ b/app/lib/provider/movie/search.py
- @@ -23,7 +23,7 @@ class movieSearcher():
- # Config imdbWrapper
- self.imdb = imdbWrapper(self.config)
- - self.sources.append(self.theMovieDb)
- + self.sources.append(self.imdb)
- # Update the cache
- movies = Db.query(Movie).order_by(Movie.name).filter(or_(Movie.status == u'want', Movie.status == u'waiting')).all()
- @@ -34,7 +34,7 @@ class movieSearcher():
- def find(self, q, limit = 8, alternative = True):
- ''' Find movie by name '''
- - q = unicode(q).lower()
- + q = unicode(q).lower().strip()
- for source in self.sources:
- result = source.find(q, limit = limit, alternative = alternative)
- @@ -42,7 +42,8 @@ class movieSearcher():
- results = []
- for r in result:
- results.append(self.checkResult(r))
- - return results
- +
- + return results if limit > 1 else results.pop(0)
- return []
- diff --git a/app/lib/provider/rss.py b/app/lib/provider/rss.py
- index 23ee5a6..f3d07bc 100644
- --- a/app/lib/provider/rss.py
- +++ b/app/lib/provider/rss.py
- @@ -2,6 +2,7 @@ from app import latinToAscii
- from app.config.cplog import CPLog
- from string import ascii_letters, digits
- from urllib2 import URLError
- +import cherrypy
- import math
- import re
- import time
- @@ -75,6 +76,8 @@ class rss:
- def isAvailable(self, testUrl):
- + if cherrypy.config.get('debug'): return True
- +
- now = time.time()
- if self.availableCheck < now - 900:
- diff --git a/app/lib/provider/subtitle/sources/opensubtitles.py b/app/lib/provider/subtitle/sources/opensubtitles.py
- index 439d44f..6441e90 100644
- --- a/app/lib/provider/subtitle/sources/opensubtitles.py
- +++ b/app/lib/provider/subtitle/sources/opensubtitles.py
- @@ -1,3 +1,4 @@
- +from app import latinToAscii
- from app.config.cplog import CPLog
- from app.lib import hashFile
- from app.lib.provider.subtitle.base import subtitleBase
- @@ -17,6 +18,7 @@ class openSubtitles(subtitleBase):
- siteUrl = "http://www.opensubtitles.org/"
- searchUrl = "http://api.opensubtitles.org/xml-rpc"
- hashes = {}
- + token = None
- def __init__(self, config, extensions):
- self.config = config
- @@ -28,20 +30,26 @@ class openSubtitles(subtitleBase):
- return self.config.get('Subtitles', value)
- def login(self):
- - self.wait()
- - self.server = xmlrpclib.Server(self.searchUrl)
- - self.lastUse = time.time()
- - try:
- - log_result = self.server.LogIn("", "", "eng", "CouchPotato")
- - self.token = log_result["token"]
- - except Exception:
- - log.error("Open subtitles could not be contacted for login")
- - self.token = None
- + if not self.token:
- + self.wait()
- + self.server = xmlrpclib.Server(self.searchUrl)
- + self.lastUse = time.time()
- +
- + try:
- + log_result = self.server.LogIn("", "", "eng", "CouchPotato")
- + self.token = log_result["token"]
- + log.debug("Logged into OpenSubtitles %s." % self.token)
- + except Exception:
- + log.error("OpenSubtitles could not be contacted for login")
- + self.token = None
- + return False
- +
- + return True;
- def find(self, movie):
- - if not self.isAvailable(self.siteUrl):
- + if not self.isAvailable(self.siteUrl) and self.login():
- return
- data = {
- @@ -78,7 +86,7 @@ class openSubtitles(subtitleBase):
- return data
- def getInfo(self, filePath):
- - key = md5(filePath).digest()
- + key = md5(latinToAscii(filePath)).digest()
- if not self.hashes.get(key):
- self.hashes[key] = {
- 'moviehash': hashFile(filePath),
- diff --git a/app/lib/provider/yarr/base.py b/app/lib/provider/yarr/base.py
- index c4ffcbf..9fce63a 100644
- --- a/app/lib/provider/yarr/base.py
- +++ b/app/lib/provider/yarr/base.py
- @@ -19,7 +19,7 @@ class nzbBase(rss):
- 'unrated:1',
- 'x264:1',
- 'DTS:4', 'AC3:2',
- - '720p:2', '1080p:2', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1',
- + '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1',
- 'metis:1', 'diamond:1', 'wiki:1', 'CBGB:1',
- 'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10'
- ]
- @@ -79,6 +79,13 @@ class nzbBase(rss):
- if str(movie.year) in name:
- score = score + 1
- + # Contains preferred word
- + nzbWords = re.split('\W+', self.toSearchString(name).lower())
- + preferredWords = self.config.get('global', 'preferredWords').split(',')
- + for word in preferredWords:
- + if word.strip() and word.strip().lower() in nzbWords:
- + score = score + 100
- +
- return score
- def nameRatioScore(self, nzbName, movieName):
- @@ -93,7 +100,7 @@ class nzbBase(rss):
- else:
- return 0
- - def isCorrectMovie(self, item, movie, qualityType, imdbResults = False):
- + def isCorrectMovie(self, item, movie, qualityType, imdbResults = False, singleCategory = False):
- # Ignore already added.
- if self.alreadyTried(item, movie.id):
- @@ -112,7 +119,7 @@ class nzbBase(rss):
- type = q.types.get(qualityType)
- # Contains lower quality string
- - if self.containsOtherQuality(item.name, type):
- + if self.containsOtherQuality(item.name, type, singleCategory):
- log.info('Wrong: %s, looking for %s' % (item.name, type['label']))
- return False
- @@ -148,7 +155,7 @@ class nzbBase(rss):
- def alreadyTried(self, nzb, movie):
- return Db.query(History).filter(and_(History.movie == movie, History.value == str(nzb.id) + '-' + str(nzb.size), History.status == u'ignore')).first()
- - def containsOtherQuality(self, name, preferedType):
- + def containsOtherQuality(self, name, preferedType, singleCategory = False):
- nzbWords = re.split('\W+', self.toSearchString(name).lower())
- @@ -168,6 +175,9 @@ class nzbBase(rss):
- if found.get(allowed):
- del found[allowed]
- + if (len(found) == 0 and singleCategory):
- + return False
- +
- return not (found.get(preferedType['key']) and len(found) == 1)
- def checkIMDB(self, haystack, imdbId):
- diff --git a/app/lib/provider/yarr/sources/newznab.py b/app/lib/provider/yarr/sources/newznab.py
- index 74c0d3e..6c8bd77 100644
- --- a/app/lib/provider/yarr/sources/newznab.py
- +++ b/app/lib/provider/yarr/sources/newznab.py
- @@ -48,15 +48,17 @@ class newznab(nzbBase):
- if not self.enabled() or not self.isAvailable(self.getUrl(self.searchUrl)):
- return results
- + catId = self.getCatId(type)
- arguments = urlencode({
- 'imdbid': movie.imdb.replace('tt', ''),
- - 'cat': self.getCatId(type),
- + 'cat': catId,
- 'apikey': self.conf('apikey'),
- 't': self.searchUrl,
- 'extended': 1
- })
- url = "%s&%s" % (self.getUrl(self.searchUrl), arguments)
- - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
- + cacheId = str(movie.imdb) + '-' + str(catId)
- + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
- try:
- cached = False
- @@ -106,7 +108,7 @@ class newznab(nzbBase):
- new.content = self.gettextelement(nzb, "description")
- new.score = self.calcScore(new, movie)
- - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True):
- + if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
- results.append(new)
- log.info('Found: %s' % new.name)
- diff --git a/app/lib/provider/yarr/sources/nzbmatrix.py b/app/lib/provider/yarr/sources/nzbmatrix.py
- index 8fec3f7..4c7f87d 100644
- --- a/app/lib/provider/yarr/sources/nzbmatrix.py
- +++ b/app/lib/provider/yarr/sources/nzbmatrix.py
- @@ -44,15 +44,17 @@ class nzbMatrix(nzbBase):
- if not self.enabled() or not self.isAvailable(self.searchUrl):
- return results
- + catId = self.getCatId(type)
- arguments = urlencode({
- 'term': movie.imdb,
- - 'subcat': self.getCatId(type),
- + 'subcat': catId,
- 'username': self.conf('username'),
- 'apikey': self.conf('apikey'),
- 'searchin': 'weblink'
- })
- url = "%s?%s" % (self.searchUrl, arguments)
- - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
- + cacheId = str(movie.imdb) + '-' + str(catId)
- + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
- try:
- cached = False
- @@ -103,7 +105,7 @@ class nzbMatrix(nzbBase):
- new.score = self.calcScore(new, movie)
- new.checkNZB = True
- - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True):
- + if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
- results.append(new)
- log.info('Found: %s' % new.name)
- diff --git a/app/lib/provider/yarr/sources/nzbs.py b/app/lib/provider/yarr/sources/nzbs.py
- index 3a0bc0b..d61ad67 100644
- --- a/app/lib/provider/yarr/sources/nzbs.py
- +++ b/app/lib/provider/yarr/sources/nzbs.py
- @@ -43,16 +43,18 @@ class nzbs(nzbBase):
- if not self.enabled() or not self.isAvailable(self.apiUrl + '?test' + self.getApiExt()):
- return results
- + catId = self.getCatId(type)
- arguments = urlencode({
- 'action':'search',
- 'q': self.toSearchString(movie.name),
- - 'catid':self.getCatId(type),
- - 'i':self.conf('id'),
- - 'h':self.conf('key'),
- + 'catid': catId,
- + 'i': self.conf('id'),
- + 'h': self.conf('key'),
- 'age': self.config.get('NZB', 'retention')
- })
- url = "%s?%s" % (self.apiUrl, arguments)
- - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
- + cacheId = str(movie.imdb) + '-' + str(catId)
- + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
- try:
- cached = False
- @@ -106,7 +108,7 @@ class nzbs(nzbBase):
- new.content = self.gettextelement(nzb, "description")
- new.score = self.calcScore(new, movie)
- - if self.isCorrectMovie(new, movie, type):
- + if self.isCorrectMovie(new, movie, type, singleCategory = singleCat):
- results.append(new)
- log.info('Found: %s' % new.name)
- diff --git a/app/lib/qualities.py b/app/lib/qualities.py
- index 457da79..ad14fd5 100644
- --- a/app/lib/qualities.py
- +++ b/app/lib/qualities.py
- @@ -15,7 +15,7 @@ class Qualities:
- 'dvdr': {'key': 'dvdr', 'size': (3000, 10000), 'order':4, 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img']},
- 'dvdrip': {'key': 'dvdrip', 'size': (600, 2400), 'order':5, 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
- 'scr': {'key': 'scr', 'size': (600, 1000), 'order':6, 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
- - 'r5': {'key': 'r5', 'size': (600, 1000), 'order':7, 'label': 'R5', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
- + 'r5': {'key': 'r5', 'size': (600, 1000), 'order':7, 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
- 'tc': {'key': 'tc', 'size': (600, 1000), 'order':8, 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
- 'ts': {'key': 'ts', 'size': (600, 1000), 'order':9, 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
- 'cam': {'key': 'cam', 'size': (600, 1000), 'order':10, 'label': 'Cam', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
- @@ -168,19 +168,17 @@ class Qualities:
- found = False
- for file in files:
- - checkThis = os.path.join(file.get('path'), file.get('filename'))
- -
- for type, quality in self.getTypes():
- # Check tags
- - if type in checkThis.lower(): found = True
- + if type in file.lower(): found = True
- for alt in quality.get('alternative'):
- - if alt in checkThis.lower():
- + if alt in file.lower():
- found = True
- # Check extension + filesize
- for ext in quality.get('ext'):
- - size = (os.path.getsize(checkThis) / 1024 / 1024)
- - if ext in checkThis.lower() and size >= self.minimumSize(type) and size <= self.maximumSize(type):
- + size = (os.path.getsize(file) / 1024 / 1024)
- + if ext in file.lower() and size >= self.minimumSize(type) and size <= self.maximumSize(type):
- found = True
- if found:
- diff --git a/app/views/base.html b/app/views/base.html
- index 8795c55..cd3195a 100644
- --- a/app/views/base.html
- +++ b/app/views/base.html
- @@ -38,15 +38,14 @@
- ]
- miniJs = Minify.js(jsFiles)
- %>
- - % if debug:
- +% if debug:
- % for jsFile in jsFiles:
- <script src="media/script/${jsFile}" type="text/javascript"></script>
- % endfor
- - % else:
- +% else:
- <script src="${miniJs}" type="text/javascript"></script>
- - % endif
- +% endif
- -
- <div id="header">
- <div class="navigation">
- <% tabs = [
- @@ -67,29 +66,14 @@
- </div>
- <script type="text/javascript">
- -
- - window.addEvent('domready', function(){
- - var showConfigPopup = function(e){
- - (e).stop()
- + window.addEvent('domready', function(){
- - var popup = new Element('div', {
- - 'class':'configPopup'
- - }).inject(document.body);
- -
- - new Request.HTML({
- - 'url': $('showConfig').get('href'),
- - 'update': popup,
- - 'method': 'get'
- - }).send()
- -
- - }
- $('showConfig').addEvents({
- - 'click': showConfigPopup,
- 'mouseover': function(){ this.setStyle('opacity', 0.5) },
- 'mouseout': function(){ this.setStyle('opacity', 1) }
- });
- -
- +
- var hasUpdate = $(document.body).getElement('.updateAvailable');
- var updateNow = function(updateThis){
- var us = new Spinner(updateThis).show()
- @@ -102,7 +86,7 @@
- }
- }).send()
- }
- -
- +
- if(hasUpdate){
- var clone = hasUpdate.clone()
- .addClass('noticeMe')
- @@ -119,7 +103,7 @@
- })
- }
- }
- -
- +
- })
- </script>
- diff --git a/app/views/config/index.html b/app/views/config/index.html
- index a2f5323..59c4330 100644
- --- a/app/views/config/index.html
- +++ b/app/views/config/index.html
- @@ -1,20 +1,18 @@
- <div id="configForm">
- <ul>
- <li><a href="#general">General</a></li>
- - <li><a href="#nzb">NZBs</a></li>
- - <li><a href="#torrents">Torrents</a></li>
- + <li><a href="#content">NZBs / Torrents</a></li>
- <li><a href="#providers">Providers</a></li>
- <li><a href="#quality">Quality</a></li>
- <li><a href="#renaming">Renaming</a></li>
- - <li><a href="#trailers">Trailers</a></li>
- - <li><a href="#subtitles">Subtitles</a></li>
- + <li><a href="#extra">Trailers / Subtitles</a></li>
- <li><a href="#userscript">Userscript</a></li>
- <li><a href="#about">About</a></li>
- </ul>
- <form class="uniForm" action="${url(controller="config", action="save")}" method="post">
- - <div class="formContent">
- - <fieldset class="general inlineLabels">
- + <div class="group general">
- + <fieldset class="inlineLabels">
- <h3>General (need manual restart)</h3>
- <div class="ctrlHolder">
- <label for="">Host</label>
- @@ -37,7 +35,7 @@
- <input type="checkbox" name="global.launchbrowser" value="True" ${' checked="checked"' if config.get('global', 'launchbrowser') else ''} />
- </div>
- </fieldset>
- - <fieldset class="general inlineLabels">
- + <fieldset class="inlineLabels">
- <h3>Search</h3>
- <div class="ctrlHolder">
- <label for="">Search every</label>
- @@ -50,8 +48,19 @@
- Comma seperated. NZB/Torrents containing these words are ignored. Example: GERMAN, DUBBED, HDTV
- </p>
- </div>
- + <div class="ctrlHolder">
- + <label for="">Preferred words</label>
- + <input type="text" name="global.preferredWords" value="${config.get('global', 'preferredWords')}" class="textInput large"/>
- + <p class="formHint">
- + Comma seperated. NZB/Torrents containing these words are preferred. Example: Diamond, BluRay, DTS
- + </p>
- + </div>
- </fieldset>
- - <fieldset class="nzb inlineLabels" id="nzbsFieldset">
- + </div>
- +
- + <div class="group content">
- + <fieldset class="inlineLabels" id="nzbsFieldset">
- + <h3>NZBs</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- <input type="checkbox" name="NZB.enabled" value="True" ${' checked="checked"' if config.get('NZB', 'enabled') else ''} />
- @@ -110,7 +119,8 @@
- </div>
- </fieldset>
- - <fieldset class="torrents inlineLabels" id="torrentsFieldset">
- + <fieldset class="inlineLabels" id="torrentsFieldset">
- + <h3>Torrents</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- <input type="checkbox" name="Torrents.enabled" value="True" ${' checked="checked"' if config.get('Torrents', 'enabled') else ''} />
- @@ -130,8 +140,10 @@
- </p>
- </div>
- </fieldset>
- -
- - <fieldset class="renaming inlineLabels" id="renamingFieldset">
- + </div>
- +
- + <div class="group renaming" id="renamingFieldset">
- + <fieldset class="inlineLabels">
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- <input type="checkbox" name="Renamer.enabled" value="True" ${' checked="checked"' if config.get('Renamer', 'enabled') else ''} />
- @@ -174,6 +186,8 @@
- <span><strong>cdNr</strong>: 1</span>
- </p>
- </div>
- + </fieldset>
- + <fieldset class="inlineLabels">
- <div class="ctrlHolder">
- <label for="">Name separator</label>
- <select name="Renamer.separator" class="textInput large">
- @@ -193,38 +207,10 @@
- </p>
- </div>
- </fieldset>
- - <fieldset class="trailers inlineLabels">
- - <div class="ctrlHolder">
- - <label for="">Download trailer</label>
- - <select name="Trailer.quality" class="textInput large">
- - <option value="">None</option>
- - % for format in trailerFormats:
- - <option value="${format}"${' selected="selected"' if format == config.get('Trailer', 'quality') else ''}>${format}</option>
- - % endfor
- - </select>
- - <p class="formHint">
- - If None, no trailer will be downloaded.
- - </p>
- - </div>
- - <div class="ctrlHolder">
- - <label for="">Naming</label>
- - <select name="Trailer.name" class="textInput large">
- - <option value="movie-trailer"${' selected="selected"' if 'movie-trailer' == config.get('Trailer', 'name') else ''}>movie-trailer.ext</option>
- - <option value="moviename-trailer"${' selected="selected"' if 'moviename-trailer' == config.get('Trailer', 'name') else ''}><filename>-trailer.ext</option>
- - </select>
- - </div>
- - </fieldset>
- + </div>
- - <fieldset class="providers inlineLabels">
- - <div class="ctrlHolder">
- - <label for="">TheMovieDB Api-Key</label>
- - <input type="text" name="TheMovieDB.key" value="${config.get('TheMovieDB', 'key')}" class="textInput large"/>
- - <p class="formHint">
- - Only change if you want to use your own key.
- - </p>
- - </div>
- - </fieldset>
- - <fieldset class="providers inlineLabels" id="nzbmatrixFieldset">
- + <div class="group providers">
- + <fieldset class="inlineLabels" id="nzbmatrixFieldset">
- <h3>NZBMatrix.com</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- @@ -246,59 +232,64 @@
- </p>
- </div>
- </fieldset>
- - <fieldset class="providers inlineLabels" id="nzbsorgFieldset">
- - <h3>NZBs.org</h3>
- + <fieldset class="inlineLabels" id="newznabFieldset">
- + <h3>Newznab</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- - <input type="checkbox" name="NZBsorg.enabled" value="True" ${' checked="checked"' if config.get('NZBsorg', 'enabled') else ''} />
- + <input type="checkbox" name="newznab.enabled" value="True" ${' checked="checked"' if config.get('newznab', 'enabled') else ''} />
- </div>
- <div class="ctrlHolder">
- - <label for="">Id</label>
- - <input type="text" name="NZBsorg.id" value="${config.get('NZBsorg', 'id')}" class="textInput large"/>
- + <label for="">Host</label>
- + <input type="text" name="newznab.host" value="${config.get('newznab', 'host')}" class="textInput large"/>
- <p class="formHint">
- - Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, number after "&i="
- + The hostname of your newznab provider. Example: "provider.com" or "provider.com/newznab".
- </p>
- </div>
- <div class="ctrlHolder">
- - <label for="">Key</label>
- - <input type="text" name="NZBsorg.key" value="${config.get('NZBsorg', 'key')}" class="textInput large"/>
- + <label for="">Apikey</label>
- + <input type="text" name="newznab.apikey" value="${config.get('newznab', 'apikey')}" class="textInput large"/>
- <p class="formHint">
- - Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, string after "&h="
- + Can be found after login on the "API" page, bottom left. The string after "&apikey=".
- </p>
- </div>
- - </fieldset>
- - <fieldset class="providers inlineLabels" id="newznabFieldset">
- - <h3>Newznab</h3>
- <p class="formHint">
- More info can be found at <a href="http://newznab.com" target="_blank">Newznab.com</a>.
- </p>
- + </fieldset>
- + <fieldset class="inlineLabels" id="nzbsorgFieldset">
- + <h3>NZBs.org</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- - <input type="checkbox" name="newznab.enabled" value="True" ${' checked="checked"' if config.get('newznab', 'enabled') else ''} />
- + <input type="checkbox" name="NZBsorg.enabled" value="True" ${' checked="checked"' if config.get('NZBsorg', 'enabled') else ''} />
- </div>
- <div class="ctrlHolder">
- - <label for="">Host</label>
- - <input type="text" name="newznab.host" value="${config.get('newznab', 'host')}" class="textInput large"/>
- + <label for="">Id</label>
- + <input type="text" name="NZBsorg.id" value="${config.get('NZBsorg', 'id')}" class="textInput large"/>
- <p class="formHint">
- - The hostname of your newznab provider. Example: "provider.com" or "provider.com/newznab".
- + Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, number after "&i="
- </p>
- </div>
- <div class="ctrlHolder">
- - <label for="">Apikey</label>
- - <input type="text" name="newznab.apikey" value="${config.get('newznab', 'apikey')}" class="textInput large"/>
- + <label for="">Key</label>
- + <input type="text" name="NZBsorg.key" value="${config.get('NZBsorg', 'key')}" class="textInput large"/>
- <p class="formHint">
- - Can be found after login on the "API" page, bottom left. The string after "&apikey=".
- + Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, string after "&h="
- </p>
- </div>
- </fieldset>
- + </div>
- - <fieldset class="userscript inlineLabels">
- + <div class="group userscript">
- + <fieldset class="inlineLabels">
- <a href="CouchPotato.user.js" class="submit userscriptInstall"><img src="media/images/userscriptPreview.png" /><br />Install UserScript</a>
- <p style="font-size:11px">
- Need a recent version of <a href="http://www.google.com/chrome/" target="_blank">Chrome</a> or when using Firefox, the <a href="https://addons.mozilla.org/en-US/firefox/addon/748/" target="_blank">Greasemonkey addon</a>.
- </p>
- </fieldset>
- - <fieldset class="about inlineLabels">
- + </div>
- +
- + <div class="group about">
- + <fieldset class="inlineLabels">
- <div>
- <a href="${url(controller='config',action='exit')}" class="submit">Shutdown</a><span class="or">or</span>
- <a href="${url(controller='config',action='restart')}" class="submit restart">Restart</a>
- @@ -317,16 +308,19 @@
- <a href="http://couchpotatoapp.com" target="_blank">CouchPotatoApp.com</a>
- </div>
- </fieldset>
- -
- - <fieldset class="quality inlineLabels qualityTemplates">
- + </div>
- +
- + <div class="group quality">
- + <fieldset class="inlineLabels qualityTemplates">
- <h3>Custom Qualities</h3>
- - <div class="customList"></div>
- - <div class="ctrlHolder">
- + <div>
- <a href="#" id="newTemplate">Create a new custom quality.</a>
- </div>
- + <div class="customList"></div>
- </fieldset>
- - <fieldset class="quality inlineLabels">
- + <fieldset class="inlineLabels">
- + <h3>Default Qualities</h3>
- <div class="ctrlHolder">
- <label for="">Default quality</label>
- <select name="Quality.default" class="textInput large">
- @@ -351,7 +345,7 @@
- </div>
- </div>
- </fieldset>
- - <fieldset class="quality inlineLabels qualitySizes">
- + <fieldset class="inlineLabels qualitySizes right">
- <h3>Quality sizes</h3>
- <p class="formHint">
- Only download the quality that is between these two sizes.<br />
- @@ -373,8 +367,33 @@
- </div>
- % endfor
- </fieldset>
- -
- - <fieldset class="subtitles inlineLabels" id="subtitleFieldset">
- + </div>
- +
- + <div class="group extra">
- + <fieldset class="inlineLabels">
- + <h3>Trailers</h3>
- + <div class="ctrlHolder">
- + <label for="">Download trailer</label>
- + <select name="Trailer.quality" class="textInput large">
- + <option value="">None</option>
- + % for format in trailerFormats:
- + <option value="${format}"${' selected="selected"' if format == config.get('Trailer', 'quality') else ''}>${format}</option>
- + % endfor
- + </select>
- + <p class="formHint">
- + If None, no trailer will be downloaded.
- + </p>
- + </div>
- + <div class="ctrlHolder">
- + <label for="">Naming</label>
- + <select name="Trailer.name" class="textInput large">
- + <option value="movie-trailer"${' selected="selected"' if 'movie-trailer' == config.get('Trailer', 'name') else ''}>movie-trailer.ext</option>
- + <option value="moviename-trailer"${' selected="selected"' if 'moviename-trailer' == config.get('Trailer', 'name') else ''}><filename>-trailer.ext</option>
- + </select>
- + </div>
- + </fieldset>
- + <fieldset class="inlineLabels" id="subtitleFieldset">
- + <h3>Subtitles</h3>
- <div class="ctrlHolder checkbox">
- <label for="">Enable</label>
- <input type="checkbox" name="Subtitles.enabled" value="True" ${' checked="checked"' if config.get('Subtitles', 'enabled') else ''} />
- @@ -497,8 +516,12 @@
- new Request({
- 'url': form.get('action'),
- 'data': form,
- + 'useSpinner': true,
- + 'spinnerOptions': {
- + 'target': form
- + },
- 'onComplete': function(){
- - $(document.body).get('mask').destroy();
- + history.back()
- }
- }).send()
- });
- @@ -538,7 +561,7 @@
- // Cancel button
- form.getElements('.cancel a').addEvent('click', function(e){
- (e).stop()
- - $(document.body).get('mask').destroy();
- + history.back();
- });
- // Shutdown button
- @@ -556,10 +579,6 @@
- }
- }).send()
- });
- -
- - $('configForm').position({
- - 'position': 'center'
- - });
- // Menu
- $('configForm').getElements('ul a').addEvent('click', function(e){
- @@ -569,27 +588,17 @@
- });
- var showForm = function(el, name){
- - form.getElements('fieldset').setStyle('display', 'none');
- - form.getElements('fieldset.'+name).setStyle('display', 'block')
- + form.getElements('.group').removeClass('current')
- + form.getElements('.group.'+name).addClass('current')
- $('configForm').getElements('ul a').removeClass('current');
- el.addClass('current');
- -
- - $(document.body).get('mask').position()
- }
- var firstItem = $('configForm').getElement('ul li:last-child a');
- showForm(firstItem, firstItem.get('href').substr(1));
- -
- - $(document.body).addClass('noscroll')
- - $(document.body).mask({
- - 'hideOnClick': true,
- - 'destroyOnHide': true,
- - 'onHide': function(){
- - $(document.body).removeClass('noscroll')
- - $('configForm').getParent().destroy();
- - }
- - }).show();
- });
- -</script>
- \ No newline at end of file
- +</script>
- +
- +<%inherit file="/base.html" />
- \ No newline at end of file
- diff --git a/changelog.md b/changelog.md
- index 7d9c144..55b7621 100644
- --- a/changelog.md
- +++ b/changelog.md
- @@ -1,8 +1,14 @@
- CouchPotato
- =====
- -Version 26 (not release):
- -
- +Version 26 (not released):
- +
- +* New: Preferred words for searching in "Settings >> General"
- +* Enhancement: Better movie detection when renaming.
- +* Enhancement: Settings page instead of popup.
- +* Enhancement: R5 DVD-R is allowed
- +* Fix: OpenSubtitle didn't always stay logged in.
- +* Fix: Move .idx files along with .sub files.
- Version 25:
- diff --git a/media/script/Quality.js b/media/script/Quality.js
- index 2ea6d90..fad7bad 100644
- --- a/media/script/Quality.js
- +++ b/media/script/Quality.js
- @@ -104,7 +104,7 @@ var Quality = new Class({
- }
- })
- )
- - ).inject(this.list)
- + ).inject(this.list, 'top')
- if(properties.types)
- Object.each(properties.types, function(type){
- diff --git a/media/style/Main.css b/media/style/Main.css
- index f5121b9..4482d87 100644
- --- a/media/style/Main.css
- +++ b/media/style/Main.css
- @@ -11,6 +11,7 @@ body {
- margin: 0;
- padding: 0;
- background: #fff;
- + overflow-y: scroll;
- }
- body.noscroll { overflow: hidden; }
- @@ -662,73 +663,62 @@ h1 {
- }
- /*** Settings ***/
- -.configPopup {
- -}
- -.mask {
- - background: url('../../media/images/mask.png');
- - width: 100% !important;
- +#configForm {
- + display: block;
- + margin: 0 -10px;
- }
- - .configPopup ul {
- + #configForm ul {
- float: left;
- border-right: none;
- - width: 200px;
- - text-align: right;
- - font-size: 19px;
- - padding: 0;
- - margin: 10px 0 0;
- - position:absolute;
- - z-index:999;
- + text-align: center;
- + font-size: 16px;
- + padding: 10px 0 0;
- + margin: 0;
- + width: 100%;
- }
- - .configPopup ul li {
- + #configForm ul li {
- list-style: none;
- - border-bottom: 1px solid #fafafa;
- + float: left;
- }
- - .configPopup ul li:last-child { border: none; }
- + #configForm ul li:first-child { margin-left: 7px; }
- - .configPopup ul li a {
- + #configForm ul li a {
- display: block;
- - padding: 10px 15px 10px 0;
- + padding: 10px 15px;
- color: #000;
- outline: none;
- + border-radius: 6px 6px 0 0;
- + -webkit-border-radius: 6px 6px 0 0;
- + -moz-border-radius: 6px 6px 0 0;
- }
- - .configPopup ul li a.current {
- - font-weight: bold;
- - background: #fff;
- + #configForm ul li a.current {
- + background: #fafafa;
- }
- -
- -
- -#configForm {
- - display: block;
- - width: 700px;
- - margin: 30px auto;
- - background: #fff;
- - border: 6px solid rgba(0,0,0, 0.25);
- - -webkit-border-radius: 6px;
- - -moz-border-radius: 6px;
- - background: #f5f5f5;
- - position:fixed;
- - z-index:101;
- - -moz-box-shadow: 0 0 100px rgba(0,0,0,0.75);
- - -webkit-box-shadow: 0 0 100px rgba(0,0,0,0.75);
- -}
- - #configForm .formContent {
- - height: 510px;
- - overflow: auto;
- - background: #fff;
- + #configForm form {
- display: block;
- clear: both;
- - float: right;
- - width: 500px;
- + position: static;
- }
- + #configForm .group {
- + background: #fafafa;
- + overflow: hidden;
- + padding: 30px 12px;
- + display: none;
- + border-radius: 6px;
- + -webkit-border-radius: 6px;
- + -moz-border-radius: 6px;
- + }
- + #configForm .group.current { display: block; }
- #configForm h3 {
- font-size: 19px;
- - padding: 0;
- - margin: 0 10px 10px 10px;
- + padding: 0 20px 10px 10px;
- + margin: 0;
- + background: #fafafa;
- }
- #configForm .infoMessage {
- @@ -737,16 +727,30 @@ h1 {
- }
- #configForm fieldset {
- - width: 460px;
- - padding: 20px 0 0 20px;
- + width: 458px;
- + float: left;
- + margin-bottom: 20px;
- + background: #fdfdfd;
- + border-radius: 3px;
- + -webkit-border-radius: 3px;
- + -moz-border-radius: 3px;
- + }
- + #configForm fieldset:nth-child(odd), #configForm fieldset.left {
- + float: left;
- + }
- + #configForm fieldset:nth-child(even), #configForm fieldset.right {
- + float: right;
- }
- #configForm .buttonHolder {
- - margin-top: 0;
- + background: none;
- + margin: 0;
- }
- #configForm .ctrlHolder {
- line-height: 25px;
- + padding: 10px;
- + border-bottom: 1px solid #f3f3f3;
- }
- #configForm .ctrlHolder:last-child {
- @@ -769,15 +773,20 @@ h1 {
- margin-right: 4px !important;
- }
- - #configForm .userscriptInstall {
- - padding: 20px 15px 10px;
- + #configForm .userscript fieldset {
- text-align: center;
- font-weight:bold;
- font-size: 15px;
- + margin: 0 auto;
- + width: 100%;
- + background: transparent;
- }
- - #configForm fieldset p {
- + #configForm .userscriptInstall {
- + padding: 20px 15px 10px;
- text-align: center;
- + font-weight:bold;
- + font-size: 15px;
- }
- #configForm .formHint span {
- @@ -789,12 +798,13 @@ h1 {
- #configForm .formHint {
- text-align: left;
- padding-top: 0;
- - line-height: 20px;
- + line-height: 22px;
- }
- #configForm fieldset > .formHint {
- - margin: 0 10px;
- + margin: 10px 30px;
- font-size: 12px;
- + text-align: center;
- }
- #configForm span.or {
- @@ -804,13 +814,12 @@ h1 {
- #configForm .standardQualities {
- float: left;
- - width: 275px;
- + width: 265px;
- }
- #configForm .standardQualities .item {
- clear: both;
- - border-bottom: 1px solid #f3f3f3;
- + border-bottom: 1px solid #ececec;
- overflow: hidden;
- - background: #fff;
- }
- #configForm .standardQualities .item:last-child { border: none; }
- #configForm .standardQualities .item:hover {
- @@ -827,7 +836,7 @@ h1 {
- }
- #configForm .handle {
- - background: #fff url('../../media/images/handle.png') center no-repeat;
- + background: url('../../media/images/handle.png') center no-repeat;
- display: block;
- width: 24px;
- height: 24px;
- @@ -841,7 +850,7 @@ h1 {
- }
- #configForm .qualityTemplates .template h4 {
- - margin: 0 0 0 160px;
- + margin: 0 0 0 173px;
- font-size: 16px;
- float: left;
- display: block;
- @@ -857,7 +866,6 @@ h1 {
- #configForm .qualityTemplates .types {
- float: left;
- - width: 275px;
- }
- #configForm .qualityTemplates .header {
- @@ -873,16 +881,16 @@ h1 {
- #configForm .qualityTemplates span.markComplete {
- display: block;
- float: left;
- - width: 40px;
- + width: 50px;
- text-align: center;
- }
- #configForm .qualityTemplates .item {
- display: block;
- clear: both;
- - border-bottom: 1px solid #f3f3f3;
- + border-bottom: 1px solid #ececec;
- overflow: hidden;
- - background: #fff;
- + width: 285px;
- }
- #configForm .qualityTemplates .item:last-child { border: none; }
- #configForm .qualityTemplates .item:hover {
- @@ -890,7 +898,7 @@ h1 {
- }
- #configForm .qualityTemplates select {
- - width: 90%;
- + width: 95%;
- margin: 3px 0 0 0;
- }
- @@ -922,13 +930,10 @@ h1 {
- }
- #configForm .qualitySizes {
- - overflow: hidden;
- }
- #configForm .qualitySizes label {
- - text-align: right;
- }
- #configForm .qualitySizes .ctrlHolder {
- - padding: 4px 10px 3px;
- }
- #configForm .qualitySizes .item {
- @@ -941,7 +946,7 @@ h1 {
- #configForm .qualitySizes .size {
- float: right;
- - width: 275px;
- + width: 270px;
- }
- #configForm .qualitySizes .size span {
- @@ -958,37 +963,42 @@ h1 {
- font-weight: bold;
- }
- - #configForm fieldset.about {
- - padding: 65px 20px 0;
- + #configForm .about {
- + padding: 65px 220px 20px;
- text-align:center;
- }
- + #configForm .about fieldset {
- + width: 100%;
- + background: transparent;
- + }
- - #configForm fieldset.about .submit {
- - background-color: #ff0000;
- - }
- -
- - #configForm fieldset.about .submit.restart {
- - background-color: #f78a00;
- - }
- -
- - #configForm #versionInfo {
- - margin-top: 30px;
- - }
- -
- - #configForm #donate {
- - padding: 50px 30px 10px;
- - font-style: italic;
- - font-size: 15px;
- - color: #999;
- - }
- + #configForm .about .submit {
- + background-color: #ff0000;
- + }
- +
- + #configForm .about .submit.restart {
- + background-color: #f78a00;
- + }
- - #configForm #donate div {
- - padding: 20px 0;
- + #configForm #versionInfo {
- + margin-top: 30px;
- + }
- +
- + #configForm #donate {
- + padding: 50px 30px 10px;
- + font-style: italic;
- + font-size: 15px;
- + color: #999;
- + }
- +
- + #configForm #donate div {
- + padding: 20px 0;
- + }
- +
- + #configForm .site {
- + font-size: 16px;
- + margin: 20px;
- }
- - #configForm .site {
- - font-size: 16px;
- - margin-top: 50px;
- - }
- .question {
- display: block;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement