Advertisement
Guest User

cable_guy

a guest
Jan 20th, 2011
240
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 73.87 KB | None | 0 0
  1. diff --git a/app/config/configApp.py b/app/config/configApp.py
  2. index ff37120..6ebda3c 100644
  3. --- a/app/config/configApp.py
  4. +++ b/app/config/configApp.py
  5. @@ -45,6 +45,7 @@ class configApp():
  6. self.setDefault('global', 'launchbrowser', True)
  7. self.setDefault('global', 'urlBase', '')
  8. self.setDefault('global', 'ignoreWords', '')
  9. + self.setDefault('global', 'preferredWords', '')
  10.  
  11. self.addSection('Renamer')
  12. self.setDefault('Renamer', 'enabled', False)
  13. diff --git a/app/config/updater.py b/app/config/updater.py
  14. index 2ff5267..58c028a 100644
  15. --- a/app/config/updater.py
  16. +++ b/app/config/updater.py
  17. @@ -5,6 +5,7 @@ from imdb.parser.http.bsouplxml._bsoup import SoupStrainer, BeautifulSoup
  18. from urllib2 import URLError
  19. import cherrypy
  20. import os
  21. +import subprocess
  22. import tarfile
  23. import time
  24. import urllib2
  25. @@ -33,6 +34,7 @@ class Updater(SimplePlugin):
  26. self.debug = cherrypy.config['debug']
  27. self.updatePath = os.path.join(self.cachePath, 'updates')
  28. self.historyFile = os.path.join(self.updatePath, 'history.txt')
  29. + self.tryGit = os.path.isdir(os.path.join(self.runPath, '.git'))
  30.  
  31. if not os.path.isdir(self.updatePath):
  32. os.mkdir(self.updatePath)
  33. @@ -56,7 +58,7 @@ class Updater(SimplePlugin):
  34. log.info("Updating")
  35. self.running = True
  36.  
  37. - result = self.doUpdateUnix()
  38. + result = self.doUpdate()
  39.  
  40. time.sleep(1)
  41. cherrypy.engine.restart()
  42. @@ -70,31 +72,47 @@ class Updater(SimplePlugin):
  43. if self.isFrozen:
  44. self.version = 'Windows build r%d' % version.windows
  45. else:
  46. - handle = open(self.historyFile, "r")
  47. - lineList = handle.readlines()
  48. - handle.close()
  49. - self.version = lineList[-1].replace('RuudBurger-CouchPotato-', '').replace('.tar.gz', '')
  50. + if self.tryGit:
  51. + try:
  52. + p = subprocess.Popen('git rev-parse HEAD', stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell = True, cwd = os.getcwd())
  53. + output, err = p.communicate()
  54. + self.version = output[:7]
  55. + except:
  56. + log.error('Failed using GIT, falling back on normal version check.')
  57. + self.tryGit = False
  58. + return self.getVersion(force)
  59. + else:
  60. + handle = open(self.historyFile, "r")
  61. + lineList = handle.readlines()
  62. + handle.close()
  63. + self.version = lineList[-1].replace('RuudBurger-CouchPotato-', '').replace('.tar.gz', '')
  64.  
  65. return self.version
  66.  
  67. def checkForUpdate(self):
  68.  
  69. - if self.debug:
  70. - return
  71. +# if self.debug:
  72. +# return
  73. +
  74. + if not self.version:
  75. + self.getVersion()
  76.  
  77. if self.isFrozen:
  78. self.updateAvailable = self.checkForUpdateWindows()
  79. else:
  80. - update = self.checkForUpdateUnix()
  81. + update = self.checkGitHubForUpdate()
  82. if update:
  83. history = open(self.historyFile, 'r').read()
  84. - self.updateAvailable = update.get('name').replace('.tar.gz', '') not in history
  85. + if self.tryGit:
  86. + self.updateAvailable = self.version not in update.get('name').replace('.tar.gz', '')
  87. + else:
  88. + self.updateAvailable = update.get('name').replace('.tar.gz', '') not in history
  89.  
  90. self.availableString = 'Update available' if self.updateAvailable else 'No update available'
  91. self.lastCheck = time.time()
  92. log.info(self.availableString)
  93.  
  94. - def checkForUpdateUnix(self):
  95. + def checkGitHubForUpdate(self):
  96. try:
  97. data = urllib2.urlopen(self.url, timeout = self.timeout)
  98. except (IOError, URLError):
  99. @@ -136,13 +154,33 @@ class Updater(SimplePlugin):
  100.  
  101. return False
  102.  
  103. - def doUpdateUnix(self):
  104. - update = self.checkForUpdateUnix()
  105. + def doUpdate(self):
  106. + update = self.checkGitHubForUpdate()
  107. if not update:
  108. return False
  109.  
  110. name = update.get('name')
  111. data = update.get('data')
  112. +
  113. + # Try git first
  114. + if self.tryGit:
  115. + try:
  116. + p = subprocess.Popen('git pull', stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell = True, cwd = os.getcwd())
  117. + output, err = p.communicate()
  118. +
  119. + if 'Aborting.' in output:
  120. + log.error("Couldn't update via git.")
  121. + self.tryGit = False
  122. + return False
  123. + elif 'Already up-to-date.' in output:
  124. + log.info('No need to update, already using latest version')
  125. + return True
  126. +
  127. + log.info('Update to %s successful, using GIT.' % name)
  128. + return True
  129. + except:
  130. + pass
  131. +
  132. destination = os.path.join(self.updatePath, update.get('name'))
  133.  
  134. if not os.path.isfile(destination):
  135. diff --git a/app/lib/cron/renamer.py b/app/lib/cron/renamer.py
  136. index dd19cd0..240d79b 100644
  137. --- a/app/lib/cron/renamer.py
  138. +++ b/app/lib/cron/renamer.py
  139. @@ -2,6 +2,7 @@ from app import latinToAscii
  140. from app.config.cplog import CPLog
  141. from app.config.db import Movie, RenameHistory, Session as Db, MovieQueue
  142. from app.lib.cron.base import cronBase
  143. +from app.lib.library import Library
  144. from app.lib.qualities import Qualities
  145. import cherrypy
  146. import fnmatch
  147. @@ -9,11 +10,10 @@ import os
  148. import re
  149. import shutil
  150. import time
  151. -import traceback
  152.  
  153. log = CPLog(__name__)
  154.  
  155. -class RenamerCron(cronBase):
  156. +class RenamerCron(cronBase, Library):
  157.  
  158. ''' Cronjob for renaming movies '''
  159.  
  160. @@ -21,16 +21,16 @@ class RenamerCron(cronBase):
  161. interval = 1 #minutes
  162. intervalSec = 10
  163. config = {}
  164. - trailer = {}
  165. - minimalFileSize = 1024 * 1024 * 10 # 10MB
  166. - ignoredInPath = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '/._'] #unpacking, smb-crap
  167. + #trailer = {}
  168. + #minimalFileSize = 1024 * 1024 * 10 # 10MB
  169. + #ignoredInPath = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '/._'] #unpacking, smb-crap
  170.  
  171. # Filetypes
  172. - movieExt = ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso', '*.img']
  173. - nfoExt = ['*.nfo']
  174. - audioCodecs = ['DTS', 'AC3', 'AC3D', 'MP3']
  175. - videoCodecs = ['x264', 'DivX', 'XViD']
  176. - subExt = ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass']
  177. + #movieExt = ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso', '*.img']
  178. + #nfoExt = ['*.nfo']
  179. + #audioCodecs = ['DTS', 'AC3', 'AC3D', 'MP3']
  180. + #videoCodecs = ['x264', 'DivX', 'XViD']
  181. + #subExt = ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass']
  182.  
  183. def conf(self, option):
  184. return self.config.get('Renamer', option)
  185. @@ -72,46 +72,28 @@ class RenamerCron(cronBase):
  186. if self.isDisabled():
  187. return
  188.  
  189. - allFiles = self.findFiles()
  190. + allMovies = self.getMovies(self.conf('download'))
  191.  
  192. - if allFiles:
  193. + if allMovies:
  194. log.debug("Ready to rename some files.")
  195.  
  196. - for files in allFiles:
  197. -
  198. - # See if imdb link is in nfo file
  199. - nfo = files.get('nfo')
  200. - movie = {}
  201. - if nfo:
  202. - nfoFile = open(os.path.join(nfo.get('path'), nfo.get('filename')), 'r').read()
  203. - imdbId = self.getImdb(nfoFile)
  204. - if imdbId:
  205. - log.info('Found movie via nfo file.')
  206. - movie = {
  207. - 'movie': self.getMovie(imdbId),
  208. - 'queue': None
  209. - }
  210. - # Try other methods
  211. - if not movie:
  212. - movie = self.determineMovie(files)
  213. -
  214. - if movie and movie.get('movie'):
  215. - finalDestination = self.renameFiles(files, movie['movie'], movie['queue'])
  216. - if self.config.get('Trailer', 'quality'):
  217. - self.trailerQueue.put({'movieId': movie['movie'].id, 'destination':finalDestination})
  218. -
  219. - # Search for subtitles
  220. + for movie in allMovies:
  221. +
  222. + if movie.get('match'):
  223. + finalDestination = self.renameFiles(movie)
  224. +
  225. + # Search for trailer & subtitles
  226. + cherrypy.config['cron']['trailer'].forDirectory(finalDestination['directory'])
  227. cherrypy.config['cron']['subtitle'].forDirectory(finalDestination['directory'])
  228. else:
  229. try:
  230. - file = files['files'][0]
  231. - path = file['path'].split(os.sep)
  232. + path = movie['path'].split(os.sep)
  233. path.extend(['_UNKNOWN_' + path.pop()])
  234. - shutil.move(file['path'], os.sep.join(path))
  235. + shutil.move(movie['path'], os.sep.join(path))
  236. except IOError:
  237. pass
  238.  
  239. - log.info('No Match found for: %s' % str(files['files']))
  240. + log.info('No Match found for: %s' % str(movie['info']['name']))
  241.  
  242. # Cleanup
  243. if self.conf('cleanup'):
  244. @@ -125,8 +107,9 @@ class RenamerCron(cronBase):
  245. log.debug(subfiles)
  246.  
  247. # Stop if something is unpacking
  248. - if '_unpack' in root.lower() or '_failed_' in root.lower() or '_unknown_' in root.lower():
  249. - break
  250. + for ignore in self.ignoredInPath:
  251. + if ignore in root.lower():
  252. + break
  253.  
  254. for filename in filenames:
  255. fullFilePath = os.path.join(root, filename)
  256. @@ -146,26 +129,13 @@ class RenamerCron(cronBase):
  257. except OSError:
  258. log.error('Tried to clean-up download folder, but "%s" isn\'t empty.' % root)
  259.  
  260. - def getQueue(self, movie):
  261. -
  262. - log.info('Finding quality for %s.' % movie.name)
  263. -
  264. - try:
  265. - # Assuming quality is the top most, as that should be the last downloaded..
  266. - for queue in movie.queue:
  267. - if queue.name:
  268. - return queue
  269. -
  270. - except TypeError:
  271. - return None
  272. -
  273. - def renameFiles(self, files, movie, queue = None):
  274. + def renameFiles(self, movie):
  275. '''
  276. rename files based on movie data & conf
  277. '''
  278.  
  279. multiple = False
  280. - if len(files['files']) > 1:
  281. + if len(movie['files']) > 1:
  282. multiple = True
  283.  
  284. destination = self.conf('destination')
  285. @@ -173,53 +143,35 @@ class RenamerCron(cronBase):
  286. fileNaming = self.conf('filenaming')
  287.  
  288. # Remove weird chars from moviename
  289. - moviename = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie.name)
  290. + moviename = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie['info'].get('name'))
  291.  
  292. # Put 'The' at the end
  293. namethe = moviename
  294. if moviename[:3].lower() == 'the':
  295. namethe = moviename[3:] + ', The'
  296.  
  297. - #quality
  298. - if not queue:
  299. - queue = self.getQueue(movie)
  300. -
  301. - if not queue:
  302. - quality = Qualities().guess(files['files'])
  303. - queueId = 0
  304. - else:
  305. - quality = Qualities.types[queue.qualityType]['label']
  306. - queueId = queue.id
  307. -
  308. replacements = {
  309. 'cd': '',
  310. 'cdNr': '',
  311. 'ext': '.mkv',
  312. 'namethe': namethe.strip(),
  313. 'thename': moviename.strip(),
  314. - 'year': movie.year,
  315. + 'year': movie['info']['year'],
  316. 'first': namethe[0].upper(),
  317. - 'quality': quality,
  318. + 'quality': movie['info']['quality'],
  319. + 'video': movie['info']['codec']['video'],
  320. + 'audio': movie['info']['codec']['audio'],
  321. + 'group': movie['info']['group']
  322. }
  323. +
  324. if multiple:
  325. cd = 1
  326.  
  327. justAdded = []
  328. -
  329. - totalSize = 0
  330. - for file in files['files']:
  331. - fullPath = os.path.join(file['path'], file['filename'])
  332. - totalSize += os.path.getsize(fullPath)
  333. -
  334. - # Do something with ISO, as they should be between DVDRip and BRRIP
  335. - ext = os.path.splitext(file['filename'])[1].lower()[1:]
  336. - if ext == 'iso':
  337. - totalSize -= (os.path.getsize(fullPath) / 1.6)
  338. - log.info('Total size of new files is %s.' % int(totalSize / 1024 / 1024))
  339. -
  340. finalDestination = None
  341. finalFilename = self.doReplace(fileNaming, replacements)
  342. - for file in sorted(files['files']):
  343. +
  344. + for file in movie['files']:
  345. log.info('Trying to find a home for: %s' % latinToAscii(file['filename']))
  346.  
  347. replacements['ext'] = file['ext']
  348. @@ -228,34 +180,26 @@ class RenamerCron(cronBase):
  349. replacements['cd'] = ' cd' + str(cd)
  350. replacements['cdNr'] = ' ' + str(cd)
  351.  
  352. - replacements['original'] = file['root']
  353. - replacements['video'] = self.getCodec(file['filename'], RenamerCron.videoCodecs)
  354. - replacements['audio'] = self.getCodec(file['filename'], RenamerCron.audioCodecs)
  355. - replacements['group'] = self.getGroup(file['root'])
  356. + replacements['original'] = file['filename']
  357.  
  358. folder = self.doReplace(folderNaming, replacements)
  359. filename = self.doReplace(fileNaming, replacements)
  360.  
  361. - old = os.path.join(file['path'], file['filename'])
  362. + old = os.path.join(movie['path'], file['filename'])
  363. dest = os.path.join(destination, folder, filename)
  364.  
  365. finalDestination = os.path.dirname(dest)
  366. if not os.path.isdir(finalDestination):
  367.  
  368. - # Use same permissions as conf('destination') folder
  369. try:
  370. - #mode = os.stat(destination)
  371. - #chmod = mode[ST_MODE] & 07777
  372. log.info('Creating directory %s' % finalDestination)
  373. os.makedirs(finalDestination)
  374. shutil.copymode(destination, finalDestination)
  375. - #os.chmod(finalDestination, chmod)
  376. except OSError:
  377. - log.error('Failed setting permissions for %s' % finalDestination)
  378. - os.makedirs(finalDestination)
  379. + log.error('Failed changing permissions %s' % finalDestination)
  380.  
  381. # Remove old if better quality
  382. - removed = self.removeOld(os.path.join(destination, folder), justAdded, totalSize)
  383. + removed = self.removeOld(os.path.join(destination, folder), justAdded, movie['info']['size'])
  384.  
  385. if not os.path.isfile(dest) and removed:
  386. log.info('Moving file "%s" to %s.' % (latinToAscii(old), dest))
  387. @@ -272,17 +216,21 @@ class RenamerCron(cronBase):
  388. break
  389.  
  390. #get subtitle if any & move
  391. - if len(files['subtitles']) > 0:
  392. - log.info('Moving matching subtitle.')
  393. - subtitle = files['subtitles'].pop(0)
  394. - replacements['ext'] = subtitle['ext']
  395. - subFilename = self.doReplace(fileNaming, replacements)
  396. - shutil.move(os.path.join(subtitle['path'], subtitle['filename']), os.path.join(destination, folder, subFilename))
  397. + for type in movie['subtitles']:
  398. + if len(movie['subtitles'][type]) > 0:
  399. + log.info('Moving matching subtitle.')
  400. +
  401. + subtitle = movie['subtitles'][type].pop(0)
  402. + replacements['ext'] = subtitle['ext']
  403. + subDest = os.path.join(destination, folder, self.doReplace(fileNaming, replacements))
  404. +
  405. + shutil.move(os.path.join(movie['path'], subtitle['filename']), subDest)
  406. + justAdded.append(subDest) # Add to ignore list when removing stuff.
  407.  
  408. # Add to renaming history
  409. h = RenameHistory()
  410. - h.movieId = movie.id
  411. - h.movieQueue = queueId
  412. + h.movieId = movie['movie'].id
  413. + h.movieQueue = movie['history'].movieQueue if movie['history'] else 0
  414. h.old = unicode(old.decode('utf-8'))
  415. h.new = unicode(dest.decode('utf-8'))
  416. Db.add(h)
  417. @@ -292,11 +240,11 @@ class RenamerCron(cronBase):
  418. cd += 1
  419.  
  420. # Mark movie downloaded
  421. - if queueId > 0:
  422. - if queue.markComplete:
  423. + if movie['queue'] and movie['queue'].id > 0:
  424. + if movie['queue'].markComplete:
  425. movie.status = u'downloaded'
  426.  
  427. - queue.completed = True
  428. + movie['queue'].completed = True
  429. Db.flush()
  430.  
  431. return {
  432. @@ -326,7 +274,7 @@ class RenamerCron(cronBase):
  433. if ('*.' + ext in self.movieExt or '*.' + ext in self.subExt) and not '-trailer' in filename:
  434. files.append(fullPath)
  435.  
  436. - log.info('Quality Old: %s, New %s.' % (int(oldSize / 1024 / 1024), int(newSize / 1024 / 1024)))
  437. + log.info('Quality Old: %d, New %d.' % (int(oldSize) / 1024 / 1024, int(newSize) / 1024 / 1024))
  438. if oldSize < newSize:
  439. for file in files:
  440. try:
  441. @@ -357,159 +305,6 @@ class RenamerCron(cronBase):
  442. def replaceDoubles(self, string):
  443. return string.replace(' ', ' ').replace(' .', '.')
  444.  
  445. - def getMovie(self, imdbId):
  446. - '''
  447. - Get movie based on IMDB id.
  448. - If not in local DB, go fetch it from theMovieDb
  449. - '''
  450. -
  451. - movie = Db.query(Movie).filter_by(imdb = imdbId).first()
  452. -
  453. - if not movie:
  454. - movie = self.searcher.get('movie').findByImdbId(imdbId)
  455. -
  456. - return movie
  457. -
  458. - def determineMovie(self, files):
  459. - '''
  460. - Try find movie based on folder names and MovieQueue table
  461. - '''
  462. -
  463. - for file in files['files']:
  464. - dirnames = file['path'].split(os.path.sep)
  465. - dirnames.append(file['filename'])
  466. - dirnames.reverse()
  467. -
  468. - for dir in dirnames:
  469. - dir = latinToAscii(dir)
  470. -
  471. - # check and see if name is in queue
  472. - queue = Db.query(MovieQueue).filter_by(name = dir).first()
  473. - if queue:
  474. - log.info('Found movie via MovieQueue.')
  475. - return {
  476. - 'queue': queue,
  477. - 'movie': queue.Movie
  478. - }
  479. -
  480. - # last resort, match every word in path to db
  481. - lastResort = {}
  482. - dirSplit = re.split('\W+', dir.lower())
  483. - for s in dirSplit:
  484. - if s:
  485. - results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).all()
  486. - for r in results:
  487. - lastResort[r.id] = r
  488. -
  489. - for l in lastResort.itervalues():
  490. - wordCount = 0
  491. - words = re.split('\W+', l.name.lower())
  492. - for word in words:
  493. - if word in dir.lower():
  494. - wordCount += 1
  495. -
  496. - if wordCount == len(words) and len(words) > 0 and str(l.year) in dir:
  497. - log.info('Found via last resort searching.')
  498. - return {
  499. - 'queue': None,
  500. - 'movie': l
  501. - }
  502. - # Try finding movie on theMovieDB
  503. - # more checking here..
  504. -
  505. - return False
  506. -
  507. - def getImdb(self, txt):
  508. -
  509. - try:
  510. - m = re.search('imdb.com/title/(?P<id>tt[0-9]+)', txt)
  511. - id = m.group('id')
  512. - if id:
  513. - return id
  514. - except AttributeError:
  515. - return False
  516. -
  517. - return False
  518. -
  519. - def getCodec(self, filename, codecs):
  520. - codecs = map(re.escape, codecs)
  521. - try:
  522. - codec = re.search('[^A-Z0-9](?P<codec>' + '|'.join(codecs) + ')[^A-Z0-9]', filename, re.I)
  523. - return (codec and codec.group('codec')) or 'unknown'
  524. - return 'unknown'
  525. - except:
  526. - log.info('Renaming: ' + traceback.format_exc())
  527. - return 'Exception'
  528. -
  529. - def getGroup(self, filename):
  530. - try:
  531. - group = re.search('-(?P<group>[A-Z0-9]+)$', filename, re.I)
  532. - return (group and group.group('group')) or 'unknown'
  533. - except:
  534. - log.info('Renaming: ' + traceback.format_exc())
  535. - return 'Exception'
  536. -
  537. - def findFiles(self):
  538. -
  539. - files = []
  540. -
  541. - path = self.conf('download')
  542. - for dir in os.listdir(path):
  543. - fullDirPath = os.path.join(path, dir)
  544. -
  545. - for root, subfiles, filenames in os.walk(fullDirPath):
  546. -
  547. - subfiles = {'nfo':{}, 'files':[], 'subtitles':[]}
  548. -
  549. - patterns = []
  550. - patterns.extend(self.movieExt)
  551. - patterns.extend(self.nfoExt)
  552. - patterns.extend(self.subExt)
  553. -
  554. - for pattern in patterns:
  555. - for filename in fnmatch.filter(filenames, pattern):
  556. - new = {
  557. - 'path': root,
  558. - 'filename': filename,
  559. - 'root' : os.path.splitext(filename)[0],
  560. - 'ext': os.path.splitext(filename)[1].lower()[1:], #[1:]to remove . from extension
  561. - }
  562. -
  563. - #nfo file
  564. - if('*.' + new.get('ext') in self.nfoExt):
  565. - subfiles['nfo'] = new
  566. - #subtitle file
  567. - elif('*.' + new.get('ext') in self.subExt):
  568. - subfiles['subtitles'].append(new)
  569. - else:
  570. - #ignore movies files / or not
  571. - if not self.ignoreFile(os.path.join(root, filename)):
  572. - subfiles['files'].append(new)
  573. -
  574. - if subfiles['files']:
  575. - files.append(subfiles)
  576. -
  577. - return files
  578. -
  579. - def ignoreFile(self, file):
  580. -
  581. - if re.search('(^|[\W_])sample\d*[\W_]', file.lower()):
  582. - return True
  583. -
  584. - # minimal size
  585. - if os.path.getsize(file) < self.minimalFileSize:
  586. - log.info('File to small.')
  587. - return True
  588. -
  589. - # ignoredpaths
  590. - for i in self.ignoredInPath:
  591. - if i in file.lower():
  592. - log.debug('File still unpacking.')
  593. - return True
  594. -
  595. - # All is OK
  596. - return False
  597. -
  598.  
  599. def startRenamerCron(config, searcher, debug):
  600. cron = RenamerCron()
  601. diff --git a/app/lib/cron/subtitle.py b/app/lib/cron/subtitle.py
  602. index 634892c..ad1db61 100644
  603. --- a/app/lib/cron/subtitle.py
  604. +++ b/app/lib/cron/subtitle.py
  605. @@ -97,7 +97,7 @@ class SubtitleCron(rss, cronBase, Library):
  606. movies = self.getMovies(directory)
  607.  
  608. for movie in movies:
  609. - if not movie.get('subtitles'):
  610. + if not movie['subtitles']['files']:
  611. subtitleQueue.put(movie)
  612.  
  613. log.info('Done adding movies to subtitle search.')
  614. diff --git a/app/lib/cron/trailer.py b/app/lib/cron/trailer.py
  615. index 2c7ff41..6f38e45 100644
  616. --- a/app/lib/cron/trailer.py
  617. +++ b/app/lib/cron/trailer.py
  618. @@ -69,7 +69,7 @@ class TrailerCron(rss, cronBase, Library):
  619. elif not self.isEnabled():
  620. return
  621.  
  622. - log.info('Adding movies to trailer search.')
  623. + if not directory: log.info('Adding movies to trailer search.')
  624. self.searchingExisting = time.time()
  625. movies = self.getMovies(directory)
  626.  
  627. @@ -77,14 +77,7 @@ class TrailerCron(rss, cronBase, Library):
  628. if not movie.get('trailer') or force:
  629. trailerQueue.put(movie)
  630.  
  631. - log.info('Done adding movies to trailer search.')
  632. -
  633. - def findYear(self, text):
  634. - matches = re.search('(?P<year>[0-9]{4})', text)
  635. - if matches:
  636. - return matches.group('year')
  637. -
  638. - return None
  639. + if not directory: log.info('Done adding movies to trailer search.')
  640.  
  641. def search(self, movie):
  642. log.info('Search for trailer for: %s' % movie['folder'])
  643. diff --git a/app/lib/library.py b/app/lib/library.py
  644. index 9bf371e..7a09c7b 100644
  645. --- a/app/lib/library.py
  646. +++ b/app/lib/library.py
  647. @@ -2,6 +2,7 @@ from app import latinToAscii
  648. from app.config.cplog import CPLog
  649. from app.config.db import Movie, Session as Db, MovieQueue
  650. from app.lib import hashFile
  651. +from app.lib.qualities import Qualities
  652. import cherrypy
  653. import fnmatch
  654. import os
  655. @@ -19,10 +20,17 @@ class Library:
  656. extensions = {
  657. 'movie': ['*.mkv', '*.wmv', '*.avi', '*.mpg', '*.mpeg', '*.mp4', '*.m2ts', '*.iso'],
  658. 'nfo': ['*.nfo'],
  659. - 'subtitle': ['*.sub', '*.srt', '*.idx', '*.ssa', '*.ass'],
  660. + 'subtitle': ['*.sub', '*.srt', '*.ssa', '*.ass'],
  661. + 'subtitleExtras': ['*.idx'],
  662. 'trailer': ['*.mov', '*.mp4', '*.flv']
  663. }
  664. + codecs = {
  665. + 'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
  666. + 'video': ['x264', 'divx', 'xvid']
  667. + }
  668. +
  669. # From Plex/XBMC
  670. + 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]*'
  671. multipartRegEx = [
  672. '[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
  673. '[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
  674. @@ -37,8 +45,9 @@ class Library:
  675. def getMovies(self, folder = None):
  676.  
  677. movies = []
  678. + qualities = Qualities()
  679.  
  680. - movieFolder = folder if folder else self.config.get('Renamer', 'destination')
  681. + movieFolder = unicode(folder if folder else self.config.get('Renamer', 'destination'))
  682. if not os.path.isdir(movieFolder):
  683. log.error('Can\'t find directory: %s' % movieFolder)
  684. return movies
  685. @@ -48,10 +57,27 @@ class Library:
  686.  
  687. movie = {
  688. 'movie': None,
  689. + 'queue': 0,
  690. + 'match': False,
  691. + 'info': {
  692. + 'name': None,
  693. + 'year': None,
  694. + 'quality': '',
  695. + 'size': 0,
  696. + 'codec': {
  697. + 'video': '',
  698. + 'audio': ''
  699. + },
  700. + 'group': ''
  701. + },
  702. 'history': None,
  703. 'path': root,
  704. 'folder': root.split(os.path.sep)[-1:].pop(),
  705. - 'nfo':[], 'files':[], 'subtitles':[], 'trailer':[]
  706. + 'nfo':[], 'files':[], 'trailer':[],
  707. + 'subtitles':{
  708. + 'files': [],
  709. + 'extras': []
  710. + }
  711. }
  712.  
  713. patterns = []
  714. @@ -59,7 +85,7 @@ class Library:
  715. patterns.extend(extType)
  716.  
  717. for pattern in patterns:
  718. - for filename in fnmatch.filter(filenames, pattern):
  719. + for filename in fnmatch.filter(sorted(filenames), pattern):
  720. fullFilePath = os.path.join(root, filename)
  721. new = {
  722. 'filename': filename,
  723. @@ -71,23 +97,22 @@ class Library:
  724. movie['nfo'].append(filename)
  725. #subtitle file
  726. elif('*.' + new.get('ext') in self.extensions['subtitle']):
  727. - movie['subtitles'].append(new)
  728. + movie['subtitles']['files'].append(new)
  729. + #idx files
  730. + elif('*.' + new.get('ext') in self.extensions['subtitleExtras']):
  731. + movie['subtitles']['extras'].append(new)
  732. #trailer file
  733. elif re.search('(^|[\W_])trailer\d*[\W_]', filename.lower()) and self.filesizeBetween(fullFilePath, 2, 250):
  734. movie['trailer'].append(new)
  735. else:
  736. #ignore movies files / or not
  737. if self.keepFile(fullFilePath):
  738. + new['hash'] = hashFile(fullFilePath) # Add movie hash
  739. + new['size'] = os.path.getsize(fullFilePath) # File size
  740. movie['files'].append(new)
  741.  
  742. if movie['files']:
  743.  
  744. - # Check hash
  745. -# fullPath = os.path.join(movie['path'], movie['files'][0]['filename'])
  746. -# hash = hashFile(fullPath)
  747. -# bytesize = os.path.getsize(fullPath)
  748. -# results = cherrypy.config['searchers']['movie'].findByHash(hash, bytesize)
  749. -
  750. # Find movie by nfo
  751. if movie['nfo']:
  752. for nfo in movie['nfo']:
  753. @@ -103,6 +128,22 @@ class Library:
  754. if movie['movie']:
  755. movie['history'] = self.getHistory(movie['movie'])
  756.  
  757. + movie['match'] = True
  758. + movie['info']['name'] = movie['movie'].name
  759. + movie['info']['year'] = movie['movie'].year
  760. + try:
  761. + movie['info']['quality'] = movie['history'].movieQueue.qualityType
  762. + except:
  763. + movie['info']['quality'] = qualities.guess([os.path.join(movie['path'], file['filename']) for file in movie['files']])
  764. +
  765. + for file in movie['files']:
  766. + movie['info']['size'] += file['size']
  767. +
  768. + movie['info']['size'] = str(movie['info']['size'])
  769. + movie['info']['group'] = self.getGroup(movie['folder'])
  770. + movie['info']['codec']['video'] = self.getCodec(movie['folder'], self.codecs['video'])
  771. + movie['info']['codec']['audio'] = self.getCodec(movie['folder'], self.codecs['audio'])
  772. +
  773. # Create filename without cd1/cd2 etc
  774. movie['filename'] = self.removeMultipart(os.path.splitext(movie['files'][0]['filename'])[0])
  775.  
  776. @@ -126,7 +167,6 @@ class Library:
  777. return name
  778.  
  779. def getHistory(self, movie):
  780. -
  781. for queue in movie.queue:
  782. if queue.renamehistory and queue.renamehistory[0]:
  783. return queue.renamehistory
  784. @@ -135,41 +175,96 @@ class Library:
  785.  
  786. def determineMovie(self, movie):
  787.  
  788. + movieName = self.cleanName(movie['folder'])
  789. + movieYear = self.findYear(movie['folder'])
  790. +
  791. + # check and see if name is in queue
  792. + queue = Db.query(MovieQueue).filter_by(name = movieName).first()
  793. + if queue:
  794. + log.info('Found movie via MovieQueue.')
  795. + return queue.Movie
  796. +
  797. for file in movie['files']:
  798. dirnames = movie['path'].split(os.path.sep)
  799. dirnames.append(file['filename'])
  800. dirnames.reverse()
  801.  
  802. for dir in dirnames:
  803. - dir = latinToAscii(dir)
  804. -
  805. - # check and see if name is in queue
  806. - queue = Db.query(MovieQueue).filter_by(name = dir).first()
  807. - if queue:
  808. - log.info('Found movie via MovieQueue.')
  809. - return queue.Movie
  810. + dir = self.cleanName(dir)
  811.  
  812. # last resort, match every word in path to db
  813. lastResort = {}
  814. dirSplit = re.split('\W+', dir.lower())
  815. for s in dirSplit:
  816. if s:
  817. - results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).all()
  818. + results = Db.query(Movie).filter(Movie.name.like('%' + s + '%')).filter_by(year = movieYear).all()
  819. for r in results:
  820. lastResort[r.id] = r
  821.  
  822. for l in lastResort.itervalues():
  823. wordCount = 0
  824. - words = re.split('\W+', l.name.lower())
  825. - for word in words:
  826. - if word in dir.lower():
  827. + for word in dirSplit:
  828. + if word in l.name.lower():
  829. wordCount += 1
  830.  
  831. - if wordCount == len(words) and len(words) > 0 and str(l.year) in dir:
  832. + if wordCount == len(dirSplit) and len(dirSplit) > 0:
  833. return l
  834.  
  835. + # Search tMDB
  836. + if movieName:
  837. + log.info('Searching for "%s".' % movie['folder'])
  838. + result = cherrypy.config['searchers']['movie'].find(movieName + ' ' + movieYear, limit = 1)
  839. + if result:
  840. + movie = self.getMovieByIMDB(result.imdb)
  841. +
  842. + if not movie:
  843. + new = Movie()
  844. + Db.add(new)
  845. +
  846. + try:
  847. + # Add found movie as downloaded
  848. + new.status = u'downloaded'
  849. + new.name = result.name
  850. + new.imdb = result.imdb
  851. + new.movieDb = result.id
  852. + new.year = result.year
  853. + Db.flush()
  854. +
  855. + return new
  856. + except Exception, e:
  857. + log.error('Movie could not be added to database %s. %s' % (result, e))
  858. + else:
  859. + return movie
  860. +
  861. return None
  862.  
  863. + def cleanName(self, text):
  864. + cleaned = ' '.join(re.split('\W+', latinToAscii(text).lower()))
  865. + cleaned = re.sub(self.clean, ' ', cleaned)
  866. + year = self.findYear(cleaned)
  867. +
  868. + if year: # Split name on year
  869. + try:
  870. + movieName = cleaned.split(year).pop(0).strip()
  871. + return movieName
  872. + except:
  873. + pass
  874. + else: # Split name on multiple spaces
  875. + try:
  876. + movieName = cleaned.split(' ').pop(0).strip()
  877. + return movieName
  878. + except:
  879. + pass
  880. +
  881. + return None
  882. +
  883. + def findYear(self, text):
  884. + matches = re.search('(?P<year>[0-9]{4})', text)
  885. + if matches:
  886. + return matches.group('year')
  887. +
  888. + return ''
  889. +
  890. def getImdb(self, txt):
  891. try:
  892. m = re.search('(?P<id>tt[0-9{7}]+)', txt)
  893. @@ -189,6 +284,21 @@ class Library:
  894.  
  895. return Db.query(Movie).filter_by(imdb = imdbId).first()
  896.  
  897. + def getCodec(self, filename, codecs):
  898. + codecs = map(re.escape, codecs)
  899. + try:
  900. + codec = re.search('[^A-Z0-9](?P<codec>' + '|'.join(codecs) + ')[^A-Z0-9]', filename, re.I)
  901. + return (codec and codec.group('codec')) or ''
  902. + except:
  903. + return ''
  904. +
  905. + def getGroup(self, filename):
  906. + try:
  907. + group = re.search('-(?P<group>[A-Z0-9]+)$', filename, re.I)
  908. + return (group and group.group('group')) or ''
  909. + except:
  910. + return ''
  911. +
  912. def filesizeBetween(self, file, min = 0, max = 100000):
  913. try:
  914. return (min * 1048576) < os.path.getsize(file) < (max * 1048576)
  915. diff --git a/app/lib/provider/movie/search.py b/app/lib/provider/movie/search.py
  916. index 57ec717..a6d8f9b 100644
  917. --- a/app/lib/provider/movie/search.py
  918. +++ b/app/lib/provider/movie/search.py
  919. @@ -23,7 +23,7 @@ class movieSearcher():
  920.  
  921. # Config imdbWrapper
  922. self.imdb = imdbWrapper(self.config)
  923. - self.sources.append(self.theMovieDb)
  924. + self.sources.append(self.imdb)
  925.  
  926. # Update the cache
  927. movies = Db.query(Movie).order_by(Movie.name).filter(or_(Movie.status == u'want', Movie.status == u'waiting')).all()
  928. @@ -34,7 +34,7 @@ class movieSearcher():
  929. def find(self, q, limit = 8, alternative = True):
  930. ''' Find movie by name '''
  931.  
  932. - q = unicode(q).lower()
  933. + q = unicode(q).lower().strip()
  934.  
  935. for source in self.sources:
  936. result = source.find(q, limit = limit, alternative = alternative)
  937. @@ -42,7 +42,8 @@ class movieSearcher():
  938. results = []
  939. for r in result:
  940. results.append(self.checkResult(r))
  941. - return results
  942. +
  943. + return results if limit > 1 else results.pop(0)
  944.  
  945. return []
  946.  
  947. diff --git a/app/lib/provider/rss.py b/app/lib/provider/rss.py
  948. index 23ee5a6..f3d07bc 100644
  949. --- a/app/lib/provider/rss.py
  950. +++ b/app/lib/provider/rss.py
  951. @@ -2,6 +2,7 @@ from app import latinToAscii
  952. from app.config.cplog import CPLog
  953. from string import ascii_letters, digits
  954. from urllib2 import URLError
  955. +import cherrypy
  956. import math
  957. import re
  958. import time
  959. @@ -75,6 +76,8 @@ class rss:
  960.  
  961. def isAvailable(self, testUrl):
  962.  
  963. + if cherrypy.config.get('debug'): return True
  964. +
  965. now = time.time()
  966.  
  967. if self.availableCheck < now - 900:
  968. diff --git a/app/lib/provider/subtitle/sources/opensubtitles.py b/app/lib/provider/subtitle/sources/opensubtitles.py
  969. index 439d44f..6441e90 100644
  970. --- a/app/lib/provider/subtitle/sources/opensubtitles.py
  971. +++ b/app/lib/provider/subtitle/sources/opensubtitles.py
  972. @@ -1,3 +1,4 @@
  973. +from app import latinToAscii
  974. from app.config.cplog import CPLog
  975. from app.lib import hashFile
  976. from app.lib.provider.subtitle.base import subtitleBase
  977. @@ -17,6 +18,7 @@ class openSubtitles(subtitleBase):
  978. siteUrl = "http://www.opensubtitles.org/"
  979. searchUrl = "http://api.opensubtitles.org/xml-rpc"
  980. hashes = {}
  981. + token = None
  982.  
  983. def __init__(self, config, extensions):
  984. self.config = config
  985. @@ -28,20 +30,26 @@ class openSubtitles(subtitleBase):
  986. return self.config.get('Subtitles', value)
  987.  
  988. def login(self):
  989. - self.wait()
  990. - self.server = xmlrpclib.Server(self.searchUrl)
  991. - self.lastUse = time.time()
  992.  
  993. - try:
  994. - log_result = self.server.LogIn("", "", "eng", "CouchPotato")
  995. - self.token = log_result["token"]
  996. - except Exception:
  997. - log.error("Open subtitles could not be contacted for login")
  998. - self.token = None
  999. + if not self.token:
  1000. + self.wait()
  1001. + self.server = xmlrpclib.Server(self.searchUrl)
  1002. + self.lastUse = time.time()
  1003. +
  1004. + try:
  1005. + log_result = self.server.LogIn("", "", "eng", "CouchPotato")
  1006. + self.token = log_result["token"]
  1007. + log.debug("Logged into OpenSubtitles %s." % self.token)
  1008. + except Exception:
  1009. + log.error("OpenSubtitles could not be contacted for login")
  1010. + self.token = None
  1011. + return False
  1012. +
  1013. + return True;
  1014.  
  1015. def find(self, movie):
  1016.  
  1017. - if not self.isAvailable(self.siteUrl):
  1018. + if not self.isAvailable(self.siteUrl) and self.login():
  1019. return
  1020.  
  1021. data = {
  1022. @@ -78,7 +86,7 @@ class openSubtitles(subtitleBase):
  1023. return data
  1024.  
  1025. def getInfo(self, filePath):
  1026. - key = md5(filePath).digest()
  1027. + key = md5(latinToAscii(filePath)).digest()
  1028. if not self.hashes.get(key):
  1029. self.hashes[key] = {
  1030. 'moviehash': hashFile(filePath),
  1031. diff --git a/app/lib/provider/yarr/base.py b/app/lib/provider/yarr/base.py
  1032. index c4ffcbf..9fce63a 100644
  1033. --- a/app/lib/provider/yarr/base.py
  1034. +++ b/app/lib/provider/yarr/base.py
  1035. @@ -19,7 +19,7 @@ class nzbBase(rss):
  1036. 'unrated:1',
  1037. 'x264:1',
  1038. 'DTS:4', 'AC3:2',
  1039. - '720p:2', '1080p:2', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1',
  1040. + '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1',
  1041. 'metis:1', 'diamond:1', 'wiki:1', 'CBGB:1',
  1042. 'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10'
  1043. ]
  1044. @@ -79,6 +79,13 @@ class nzbBase(rss):
  1045. if str(movie.year) in name:
  1046. score = score + 1
  1047.  
  1048. + # Contains preferred word
  1049. + nzbWords = re.split('\W+', self.toSearchString(name).lower())
  1050. + preferredWords = self.config.get('global', 'preferredWords').split(',')
  1051. + for word in preferredWords:
  1052. + if word.strip() and word.strip().lower() in nzbWords:
  1053. + score = score + 100
  1054. +
  1055. return score
  1056.  
  1057. def nameRatioScore(self, nzbName, movieName):
  1058. @@ -93,7 +100,7 @@ class nzbBase(rss):
  1059. else:
  1060. return 0
  1061.  
  1062. - def isCorrectMovie(self, item, movie, qualityType, imdbResults = False):
  1063. + def isCorrectMovie(self, item, movie, qualityType, imdbResults = False, singleCategory = False):
  1064.  
  1065. # Ignore already added.
  1066. if self.alreadyTried(item, movie.id):
  1067. @@ -112,7 +119,7 @@ class nzbBase(rss):
  1068. type = q.types.get(qualityType)
  1069.  
  1070. # Contains lower quality string
  1071. - if self.containsOtherQuality(item.name, type):
  1072. + if self.containsOtherQuality(item.name, type, singleCategory):
  1073. log.info('Wrong: %s, looking for %s' % (item.name, type['label']))
  1074. return False
  1075.  
  1076. @@ -148,7 +155,7 @@ class nzbBase(rss):
  1077. def alreadyTried(self, nzb, movie):
  1078. return Db.query(History).filter(and_(History.movie == movie, History.value == str(nzb.id) + '-' + str(nzb.size), History.status == u'ignore')).first()
  1079.  
  1080. - def containsOtherQuality(self, name, preferedType):
  1081. + def containsOtherQuality(self, name, preferedType, singleCategory = False):
  1082.  
  1083. nzbWords = re.split('\W+', self.toSearchString(name).lower())
  1084.  
  1085. @@ -168,6 +175,9 @@ class nzbBase(rss):
  1086. if found.get(allowed):
  1087. del found[allowed]
  1088.  
  1089. + if (len(found) == 0 and singleCategory):
  1090. + return False
  1091. +
  1092. return not (found.get(preferedType['key']) and len(found) == 1)
  1093.  
  1094. def checkIMDB(self, haystack, imdbId):
  1095. diff --git a/app/lib/provider/yarr/sources/newznab.py b/app/lib/provider/yarr/sources/newznab.py
  1096. index 74c0d3e..6c8bd77 100644
  1097. --- a/app/lib/provider/yarr/sources/newznab.py
  1098. +++ b/app/lib/provider/yarr/sources/newznab.py
  1099. @@ -48,15 +48,17 @@ class newznab(nzbBase):
  1100. if not self.enabled() or not self.isAvailable(self.getUrl(self.searchUrl)):
  1101. return results
  1102.  
  1103. + catId = self.getCatId(type)
  1104. arguments = urlencode({
  1105. 'imdbid': movie.imdb.replace('tt', ''),
  1106. - 'cat': self.getCatId(type),
  1107. + 'cat': catId,
  1108. 'apikey': self.conf('apikey'),
  1109. 't': self.searchUrl,
  1110. 'extended': 1
  1111. })
  1112. url = "%s&%s" % (self.getUrl(self.searchUrl), arguments)
  1113. - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
  1114. + cacheId = str(movie.imdb) + '-' + str(catId)
  1115. + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
  1116.  
  1117. try:
  1118. cached = False
  1119. @@ -106,7 +108,7 @@ class newznab(nzbBase):
  1120. new.content = self.gettextelement(nzb, "description")
  1121. new.score = self.calcScore(new, movie)
  1122.  
  1123. - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True):
  1124. + if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
  1125. results.append(new)
  1126. log.info('Found: %s' % new.name)
  1127.  
  1128. diff --git a/app/lib/provider/yarr/sources/nzbmatrix.py b/app/lib/provider/yarr/sources/nzbmatrix.py
  1129. index 8fec3f7..4c7f87d 100644
  1130. --- a/app/lib/provider/yarr/sources/nzbmatrix.py
  1131. +++ b/app/lib/provider/yarr/sources/nzbmatrix.py
  1132. @@ -44,15 +44,17 @@ class nzbMatrix(nzbBase):
  1133. if not self.enabled() or not self.isAvailable(self.searchUrl):
  1134. return results
  1135.  
  1136. + catId = self.getCatId(type)
  1137. arguments = urlencode({
  1138. 'term': movie.imdb,
  1139. - 'subcat': self.getCatId(type),
  1140. + 'subcat': catId,
  1141. 'username': self.conf('username'),
  1142. 'apikey': self.conf('apikey'),
  1143. 'searchin': 'weblink'
  1144. })
  1145. url = "%s?%s" % (self.searchUrl, arguments)
  1146. - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
  1147. + cacheId = str(movie.imdb) + '-' + str(catId)
  1148. + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
  1149.  
  1150. try:
  1151. cached = False
  1152. @@ -103,7 +105,7 @@ class nzbMatrix(nzbBase):
  1153. new.score = self.calcScore(new, movie)
  1154. new.checkNZB = True
  1155.  
  1156. - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True):
  1157. + if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
  1158. results.append(new)
  1159. log.info('Found: %s' % new.name)
  1160.  
  1161. diff --git a/app/lib/provider/yarr/sources/nzbs.py b/app/lib/provider/yarr/sources/nzbs.py
  1162. index 3a0bc0b..d61ad67 100644
  1163. --- a/app/lib/provider/yarr/sources/nzbs.py
  1164. +++ b/app/lib/provider/yarr/sources/nzbs.py
  1165. @@ -43,16 +43,18 @@ class nzbs(nzbBase):
  1166. if not self.enabled() or not self.isAvailable(self.apiUrl + '?test' + self.getApiExt()):
  1167. return results
  1168.  
  1169. + catId = self.getCatId(type)
  1170. arguments = urlencode({
  1171. 'action':'search',
  1172. 'q': self.toSearchString(movie.name),
  1173. - 'catid':self.getCatId(type),
  1174. - 'i':self.conf('id'),
  1175. - 'h':self.conf('key'),
  1176. + 'catid': catId,
  1177. + 'i': self.conf('id'),
  1178. + 'h': self.conf('key'),
  1179. 'age': self.config.get('NZB', 'retention')
  1180. })
  1181. url = "%s?%s" % (self.apiUrl, arguments)
  1182. - cacheId = str(movie.imdb) + '-' + str(self.getCatId(type))
  1183. + cacheId = str(movie.imdb) + '-' + str(catId)
  1184. + singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
  1185.  
  1186. try:
  1187. cached = False
  1188. @@ -106,7 +108,7 @@ class nzbs(nzbBase):
  1189. new.content = self.gettextelement(nzb, "description")
  1190. new.score = self.calcScore(new, movie)
  1191.  
  1192. - if self.isCorrectMovie(new, movie, type):
  1193. + if self.isCorrectMovie(new, movie, type, singleCategory = singleCat):
  1194. results.append(new)
  1195. log.info('Found: %s' % new.name)
  1196.  
  1197. diff --git a/app/lib/qualities.py b/app/lib/qualities.py
  1198. index 457da79..ad14fd5 100644
  1199. --- a/app/lib/qualities.py
  1200. +++ b/app/lib/qualities.py
  1201. @@ -15,7 +15,7 @@ class Qualities:
  1202. 'dvdr': {'key': 'dvdr', 'size': (3000, 10000), 'order':4, 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img']},
  1203. 'dvdrip': {'key': 'dvdrip', 'size': (600, 2400), 'order':5, 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
  1204. 'scr': {'key': 'scr', 'size': (600, 1000), 'order':6, 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
  1205. - 'r5': {'key': 'r5', 'size': (600, 1000), 'order':7, 'label': 'R5', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
  1206. + 'r5': {'key': 'r5', 'size': (600, 1000), 'order':7, 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
  1207. 'tc': {'key': 'tc', 'size': (600, 1000), 'order':8, 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
  1208. 'ts': {'key': 'ts', 'size': (600, 1000), 'order':9, 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
  1209. 'cam': {'key': 'cam', 'size': (600, 1000), 'order':10, 'label': 'Cam', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
  1210. @@ -168,19 +168,17 @@ class Qualities:
  1211. found = False
  1212.  
  1213. for file in files:
  1214. - checkThis = os.path.join(file.get('path'), file.get('filename'))
  1215. -
  1216. for type, quality in self.getTypes():
  1217. # Check tags
  1218. - if type in checkThis.lower(): found = True
  1219. + if type in file.lower(): found = True
  1220. for alt in quality.get('alternative'):
  1221. - if alt in checkThis.lower():
  1222. + if alt in file.lower():
  1223. found = True
  1224.  
  1225. # Check extension + filesize
  1226. for ext in quality.get('ext'):
  1227. - size = (os.path.getsize(checkThis) / 1024 / 1024)
  1228. - if ext in checkThis.lower() and size >= self.minimumSize(type) and size <= self.maximumSize(type):
  1229. + size = (os.path.getsize(file) / 1024 / 1024)
  1230. + if ext in file.lower() and size >= self.minimumSize(type) and size <= self.maximumSize(type):
  1231. found = True
  1232.  
  1233. if found:
  1234. diff --git a/app/views/base.html b/app/views/base.html
  1235. index 8795c55..cd3195a 100644
  1236. --- a/app/views/base.html
  1237. +++ b/app/views/base.html
  1238. @@ -38,15 +38,14 @@
  1239. ]
  1240. miniJs = Minify.js(jsFiles)
  1241. %>
  1242. - % if debug:
  1243. +% if debug:
  1244. % for jsFile in jsFiles:
  1245. <script src="media/script/${jsFile}" type="text/javascript"></script>
  1246. % endfor
  1247. - % else:
  1248. +% else:
  1249. <script src="${miniJs}" type="text/javascript"></script>
  1250. - % endif
  1251. +% endif
  1252.  
  1253. -
  1254. <div id="header">
  1255. <div class="navigation">
  1256. <% tabs = [
  1257. @@ -67,29 +66,14 @@
  1258. </div>
  1259.  
  1260. <script type="text/javascript">
  1261. -
  1262. - window.addEvent('domready', function(){
  1263.  
  1264. - var showConfigPopup = function(e){
  1265. - (e).stop()
  1266. + window.addEvent('domready', function(){
  1267.  
  1268. - var popup = new Element('div', {
  1269. - 'class':'configPopup'
  1270. - }).inject(document.body);
  1271. -
  1272. - new Request.HTML({
  1273. - 'url': $('showConfig').get('href'),
  1274. - 'update': popup,
  1275. - 'method': 'get'
  1276. - }).send()
  1277. -
  1278. - }
  1279. $('showConfig').addEvents({
  1280. - 'click': showConfigPopup,
  1281. 'mouseover': function(){ this.setStyle('opacity', 0.5) },
  1282. 'mouseout': function(){ this.setStyle('opacity', 1) }
  1283. });
  1284. -
  1285. +
  1286. var hasUpdate = $(document.body).getElement('.updateAvailable');
  1287. var updateNow = function(updateThis){
  1288. var us = new Spinner(updateThis).show()
  1289. @@ -102,7 +86,7 @@
  1290. }
  1291. }).send()
  1292. }
  1293. -
  1294. +
  1295. if(hasUpdate){
  1296. var clone = hasUpdate.clone()
  1297. .addClass('noticeMe')
  1298. @@ -119,7 +103,7 @@
  1299. })
  1300. }
  1301. }
  1302. -
  1303. +
  1304. })
  1305.  
  1306. </script>
  1307. diff --git a/app/views/config/index.html b/app/views/config/index.html
  1308. index a2f5323..59c4330 100644
  1309. --- a/app/views/config/index.html
  1310. +++ b/app/views/config/index.html
  1311. @@ -1,20 +1,18 @@
  1312. <div id="configForm">
  1313. <ul>
  1314. <li><a href="#general">General</a></li>
  1315. - <li><a href="#nzb">NZBs</a></li>
  1316. - <li><a href="#torrents">Torrents</a></li>
  1317. + <li><a href="#content">NZBs / Torrents</a></li>
  1318. <li><a href="#providers">Providers</a></li>
  1319. <li><a href="#quality">Quality</a></li>
  1320. <li><a href="#renaming">Renaming</a></li>
  1321. - <li><a href="#trailers">Trailers</a></li>
  1322. - <li><a href="#subtitles">Subtitles</a></li>
  1323. + <li><a href="#extra">Trailers / Subtitles</a></li>
  1324. <li><a href="#userscript">Userscript</a></li>
  1325. <li><a href="#about">About</a></li>
  1326. </ul>
  1327.  
  1328. <form class="uniForm" action="${url(controller="config", action="save")}" method="post">
  1329. - <div class="formContent">
  1330. - <fieldset class="general inlineLabels">
  1331. + <div class="group general">
  1332. + <fieldset class="inlineLabels">
  1333. <h3>General (need manual restart)</h3>
  1334. <div class="ctrlHolder">
  1335. <label for="">Host</label>
  1336. @@ -37,7 +35,7 @@
  1337. <input type="checkbox" name="global.launchbrowser" value="True" ${' checked="checked"' if config.get('global', 'launchbrowser') else ''} />
  1338. </div>
  1339. </fieldset>
  1340. - <fieldset class="general inlineLabels">
  1341. + <fieldset class="inlineLabels">
  1342. <h3>Search</h3>
  1343. <div class="ctrlHolder">
  1344. <label for="">Search every</label>
  1345. @@ -50,8 +48,19 @@
  1346. Comma seperated. NZB/Torrents containing these words are ignored. Example: GERMAN, DUBBED, HDTV
  1347. </p>
  1348. </div>
  1349. + <div class="ctrlHolder">
  1350. + <label for="">Preferred words</label>
  1351. + <input type="text" name="global.preferredWords" value="${config.get('global', 'preferredWords')}" class="textInput large"/>
  1352. + <p class="formHint">
  1353. + Comma seperated. NZB/Torrents containing these words are preferred. Example: Diamond, BluRay, DTS
  1354. + </p>
  1355. + </div>
  1356. </fieldset>
  1357. - <fieldset class="nzb inlineLabels" id="nzbsFieldset">
  1358. + </div>
  1359. +
  1360. + <div class="group content">
  1361. + <fieldset class="inlineLabels" id="nzbsFieldset">
  1362. + <h3>NZBs</h3>
  1363. <div class="ctrlHolder checkbox">
  1364. <label for="">Enable</label>
  1365. <input type="checkbox" name="NZB.enabled" value="True" ${' checked="checked"' if config.get('NZB', 'enabled') else ''} />
  1366. @@ -110,7 +119,8 @@
  1367. </div>
  1368. </fieldset>
  1369.  
  1370. - <fieldset class="torrents inlineLabels" id="torrentsFieldset">
  1371. + <fieldset class="inlineLabels" id="torrentsFieldset">
  1372. + <h3>Torrents</h3>
  1373. <div class="ctrlHolder checkbox">
  1374. <label for="">Enable</label>
  1375. <input type="checkbox" name="Torrents.enabled" value="True" ${' checked="checked"' if config.get('Torrents', 'enabled') else ''} />
  1376. @@ -130,8 +140,10 @@
  1377. </p>
  1378. </div>
  1379. </fieldset>
  1380. -
  1381. - <fieldset class="renaming inlineLabels" id="renamingFieldset">
  1382. + </div>
  1383. +
  1384. + <div class="group renaming" id="renamingFieldset">
  1385. + <fieldset class="inlineLabels">
  1386. <div class="ctrlHolder checkbox">
  1387. <label for="">Enable</label>
  1388. <input type="checkbox" name="Renamer.enabled" value="True" ${' checked="checked"' if config.get('Renamer', 'enabled') else ''} />
  1389. @@ -174,6 +186,8 @@
  1390. <span><strong>cdNr</strong>: 1</span>
  1391. </p>
  1392. </div>
  1393. + </fieldset>
  1394. + <fieldset class="inlineLabels">
  1395. <div class="ctrlHolder">
  1396. <label for="">Name separator</label>
  1397. <select name="Renamer.separator" class="textInput large">
  1398. @@ -193,38 +207,10 @@
  1399. </p>
  1400. </div>
  1401. </fieldset>
  1402. - <fieldset class="trailers inlineLabels">
  1403. - <div class="ctrlHolder">
  1404. - <label for="">Download trailer</label>
  1405. - <select name="Trailer.quality" class="textInput large">
  1406. - <option value="">None</option>
  1407. - % for format in trailerFormats:
  1408. - <option value="${format}"${' selected="selected"' if format == config.get('Trailer', 'quality') else ''}>${format}</option>
  1409. - % endfor
  1410. - </select>
  1411. - <p class="formHint">
  1412. - If None, no trailer will be downloaded.
  1413. - </p>
  1414. - </div>
  1415. - <div class="ctrlHolder">
  1416. - <label for="">Naming</label>
  1417. - <select name="Trailer.name" class="textInput large">
  1418. - <option value="movie-trailer"${' selected="selected"' if 'movie-trailer' == config.get('Trailer', 'name') else ''}>movie-trailer.ext</option>
  1419. - <option value="moviename-trailer"${' selected="selected"' if 'moviename-trailer' == config.get('Trailer', 'name') else ''}>&lt;filename&gt;-trailer.ext</option>
  1420. - </select>
  1421. - </div>
  1422. - </fieldset>
  1423. + </div>
  1424.  
  1425. - <fieldset class="providers inlineLabels">
  1426. - <div class="ctrlHolder">
  1427. - <label for="">TheMovieDB Api-Key</label>
  1428. - <input type="text" name="TheMovieDB.key" value="${config.get('TheMovieDB', 'key')}" class="textInput large"/>
  1429. - <p class="formHint">
  1430. - Only change if you want to use your own key.
  1431. - </p>
  1432. - </div>
  1433. - </fieldset>
  1434. - <fieldset class="providers inlineLabels" id="nzbmatrixFieldset">
  1435. + <div class="group providers">
  1436. + <fieldset class="inlineLabels" id="nzbmatrixFieldset">
  1437. <h3>NZBMatrix.com</h3>
  1438. <div class="ctrlHolder checkbox">
  1439. <label for="">Enable</label>
  1440. @@ -246,59 +232,64 @@
  1441. </p>
  1442. </div>
  1443. </fieldset>
  1444. - <fieldset class="providers inlineLabels" id="nzbsorgFieldset">
  1445. - <h3>NZBs.org</h3>
  1446. + <fieldset class="inlineLabels" id="newznabFieldset">
  1447. + <h3>Newznab</h3>
  1448. <div class="ctrlHolder checkbox">
  1449. <label for="">Enable</label>
  1450. - <input type="checkbox" name="NZBsorg.enabled" value="True" ${' checked="checked"' if config.get('NZBsorg', 'enabled') else ''} />
  1451. + <input type="checkbox" name="newznab.enabled" value="True" ${' checked="checked"' if config.get('newznab', 'enabled') else ''} />
  1452. </div>
  1453. <div class="ctrlHolder">
  1454. - <label for="">Id</label>
  1455. - <input type="text" name="NZBsorg.id" value="${config.get('NZBsorg', 'id')}" class="textInput large"/>
  1456. + <label for="">Host</label>
  1457. + <input type="text" name="newznab.host" value="${config.get('newznab', 'host')}" class="textInput large"/>
  1458. <p class="formHint">
  1459. - Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, number after "&amp;i="
  1460. + The hostname of your newznab provider. Example: "provider.com" or "provider.com/newznab".
  1461. </p>
  1462. </div>
  1463. <div class="ctrlHolder">
  1464. - <label for="">Key</label>
  1465. - <input type="text" name="NZBsorg.key" value="${config.get('NZBsorg', 'key')}" class="textInput large"/>
  1466. + <label for="">Apikey</label>
  1467. + <input type="text" name="newznab.apikey" value="${config.get('newznab', 'apikey')}" class="textInput large"/>
  1468. <p class="formHint">
  1469. - Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, string after "&amp;h="
  1470. + Can be found after login on the "API" page, bottom left. The string after "&amp;apikey=".
  1471. </p>
  1472. </div>
  1473. - </fieldset>
  1474. - <fieldset class="providers inlineLabels" id="newznabFieldset">
  1475. - <h3>Newznab</h3>
  1476. <p class="formHint">
  1477. More info can be found at <a href="http://newznab.com" target="_blank">Newznab.com</a>.
  1478. </p>
  1479. + </fieldset>
  1480. + <fieldset class="inlineLabels" id="nzbsorgFieldset">
  1481. + <h3>NZBs.org</h3>
  1482. <div class="ctrlHolder checkbox">
  1483. <label for="">Enable</label>
  1484. - <input type="checkbox" name="newznab.enabled" value="True" ${' checked="checked"' if config.get('newznab', 'enabled') else ''} />
  1485. + <input type="checkbox" name="NZBsorg.enabled" value="True" ${' checked="checked"' if config.get('NZBsorg', 'enabled') else ''} />
  1486. </div>
  1487. <div class="ctrlHolder">
  1488. - <label for="">Host</label>
  1489. - <input type="text" name="newznab.host" value="${config.get('newznab', 'host')}" class="textInput large"/>
  1490. + <label for="">Id</label>
  1491. + <input type="text" name="NZBsorg.id" value="${config.get('NZBsorg', 'id')}" class="textInput large"/>
  1492. <p class="formHint">
  1493. - The hostname of your newznab provider. Example: "provider.com" or "provider.com/newznab".
  1494. + Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, number after "&amp;i="
  1495. </p>
  1496. </div>
  1497. <div class="ctrlHolder">
  1498. - <label for="">Apikey</label>
  1499. - <input type="text" name="newznab.apikey" value="${config.get('newznab', 'apikey')}" class="textInput large"/>
  1500. + <label for="">Key</label>
  1501. + <input type="text" name="NZBsorg.key" value="${config.get('NZBsorg', 'key')}" class="textInput large"/>
  1502. <p class="formHint">
  1503. - Can be found after login on the "API" page, bottom left. The string after "&amp;apikey=".
  1504. + Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, string after "&amp;h="
  1505. </p>
  1506. </div>
  1507. </fieldset>
  1508. + </div>
  1509.  
  1510. - <fieldset class="userscript inlineLabels">
  1511. + <div class="group userscript">
  1512. + <fieldset class="inlineLabels">
  1513. <a href="CouchPotato.user.js" class="submit userscriptInstall"><img src="media/images/userscriptPreview.png" /><br />Install UserScript</a>
  1514. <p style="font-size:11px">
  1515. 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>.
  1516. </p>
  1517. </fieldset>
  1518. - <fieldset class="about inlineLabels">
  1519. + </div>
  1520. +
  1521. + <div class="group about">
  1522. + <fieldset class="inlineLabels">
  1523. <div>
  1524. <a href="${url(controller='config',action='exit')}" class="submit">Shutdown</a><span class="or">or</span>
  1525. <a href="${url(controller='config',action='restart')}" class="submit restart">Restart</a>
  1526. @@ -317,16 +308,19 @@
  1527. <a href="http://couchpotatoapp.com" target="_blank">CouchPotatoApp.com</a>
  1528. </div>
  1529. </fieldset>
  1530. -
  1531. - <fieldset class="quality inlineLabels qualityTemplates">
  1532. + </div>
  1533. +
  1534. + <div class="group quality">
  1535. + <fieldset class="inlineLabels qualityTemplates">
  1536. <h3>Custom Qualities</h3>
  1537. - <div class="customList"></div>
  1538. - <div class="ctrlHolder">
  1539. + <div>
  1540. <a href="#" id="newTemplate">Create a new custom quality.</a>
  1541. </div>
  1542. + <div class="customList"></div>
  1543. </fieldset>
  1544.  
  1545. - <fieldset class="quality inlineLabels">
  1546. + <fieldset class="inlineLabels">
  1547. + <h3>Default Qualities</h3>
  1548. <div class="ctrlHolder">
  1549. <label for="">Default quality</label>
  1550. <select name="Quality.default" class="textInput large">
  1551. @@ -351,7 +345,7 @@
  1552. </div>
  1553. </div>
  1554. </fieldset>
  1555. - <fieldset class="quality inlineLabels qualitySizes">
  1556. + <fieldset class="inlineLabels qualitySizes right">
  1557. <h3>Quality sizes</h3>
  1558. <p class="formHint">
  1559. Only download the quality that is between these two sizes.<br />
  1560. @@ -373,8 +367,33 @@
  1561. </div>
  1562. % endfor
  1563. </fieldset>
  1564. -
  1565. - <fieldset class="subtitles inlineLabels" id="subtitleFieldset">
  1566. + </div>
  1567. +
  1568. + <div class="group extra">
  1569. + <fieldset class="inlineLabels">
  1570. + <h3>Trailers</h3>
  1571. + <div class="ctrlHolder">
  1572. + <label for="">Download trailer</label>
  1573. + <select name="Trailer.quality" class="textInput large">
  1574. + <option value="">None</option>
  1575. + % for format in trailerFormats:
  1576. + <option value="${format}"${' selected="selected"' if format == config.get('Trailer', 'quality') else ''}>${format}</option>
  1577. + % endfor
  1578. + </select>
  1579. + <p class="formHint">
  1580. + If None, no trailer will be downloaded.
  1581. + </p>
  1582. + </div>
  1583. + <div class="ctrlHolder">
  1584. + <label for="">Naming</label>
  1585. + <select name="Trailer.name" class="textInput large">
  1586. + <option value="movie-trailer"${' selected="selected"' if 'movie-trailer' == config.get('Trailer', 'name') else ''}>movie-trailer.ext</option>
  1587. + <option value="moviename-trailer"${' selected="selected"' if 'moviename-trailer' == config.get('Trailer', 'name') else ''}>&lt;filename&gt;-trailer.ext</option>
  1588. + </select>
  1589. + </div>
  1590. + </fieldset>
  1591. + <fieldset class="inlineLabels" id="subtitleFieldset">
  1592. + <h3>Subtitles</h3>
  1593. <div class="ctrlHolder checkbox">
  1594. <label for="">Enable</label>
  1595. <input type="checkbox" name="Subtitles.enabled" value="True" ${' checked="checked"' if config.get('Subtitles', 'enabled') else ''} />
  1596. @@ -497,8 +516,12 @@
  1597. new Request({
  1598. 'url': form.get('action'),
  1599. 'data': form,
  1600. + 'useSpinner': true,
  1601. + 'spinnerOptions': {
  1602. + 'target': form
  1603. + },
  1604. 'onComplete': function(){
  1605. - $(document.body).get('mask').destroy();
  1606. + history.back()
  1607. }
  1608. }).send()
  1609. });
  1610. @@ -538,7 +561,7 @@
  1611. // Cancel button
  1612. form.getElements('.cancel a').addEvent('click', function(e){
  1613. (e).stop()
  1614. - $(document.body).get('mask').destroy();
  1615. + history.back();
  1616. });
  1617.  
  1618. // Shutdown button
  1619. @@ -556,10 +579,6 @@
  1620. }
  1621. }).send()
  1622. });
  1623. -
  1624. - $('configForm').position({
  1625. - 'position': 'center'
  1626. - });
  1627.  
  1628. // Menu
  1629. $('configForm').getElements('ul a').addEvent('click', function(e){
  1630. @@ -569,27 +588,17 @@
  1631.  
  1632. });
  1633. var showForm = function(el, name){
  1634. - form.getElements('fieldset').setStyle('display', 'none');
  1635. - form.getElements('fieldset.'+name).setStyle('display', 'block')
  1636. + form.getElements('.group').removeClass('current')
  1637. + form.getElements('.group.'+name).addClass('current')
  1638.  
  1639. $('configForm').getElements('ul a').removeClass('current');
  1640. el.addClass('current');
  1641. -
  1642. - $(document.body).get('mask').position()
  1643. }
  1644. var firstItem = $('configForm').getElement('ul li:last-child a');
  1645. showForm(firstItem, firstItem.get('href').substr(1));
  1646. -
  1647. - $(document.body).addClass('noscroll')
  1648. - $(document.body).mask({
  1649. - 'hideOnClick': true,
  1650. - 'destroyOnHide': true,
  1651. - 'onHide': function(){
  1652. - $(document.body).removeClass('noscroll')
  1653. - $('configForm').getParent().destroy();
  1654. - }
  1655. - }).show();
  1656.  
  1657. });
  1658.  
  1659. -</script>
  1660. \ No newline at end of file
  1661. +</script>
  1662. +
  1663. +<%inherit file="/base.html" />
  1664. \ No newline at end of file
  1665. diff --git a/changelog.md b/changelog.md
  1666. index 7d9c144..55b7621 100644
  1667. --- a/changelog.md
  1668. +++ b/changelog.md
  1669. @@ -1,8 +1,14 @@
  1670. CouchPotato
  1671. =====
  1672.  
  1673. -Version 26 (not release):
  1674. -
  1675. +Version 26 (not released):
  1676. +
  1677. +* New: Preferred words for searching in "Settings >> General"
  1678. +* Enhancement: Better movie detection when renaming.
  1679. +* Enhancement: Settings page instead of popup.
  1680. +* Enhancement: R5 DVD-R is allowed
  1681. +* Fix: OpenSubtitle didn't always stay logged in.
  1682. +* Fix: Move .idx files along with .sub files.
  1683.  
  1684. Version 25:
  1685.  
  1686. diff --git a/media/script/Quality.js b/media/script/Quality.js
  1687. index 2ea6d90..fad7bad 100644
  1688. --- a/media/script/Quality.js
  1689. +++ b/media/script/Quality.js
  1690. @@ -104,7 +104,7 @@ var Quality = new Class({
  1691. }
  1692. })
  1693. )
  1694. - ).inject(this.list)
  1695. + ).inject(this.list, 'top')
  1696.  
  1697. if(properties.types)
  1698. Object.each(properties.types, function(type){
  1699. diff --git a/media/style/Main.css b/media/style/Main.css
  1700. index f5121b9..4482d87 100644
  1701. --- a/media/style/Main.css
  1702. +++ b/media/style/Main.css
  1703. @@ -11,6 +11,7 @@ body {
  1704. margin: 0;
  1705. padding: 0;
  1706. background: #fff;
  1707. + overflow-y: scroll;
  1708. }
  1709. body.noscroll { overflow: hidden; }
  1710.  
  1711. @@ -662,73 +663,62 @@ h1 {
  1712. }
  1713.  
  1714. /*** Settings ***/
  1715. -.configPopup {
  1716. -}
  1717. -.mask {
  1718. - background: url('../../media/images/mask.png');
  1719. - width: 100% !important;
  1720. +#configForm {
  1721. + display: block;
  1722. + margin: 0 -10px;
  1723. }
  1724.  
  1725. - .configPopup ul {
  1726. + #configForm ul {
  1727. float: left;
  1728. border-right: none;
  1729. - width: 200px;
  1730. - text-align: right;
  1731. - font-size: 19px;
  1732. - padding: 0;
  1733. - margin: 10px 0 0;
  1734. - position:absolute;
  1735. - z-index:999;
  1736. + text-align: center;
  1737. + font-size: 16px;
  1738. + padding: 10px 0 0;
  1739. + margin: 0;
  1740. + width: 100%;
  1741. }
  1742.  
  1743. - .configPopup ul li {
  1744. + #configForm ul li {
  1745. list-style: none;
  1746. - border-bottom: 1px solid #fafafa;
  1747. + float: left;
  1748. }
  1749. - .configPopup ul li:last-child { border: none; }
  1750. + #configForm ul li:first-child { margin-left: 7px; }
  1751.  
  1752. - .configPopup ul li a {
  1753. + #configForm ul li a {
  1754. display: block;
  1755. - padding: 10px 15px 10px 0;
  1756. + padding: 10px 15px;
  1757. color: #000;
  1758. outline: none;
  1759. + border-radius: 6px 6px 0 0;
  1760. + -webkit-border-radius: 6px 6px 0 0;
  1761. + -moz-border-radius: 6px 6px 0 0;
  1762. }
  1763.  
  1764. - .configPopup ul li a.current {
  1765. - font-weight: bold;
  1766. - background: #fff;
  1767. + #configForm ul li a.current {
  1768. + background: #fafafa;
  1769. }
  1770. -
  1771. -
  1772. -#configForm {
  1773. - display: block;
  1774. - width: 700px;
  1775. - margin: 30px auto;
  1776. - background: #fff;
  1777. - border: 6px solid rgba(0,0,0, 0.25);
  1778. - -webkit-border-radius: 6px;
  1779. - -moz-border-radius: 6px;
  1780. - background: #f5f5f5;
  1781. - position:fixed;
  1782. - z-index:101;
  1783. - -moz-box-shadow: 0 0 100px rgba(0,0,0,0.75);
  1784. - -webkit-box-shadow: 0 0 100px rgba(0,0,0,0.75);
  1785. -}
  1786.  
  1787. - #configForm .formContent {
  1788. - height: 510px;
  1789. - overflow: auto;
  1790. - background: #fff;
  1791. + #configForm form {
  1792. display: block;
  1793. clear: both;
  1794. - float: right;
  1795. - width: 500px;
  1796. + position: static;
  1797. }
  1798. + #configForm .group {
  1799. + background: #fafafa;
  1800. + overflow: hidden;
  1801. + padding: 30px 12px;
  1802. + display: none;
  1803. + border-radius: 6px;
  1804. + -webkit-border-radius: 6px;
  1805. + -moz-border-radius: 6px;
  1806. + }
  1807. + #configForm .group.current { display: block; }
  1808.  
  1809. #configForm h3 {
  1810. font-size: 19px;
  1811. - padding: 0;
  1812. - margin: 0 10px 10px 10px;
  1813. + padding: 0 20px 10px 10px;
  1814. + margin: 0;
  1815. + background: #fafafa;
  1816. }
  1817.  
  1818. #configForm .infoMessage {
  1819. @@ -737,16 +727,30 @@ h1 {
  1820. }
  1821.  
  1822. #configForm fieldset {
  1823. - width: 460px;
  1824. - padding: 20px 0 0 20px;
  1825. + width: 458px;
  1826. + float: left;
  1827. + margin-bottom: 20px;
  1828. + background: #fdfdfd;
  1829. + border-radius: 3px;
  1830. + -webkit-border-radius: 3px;
  1831. + -moz-border-radius: 3px;
  1832. + }
  1833. + #configForm fieldset:nth-child(odd), #configForm fieldset.left {
  1834. + float: left;
  1835. + }
  1836. + #configForm fieldset:nth-child(even), #configForm fieldset.right {
  1837. + float: right;
  1838. }
  1839.  
  1840. #configForm .buttonHolder {
  1841. - margin-top: 0;
  1842. + background: none;
  1843. + margin: 0;
  1844. }
  1845.  
  1846. #configForm .ctrlHolder {
  1847. line-height: 25px;
  1848. + padding: 10px;
  1849. + border-bottom: 1px solid #f3f3f3;
  1850. }
  1851.  
  1852. #configForm .ctrlHolder:last-child {
  1853. @@ -769,15 +773,20 @@ h1 {
  1854. margin-right: 4px !important;
  1855. }
  1856.  
  1857. - #configForm .userscriptInstall {
  1858. - padding: 20px 15px 10px;
  1859. + #configForm .userscript fieldset {
  1860. text-align: center;
  1861. font-weight:bold;
  1862. font-size: 15px;
  1863. + margin: 0 auto;
  1864. + width: 100%;
  1865. + background: transparent;
  1866. }
  1867.  
  1868. - #configForm fieldset p {
  1869. + #configForm .userscriptInstall {
  1870. + padding: 20px 15px 10px;
  1871. text-align: center;
  1872. + font-weight:bold;
  1873. + font-size: 15px;
  1874. }
  1875.  
  1876. #configForm .formHint span {
  1877. @@ -789,12 +798,13 @@ h1 {
  1878. #configForm .formHint {
  1879. text-align: left;
  1880. padding-top: 0;
  1881. - line-height: 20px;
  1882. + line-height: 22px;
  1883. }
  1884.  
  1885. #configForm fieldset > .formHint {
  1886. - margin: 0 10px;
  1887. + margin: 10px 30px;
  1888. font-size: 12px;
  1889. + text-align: center;
  1890. }
  1891.  
  1892. #configForm span.or {
  1893. @@ -804,13 +814,12 @@ h1 {
  1894.  
  1895. #configForm .standardQualities {
  1896. float: left;
  1897. - width: 275px;
  1898. + width: 265px;
  1899. }
  1900. #configForm .standardQualities .item {
  1901. clear: both;
  1902. - border-bottom: 1px solid #f3f3f3;
  1903. + border-bottom: 1px solid #ececec;
  1904. overflow: hidden;
  1905. - background: #fff;
  1906. }
  1907. #configForm .standardQualities .item:last-child { border: none; }
  1908. #configForm .standardQualities .item:hover {
  1909. @@ -827,7 +836,7 @@ h1 {
  1910. }
  1911.  
  1912. #configForm .handle {
  1913. - background: #fff url('../../media/images/handle.png') center no-repeat;
  1914. + background: url('../../media/images/handle.png') center no-repeat;
  1915. display: block;
  1916. width: 24px;
  1917. height: 24px;
  1918. @@ -841,7 +850,7 @@ h1 {
  1919. }
  1920.  
  1921. #configForm .qualityTemplates .template h4 {
  1922. - margin: 0 0 0 160px;
  1923. + margin: 0 0 0 173px;
  1924. font-size: 16px;
  1925. float: left;
  1926. display: block;
  1927. @@ -857,7 +866,6 @@ h1 {
  1928.  
  1929. #configForm .qualityTemplates .types {
  1930. float: left;
  1931. - width: 275px;
  1932. }
  1933.  
  1934. #configForm .qualityTemplates .header {
  1935. @@ -873,16 +881,16 @@ h1 {
  1936. #configForm .qualityTemplates span.markComplete {
  1937. display: block;
  1938. float: left;
  1939. - width: 40px;
  1940. + width: 50px;
  1941. text-align: center;
  1942. }
  1943.  
  1944. #configForm .qualityTemplates .item {
  1945. display: block;
  1946. clear: both;
  1947. - border-bottom: 1px solid #f3f3f3;
  1948. + border-bottom: 1px solid #ececec;
  1949. overflow: hidden;
  1950. - background: #fff;
  1951. + width: 285px;
  1952. }
  1953. #configForm .qualityTemplates .item:last-child { border: none; }
  1954. #configForm .qualityTemplates .item:hover {
  1955. @@ -890,7 +898,7 @@ h1 {
  1956. }
  1957.  
  1958. #configForm .qualityTemplates select {
  1959. - width: 90%;
  1960. + width: 95%;
  1961. margin: 3px 0 0 0;
  1962. }
  1963.  
  1964. @@ -922,13 +930,10 @@ h1 {
  1965. }
  1966.  
  1967. #configForm .qualitySizes {
  1968. - overflow: hidden;
  1969. }
  1970. #configForm .qualitySizes label {
  1971. - text-align: right;
  1972. }
  1973. #configForm .qualitySizes .ctrlHolder {
  1974. - padding: 4px 10px 3px;
  1975. }
  1976.  
  1977. #configForm .qualitySizes .item {
  1978. @@ -941,7 +946,7 @@ h1 {
  1979.  
  1980. #configForm .qualitySizes .size {
  1981. float: right;
  1982. - width: 275px;
  1983. + width: 270px;
  1984. }
  1985.  
  1986. #configForm .qualitySizes .size span {
  1987. @@ -958,37 +963,42 @@ h1 {
  1988. font-weight: bold;
  1989. }
  1990.  
  1991. - #configForm fieldset.about {
  1992. - padding: 65px 20px 0;
  1993. + #configForm .about {
  1994. + padding: 65px 220px 20px;
  1995. text-align:center;
  1996. }
  1997. + #configForm .about fieldset {
  1998. + width: 100%;
  1999. + background: transparent;
  2000. + }
  2001.  
  2002. - #configForm fieldset.about .submit {
  2003. - background-color: #ff0000;
  2004. - }
  2005. -
  2006. - #configForm fieldset.about .submit.restart {
  2007. - background-color: #f78a00;
  2008. - }
  2009. -
  2010. - #configForm #versionInfo {
  2011. - margin-top: 30px;
  2012. - }
  2013. -
  2014. - #configForm #donate {
  2015. - padding: 50px 30px 10px;
  2016. - font-style: italic;
  2017. - font-size: 15px;
  2018. - color: #999;
  2019. - }
  2020. + #configForm .about .submit {
  2021. + background-color: #ff0000;
  2022. + }
  2023. +
  2024. + #configForm .about .submit.restart {
  2025. + background-color: #f78a00;
  2026. + }
  2027.  
  2028. - #configForm #donate div {
  2029. - padding: 20px 0;
  2030. + #configForm #versionInfo {
  2031. + margin-top: 30px;
  2032. + }
  2033. +
  2034. + #configForm #donate {
  2035. + padding: 50px 30px 10px;
  2036. + font-style: italic;
  2037. + font-size: 15px;
  2038. + color: #999;
  2039. + }
  2040. +
  2041. + #configForm #donate div {
  2042. + padding: 20px 0;
  2043. + }
  2044. +
  2045. + #configForm .site {
  2046. + font-size: 16px;
  2047. + margin: 20px;
  2048. }
  2049. - #configForm .site {
  2050. - font-size: 16px;
  2051. - margin-top: 50px;
  2052. - }
  2053.  
  2054. .question {
  2055. display: block;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement