Merge branch 'refs/heads/develop' into tv
This commit is contained in:
@@ -47,7 +47,7 @@ config = [{
|
||||
{
|
||||
'name': 'remove_complete',
|
||||
'label': 'Remove torrent',
|
||||
'default': False,
|
||||
'default': True,
|
||||
'advanced': True,
|
||||
'type': 'bool',
|
||||
'description': 'Remove the torrent from Transmission after it finished seeding.',
|
||||
|
||||
@@ -129,9 +129,9 @@ class Transmission(Downloader):
|
||||
|
||||
def pause(self, item, pause = True):
|
||||
if pause:
|
||||
return self.trpc.stop_torrent(item['hashString'])
|
||||
return self.trpc.stop_torrent(item['id'])
|
||||
else:
|
||||
return self.trpc.start_torrent(item['hashString'])
|
||||
return self.trpc.start_torrent(item['id'])
|
||||
|
||||
def removeFailed(self, item):
|
||||
log.info('%s failed downloading, deleting...', item['name'])
|
||||
|
||||
@@ -39,7 +39,7 @@ config = [{
|
||||
{
|
||||
'name': 'remove_complete',
|
||||
'label': 'Remove torrent',
|
||||
'default': False,
|
||||
'default': True,
|
||||
'advanced': True,
|
||||
'type': 'bool',
|
||||
'description': 'Remove the torrent from uTorrent after it finished seeding.',
|
||||
|
||||
@@ -10,7 +10,9 @@ from multipartpost import MultipartPostHandler
|
||||
import cookielib
|
||||
import httplib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
@@ -52,7 +54,7 @@ class uTorrent(Downloader):
|
||||
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.')
|
||||
|
||||
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
|
||||
log.info('Updated uTorrent settings to not set the files to read only after completing.')
|
||||
|
||||
@@ -93,7 +95,7 @@ class uTorrent(Downloader):
|
||||
else:
|
||||
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)
|
||||
if self.conf('paused', default = 0):
|
||||
self.utorrent_api.pause_torrent(torrent_hash)
|
||||
@@ -130,8 +132,10 @@ class uTorrent(Downloader):
|
||||
status = 'busy'
|
||||
if 'Finished' in item[21]:
|
||||
status = 'completed'
|
||||
self.removeReadOnly(item[26])
|
||||
elif 'Seeding' in item[21]:
|
||||
status = 'seeding'
|
||||
self.removeReadOnly(item[26])
|
||||
|
||||
statuses.append({
|
||||
'id': item[0],
|
||||
@@ -145,10 +149,10 @@ class uTorrent(Downloader):
|
||||
|
||||
return statuses
|
||||
|
||||
def pause(self, download_info, pause = True):
|
||||
def pause(self, item, pause = True):
|
||||
if not self.connect():
|
||||
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):
|
||||
log.info('%s failed downloading, deleting...', item['name'])
|
||||
@@ -161,6 +165,13 @@ class uTorrent(Downloader):
|
||||
if not self.connect():
|
||||
return False
|
||||
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):
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ def getImdb(txt, check_inside = True, multiple = False):
|
||||
try:
|
||||
ids = re.findall('(tt\d{7})', txt)
|
||||
if multiple:
|
||||
return ids if len(ids) > 0 else []
|
||||
return list(set(ids)) if len(ids) > 0 else []
|
||||
return ids[0]
|
||||
except IndexError:
|
||||
pass
|
||||
@@ -140,7 +140,11 @@ def tryInt(s):
|
||||
except: return 0
|
||||
|
||||
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
|
||||
|
||||
def natsortKey(s):
|
||||
|
||||
@@ -38,6 +38,14 @@ config = [{
|
||||
'advanced': True,
|
||||
'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',
|
||||
'default': 0,
|
||||
|
||||
@@ -13,7 +13,7 @@ log = CPLog(__name__)
|
||||
|
||||
class XBMC(Notification):
|
||||
|
||||
listen_to = ['renamer.after']
|
||||
listen_to = ['renamer.after', 'movie.snatched']
|
||||
use_json_notifications = {}
|
||||
http_time_between_calls = 0
|
||||
|
||||
@@ -33,15 +33,19 @@ class XBMC(Notification):
|
||||
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
|
||||
]
|
||||
|
||||
if not self.conf('only_first') or hosts.index(host) == 0:
|
||||
calls.append(('VideoLibrary.Scan', {}))
|
||||
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
||||
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)
|
||||
response = self.request(host, calls)
|
||||
else:
|
||||
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', {})])
|
||||
max_successful += 1
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ class Automation(Plugin):
|
||||
movie_ids = []
|
||||
|
||||
for imdb_id in movies:
|
||||
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
prop_name = 'automation.added.%s' % imdb_id
|
||||
added = Env.prop(prop_name, default = False)
|
||||
if not added:
|
||||
@@ -35,5 +39,11 @@ class Automation(Plugin):
|
||||
Env.prop(prop_name, True)
|
||||
|
||||
for movie_id in movie_ids:
|
||||
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
fireEvent('movie.searcher.single', movie_dict)
|
||||
|
||||
return True
|
||||
7
couchpotato/core/plugins/renamer/__init__.py
Normal file → Executable file
7
couchpotato/core/plugins/renamer/__init__.py
Normal file → Executable file
@@ -27,6 +27,7 @@ rename_options = {
|
||||
'imdb_id': 'IMDB id (tt0123456)',
|
||||
'cd': 'CD number (cd1)',
|
||||
'cd_nr': 'Just the cd nr. (1)',
|
||||
'mpaa': 'MPAA Rating',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -119,10 +120,10 @@ config = [{
|
||||
{
|
||||
'name': 'file_action',
|
||||
'label': 'Torrent File Action',
|
||||
'default': 'move',
|
||||
'default': 'link',
|
||||
'type': 'dropdown',
|
||||
'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Move & Sym link', 'move_symlink')],
|
||||
'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.',
|
||||
'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
|
||||
'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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -205,6 +205,7 @@ class Renamer(Plugin):
|
||||
'imdb_id': library['identifier'],
|
||||
'cd': '',
|
||||
'cd_nr': '',
|
||||
'mpaa': library['info'].get('mpaa', ''),
|
||||
}
|
||||
|
||||
for file_type in group['files']:
|
||||
@@ -212,7 +213,7 @@ class Renamer(Plugin):
|
||||
# Move nfo depending on settings
|
||||
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||
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]:
|
||||
remove_files.append(current_file)
|
||||
continue
|
||||
@@ -394,7 +395,7 @@ class Renamer(Plugin):
|
||||
|
||||
# Remove leftover files
|
||||
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')
|
||||
for current_file in group['files']['leftover']:
|
||||
remove_files.append(current_file)
|
||||
@@ -451,8 +452,7 @@ class Renamer(Plugin):
|
||||
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
|
||||
if self.movieInFromFolder(movie_folder) and \
|
||||
self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
|
||||
if self.movieInFromFolder(movie_folder) and self.downloadIsTorrent(download_info):
|
||||
self.tagDir(group, 'renamed_already')
|
||||
|
||||
# Remove matching releases
|
||||
@@ -463,8 +463,7 @@ class Renamer(Plugin):
|
||||
except:
|
||||
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc()))
|
||||
|
||||
if group['dirname'] and group['parentdir'] and \
|
||||
not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
|
||||
if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(download_info):
|
||||
try:
|
||||
log.info('Deleting folder: %s', 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:
|
||||
self.createFile(ignore_file, text)
|
||||
|
||||
def untagDir(self, folder, tag = None):
|
||||
def untagDir(self, folder, tag = ''):
|
||||
if not os.path.isdir(folder):
|
||||
return
|
||||
|
||||
# Remove any .ignore files
|
||||
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)))
|
||||
|
||||
def hastagDir(self, folder, tag = None):
|
||||
def hastagDir(self, folder, tag = ''):
|
||||
if not os.path.isdir(folder):
|
||||
return False
|
||||
|
||||
# Find any .ignore files
|
||||
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 False
|
||||
@@ -549,17 +548,23 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
try:
|
||||
if forcemove:
|
||||
shutil.move(old, dest)
|
||||
elif self.conf('file_action') == 'hardlink':
|
||||
try:
|
||||
link(old, dest)
|
||||
except:
|
||||
log.error('Couldn\'t hardlink file "%s" to "%s". Copying instead. Error: %s. ', (old, dest, traceback.format_exc()))
|
||||
shutil.copy(old, dest)
|
||||
elif self.conf('file_action') == 'copy':
|
||||
shutil.copy(old, dest)
|
||||
elif self.conf('file_action') == 'move_symlink':
|
||||
shutil.move(old, dest)
|
||||
symlink(dest, old)
|
||||
elif self.conf('file_action') == 'link':
|
||||
# First try to hardlink
|
||||
try:
|
||||
log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
|
||||
link(old, dest)
|
||||
except:
|
||||
# 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)
|
||||
try:
|
||||
symlink(dest, old + '.link')
|
||||
os.unlink(old)
|
||||
os.rename(old + '.link', old)
|
||||
except:
|
||||
log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc()))
|
||||
else:
|
||||
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:
|
||||
# Ask the renamer to scan the item
|
||||
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('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)
|
||||
if item['process_complete']:
|
||||
#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):
|
||||
return item['id'] and item['downloader'] and item['folder']
|
||||
|
||||
|
||||
def movieInFromFolder(self, movie_folder):
|
||||
return movie_folder and self.conf('from') in movie_folder or not movie_folder
|
||||
|
||||
@@ -329,14 +329,17 @@ class Scanner(Plugin):
|
||||
|
||||
del movie_files
|
||||
|
||||
total_found = len(valid_files)
|
||||
|
||||
# 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)))
|
||||
download_info = None
|
||||
|
||||
# Determine file types
|
||||
processed_movies = {}
|
||||
total_found = len(valid_files)
|
||||
while True and not self.shuttingDown():
|
||||
try:
|
||||
identifier, group = valid_files.popitem()
|
||||
|
||||
@@ -9,7 +9,7 @@ config = [{
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'watchlist_providers',
|
||||
'name': 'imdb_automation',
|
||||
'name': 'imdb_automation_watchlist',
|
||||
'label': 'IMDB',
|
||||
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the CSV link.',
|
||||
'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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
|
||||
@@ -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.variable import getImdb, splitString, tryInt
|
||||
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
import traceback
|
||||
|
||||
from couchpotato.core.providers.base import MultiProvider
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class IMDB(Automation, RSS):
|
||||
class IMDB(MultiProvider):
|
||||
|
||||
def getTypes(self):
|
||||
return [IMDBWatchlist, IMDBAutomation]
|
||||
|
||||
|
||||
class IMDBBase(Automation, RSS):
|
||||
|
||||
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):
|
||||
|
||||
movies = []
|
||||
|
||||
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||
urls = splitString(self.conf('automation_urls'))
|
||||
watchlist_enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||
watchlist_urls = splitString(self.conf('automation_urls'))
|
||||
|
||||
index = -1
|
||||
for url in urls:
|
||||
for watchlist_url in watchlist_urls:
|
||||
|
||||
index += 1
|
||||
if not enablers[index]:
|
||||
if not watchlist_enablers[index]:
|
||||
continue
|
||||
|
||||
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 []
|
||||
|
||||
for imdb in imdbs:
|
||||
movies.append(imdb)
|
||||
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
except:
|
||||
log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc()))
|
||||
|
||||
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
|
||||
|
||||
@@ -11,19 +11,31 @@ config = [{
|
||||
'list': 'automation_providers',
|
||||
'name': 'rottentomatoes_automation',
|
||||
'label': 'Rottentomatoes',
|
||||
'description': 'Imports movies from the rottentomatoes "in theaters"-feed.',
|
||||
'description': 'Imports movies from rottentomatoes rss feeds specified below.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
'default': False,
|
||||
'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',
|
||||
'default': '80',
|
||||
'label': 'Tomatometer',
|
||||
'description': 'Use as extra scoring requirement',
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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.providers.automation.base import Automation
|
||||
from xml.etree.ElementTree import QName
|
||||
@@ -11,38 +11,42 @@ log = CPLog(__name__)
|
||||
class Rottentomatoes(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
urls = {
|
||||
'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/',
|
||||
'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
|
||||
}
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
movies = []
|
||||
|
||||
rss_movies = self.getRSSData(self.urls['theater'])
|
||||
rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent'))
|
||||
rotten_tomatoes_namespace = 'http://www.rottentomatoes.com/xmlns/rtmovie/'
|
||||
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")
|
||||
result = re.search('(?<=%\s).*', value)
|
||||
if not urls[url]:
|
||||
continue
|
||||
|
||||
if result:
|
||||
rss_movies = self.getRSSData(url)
|
||||
rating_tag = str(QName(rotten_tomatoes_namespace, 'tomatometer_percent'))
|
||||
|
||||
log.info2('Something smells...')
|
||||
rating = tryInt(self.getTextElement(movie, rating_tag))
|
||||
name = result.group(0)
|
||||
for movie in rss_movies:
|
||||
|
||||
if rating < tryInt(self.conf('tomatometer_percent')):
|
||||
log.info2('%s seems to be rotten...', name)
|
||||
else:
|
||||
value = self.getTextElement(movie, "title")
|
||||
result = re.search('(?<=%\s).*', value)
|
||||
|
||||
log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
|
||||
year = datetime.datetime.now().strftime("%Y")
|
||||
imdb = self.search(name, year)
|
||||
if result:
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
log.info2('Something smells...')
|
||||
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
|
||||
|
||||
@@ -28,6 +28,7 @@ class MovieResultModifier(Plugin):
|
||||
'tagline': '',
|
||||
'imdb': '',
|
||||
'genres': [],
|
||||
'mpaa': None
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
1
couchpotato/core/providers/movie/omdbapi/main.py
Normal file → Executable file
1
couchpotato/core/providers/movie/omdbapi/main.py
Normal file → Executable file
@@ -95,6 +95,7 @@ class OMDBAPI(MovieProvider):
|
||||
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', '').replace(',', ''))),
|
||||
},
|
||||
'imdb': str(movie.get('imdbID', '')),
|
||||
'mpaa': str(movie.get('Rated', '')),
|
||||
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
|
||||
'released': movie.get('Released'),
|
||||
'year': year if isinstance(year, (int)) else None,
|
||||
|
||||
@@ -167,6 +167,7 @@ class TheMovieDb(MovieProvider):
|
||||
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
||||
},
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'mpaa': movie.get('certification', ''),
|
||||
'runtime': movie.get('runtime'),
|
||||
'released': movie.get('released'),
|
||||
'year': year,
|
||||
|
||||
@@ -214,7 +214,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
# app.debug = development
|
||||
config = {
|
||||
'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',
|
||||
'ssl_cert': Env.setting('ssl_cert', default = None),
|
||||
'ssl_key': Env.setting('ssl_key', default = None),
|
||||
|
||||
@@ -22,17 +22,18 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
if($(window).getSize().x <= 480)
|
||||
window.addEvent('load', function() {
|
||||
window.addEvent('load', function(){
|
||||
|
||||
setTimeout(function(){
|
||||
window.scrollTo(0, 1);
|
||||
window.scrollTo(0, 0);
|
||||
}, 100);
|
||||
if(window.getSize().x <= 480)
|
||||
setTimeout(function(){
|
||||
window.scrollTo(0, 1);
|
||||
window.scrollTo(0, 0);
|
||||
}, 100);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
|
||||
new Uniform();
|
||||
|
||||
Api.setup({
|
||||
|
||||
79
init/ubuntu
79
init/ubuntu
@@ -26,48 +26,89 @@ NAME=couchpotato
|
||||
# App name
|
||||
DESC=CouchPotato
|
||||
|
||||
# Path to app root
|
||||
CP_APP_PATH=${APP_PATH-/usr/local/sbin/CouchPotatoServer/}
|
||||
## Don't edit this file
|
||||
## 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
|
||||
|
||||
# User to run CP as
|
||||
CP_RUN_AS=${RUN_AS-root}
|
||||
# Run CP as username
|
||||
RUN_AS=${CP_USER-couchpotato}
|
||||
|
||||
# Path to python bin
|
||||
CP_DAEMON=${DAEMON_PATH-/usr/bin/python}
|
||||
# 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}
|
||||
|
||||
# Path to store PID file
|
||||
CP_PID_FILE=${PID_FILE-/var/run/couchpotato.pid}
|
||||
PID_FILE=${CP_PID_FILE-/var/run/couchpotato.pid}
|
||||
|
||||
# Other startup args
|
||||
CP_DAEMON_OPTS=" CouchPotato.py --daemon --pid_file=${CP_PID_FILE}"
|
||||
# path to python bin
|
||||
DAEMON=${PYTHON_BIN-/usr/bin/python}
|
||||
|
||||
test -x $CP_DAEMON || exit 0
|
||||
# Extra daemon option like: CP_OPTS=" --config=/home/couchpotato/couchpotato.ini"
|
||||
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}"
|
||||
|
||||
|
||||
test -x $DAEMON || exit 0
|
||||
|
||||
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
|
||||
start)
|
||||
echo "Starting $DESC"
|
||||
rm -rf $CP_PID_FILE || return 1
|
||||
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
|
||||
start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
|
||||
;;
|
||||
stop)
|
||||
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)
|
||||
echo "Restarting $DESC"
|
||||
start-stop-daemon --stop --pidfile $CP_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 --stop --pidfile $PID_FILE --retry 15
|
||||
start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p $CP_PID_FILE "$CP_DAEMON" "$NAME"
|
||||
status_of_proc -p $PID_FILE "$DAEMON" "$NAME"
|
||||
;;
|
||||
*)
|
||||
N=/etc/init.d/$NAME
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# COPY THIS FILE TO /etc/default/couchpotato
|
||||
# OPTIONS: APP_PATH, RUN_AS, DAEMON_PATH, CP_PID_FILE
|
||||
# COPY THIS FILE TO /etc/default/couchpotato
|
||||
# OPTIONS: CP_HOME, CP_USER, CP_DATA, CP_PIDFILE, PYTHON_BIN, CP_OPTS, SSD_OPTS
|
||||
|
||||
APP_PATH=
|
||||
RUN_AS=root
|
||||
CP_HOME=
|
||||
CP_USER=root
|
||||
Reference in New Issue
Block a user