Browse Source

Merge branch 'refs/heads/develop' into tv

pull/2139/merge
Ruud 12 years ago
parent
commit
73efd5549f
  1. 2
      couchpotato/core/downloaders/transmission/__init__.py
  2. 4
      couchpotato/core/downloaders/transmission/main.py
  3. 2
      couchpotato/core/downloaders/utorrent/__init__.py
  4. 19
      couchpotato/core/downloaders/utorrent/main.py
  5. 8
      couchpotato/core/helpers/variable.py
  6. 8
      couchpotato/core/notifications/xbmc/__init__.py
  7. 12
      couchpotato/core/notifications/xbmc/main.py
  8. 10
      couchpotato/core/plugins/automation/main.py
  9. 7
      couchpotato/core/plugins/renamer/__init__.py
  10. 45
      couchpotato/core/plugins/renamer/main.py
  11. 7
      couchpotato/core/plugins/scanner/main.py
  12. 30
      couchpotato/core/providers/automation/imdb/__init__.py
  13. 76
      couchpotato/core/providers/automation/imdb/main.py
  14. 16
      couchpotato/core/providers/automation/rottentomatoes/__init__.py
  15. 48
      couchpotato/core/providers/automation/rottentomatoes/main.py
  16. 1
      couchpotato/core/providers/movie/_modifier/main.py
  17. 1
      couchpotato/core/providers/movie/omdbapi/main.py
  18. 1
      couchpotato/core/providers/movie/themoviedb/main.py
  19. 2
      couchpotato/runner.py
  20. 15
      couchpotato/templates/index.html
  21. 81
      init/ubuntu
  22. 8
      init/ubuntu.default

2
couchpotato/core/downloaders/transmission/__init__.py

@ -47,7 +47,7 @@ config = [{
{ {
'name': 'remove_complete', 'name': 'remove_complete',
'label': 'Remove torrent', 'label': 'Remove torrent',
'default': False, 'default': True,
'advanced': True, 'advanced': True,
'type': 'bool', 'type': 'bool',
'description': 'Remove the torrent from Transmission after it finished seeding.', 'description': 'Remove the torrent from Transmission after it finished seeding.',

4
couchpotato/core/downloaders/transmission/main.py

@ -129,9 +129,9 @@ class Transmission(Downloader):
def pause(self, item, pause = True): def pause(self, item, pause = True):
if pause: if pause:
return self.trpc.stop_torrent(item['hashString']) return self.trpc.stop_torrent(item['id'])
else: else:
return self.trpc.start_torrent(item['hashString']) return self.trpc.start_torrent(item['id'])
def removeFailed(self, item): def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name']) log.info('%s failed downloading, deleting...', item['name'])

2
couchpotato/core/downloaders/utorrent/__init__.py

@ -39,7 +39,7 @@ config = [{
{ {
'name': 'remove_complete', 'name': 'remove_complete',
'label': 'Remove torrent', 'label': 'Remove torrent',
'default': False, 'default': True,
'advanced': True, 'advanced': True,
'type': 'bool', 'type': 'bool',
'description': 'Remove the torrent from uTorrent after it finished seeding.', 'description': 'Remove the torrent from uTorrent after it finished seeding.',

19
couchpotato/core/downloaders/utorrent/main.py

@ -10,7 +10,9 @@ from multipartpost import MultipartPostHandler
import cookielib import cookielib
import httplib import httplib
import json import json
import os
import re import re
import stat
import time import time
import urllib import urllib
import urllib2 import urllib2
@ -52,7 +54,7 @@ class uTorrent(Downloader):
new_settings['seed_prio_limitul_flag'] = True new_settings['seed_prio_limitul_flag'] = True
log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.') log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.')
if settings.get('bt.read_only_on_complete'): #This doesnt work as this option seems to be not available through the api if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
new_settings['bt.read_only_on_complete'] = False new_settings['bt.read_only_on_complete'] = False
log.info('Updated uTorrent settings to not set the files to read only after completing.') log.info('Updated uTorrent settings to not set the files to read only after completing.')
@ -93,7 +95,7 @@ class uTorrent(Downloader):
else: else:
self.utorrent_api.add_torrent_file(torrent_filename, filedata) self.utorrent_api.add_torrent_file(torrent_filename, filedata)
# Change settings of added torrents # Change settings of added torrent
self.utorrent_api.set_torrent(torrent_hash, torrent_params) self.utorrent_api.set_torrent(torrent_hash, torrent_params)
if self.conf('paused', default = 0): if self.conf('paused', default = 0):
self.utorrent_api.pause_torrent(torrent_hash) self.utorrent_api.pause_torrent(torrent_hash)
@ -130,8 +132,10 @@ class uTorrent(Downloader):
status = 'busy' status = 'busy'
if 'Finished' in item[21]: if 'Finished' in item[21]:
status = 'completed' status = 'completed'
self.removeReadOnly(item[26])
elif 'Seeding' in item[21]: elif 'Seeding' in item[21]:
status = 'seeding' status = 'seeding'
self.removeReadOnly(item[26])
statuses.append({ statuses.append({
'id': item[0], 'id': item[0],
@ -145,10 +149,10 @@ class uTorrent(Downloader):
return statuses return statuses
def pause(self, download_info, pause = True): def pause(self, item, pause = True):
if not self.connect(): if not self.connect():
return False return False
return self.utorrent_api.pause_torrent(download_info['id'], pause) return self.utorrent_api.pause_torrent(item['id'], pause)
def removeFailed(self, item): def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name']) log.info('%s failed downloading, deleting...', item['name'])
@ -161,6 +165,13 @@ class uTorrent(Downloader):
if not self.connect(): if not self.connect():
return False return False
return self.utorrent_api.remove_torrent(item['id'], remove_data = delete_files) return self.utorrent_api.remove_torrent(item['id'], remove_data = delete_files)
def removeReadOnly(self, folder):
#Removes all read-only flags in a folder
if folder and os.path.isdir(folder):
for root, folders, filenames in os.walk(folder):
for filename in filenames:
os.chmod(os.path.join(root, filename), stat.S_IWRITE)
class uTorrentAPI(object): class uTorrentAPI(object):

8
couchpotato/core/helpers/variable.py

@ -128,7 +128,7 @@ def getImdb(txt, check_inside = True, multiple = False):
try: try:
ids = re.findall('(tt\d{7})', txt) ids = re.findall('(tt\d{7})', txt)
if multiple: if multiple:
return ids if len(ids) > 0 else [] return list(set(ids)) if len(ids) > 0 else []
return ids[0] return ids[0]
except IndexError: except IndexError:
pass pass
@ -140,7 +140,11 @@ def tryInt(s):
except: return 0 except: return 0
def tryFloat(s): def tryFloat(s):
try: return float(s) if '.' in s else tryInt(s) try:
if isinstance(s, str):
return float(s) if '.' in s else tryInt(s)
else:
return float(s)
except: return 0 except: return 0
def natsortKey(s): def natsortKey(s):

8
couchpotato/core/notifications/xbmc/__init__.py

@ -39,6 +39,14 @@ config = [{
'description': 'Only update the first host when movie snatched, useful for synced XBMC', 'description': 'Only update the first host when movie snatched, useful for synced XBMC',
}, },
{ {
'name': 'remote_dir_scan',
'label': 'Remote Folder Scan',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Only scan new movie folder at remote XBMC servers. Works if movie location is the same.',
},
{
'name': 'on_snatch', 'name': 'on_snatch',
'default': 0, 'default': 0,
'type': 'bool', 'type': 'bool',

12
couchpotato/core/notifications/xbmc/main.py

@ -13,7 +13,7 @@ log = CPLog(__name__)
class XBMC(Notification): class XBMC(Notification):
listen_to = ['renamer.after'] listen_to = ['renamer.after', 'movie.snatched']
use_json_notifications = {} use_json_notifications = {}
http_time_between_calls = 0 http_time_between_calls = 0
@ -33,15 +33,19 @@ class XBMC(Notification):
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}), ('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
] ]
if not self.conf('only_first') or hosts.index(host) == 0: if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
calls.append(('VideoLibrary.Scan', {})) param = {}
if self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0]):
param = {'directory': data['destination_dir']}
calls.append(('VideoLibrary.Scan', param))
max_successful += len(calls) max_successful += len(calls)
response = self.request(host, calls) response = self.request(host, calls)
else: else:
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message}) response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
if not self.conf('only_first') or hosts.index(host) == 0: if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
response += self.request(host, [('VideoLibrary.Scan', {})]) response += self.request(host, [('VideoLibrary.Scan', {})])
max_successful += 1 max_successful += 1

10
couchpotato/core/plugins/automation/main.py

@ -26,6 +26,10 @@ class Automation(Plugin):
movie_ids = [] movie_ids = []
for imdb_id in movies: for imdb_id in movies:
if self.shuttingDown():
break
prop_name = 'automation.added.%s' % imdb_id prop_name = 'automation.added.%s' % imdb_id
added = Env.prop(prop_name, default = False) added = Env.prop(prop_name, default = False)
if not added: if not added:
@ -35,5 +39,11 @@ class Automation(Plugin):
Env.prop(prop_name, True) Env.prop(prop_name, True)
for movie_id in movie_ids: for movie_id in movie_ids:
if self.shuttingDown():
break
movie_dict = fireEvent('movie.get', movie_id, single = True) movie_dict = fireEvent('movie.get', movie_id, single = True)
fireEvent('movie.searcher.single', movie_dict) fireEvent('movie.searcher.single', movie_dict)
return True

7
couchpotato/core/plugins/renamer/__init__.py

@ -27,6 +27,7 @@ rename_options = {
'imdb_id': 'IMDB id (tt0123456)', 'imdb_id': 'IMDB id (tt0123456)',
'cd': 'CD number (cd1)', 'cd': 'CD number (cd1)',
'cd_nr': 'Just the cd nr. (1)', 'cd_nr': 'Just the cd nr. (1)',
'mpaa': 'MPAA Rating',
}, },
} }
@ -119,10 +120,10 @@ config = [{
{ {
'name': 'file_action', 'name': 'file_action',
'label': 'Torrent File Action', 'label': 'Torrent File Action',
'default': 'move', 'default': 'link',
'type': 'dropdown', 'type': 'dropdown',
'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Move & Sym link', 'move_symlink')], 'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
'description': 'Define which kind of file operation you want to use for torrents. Before you start using <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.', 'description': '<strong>Link</strong> or <strong>Copy</strong> after downloading completed (and allow for seeding), or <strong>Move</strong> after seeding completed. Link first tries <a href="http://en.wikipedia.org/wiki/Hard_link">hard link</a>, then <a href="http://en.wikipedia.org/wiki/Sym_link">sym link</a> and falls back to Copy.',
'advanced': True, 'advanced': True,
}, },
{ {

45
couchpotato/core/plugins/renamer/main.py

@ -205,6 +205,7 @@ class Renamer(Plugin):
'imdb_id': library['identifier'], 'imdb_id': library['identifier'],
'cd': '', 'cd': '',
'cd_nr': '', 'cd_nr': '',
'mpaa': library['info'].get('mpaa', ''),
} }
for file_type in group['files']: for file_type in group['files']:
@ -212,7 +213,7 @@ class Renamer(Plugin):
# Move nfo depending on settings # Move nfo depending on settings
if file_type is 'nfo' and not self.conf('rename_nfo'): if file_type is 'nfo' and not self.conf('rename_nfo'):
log.debug('Skipping, renaming of %s disabled', file_type) log.debug('Skipping, renaming of %s disabled', file_type)
if self.conf('cleanup') and not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)): if self.conf('cleanup') and not self.downloadIsTorrent(download_info):
for current_file in group['files'][file_type]: for current_file in group['files'][file_type]:
remove_files.append(current_file) remove_files.append(current_file)
continue continue
@ -394,7 +395,7 @@ class Renamer(Plugin):
# Remove leftover files # Remove leftover files
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \ if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \
not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)): not self.downloadIsTorrent(download_info):
log.debug('Removing leftover files') log.debug('Removing leftover files')
for current_file in group['files']['leftover']: for current_file in group['files']['leftover']:
remove_files.append(current_file) remove_files.append(current_file)
@ -451,8 +452,7 @@ class Renamer(Plugin):
self.tagDir(group, 'failed_rename') self.tagDir(group, 'failed_rename')
# Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent # Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
if self.movieInFromFolder(movie_folder) and \ if self.movieInFromFolder(movie_folder) and self.downloadIsTorrent(download_info):
self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
self.tagDir(group, 'renamed_already') self.tagDir(group, 'renamed_already')
# Remove matching releases # Remove matching releases
@ -463,8 +463,7 @@ class Renamer(Plugin):
except: except:
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc())) log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc()))
if group['dirname'] and group['parentdir'] and \ if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(download_info):
not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
try: try:
log.info('Deleting folder: %s', group['parentdir']) log.info('Deleting folder: %s', group['parentdir'])
self.deleteEmptyFolder(group['parentdir']) self.deleteEmptyFolder(group['parentdir'])
@ -524,22 +523,22 @@ Remove it if you want it to be renamed (again, or at least let it try again)
if ignore_file: if ignore_file:
self.createFile(ignore_file, text) self.createFile(ignore_file, text)
def untagDir(self, folder, tag = None): def untagDir(self, folder, tag = ''):
if not os.path.isdir(folder): if not os.path.isdir(folder):
return return
# Remove any .ignore files # Remove any .ignore files
for root, dirnames, filenames in os.walk(folder): for root, dirnames, filenames in os.walk(folder):
for filename in fnmatch.filter(filenames, '%s.ignore' % tag if tag else '*'): for filename in fnmatch.filter(filenames, '*%s.ignore' % tag):
os.remove((os.path.join(root, filename))) os.remove((os.path.join(root, filename)))
def hastagDir(self, folder, tag = None): def hastagDir(self, folder, tag = ''):
if not os.path.isdir(folder): if not os.path.isdir(folder):
return False return False
# Find any .ignore files # Find any .ignore files
for root, dirnames, filenames in os.walk(folder): for root, dirnames, filenames in os.walk(folder):
if fnmatch.filter(filenames, '%s.ignore' % tag if tag else '*'): if fnmatch.filter(filenames, '*%s.ignore' % tag):
return True return True
return False return False
@ -549,17 +548,23 @@ Remove it if you want it to be renamed (again, or at least let it try again)
try: try:
if forcemove: if forcemove:
shutil.move(old, dest) shutil.move(old, dest)
elif self.conf('file_action') == 'hardlink': elif self.conf('file_action') == 'copy':
shutil.copy(old, dest)
elif self.conf('file_action') == 'link':
# First try to hardlink
try: try:
log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
link(old, dest) link(old, dest)
except: except:
log.error('Couldn\'t hardlink file "%s" to "%s". Copying instead. Error: %s. ', (old, dest, traceback.format_exc())) # Try to simlink next
log.debug('Couldn\'t hardlink file "%s" to "%s". Simlinking instead. Error: %s. ', (old, dest, traceback.format_exc()))
shutil.copy(old, dest) shutil.copy(old, dest)
elif self.conf('file_action') == 'copy': try:
shutil.copy(old, dest) symlink(dest, old + '.link')
elif self.conf('file_action') == 'move_symlink': os.unlink(old)
shutil.move(old, dest) os.rename(old + '.link', old)
symlink(dest, old) except:
log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc()))
else: else:
shutil.move(old, dest) shutil.move(old, dest)
@ -764,10 +769,10 @@ Remove it if you want it to be renamed (again, or at least let it try again)
for item in scan_items: for item in scan_items:
# Ask the renamer to scan the item # Ask the renamer to scan the item
if item['scan']: if item['scan']:
if item['pause'] and self.conf('file_action') == 'move_symlink': if item['pause'] and self.conf('file_action') == 'link':
fireEvent('download.pause', item = item, pause = True, single = True) fireEvent('download.pause', item = item, pause = True, single = True)
fireEvent('renamer.scan', download_info = item) fireEvent('renamer.scan', download_info = item)
if item['pause'] and self.conf('file_action') == 'move_symlink': if item['pause'] and self.conf('file_action') == 'link':
fireEvent('download.pause', item = item, pause = False, single = True) fireEvent('download.pause', item = item, pause = False, single = True)
if item['process_complete']: if item['process_complete']:
#First make sure the files were succesfully processed #First make sure the files were succesfully processed
@ -826,6 +831,6 @@ Remove it if you want it to be renamed (again, or at least let it try again)
def statusInfoComplete(self, item): def statusInfoComplete(self, item):
return item['id'] and item['downloader'] and item['folder'] return item['id'] and item['downloader'] and item['folder']
def movieInFromFolder(self, movie_folder): def movieInFromFolder(self, movie_folder):
return movie_folder and self.conf('from') in movie_folder or not movie_folder return movie_folder and self.conf('from') in movie_folder or not movie_folder

7
couchpotato/core/plugins/scanner/main.py

@ -329,14 +329,17 @@ class Scanner(Plugin):
del movie_files del movie_files
total_found = len(valid_files)
# Make sure only one movie was found if a download ID is provided # Make sure only one movie was found if a download ID is provided
if download_info and not len(valid_files) == 1: if download_info and total_found == 0:
log.info('Download ID provided (%s), but no groups found! Make sure the download contains valid media files (fully extracted).', download_info.get('imdb_id'))
elif download_info and total_found > 1:
log.info('Download ID provided (%s), but more than one group found (%s). Ignoring Download ID...', (download_info.get('imdb_id'), len(valid_files))) log.info('Download ID provided (%s), but more than one group found (%s). Ignoring Download ID...', (download_info.get('imdb_id'), len(valid_files)))
download_info = None download_info = None
# Determine file types # Determine file types
processed_movies = {} processed_movies = {}
total_found = len(valid_files)
while True and not self.shuttingDown(): while True and not self.shuttingDown():
try: try:
identifier, group = valid_files.popitem() identifier, group = valid_files.popitem()

30
couchpotato/core/providers/automation/imdb/__init__.py

@ -9,7 +9,7 @@ config = [{
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'watchlist_providers', 'list': 'watchlist_providers',
'name': 'imdb_automation', 'name': 'imdb_automation_watchlist',
'label': 'IMDB', 'label': 'IMDB',
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the CSV link.', 'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the CSV link.',
'options': [ 'options': [
@ -30,5 +30,33 @@ config = [{
}, },
], ],
}, },
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'imdb_automation_charts',
'label': 'IMDB',
'description': 'Import movies from IMDB Charts',
'options': [
{
'name': 'automation_providers_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'automation_charts_theater',
'type': 'bool',
'label': 'In Theaters',
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
'default': True,
},
{
'name': 'automation_charts_top250',
'type': 'bool',
'label': 'TOP 250',
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
'default': True,
},
],
},
], ],
}] }]

76
couchpotato/core/providers/automation/imdb/main.py

@ -1,38 +1,100 @@
import traceback
from bs4 import BeautifulSoup
from couchpotato import fireEvent
from couchpotato.core.helpers.rss import RSS from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation from couchpotato.core.providers.automation.base import Automation
import traceback
from couchpotato.core.providers.base import MultiProvider
log = CPLog(__name__) log = CPLog(__name__)
class IMDB(Automation, RSS): class IMDB(MultiProvider):
def getTypes(self):
return [IMDBWatchlist, IMDBAutomation]
class IMDBBase(Automation, RSS):
interval = 1800 interval = 1800
def getInfo(self, imdb_id):
return fireEvent('movie.info', identifier = imdb_id, merge = True)
class IMDBWatchlist(IMDBBase):
enabled_option = 'automation_enabled'
def getIMDBids(self): def getIMDBids(self):
movies = [] movies = []
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))] watchlist_enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
urls = splitString(self.conf('automation_urls')) watchlist_urls = splitString(self.conf('automation_urls'))
index = -1 index = -1
for url in urls: for watchlist_url in watchlist_urls:
index += 1 index += 1
if not enablers[index]: if not watchlist_enablers[index]:
continue continue
try: try:
rss_data = self.getHTMLData(url) log.debug('Started IMDB watchlists: %s', watchlist_url)
rss_data = self.getHTMLData(watchlist_url)
imdbs = getImdb(rss_data, multiple = True) if rss_data else [] imdbs = getImdb(rss_data, multiple = True) if rss_data else []
for imdb in imdbs: for imdb in imdbs:
movies.append(imdb) movies.append(imdb)
if self.shuttingDown():
break
except: except:
log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc())) log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc()))
return movies return movies
class IMDBAutomation(IMDBBase):
enabled_option = 'automation_providers_enabled'
chart_urls = {
'theater': 'http://www.imdb.com/movies-in-theaters/',
'top250': 'http://www.imdb.com/chart/top',
}
def getIMDBids(self):
movies = []
for url in self.chart_urls:
if self.conf('automation_charts_%s' % url):
data = self.getHTMLData(self.chart_urls[url])
if data:
html = BeautifulSoup(data)
try:
result_div = html.find('div', attrs = {'id': 'main'})
imdb_ids = getImdb(str(result_div), multiple = True)
for imdb_id in imdb_ids:
info = self.getInfo(imdb_id)
if info and self.isMinimalMovie(info):
movies.append(imdb_id)
if self.shuttingDown():
break
except:
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
return movies

16
couchpotato/core/providers/automation/rottentomatoes/__init__.py

@ -11,7 +11,7 @@ config = [{
'list': 'automation_providers', 'list': 'automation_providers',
'name': 'rottentomatoes_automation', 'name': 'rottentomatoes_automation',
'label': 'Rottentomatoes', 'label': 'Rottentomatoes',
'description': 'Imports movies from the rottentomatoes "in theaters"-feed.', 'description': 'Imports movies from rottentomatoes rss feeds specified below.',
'options': [ 'options': [
{ {
'name': 'automation_enabled', 'name': 'automation_enabled',
@ -19,11 +19,23 @@ config = [{
'type': 'enabler', 'type': 'enabler',
}, },
{ {
'name': 'automation_urls_use',
'label': 'Use',
'default': '1',
},
{
'name': 'automation_urls',
'label': 'url',
'type': 'combined',
'combine': ['automation_urls_use', 'automation_urls'],
'default': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
},
{
'name': 'tomatometer_percent', 'name': 'tomatometer_percent',
'default': '80', 'default': '80',
'label': 'Tomatometer', 'label': 'Tomatometer',
'description': 'Use as extra scoring requirement', 'description': 'Use as extra scoring requirement',
} },
], ],
}, },
], ],

48
couchpotato/core/providers/automation/rottentomatoes/main.py

@ -1,5 +1,5 @@
from couchpotato.core.helpers.rss import RSS from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation from couchpotato.core.providers.automation.base import Automation
from xml.etree.ElementTree import QName from xml.etree.ElementTree import QName
@ -11,38 +11,42 @@ log = CPLog(__name__)
class Rottentomatoes(Automation, RSS): class Rottentomatoes(Automation, RSS):
interval = 1800 interval = 1800
urls = {
'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/',
'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
}
def getIMDBids(self): def getIMDBids(self):
movies = [] movies = []
rss_movies = self.getRSSData(self.urls['theater']) rotten_tomatoes_namespace = 'http://www.rottentomatoes.com/xmlns/rtmovie/'
rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent')) urls = dict(zip(splitString(self.conf('automation_urls')), [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]))
for movie in rss_movies: for url in urls:
value = self.getTextElement(movie, "title") if not urls[url]:
result = re.search('(?<=%\s).*', value) continue
if result: rss_movies = self.getRSSData(url)
rating_tag = str(QName(rotten_tomatoes_namespace, 'tomatometer_percent'))
log.info2('Something smells...') for movie in rss_movies:
rating = tryInt(self.getTextElement(movie, rating_tag))
name = result.group(0)
if rating < tryInt(self.conf('tomatometer_percent')): value = self.getTextElement(movie, "title")
log.info2('%s seems to be rotten...', name) result = re.search('(?<=%\s).*', value)
else:
log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name)) if result:
year = datetime.datetime.now().strftime("%Y")
imdb = self.search(name, year)
if imdb and self.isMinimalMovie(imdb): log.info2('Something smells...')
movies.append(imdb['imdb']) rating = tryInt(self.getTextElement(movie, rating_tag))
name = result.group(0)
if rating < tryInt(self.conf('tomatometer_percent')):
log.info2('%s seems to be rotten...', name)
else:
log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
year = datetime.datetime.now().strftime("%Y")
imdb = self.search(name, year)
if imdb and self.isMinimalMovie(imdb):
movies.append(imdb['imdb'])
return movies return movies

1
couchpotato/core/providers/movie/_modifier/main.py

@ -28,6 +28,7 @@ class MovieResultModifier(Plugin):
'tagline': '', 'tagline': '',
'imdb': '', 'imdb': '',
'genres': [], 'genres': [],
'mpaa': None
} }
def __init__(self): def __init__(self):

1
couchpotato/core/providers/movie/omdbapi/main.py

@ -95,6 +95,7 @@ class OMDBAPI(MovieProvider):
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', '').replace(',', ''))), #'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', '').replace(',', ''))),
}, },
'imdb': str(movie.get('imdbID', '')), 'imdb': str(movie.get('imdbID', '')),
'mpaa': str(movie.get('Rated', '')),
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')), 'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
'released': movie.get('Released'), 'released': movie.get('Released'),
'year': year if isinstance(year, (int)) else None, 'year': year if isinstance(year, (int)) else None,

1
couchpotato/core/providers/movie/themoviedb/main.py

@ -167,6 +167,7 @@ class TheMovieDb(MovieProvider):
'backdrop_original': [backdrop_original] if backdrop_original else [], 'backdrop_original': [backdrop_original] if backdrop_original else [],
}, },
'imdb': movie.get('imdb_id'), 'imdb': movie.get('imdb_id'),
'mpaa': movie.get('certification', ''),
'runtime': movie.get('runtime'), 'runtime': movie.get('runtime'),
'released': movie.get('released'), 'released': movie.get('released'),
'year': year, 'year': year,

2
couchpotato/runner.py

@ -214,7 +214,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# app.debug = development # app.debug = development
config = { config = {
'use_reloader': reloader, 'use_reloader': reloader,
'port': tryInt(Env.setting('port', default = 5000)), 'port': tryInt(Env.setting('port', default = 5050)),
'host': host if host and len(host) > 0 else '0.0.0.0', 'host': host if host and len(host) > 0 else '0.0.0.0',
'ssl_cert': Env.setting('ssl_cert', default = None), 'ssl_cert': Env.setting('ssl_cert', default = None),
'ssl_key': Env.setting('ssl_key', default = None), 'ssl_key': Env.setting('ssl_key', default = None),

15
couchpotato/templates/index.html

@ -22,17 +22,18 @@
<script type="text/javascript"> <script type="text/javascript">
if($(window).getSize().x <= 480) window.addEvent('load', function(){
window.addEvent('load', function() {
setTimeout(function(){ if(window.getSize().x <= 480)
window.scrollTo(0, 1); setTimeout(function(){
window.scrollTo(0, 0); window.scrollTo(0, 1);
}, 100); window.scrollTo(0, 0);
}, 100);
}); });
window.addEvent('domready', function() { window.addEvent('domready', function() {
new Uniform(); new Uniform();
Api.setup({ Api.setup({

81
init/ubuntu

@ -26,48 +26,89 @@ NAME=couchpotato
# App name # App name
DESC=CouchPotato DESC=CouchPotato
# Path to app root ## Don't edit this file
CP_APP_PATH=${APP_PATH-/usr/local/sbin/CouchPotatoServer/} ## Edit user configuation in /etc/default/couchpotato to change
##
## CP_USER= #$RUN_AS, username to run couchpotato under, the default is couchpotato
## CP_HOME= #$APP_PATH, the location of couchpotato.py, the default is /opt/couchpotato
## CP_DATA= #$DATA_DIR, the location of couchpotato.db, cache, logs, the default is /var/couchpotato
## CP_PIDFILE= #$PID_FILE, the location of couchpotato.pid, the default is /var/run/couchpotato.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
## CP_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for couchpotato, i.e. " --config_file=/home/couchpotato/couchpotato.ini"
## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
##
## EXAMPLE if want to run as different user
## add CP_USER=username to /etc/default/couchpotato
## otherwise default couchpotato is used
# Run CP as username
RUN_AS=${CP_USER-couchpotato}
# Path to app
# CP_HOME=path_to_app_CouchPotato.py
APP_PATH=${CP_HOME-/opt/couchpotato/}
# Data directory where couchpotato.db, cache and logs are stored
DATA_DIR=${CP_DATA-/var/couchpotato}
# User to run CP as # Path to store PID file
CP_RUN_AS=${RUN_AS-root} PID_FILE=${CP_PID_FILE-/var/run/couchpotato.pid}
# Path to python bin # path to python bin
CP_DAEMON=${DAEMON_PATH-/usr/bin/python} DAEMON=${PYTHON_BIN-/usr/bin/python}
# Path to store PID file # Extra daemon option like: CP_OPTS=" --config=/home/couchpotato/couchpotato.ini"
CP_PID_FILE=${PID_FILE-/var/run/couchpotato.pid} EXTRA_DAEMON_OPTS=${CP_OPTS-}
# Extra start-stop-daemon option like START_OPTS=" --group=users"
EXTRA_SSD_OPTS=${SSD_OPTS-}
PID_PATH=`dirname $PID_FILE`
DAEMON_OPTS=" CouchPotato.py --quiet --daemon --pid_file=${PID_FILE} --data_dir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
# Other startup args
CP_DAEMON_OPTS=" CouchPotato.py --daemon --pid_file=${CP_PID_FILE}"
test -x $CP_DAEMON || exit 0 test -x $DAEMON || exit 0
set -e set -e
. /lib/lsb/init-functions # Create PID directory if not exist and ensure the CouchPotato user can write to it
if [ ! -d $PID_PATH ]; then
mkdir -p $PID_PATH
chown $RUN_AS $PID_PATH
fi
if [ ! -d $DATA_DIR ]; then
mkdir -p $DATA_DIR
chown $RUN_AS $DATA_DIR
fi
if [ -e $PID_FILE ]; then
PID=`cat $PID_FILE`
if ! kill -0 $PID > /dev/null 2>&1; then
echo "Removing stale $PID_FILE"
rm $PID_FILE
fi
fi
case "$1" in case "$1" in
start) start)
echo "Starting $DESC" echo "Starting $DESC"
rm -rf $CP_PID_FILE || return 1 start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
touch $CP_PID_FILE
chown $CP_RUN_AS $CP_PID_FILE
start-stop-daemon -d $CP_APP_PATH -c $CP_RUN_AS --start --background --pidfile $CP_PID_FILE --exec $CP_DAEMON -- $CP_DAEMON_OPTS
;; ;;
stop) stop)
echo "Stopping $DESC" echo "Stopping $DESC"
start-stop-daemon --stop --pidfile $CP_PID_FILE --retry 15 start-stop-daemon --stop --pidfile $PID_FILE --retry 15
;; ;;
restart|force-reload) restart|force-reload)
echo "Restarting $DESC" echo "Restarting $DESC"
start-stop-daemon --stop --pidfile $CP_PID_FILE --retry 15 start-stop-daemon --stop --pidfile $PID_FILE --retry 15
start-stop-daemon -d $CP_APP_PATH -c $CP_RUN_AS --start --background --pidfile $CP_PID_FILE --exec $CP_DAEMON -- $CP_DAEMON_OPTS start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
;; ;;
status) status)
status_of_proc -p $CP_PID_FILE "$CP_DAEMON" "$NAME" status_of_proc -p $PID_FILE "$DAEMON" "$NAME"
;; ;;
*) *)
N=/etc/init.d/$NAME N=/etc/init.d/$NAME

8
init/ubuntu.default

@ -1,5 +1,5 @@
# COPY THIS FILE TO /etc/default/couchpotato # COPY THIS FILE TO /etc/default/couchpotato
# OPTIONS: APP_PATH, RUN_AS, DAEMON_PATH, CP_PID_FILE # OPTIONS: CP_HOME, CP_USER, CP_DATA, CP_PIDFILE, PYTHON_BIN, CP_OPTS, SSD_OPTS
APP_PATH= CP_HOME=
RUN_AS=root CP_USER=root
Loading…
Cancel
Save