@@ -181,13 +181,13 @@ class Core(Plugin):
|
||||
return '%sapi/%s' % (self.createBaseUrl(), Env.setting('api_key'))
|
||||
|
||||
def version(self):
|
||||
ver = fireEvent('updater.info', single = True)
|
||||
ver = fireEvent('updater.info', single = True) or {'version': {}}
|
||||
|
||||
if os.name == 'nt': platf = 'windows'
|
||||
elif 'Darwin' in platform.platform(): platf = 'osx'
|
||||
else: platf = 'linux'
|
||||
|
||||
return '%s - %s-%s - v2' % (platf, ver.get('version')['type'], ver.get('version')['hash'])
|
||||
return '%s - %s-%s - v2' % (platf, ver.get('version').get('type') or 'unknown', ver.get('version').get('hash') or 'unknown')
|
||||
|
||||
def versionView(self, **kwargs):
|
||||
return {
|
||||
@@ -290,7 +290,7 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'permission_file',
|
||||
'default': '0755',
|
||||
'default': '0644',
|
||||
'label': 'File CHMOD',
|
||||
'description': 'See Folder CHMOD description, but for files',
|
||||
},
|
||||
|
||||
@@ -205,19 +205,28 @@ class GitUpdater(BaseUpdater):
|
||||
def getVersion(self):
|
||||
|
||||
if not self.version:
|
||||
|
||||
hash = None
|
||||
date = None
|
||||
branch = self.branch
|
||||
|
||||
try:
|
||||
output = self.repo.getHead() # Yes, please
|
||||
log.debug('Git version output: %s', output.hash)
|
||||
self.version = {
|
||||
'repr': 'git:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, self.repo.getCurrentBranch().name or self.branch, output.hash[:8], datetime.fromtimestamp(output.getDate())),
|
||||
'hash': output.hash[:8],
|
||||
'date': output.getDate(),
|
||||
'type': 'git',
|
||||
'branch': self.repo.getCurrentBranch().name
|
||||
}
|
||||
|
||||
hash = output.hash[:8]
|
||||
date = output.getDate()
|
||||
branch = self.repo.getCurrentBranch().name
|
||||
except Exception as e:
|
||||
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s', e)
|
||||
return 'No GIT'
|
||||
|
||||
self.version = {
|
||||
'repr': 'git:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, branch, hash or 'unknown_hash', datetime.fromtimestamp(date) if date else 'unknown_date'),
|
||||
'hash': hash,
|
||||
'date': date,
|
||||
'type': 'git',
|
||||
'branch': branch
|
||||
}
|
||||
|
||||
return self.version
|
||||
|
||||
|
||||
+290
-275
@@ -2,6 +2,7 @@ import json
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
from sqlite3 import OperationalError
|
||||
|
||||
from CodernityDB.database import RecordNotFound
|
||||
from CodernityDB.index import IndexException, IndexNotFoundException, IndexConflict
|
||||
@@ -9,7 +10,7 @@ from couchpotato import CPLog
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import toUnicode, sp
|
||||
from couchpotato.core.helpers.variable import getImdb, tryInt
|
||||
from couchpotato.core.helpers.variable import getImdb, tryInt, randomString
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -311,312 +312,326 @@ class Database(object):
|
||||
}
|
||||
|
||||
migrate_data = {}
|
||||
rename_old = False
|
||||
|
||||
c = conn.cursor()
|
||||
try:
|
||||
|
||||
for ml in migrate_list:
|
||||
migrate_data[ml] = {}
|
||||
rows = migrate_list[ml]
|
||||
c = conn.cursor()
|
||||
|
||||
try:
|
||||
c.execute('SELECT %s FROM `%s`' % ('`' + '`,`'.join(rows) + '`', ml))
|
||||
except:
|
||||
# ignore faulty destination_id database
|
||||
if ml == 'category':
|
||||
migrate_data[ml] = {}
|
||||
for ml in migrate_list:
|
||||
migrate_data[ml] = {}
|
||||
rows = migrate_list[ml]
|
||||
|
||||
try:
|
||||
c.execute('SELECT %s FROM `%s`' % ('`' + '`,`'.join(rows) + '`', ml))
|
||||
except:
|
||||
# ignore faulty destination_id database
|
||||
if ml == 'category':
|
||||
migrate_data[ml] = {}
|
||||
else:
|
||||
rename_old = True
|
||||
raise
|
||||
|
||||
for p in c.fetchall():
|
||||
columns = {}
|
||||
for row in migrate_list[ml]:
|
||||
columns[row] = p[rows.index(row)]
|
||||
|
||||
if not migrate_data[ml].get(p[0]):
|
||||
migrate_data[ml][p[0]] = columns
|
||||
else:
|
||||
if not isinstance(migrate_data[ml][p[0]], list):
|
||||
migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]]
|
||||
migrate_data[ml][p[0]].append(columns)
|
||||
|
||||
conn.close()
|
||||
|
||||
log.info('Getting data took %s', time.time() - migrate_start)
|
||||
|
||||
db = self.getDB()
|
||||
if not db.opened:
|
||||
return
|
||||
|
||||
# Use properties
|
||||
properties = migrate_data['properties']
|
||||
log.info('Importing %s properties', len(properties))
|
||||
for x in properties:
|
||||
property = properties[x]
|
||||
Env.prop(property.get('identifier'), property.get('value'))
|
||||
|
||||
# Categories
|
||||
categories = migrate_data.get('category', [])
|
||||
log.info('Importing %s categories', len(categories))
|
||||
category_link = {}
|
||||
for x in categories:
|
||||
c = categories[x]
|
||||
|
||||
new_c = db.insert({
|
||||
'_t': 'category',
|
||||
'order': c.get('order', 999),
|
||||
'label': toUnicode(c.get('label', '')),
|
||||
'ignored': toUnicode(c.get('ignored', '')),
|
||||
'preferred': toUnicode(c.get('preferred', '')),
|
||||
'required': toUnicode(c.get('required', '')),
|
||||
'destination': toUnicode(c.get('destination', '')),
|
||||
})
|
||||
|
||||
category_link[x] = new_c.get('_id')
|
||||
|
||||
# Profiles
|
||||
log.info('Importing profiles')
|
||||
new_profiles = db.all('profile', with_doc = True)
|
||||
new_profiles_by_label = {}
|
||||
for x in new_profiles:
|
||||
|
||||
# Remove default non core profiles
|
||||
if not x['doc'].get('core'):
|
||||
db.delete(x['doc'])
|
||||
else:
|
||||
raise
|
||||
new_profiles_by_label[x['doc']['label']] = x['_id']
|
||||
|
||||
for p in c.fetchall():
|
||||
columns = {}
|
||||
for row in migrate_list[ml]:
|
||||
columns[row] = p[rows.index(row)]
|
||||
profiles = migrate_data['profile']
|
||||
profile_link = {}
|
||||
for x in profiles:
|
||||
p = profiles[x]
|
||||
|
||||
if not migrate_data[ml].get(p[0]):
|
||||
migrate_data[ml][p[0]] = columns
|
||||
exists = new_profiles_by_label.get(p.get('label'))
|
||||
|
||||
# Update existing with order only
|
||||
if exists and p.get('core'):
|
||||
profile = db.get('id', exists)
|
||||
profile['order'] = tryInt(p.get('order'))
|
||||
profile['hide'] = p.get('hide') in [1, True, 'true', 'True']
|
||||
db.update(profile)
|
||||
|
||||
profile_link[x] = profile.get('_id')
|
||||
else:
|
||||
if not isinstance(migrate_data[ml][p[0]], list):
|
||||
migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]]
|
||||
migrate_data[ml][p[0]].append(columns)
|
||||
|
||||
conn.close()
|
||||
new_profile = {
|
||||
'_t': 'profile',
|
||||
'label': p.get('label'),
|
||||
'order': int(p.get('order', 999)),
|
||||
'core': p.get('core', False),
|
||||
'qualities': [],
|
||||
'wait_for': [],
|
||||
'finish': []
|
||||
}
|
||||
|
||||
log.info('Getting data took %s', time.time() - migrate_start)
|
||||
types = migrate_data['profiletype']
|
||||
for profile_type in types:
|
||||
p_type = types[profile_type]
|
||||
if types[profile_type]['profile_id'] == p['id']:
|
||||
if p_type['quality_id']:
|
||||
new_profile['finish'].append(p_type['finish'])
|
||||
new_profile['wait_for'].append(p_type['wait_for'])
|
||||
new_profile['qualities'].append(migrate_data['quality'][p_type['quality_id']]['identifier'])
|
||||
|
||||
db = self.getDB()
|
||||
if not db.opened:
|
||||
return
|
||||
if len(new_profile['qualities']) > 0:
|
||||
new_profile.update(db.insert(new_profile))
|
||||
profile_link[x] = new_profile.get('_id')
|
||||
else:
|
||||
log.error('Corrupt profile list for "%s", using default.', p.get('label'))
|
||||
|
||||
# Use properties
|
||||
properties = migrate_data['properties']
|
||||
log.info('Importing %s properties', len(properties))
|
||||
for x in properties:
|
||||
property = properties[x]
|
||||
Env.prop(property.get('identifier'), property.get('value'))
|
||||
# Qualities
|
||||
log.info('Importing quality sizes')
|
||||
new_qualities = db.all('quality', with_doc = True)
|
||||
new_qualities_by_identifier = {}
|
||||
for x in new_qualities:
|
||||
new_qualities_by_identifier[x['doc']['identifier']] = x['_id']
|
||||
|
||||
# Categories
|
||||
categories = migrate_data.get('category', [])
|
||||
log.info('Importing %s categories', len(categories))
|
||||
category_link = {}
|
||||
for x in categories:
|
||||
c = categories[x]
|
||||
qualities = migrate_data['quality']
|
||||
quality_link = {}
|
||||
for x in qualities:
|
||||
q = qualities[x]
|
||||
q_id = new_qualities_by_identifier[q.get('identifier')]
|
||||
|
||||
new_c = db.insert({
|
||||
'_t': 'category',
|
||||
'order': c.get('order', 999),
|
||||
'label': toUnicode(c.get('label', '')),
|
||||
'ignored': toUnicode(c.get('ignored', '')),
|
||||
'preferred': toUnicode(c.get('preferred', '')),
|
||||
'required': toUnicode(c.get('required', '')),
|
||||
'destination': toUnicode(c.get('destination', '')),
|
||||
})
|
||||
quality = db.get('id', q_id)
|
||||
quality['order'] = q.get('order')
|
||||
quality['size_min'] = tryInt(q.get('size_min'))
|
||||
quality['size_max'] = tryInt(q.get('size_max'))
|
||||
db.update(quality)
|
||||
|
||||
category_link[x] = new_c.get('_id')
|
||||
quality_link[x] = quality
|
||||
|
||||
# Profiles
|
||||
log.info('Importing profiles')
|
||||
new_profiles = db.all('profile', with_doc = True)
|
||||
new_profiles_by_label = {}
|
||||
for x in new_profiles:
|
||||
# Titles
|
||||
titles = migrate_data['librarytitle']
|
||||
titles_by_library = {}
|
||||
for x in titles:
|
||||
title = titles[x]
|
||||
if title.get('default'):
|
||||
titles_by_library[title.get('libraries_id')] = title.get('title')
|
||||
|
||||
# Remove default non core profiles
|
||||
if not x['doc'].get('core'):
|
||||
db.delete(x['doc'])
|
||||
else:
|
||||
new_profiles_by_label[x['doc']['label']] = x['_id']
|
||||
# Releases
|
||||
releaseinfos = migrate_data['releaseinfo']
|
||||
for x in releaseinfos:
|
||||
info = releaseinfos[x]
|
||||
|
||||
profiles = migrate_data['profile']
|
||||
profile_link = {}
|
||||
for x in profiles:
|
||||
p = profiles[x]
|
||||
|
||||
exists = new_profiles_by_label.get(p.get('label'))
|
||||
|
||||
# Update existing with order only
|
||||
if exists and p.get('core'):
|
||||
profile = db.get('id', exists)
|
||||
profile['order'] = tryInt(p.get('order'))
|
||||
profile['hide'] = p.get('hide') in [1, True, 'true', 'True']
|
||||
db.update(profile)
|
||||
|
||||
profile_link[x] = profile.get('_id')
|
||||
else:
|
||||
|
||||
new_profile = {
|
||||
'_t': 'profile',
|
||||
'label': p.get('label'),
|
||||
'order': int(p.get('order', 999)),
|
||||
'core': p.get('core', False),
|
||||
'qualities': [],
|
||||
'wait_for': [],
|
||||
'finish': []
|
||||
}
|
||||
|
||||
types = migrate_data['profiletype']
|
||||
for profile_type in types:
|
||||
p_type = types[profile_type]
|
||||
if types[profile_type]['profile_id'] == p['id']:
|
||||
if p_type['quality_id']:
|
||||
new_profile['finish'].append(p_type['finish'])
|
||||
new_profile['wait_for'].append(p_type['wait_for'])
|
||||
new_profile['qualities'].append(migrate_data['quality'][p_type['quality_id']]['identifier'])
|
||||
|
||||
if len(new_profile['qualities']) > 0:
|
||||
new_profile.update(db.insert(new_profile))
|
||||
profile_link[x] = new_profile.get('_id')
|
||||
else:
|
||||
log.error('Corrupt profile list for "%s", using default.', p.get('label'))
|
||||
|
||||
# Qualities
|
||||
log.info('Importing quality sizes')
|
||||
new_qualities = db.all('quality', with_doc = True)
|
||||
new_qualities_by_identifier = {}
|
||||
for x in new_qualities:
|
||||
new_qualities_by_identifier[x['doc']['identifier']] = x['_id']
|
||||
|
||||
qualities = migrate_data['quality']
|
||||
quality_link = {}
|
||||
for x in qualities:
|
||||
q = qualities[x]
|
||||
q_id = new_qualities_by_identifier[q.get('identifier')]
|
||||
|
||||
quality = db.get('id', q_id)
|
||||
quality['order'] = q.get('order')
|
||||
quality['size_min'] = tryInt(q.get('size_min'))
|
||||
quality['size_max'] = tryInt(q.get('size_max'))
|
||||
db.update(quality)
|
||||
|
||||
quality_link[x] = quality
|
||||
|
||||
# Titles
|
||||
titles = migrate_data['librarytitle']
|
||||
titles_by_library = {}
|
||||
for x in titles:
|
||||
title = titles[x]
|
||||
if title.get('default'):
|
||||
titles_by_library[title.get('libraries_id')] = title.get('title')
|
||||
|
||||
# Releases
|
||||
releaseinfos = migrate_data['releaseinfo']
|
||||
for x in releaseinfos:
|
||||
info = releaseinfos[x]
|
||||
|
||||
# Skip if release doesn't exist for this info
|
||||
if not migrate_data['release'].get(info.get('release_id')):
|
||||
continue
|
||||
|
||||
if not migrate_data['release'][info.get('release_id')].get('info'):
|
||||
migrate_data['release'][info.get('release_id')]['info'] = {}
|
||||
|
||||
migrate_data['release'][info.get('release_id')]['info'][info.get('identifier')] = info.get('value')
|
||||
|
||||
releases = migrate_data['release']
|
||||
releases_by_media = {}
|
||||
for x in releases:
|
||||
release = releases[x]
|
||||
if not releases_by_media.get(release.get('movie_id')):
|
||||
releases_by_media[release.get('movie_id')] = []
|
||||
|
||||
releases_by_media[release.get('movie_id')].append(release)
|
||||
|
||||
# Type ids
|
||||
types = migrate_data['filetype']
|
||||
type_by_id = {}
|
||||
for t in types:
|
||||
type = types[t]
|
||||
type_by_id[type.get('id')] = type
|
||||
|
||||
# Media
|
||||
log.info('Importing %s media items', len(migrate_data['movie']))
|
||||
statuses = migrate_data['status']
|
||||
libraries = migrate_data['library']
|
||||
library_files = migrate_data['library_files__file_library']
|
||||
releases_files = migrate_data['release_files__file_release']
|
||||
all_files = migrate_data['file']
|
||||
poster_type = migrate_data['filetype']['poster']
|
||||
medias = migrate_data['movie']
|
||||
for x in medias:
|
||||
m = medias[x]
|
||||
|
||||
status = statuses.get(m['status_id']).get('identifier')
|
||||
l = libraries.get(m['library_id'])
|
||||
|
||||
# Only migrate wanted movies, Skip if no identifier present
|
||||
if not l or not getImdb(l.get('identifier')): continue
|
||||
|
||||
profile_id = profile_link.get(m['profile_id'])
|
||||
category_id = category_link.get(m['category_id'])
|
||||
title = titles_by_library.get(m['library_id'])
|
||||
releases = releases_by_media.get(x, [])
|
||||
info = json.loads(l.get('info', ''))
|
||||
|
||||
files = library_files.get(m['library_id'], [])
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
|
||||
added_media = fireEvent('movie.add', {
|
||||
'info': info,
|
||||
'identifier': l.get('identifier'),
|
||||
'profile_id': profile_id,
|
||||
'category_id': category_id,
|
||||
'title': title
|
||||
}, force_readd = False, search_after = False, update_after = False, notify_after = False, status = status, single = True)
|
||||
|
||||
if not added_media:
|
||||
log.error('Failed adding media %s: %s', (l.get('identifier'), info))
|
||||
continue
|
||||
|
||||
added_media['files'] = added_media.get('files', {})
|
||||
for f in files:
|
||||
ffile = all_files[f.get('file_id')]
|
||||
|
||||
# Only migrate posters
|
||||
if ffile.get('type_id') == poster_type.get('id'):
|
||||
if ffile.get('path') not in added_media['files'].get('image_poster', []) and os.path.isfile(ffile.get('path')):
|
||||
added_media['files']['image_poster'] = [ffile.get('path')]
|
||||
break
|
||||
|
||||
if 'image_poster' in added_media['files']:
|
||||
db.update(added_media)
|
||||
|
||||
for rel in releases:
|
||||
|
||||
empty_info = False
|
||||
if not rel.get('info'):
|
||||
empty_info = True
|
||||
rel['info'] = {}
|
||||
|
||||
quality = quality_link.get(rel.get('quality_id'))
|
||||
if not quality:
|
||||
# Skip if release doesn't exist for this info
|
||||
if not migrate_data['release'].get(info.get('release_id')):
|
||||
continue
|
||||
|
||||
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
||||
if not migrate_data['release'][info.get('release_id')].get('info'):
|
||||
migrate_data['release'][info.get('release_id')]['info'] = {}
|
||||
|
||||
if rel['info'].get('download_id'):
|
||||
status_support = rel['info'].get('download_status_support', False) in [True, 'true', 'True']
|
||||
rel['info']['download_info'] = {
|
||||
'id': rel['info'].get('download_id'),
|
||||
'downloader': rel['info'].get('download_downloader'),
|
||||
'status_support': status_support,
|
||||
}
|
||||
migrate_data['release'][info.get('release_id')]['info'][info.get('identifier')] = info.get('value')
|
||||
|
||||
# Add status to keys
|
||||
rel['info']['status'] = release_status
|
||||
if not empty_info:
|
||||
fireEvent('release.create_from_search', [rel['info']], added_media, quality, single = True)
|
||||
else:
|
||||
release = {
|
||||
'_t': 'release',
|
||||
'identifier': rel.get('identifier'),
|
||||
'media_id': added_media.get('_id'),
|
||||
'quality': quality.get('identifier'),
|
||||
'status': release_status,
|
||||
'last_edit': int(time.time()),
|
||||
'files': {}
|
||||
}
|
||||
releases = migrate_data['release']
|
||||
releases_by_media = {}
|
||||
for x in releases:
|
||||
release = releases[x]
|
||||
if not releases_by_media.get(release.get('movie_id')):
|
||||
releases_by_media[release.get('movie_id')] = []
|
||||
|
||||
# Add downloader info if provided
|
||||
try:
|
||||
release['download_info'] = rel['info']['download_info']
|
||||
del rel['download_info']
|
||||
except:
|
||||
pass
|
||||
releases_by_media[release.get('movie_id')].append(release)
|
||||
|
||||
# Add files
|
||||
release_files = releases_files.get(rel.get('id'), [])
|
||||
if not isinstance(release_files, list):
|
||||
release_files = [release_files]
|
||||
# Type ids
|
||||
types = migrate_data['filetype']
|
||||
type_by_id = {}
|
||||
for t in types:
|
||||
type = types[t]
|
||||
type_by_id[type.get('id')] = type
|
||||
|
||||
if len(release_files) == 0:
|
||||
# Media
|
||||
log.info('Importing %s media items', len(migrate_data['movie']))
|
||||
statuses = migrate_data['status']
|
||||
libraries = migrate_data['library']
|
||||
library_files = migrate_data['library_files__file_library']
|
||||
releases_files = migrate_data['release_files__file_release']
|
||||
all_files = migrate_data['file']
|
||||
poster_type = migrate_data['filetype']['poster']
|
||||
medias = migrate_data['movie']
|
||||
for x in medias:
|
||||
m = medias[x]
|
||||
|
||||
status = statuses.get(m['status_id']).get('identifier')
|
||||
l = libraries.get(m['library_id'])
|
||||
|
||||
# Only migrate wanted movies, Skip if no identifier present
|
||||
if not l or not getImdb(l.get('identifier')): continue
|
||||
|
||||
profile_id = profile_link.get(m['profile_id'])
|
||||
category_id = category_link.get(m['category_id'])
|
||||
title = titles_by_library.get(m['library_id'])
|
||||
releases = releases_by_media.get(x, [])
|
||||
info = json.loads(l.get('info', ''))
|
||||
|
||||
files = library_files.get(m['library_id'], [])
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
|
||||
added_media = fireEvent('movie.add', {
|
||||
'info': info,
|
||||
'identifier': l.get('identifier'),
|
||||
'profile_id': profile_id,
|
||||
'category_id': category_id,
|
||||
'title': title
|
||||
}, force_readd = False, search_after = False, update_after = False, notify_after = False, status = status, single = True)
|
||||
|
||||
if not added_media:
|
||||
log.error('Failed adding media %s: %s', (l.get('identifier'), info))
|
||||
continue
|
||||
|
||||
added_media['files'] = added_media.get('files', {})
|
||||
for f in files:
|
||||
ffile = all_files[f.get('file_id')]
|
||||
|
||||
# Only migrate posters
|
||||
if ffile.get('type_id') == poster_type.get('id'):
|
||||
if ffile.get('path') not in added_media['files'].get('image_poster', []) and os.path.isfile(ffile.get('path')):
|
||||
added_media['files']['image_poster'] = [ffile.get('path')]
|
||||
break
|
||||
|
||||
if 'image_poster' in added_media['files']:
|
||||
db.update(added_media)
|
||||
|
||||
for rel in releases:
|
||||
|
||||
empty_info = False
|
||||
if not rel.get('info'):
|
||||
empty_info = True
|
||||
rel['info'] = {}
|
||||
|
||||
quality = quality_link.get(rel.get('quality_id'))
|
||||
if not quality:
|
||||
continue
|
||||
|
||||
for f in release_files:
|
||||
rfile = all_files.get(f.get('file_id'))
|
||||
if not rfile:
|
||||
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
||||
|
||||
if rel['info'].get('download_id'):
|
||||
status_support = rel['info'].get('download_status_support', False) in [True, 'true', 'True']
|
||||
rel['info']['download_info'] = {
|
||||
'id': rel['info'].get('download_id'),
|
||||
'downloader': rel['info'].get('download_downloader'),
|
||||
'status_support': status_support,
|
||||
}
|
||||
|
||||
# Add status to keys
|
||||
rel['info']['status'] = release_status
|
||||
if not empty_info:
|
||||
fireEvent('release.create_from_search', [rel['info']], added_media, quality, single = True)
|
||||
else:
|
||||
release = {
|
||||
'_t': 'release',
|
||||
'identifier': rel.get('identifier'),
|
||||
'media_id': added_media.get('_id'),
|
||||
'quality': quality.get('identifier'),
|
||||
'status': release_status,
|
||||
'last_edit': int(time.time()),
|
||||
'files': {}
|
||||
}
|
||||
|
||||
# Add downloader info if provided
|
||||
try:
|
||||
release['download_info'] = rel['info']['download_info']
|
||||
del rel['download_info']
|
||||
except:
|
||||
pass
|
||||
|
||||
# Add files
|
||||
release_files = releases_files.get(rel.get('id'), [])
|
||||
if not isinstance(release_files, list):
|
||||
release_files = [release_files]
|
||||
|
||||
if len(release_files) == 0:
|
||||
continue
|
||||
|
||||
file_type = type_by_id.get(rfile.get('type_id')).get('identifier')
|
||||
for f in release_files:
|
||||
rfile = all_files.get(f.get('file_id'))
|
||||
if not rfile:
|
||||
continue
|
||||
|
||||
if not release['files'].get(file_type):
|
||||
release['files'][file_type] = []
|
||||
file_type = type_by_id.get(rfile.get('type_id')).get('identifier')
|
||||
|
||||
release['files'][file_type].append(rfile.get('path'))
|
||||
if not release['files'].get(file_type):
|
||||
release['files'][file_type] = []
|
||||
|
||||
try:
|
||||
rls = db.get('release_identifier', rel.get('identifier'), with_doc = True)['doc']
|
||||
rls.update(release)
|
||||
db.update(rls)
|
||||
except:
|
||||
db.insert(release)
|
||||
release['files'][file_type].append(rfile.get('path'))
|
||||
|
||||
try:
|
||||
rls = db.get('release_identifier', rel.get('identifier'), with_doc = True)['doc']
|
||||
rls.update(release)
|
||||
db.update(rls)
|
||||
except:
|
||||
db.insert(release)
|
||||
|
||||
log.info('Total migration took %s', time.time() - migrate_start)
|
||||
log.info('=' * 30)
|
||||
|
||||
rename_old = True
|
||||
|
||||
except OperationalError:
|
||||
log.error('Migrating from faulty database, probably a (too) old version: %s', traceback.format_exc())
|
||||
except:
|
||||
log.error('Migration failed: %s', traceback.format_exc())
|
||||
|
||||
log.info('Total migration took %s', time.time() - migrate_start)
|
||||
log.info('=' * 30)
|
||||
|
||||
# rename old database
|
||||
log.info('Renaming old database to %s ', old_db + '.old')
|
||||
os.rename(old_db, old_db + '.old')
|
||||
if rename_old:
|
||||
random = randomString()
|
||||
log.info('Renaming old database to %s ', '%s.%s_old' % (old_db, random))
|
||||
os.rename(old_db, '%s.%s_old' % (old_db, random))
|
||||
|
||||
if os.path.isfile(old_db + '-wal'):
|
||||
os.rename(old_db + '-wal', old_db + '-wal.old')
|
||||
if os.path.isfile(old_db + '-shm'):
|
||||
os.rename(old_db + '-shm', old_db + '-shm.old')
|
||||
if os.path.isfile(old_db + '-wal'):
|
||||
os.rename(old_db + '-wal', '%s-wal.%s_old' % (old_db, random))
|
||||
if os.path.isfile(old_db + '-shm'):
|
||||
os.rename(old_db + '-shm', '%s-shm.%s_old' % (old_db, random))
|
||||
|
||||
@@ -27,6 +27,11 @@ class Deluge(DownloaderBase):
|
||||
def connect(self, reconnect = False):
|
||||
# Load host from config and split out port.
|
||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
||||
|
||||
# Force host assignment
|
||||
if len(host) == 1:
|
||||
host.append(80)
|
||||
|
||||
if not isInt(host[1]):
|
||||
log.error('Config properties are not filled in correctly, port is missing.')
|
||||
return False
|
||||
|
||||
@@ -78,12 +78,14 @@ class Transmission(DownloaderBase):
|
||||
log.error('Failed sending torrent to Transmission')
|
||||
return False
|
||||
|
||||
data = remote_torrent.get('torrent-added') or remote_torrent.get('torrent-duplicate')
|
||||
|
||||
# Change settings of added torrents
|
||||
if torrent_params:
|
||||
self.trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||
self.trpc.set_torrent(data['hashString'], torrent_params)
|
||||
|
||||
log.info('Torrent sent to Transmission successfully.')
|
||||
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
||||
return self.downloadReturnId(data['hashString'])
|
||||
|
||||
def test(self):
|
||||
if self.connect() and self.trpc.get_session():
|
||||
|
||||
@@ -5,6 +5,7 @@ import re
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from chardet import detect
|
||||
from couchpotato.core.logger import CPLog
|
||||
import six
|
||||
|
||||
@@ -35,6 +36,9 @@ def toUnicode(original, *args):
|
||||
return six.text_type(original, *args)
|
||||
except:
|
||||
try:
|
||||
detected = detect(original)
|
||||
if detected.get('encoding') == 'utf-8':
|
||||
return original.decode('utf-8')
|
||||
return ek(original, *args)
|
||||
except:
|
||||
raise
|
||||
@@ -52,7 +56,10 @@ def ss(original, *args):
|
||||
return u_original.encode(Env.get('encoding'))
|
||||
except Exception as e:
|
||||
log.debug('Failed ss encoding char, force UTF8: %s', e)
|
||||
return u_original.encode('UTF-8')
|
||||
try:
|
||||
return u_original.encode(Env.get('encoding'), 'replace')
|
||||
except:
|
||||
return u_original.encode('utf-8', 'replace')
|
||||
|
||||
|
||||
def sp(path, *args):
|
||||
|
||||
@@ -41,11 +41,11 @@ def symlink(src, dst):
|
||||
def getUserDir():
|
||||
try:
|
||||
import pwd
|
||||
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
|
||||
os.environ['HOME'] = sp(pwd.getpwuid(os.geteuid()).pw_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
return os.path.expanduser('~')
|
||||
return sp(os.path.expanduser('~'))
|
||||
|
||||
|
||||
def getDownloadDir():
|
||||
|
||||
@@ -59,15 +59,14 @@ class CPLog(object):
|
||||
msg = ss(msg)
|
||||
|
||||
try:
|
||||
msg = msg % replace_tuple
|
||||
except:
|
||||
try:
|
||||
if isinstance(replace_tuple, tuple):
|
||||
msg = msg % tuple([ss(x) for x in list(replace_tuple)])
|
||||
else:
|
||||
msg = msg % ss(replace_tuple)
|
||||
except Exception as e:
|
||||
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, e))
|
||||
if isinstance(replace_tuple, tuple):
|
||||
msg = msg % tuple([ss(x) if not isinstance(x, (int, float)) else x for x in list(replace_tuple)])
|
||||
elif isinstance(replace_tuple, dict):
|
||||
msg = msg % dict((k, ss(v) if not isinstance(v, (int, float)) else v) for k, v in replace_tuple.iteritems())
|
||||
else:
|
||||
msg = msg % ss(replace_tuple)
|
||||
except Exception as e:
|
||||
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, e))
|
||||
|
||||
self.setup()
|
||||
if not self.is_develop:
|
||||
|
||||
@@ -95,7 +95,7 @@ class MediaBase(Plugin):
|
||||
if file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
|
||||
file_path = fireEvent('file.download', url = image, single = True)
|
||||
if file_path:
|
||||
existing_files[file_type] = [file_path]
|
||||
existing_files[file_type] = [toUnicode(file_path)]
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -456,6 +456,11 @@ class MediaPlugin(MediaBase):
|
||||
deleted = True
|
||||
elif new_media_status:
|
||||
media['status'] = new_media_status
|
||||
|
||||
# Remove profile (no use for in manage)
|
||||
if new_media_status == 'done':
|
||||
media['profile_id'] = None
|
||||
|
||||
db.update(media)
|
||||
|
||||
fireEvent('media.untag', media['_id'], 'recent', single = True)
|
||||
@@ -491,7 +496,7 @@ class MediaPlugin(MediaBase):
|
||||
}
|
||||
})
|
||||
|
||||
def restatus(self, media_id, tag_recent = True):
|
||||
def restatus(self, media_id, tag_recent = True, allowed_restatus = None):
|
||||
|
||||
try:
|
||||
db = get_db()
|
||||
@@ -526,7 +531,7 @@ class MediaPlugin(MediaBase):
|
||||
m['status'] = previous_status
|
||||
|
||||
# Only update when status has changed
|
||||
if previous_status != m['status']:
|
||||
if previous_status != m['status'] and (not allowed_restatus or m['status'] in allowed_restatus):
|
||||
db.update(m)
|
||||
|
||||
# Tag media as recent
|
||||
|
||||
@@ -178,6 +178,25 @@ class Searcher(SearcherBase):
|
||||
|
||||
return False
|
||||
|
||||
def containsWords(self, rel_name, rel_words, conf, media):
|
||||
|
||||
# Make sure it has required words
|
||||
words = splitString(self.conf('%s_words' % conf, section = 'searcher').lower())
|
||||
try: words = removeDuplicate(words + splitString(media['category'][conf].lower()))
|
||||
except: pass
|
||||
|
||||
req_match = 0
|
||||
for req_set in words:
|
||||
if len(req_set) >= 2 and (req_set[:1] + req_set[-1:]) == '//':
|
||||
if re.search(req_set[1:-1], rel_name):
|
||||
log.debug('Regex match: %s', req_set[1:-1])
|
||||
req_match += 1
|
||||
else:
|
||||
req = splitString(req_set, '&')
|
||||
req_match += len(list(set(rel_words) & set(req))) == len(req)
|
||||
|
||||
return words, req_match > 0
|
||||
|
||||
def correctWords(self, rel_name, media):
|
||||
media_title = fireEvent('searcher.get_search_title', media, single = True)
|
||||
media_words = re.split('\W+', simplifyString(media_title))
|
||||
@@ -185,31 +204,13 @@ class Searcher(SearcherBase):
|
||||
rel_name = simplifyString(rel_name)
|
||||
rel_words = re.split('\W+', rel_name)
|
||||
|
||||
# Make sure it has required words
|
||||
required_words = splitString(self.conf('required_words', section = 'searcher').lower())
|
||||
try: required_words = removeDuplicate(required_words + splitString(media['category']['required'].lower()))
|
||||
except: pass
|
||||
|
||||
req_match = 0
|
||||
for req_set in required_words:
|
||||
req = splitString(req_set, '&')
|
||||
req_match += len(list(set(rel_words) & set(req))) == len(req)
|
||||
|
||||
if len(required_words) > 0 and req_match == 0:
|
||||
required_words, contains_required = self.containsWords(rel_name, rel_words, 'required', media)
|
||||
if len(required_words) > 0 and not contains_required:
|
||||
log.info2('Wrong: Required word missing: %s', rel_name)
|
||||
return False
|
||||
|
||||
# Ignore releases
|
||||
ignored_words = splitString(self.conf('ignored_words', section = 'searcher').lower())
|
||||
try: ignored_words = removeDuplicate(ignored_words + splitString(media['category']['ignored'].lower()))
|
||||
except: pass
|
||||
|
||||
ignored_match = 0
|
||||
for ignored_set in ignored_words:
|
||||
ignored = splitString(ignored_set, '&')
|
||||
ignored_match += len(list(set(rel_words) & set(ignored))) == len(ignored)
|
||||
|
||||
if len(ignored_words) > 0 and ignored_match:
|
||||
ignored_words, contains_ignored = self.containsWords(rel_name, rel_words, 'ignored', media)
|
||||
if len(ignored_words) > 0 and contains_ignored:
|
||||
log.info2("Wrong: '%s' contains 'ignored words'", rel_name)
|
||||
return False
|
||||
|
||||
|
||||
@@ -264,3 +264,11 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
.toggle_menu h2 {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ var Charts = new Class({
|
||||
|
||||
Implements: [Options, Events],
|
||||
|
||||
shown_once: false,
|
||||
|
||||
initialize: function(options){
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
@@ -40,17 +42,13 @@ var Charts = new Class({
|
||||
)
|
||||
);
|
||||
|
||||
if( Cookie.read('suggestions_charts_menu_selected') === 'charts')
|
||||
self.el.show();
|
||||
if( Cookie.read('suggestions_charts_menu_selected') === 'charts'){
|
||||
self.show();
|
||||
self.fireEvent.delay(0, self, 'created');
|
||||
}
|
||||
else
|
||||
self.el.hide();
|
||||
|
||||
self.api_request = Api.request('charts.view', {
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
|
||||
self.fireEvent.delay(0, self, 'created');
|
||||
|
||||
},
|
||||
|
||||
fill: function(json){
|
||||
@@ -157,6 +155,24 @@ var Charts = new Class({
|
||||
|
||||
},
|
||||
|
||||
show: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.show();
|
||||
|
||||
if(!self.shown_once){
|
||||
self.api_request = Api.request('charts.view', {
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
|
||||
self.shown_once = true;
|
||||
}
|
||||
},
|
||||
|
||||
hide: function(){
|
||||
this.el.hide();
|
||||
},
|
||||
|
||||
afterAdded: function(m){
|
||||
|
||||
$(m).getElement('div.chart_number')
|
||||
|
||||
@@ -4,6 +4,7 @@ from couchpotato import tryInt
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||
from requests import HTTPError
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -32,12 +33,14 @@ class FanartTV(MovieProvider):
|
||||
|
||||
try:
|
||||
url = self.urls['api'] % identifier
|
||||
fanart_data = self.getJsonData(url)
|
||||
fanart_data = self.getJsonData(url, show_error = False)
|
||||
|
||||
if fanart_data:
|
||||
log.debug('Found images for %s', fanart_data.get('name'))
|
||||
images = self._parseMovie(fanart_data)
|
||||
|
||||
except HTTPError as e:
|
||||
log.debug('Failed getting extra art for %s: %s',
|
||||
(identifier, e))
|
||||
except:
|
||||
log.error('Failed getting extra art for %s: %s',
|
||||
(identifier, traceback.format_exc()))
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import traceback
|
||||
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, ss, tryUrlencode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||
import tmdb3
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -13,54 +12,66 @@ autoload = 'TheMovieDb'
|
||||
|
||||
|
||||
class TheMovieDb(MovieProvider):
|
||||
MAX_EXTRATHUMBS = 4
|
||||
|
||||
http_time_between_calls = .35
|
||||
|
||||
configuration = {
|
||||
'images': {
|
||||
'secure_base_url': 'https://image.tmdb.org/t/p/',
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
addEvent('info.search', self.search, priority = 3)
|
||||
addEvent('movie.search', self.search, priority = 3)
|
||||
addEvent('movie.info', self.getInfo, priority = 3)
|
||||
addEvent('movie.info_by_tmdb', self.getInfo)
|
||||
addEvent('app.load', self.config)
|
||||
|
||||
# Configure TMDB settings
|
||||
tmdb3.set_key(self.conf('api_key'))
|
||||
tmdb3.set_cache('null')
|
||||
def config(self):
|
||||
configuration = self.request('configuration')
|
||||
if configuration:
|
||||
self.configuration = configuration
|
||||
|
||||
def search(self, q, limit = 12):
|
||||
def search(self, q, limit = 3):
|
||||
""" Find movie by name """
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
search_string = simplifyString(q)
|
||||
cache_key = 'tmdb.cache.%s.%s' % (search_string, limit)
|
||||
results = self.getCache(cache_key)
|
||||
log.debug('Searching for movie: %s', q)
|
||||
|
||||
if not results:
|
||||
log.debug('Searching for movie: %s', q)
|
||||
raw = None
|
||||
try:
|
||||
name_year = fireEvent('scanner.name_year', q, single = True)
|
||||
raw = self.request('search/movie', {
|
||||
'query': name_year.get('name', q),
|
||||
'year': name_year.get('year'),
|
||||
'search_type': 'ngram' if limit > 1 else 'phrase'
|
||||
}, return_key = 'results')
|
||||
except:
|
||||
log.error('Failed searching TMDB for "%s": %s', (q, traceback.format_exc()))
|
||||
|
||||
raw = None
|
||||
results = []
|
||||
if raw:
|
||||
try:
|
||||
raw = tmdb3.searchMovie(search_string)
|
||||
except:
|
||||
log.error('Failed searching TMDB for "%s": %s', (search_string, traceback.format_exc()))
|
||||
nr = 0
|
||||
|
||||
results = []
|
||||
if raw:
|
||||
try:
|
||||
nr = 0
|
||||
for movie in raw:
|
||||
parsed_movie = self.parseMovie(movie, extended = False)
|
||||
if parsed_movie:
|
||||
results.append(parsed_movie)
|
||||
|
||||
for movie in raw:
|
||||
results.append(self.parseMovie(movie, extended = False))
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
break
|
||||
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
break
|
||||
log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results])
|
||||
|
||||
log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results])
|
||||
|
||||
self.setCache(cache_key, results)
|
||||
return results
|
||||
except SyntaxError as e:
|
||||
log.error('Failed to parse XML response: %s', e)
|
||||
return False
|
||||
return results
|
||||
except SyntaxError as e:
|
||||
log.error('Failed to parse XML response: %s', e)
|
||||
return False
|
||||
|
||||
return results
|
||||
|
||||
@@ -69,101 +80,92 @@ class TheMovieDb(MovieProvider):
|
||||
if not identifier:
|
||||
return {}
|
||||
|
||||
cache_key = 'tmdb.cache.%s%s' % (identifier, '.ex' if extended else '')
|
||||
result = self.getCache(cache_key)
|
||||
result = self.parseMovie({
|
||||
'id': identifier
|
||||
}, extended = extended)
|
||||
|
||||
if not result:
|
||||
try:
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
# noinspection PyArgumentList
|
||||
movie = tmdb3.Movie(identifier)
|
||||
try: exists = movie.title is not None
|
||||
except: exists = False
|
||||
|
||||
if exists:
|
||||
result = self.parseMovie(movie, extended = extended)
|
||||
self.setCache(cache_key, result)
|
||||
else:
|
||||
result = {}
|
||||
except:
|
||||
log.error('Failed getting info for %s: %s', (identifier, traceback.format_exc()))
|
||||
|
||||
return result
|
||||
return result or {}
|
||||
|
||||
def parseMovie(self, movie, extended = True):
|
||||
|
||||
cache_key = 'tmdb.cache.%s%s' % (movie.id, '.ex' if extended else '')
|
||||
movie_data = self.getCache(cache_key)
|
||||
# Do request, append other items
|
||||
movie = self.request('movie/%s' % movie.get('id'), {
|
||||
'append_to_response': 'alternative_titles' + (',images,casts' if extended else '')
|
||||
})
|
||||
if not movie:
|
||||
return
|
||||
|
||||
if not movie_data:
|
||||
# Images
|
||||
poster = self.getImage(movie, type = 'poster', size = 'w154')
|
||||
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
||||
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
||||
extra_thumbs = self.getMultImages(movie, type = 'backdrops', size = 'original') if extended else []
|
||||
|
||||
# Images
|
||||
poster = self.getImage(movie, type = 'poster', size = 'w154')
|
||||
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
||||
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
||||
extra_thumbs = self.getMultImages(movie, type = 'backdrops', size = 'original', n = self.MAX_EXTRATHUMBS, skipfirst = True)
|
||||
images = {
|
||||
'poster': [poster] if poster else [],
|
||||
#'backdrop': [backdrop] if backdrop else [],
|
||||
'poster_original': [poster_original] if poster_original else [],
|
||||
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
||||
'actors': {},
|
||||
'extra_thumbs': extra_thumbs
|
||||
}
|
||||
|
||||
images = {
|
||||
'poster': [poster] if poster else [],
|
||||
#'backdrop': [backdrop] if backdrop else [],
|
||||
'poster_original': [poster_original] if poster_original else [],
|
||||
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
||||
'actors': {},
|
||||
'extra_thumbs': extra_thumbs
|
||||
}
|
||||
# Genres
|
||||
try:
|
||||
genres = [genre.get('name') for genre in movie.get('genres', [])]
|
||||
except:
|
||||
genres = []
|
||||
|
||||
# Genres
|
||||
try:
|
||||
genres = [genre.name for genre in movie.genres]
|
||||
except:
|
||||
genres = []
|
||||
# 1900 is the same as None
|
||||
print movie
|
||||
year = str(movie.get('release_date') or '')[:4]
|
||||
if not movie.get('release_date') or year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
|
||||
# 1900 is the same as None
|
||||
year = str(movie.releasedate or '')[:4]
|
||||
if not movie.releasedate or year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
# Gather actors data
|
||||
actors = {}
|
||||
if extended:
|
||||
|
||||
# Gather actors data
|
||||
actors = {}
|
||||
if extended:
|
||||
for cast_item in movie.cast:
|
||||
try:
|
||||
actors[toUnicode(cast_item.name)] = toUnicode(cast_item.character)
|
||||
images['actors'][toUnicode(cast_item.name)] = self.getImage(cast_item, type = 'profile', size = 'original')
|
||||
except:
|
||||
log.debug('Error getting cast info for %s: %s', (cast_item, traceback.format_exc()))
|
||||
# Full data
|
||||
cast = movie.get('casts', {}).get('cast', [])
|
||||
|
||||
movie_data = {
|
||||
'type': 'movie',
|
||||
'via_tmdb': True,
|
||||
'tmdb_id': movie.id,
|
||||
'titles': [toUnicode(movie.title)],
|
||||
'original_title': movie.originaltitle,
|
||||
'images': images,
|
||||
'imdb': movie.imdb,
|
||||
'runtime': movie.runtime,
|
||||
'released': str(movie.releasedate),
|
||||
'year': tryInt(year, None),
|
||||
'plot': movie.overview,
|
||||
'genres': genres,
|
||||
'collection': getattr(movie.collection, 'name', None),
|
||||
'actor_roles': actors
|
||||
}
|
||||
for cast_item in cast:
|
||||
try:
|
||||
actors[toUnicode(cast_item.get('name'))] = toUnicode(cast_item.get('character'))
|
||||
images['actors'][toUnicode(cast_item.get('name'))] = self.getImage(cast_item, type = 'profile', size = 'original')
|
||||
except:
|
||||
log.debug('Error getting cast info for %s: %s', (cast_item, traceback.format_exc()))
|
||||
|
||||
movie_data = dict((k, v) for k, v in movie_data.items() if v)
|
||||
movie_data = {
|
||||
'type': 'movie',
|
||||
'via_tmdb': True,
|
||||
'tmdb_id': movie.get('id'),
|
||||
'titles': [toUnicode(movie.get('title'))],
|
||||
'original_title': movie.get('original_title'),
|
||||
'images': images,
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'runtime': movie.get('runtime'),
|
||||
'released': str(movie.get('release_date')),
|
||||
'year': tryInt(year, None),
|
||||
'plot': movie.get('overview'),
|
||||
'genres': genres,
|
||||
'collection': getattr(movie.get('belongs_to_collection'), 'name', None),
|
||||
'actor_roles': actors
|
||||
}
|
||||
|
||||
# Add alternative names
|
||||
if movie_data['original_title'] and movie_data['original_title'] not in movie_data['titles']:
|
||||
movie_data['titles'].append(movie_data['original_title'])
|
||||
movie_data = dict((k, v) for k, v in movie_data.items() if v)
|
||||
|
||||
if extended:
|
||||
for alt in movie.alternate_titles:
|
||||
alt_name = alt.title
|
||||
if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
|
||||
movie_data['titles'].append(alt_name)
|
||||
# Add alternative names
|
||||
if movie_data['original_title'] and movie_data['original_title'] not in movie_data['titles']:
|
||||
movie_data['titles'].append(movie_data['original_title'])
|
||||
|
||||
# Cache movie parsed
|
||||
self.setCache(cache_key, movie_data)
|
||||
# Add alternative titles
|
||||
alternate_titles = movie.get('alternative_titles', {}).get('titles', [])
|
||||
|
||||
for alt in alternate_titles:
|
||||
alt_name = alt.get('title')
|
||||
if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
|
||||
movie_data['titles'].append(alt_name)
|
||||
|
||||
return movie_data
|
||||
|
||||
@@ -171,36 +173,41 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
image_url = ''
|
||||
try:
|
||||
image_url = getattr(movie, type).geturl(size = size)
|
||||
path = movie.get('%s_path' % type)
|
||||
image_url = '%s%s%s' % (self.configuration['images']['secure_base_url'], size, path)
|
||||
except:
|
||||
log.debug('Failed getting %s.%s for "%s"', (type, size, ss(str(movie))))
|
||||
|
||||
return image_url
|
||||
|
||||
def getMultImages(self, movie, type = 'backdrops', size = 'original', n = -1, skipfirst = False):
|
||||
"""
|
||||
If n < 0, return all images. Otherwise return n images.
|
||||
If n > len(getattr(movie, type)), then return all images.
|
||||
If skipfirst is True, then it will skip getattr(movie, type)[0]. This
|
||||
is because backdrops[0] is typically backdrop.
|
||||
"""
|
||||
def getMultImages(self, movie, type = 'backdrops', size = 'original'):
|
||||
|
||||
image_urls = []
|
||||
try:
|
||||
images = getattr(movie, type)
|
||||
if n < 0 or n > len(images):
|
||||
num_images = len(images)
|
||||
else:
|
||||
num_images = n
|
||||
|
||||
for i in range(int(skipfirst), num_images + int(skipfirst)):
|
||||
image_urls.append(images[i].geturl(size = size))
|
||||
|
||||
for image in movie.get('images', {}).get(type, [])[1:5]:
|
||||
image_urls.append(self.getImage(image, 'file', size))
|
||||
except:
|
||||
log.debug('Failed getting %i %s.%s for "%s"', (n, type, size, ss(str(movie))))
|
||||
log.debug('Failed getting %s.%s for "%s"', (type, size, ss(str(movie))))
|
||||
|
||||
return image_urls
|
||||
|
||||
def request(self, call = '', params = {}, return_key = None):
|
||||
|
||||
params = dict((k, v) for k, v in params.items() if v)
|
||||
params = tryUrlencode(params)
|
||||
|
||||
try:
|
||||
url = 'http://api.themoviedb.org/3/%s?api_key=%s%s' % (call, self.conf('api_key'), '&%s' % params if params else '')
|
||||
data = self.getJsonData(url, show_error = False)
|
||||
except:
|
||||
log.debug('Movie not found: %s, %s', (call, params))
|
||||
data = None
|
||||
|
||||
if data and return_key and return_key in data:
|
||||
data = data.get(return_key)
|
||||
|
||||
return data
|
||||
|
||||
def isDisabled(self):
|
||||
if self.conf('api_key') == '':
|
||||
log.error('No API key provided.')
|
||||
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
|
||||
from bs4 import SoupStrainer, BeautifulSoup
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
||||
from couchpotato.core.helpers.variable import mergeDicts, getTitle, getIdentifier
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie.providers.trailer.base import TrailerProvider
|
||||
from requests import HTTPError
|
||||
@@ -29,7 +29,7 @@ class HDTrailers(TrailerProvider):
|
||||
|
||||
url = self.urls['api'] % self.movieUrlName(movie_name)
|
||||
try:
|
||||
data = self.getCache('hdtrailers.%s' % group['identifier'], url, show_error = False)
|
||||
data = self.getCache('hdtrailers.%s' % getIdentifier(group), url, show_error = False)
|
||||
except HTTPError:
|
||||
log.debug('No page found for: %s', movie_name)
|
||||
data = None
|
||||
@@ -59,7 +59,7 @@ class HDTrailers(TrailerProvider):
|
||||
|
||||
url = "%s?%s" % (self.urls['backup'], tryUrlencode({'s':movie_name}))
|
||||
try:
|
||||
data = self.getCache('hdtrailers.alt.%s' % group['identifier'], url, show_error = False)
|
||||
data = self.getCache('hdtrailers.alt.%s' % getIdentifier(group), url, show_error = False)
|
||||
except HTTPError:
|
||||
log.debug('No alternative page found for: %s', movie_name)
|
||||
data = None
|
||||
@@ -68,7 +68,7 @@ class HDTrailers(TrailerProvider):
|
||||
return results
|
||||
|
||||
try:
|
||||
html = BeautifulSoup(data, 'html.parser', parse_only = self.only_tables_tags)
|
||||
html = BeautifulSoup(data, parse_only = self.only_tables_tags)
|
||||
result_table = html.find_all('h2', text = re.compile(movie_name))
|
||||
|
||||
for h2 in result_table:
|
||||
@@ -90,7 +90,7 @@ class HDTrailers(TrailerProvider):
|
||||
|
||||
results = {'480p':[], '720p':[], '1080p':[]}
|
||||
try:
|
||||
html = BeautifulSoup(data, 'html.parser', parse_only = self.only_tables_tags)
|
||||
html = BeautifulSoup(data, parse_only = self.only_tables_tags)
|
||||
result_table = html.find('table', attrs = {'class':'bottomTable'})
|
||||
|
||||
for tr in result_table.find_all('tr'):
|
||||
|
||||
@@ -141,17 +141,17 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
previous_releases = movie.get('releases', [])
|
||||
too_early_to_search = []
|
||||
outside_eta_results = 0
|
||||
alway_search = self.conf('always_search')
|
||||
always_search = self.conf('always_search')
|
||||
ignore_eta = manual
|
||||
total_result_count = 0
|
||||
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'_id': movie['_id']}, message = 'Searching for "%s"' % default_title)
|
||||
|
||||
# Ignore eta once every 7 days
|
||||
if not alway_search:
|
||||
if not always_search:
|
||||
prop_name = 'last_ignored_eta.%s' % movie['_id']
|
||||
last_ignored_eta = float(Env.prop(prop_name, default = 0))
|
||||
if last_ignored_eta > time.time() - 604800:
|
||||
if last_ignored_eta < time.time() - 604800:
|
||||
ignore_eta = True
|
||||
Env.prop(prop_name, value = time.time())
|
||||
|
||||
@@ -166,11 +166,12 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
'quality': q_identifier,
|
||||
'finish': profile['finish'][index],
|
||||
'wait_for': tryInt(profile['wait_for'][index]),
|
||||
'3d': profile['3d'][index] if profile.get('3d') else False
|
||||
'3d': profile['3d'][index] if profile.get('3d') else False,
|
||||
'minimum_score': profile.get('minimum_score', 1),
|
||||
}
|
||||
|
||||
could_not_be_released = not self.couldBeReleased(q_identifier in pre_releases, release_dates, movie['info']['year'])
|
||||
if not alway_search and could_not_be_released:
|
||||
if not always_search and could_not_be_released:
|
||||
too_early_to_search.append(q_identifier)
|
||||
|
||||
# Skip release, if ETA isn't ignored
|
||||
@@ -196,7 +197,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
break
|
||||
|
||||
quality = fireEvent('quality.single', identifier = q_identifier, single = True)
|
||||
log.info('Search for %s in %s%s', (default_title, quality['label'], ' ignoring ETA' if alway_search or ignore_eta else ''))
|
||||
log.info('Search for %s in %s%s', (default_title, quality['label'], ' ignoring ETA' if always_search or ignore_eta else ''))
|
||||
|
||||
# Extend quality with profile customs
|
||||
quality['custom'] = quality_custom
|
||||
@@ -223,7 +224,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
log.debug('Found %s releases for "%s", but ETA isn\'t correct yet.', (results_count, default_title))
|
||||
|
||||
# Try find a valid result and download it
|
||||
if (force_download or not could_not_be_released or alway_search) and fireEvent('release.try_download_result', results, movie, quality_custom, single = True):
|
||||
if (force_download or not could_not_be_released or always_search) and fireEvent('release.try_download_result', results, movie, quality_custom, single = True):
|
||||
ret = True
|
||||
|
||||
# Remove releases that aren't found anymore
|
||||
|
||||
@@ -2,6 +2,8 @@ var SuggestList = new Class({
|
||||
|
||||
Implements: [Options, Events],
|
||||
|
||||
shown_once: false,
|
||||
|
||||
initialize: function(options){
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
@@ -44,12 +46,13 @@ var SuggestList = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected');
|
||||
if( cookie_menu_select === 'suggestions' || cookie_menu_select === null ) self.el.show(); else self.el.hide();
|
||||
var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected') || 'suggestions';
|
||||
if( cookie_menu_select === 'suggestions')
|
||||
self.show();
|
||||
else
|
||||
self.hide();
|
||||
|
||||
self.api_request = Api.request('suggestion.view', {
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
self.fireEvent('created');
|
||||
|
||||
},
|
||||
|
||||
@@ -145,6 +148,24 @@ var SuggestList = new Class({
|
||||
|
||||
},
|
||||
|
||||
show: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.show();
|
||||
|
||||
if(!self.shown_once){
|
||||
self.api_request = Api.request('suggestion.view', {
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
|
||||
self.shown_once = true;
|
||||
}
|
||||
},
|
||||
|
||||
hide: function(){
|
||||
this.el.hide();
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class Email(Notification):
|
||||
# Open the SMTP connection, via SSL if requested
|
||||
log.debug("Connecting to host %s on port %s" % (smtp_server, smtp_port))
|
||||
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
|
||||
mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server)
|
||||
mailserver = smtplib.SMTP_SSL(smtp_server, smtp_port) if ssl == 1 else smtplib.SMTP(smtp_server, smtp_port)
|
||||
|
||||
if starttls:
|
||||
log.debug("Using StartTLS to initiate the connection with the SMTP server")
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
from couchpotato.core.helpers.variable import splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from pynmwp import PyNMWP
|
||||
import six
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'NotifyMyWP'
|
||||
|
||||
|
||||
class NotifyMyWP(Notification):
|
||||
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
keys = splitString(self.conf('api_key'))
|
||||
p = PyNMWP(keys, self.conf('dev_key'))
|
||||
|
||||
response = p.push(application = self.default_title, event = message, description = message, priority = self.conf('priority'), batch_mode = len(keys) > 1)
|
||||
|
||||
for key in keys:
|
||||
if not response[key]['Code'] == six.u('200'):
|
||||
log.error('Could not send notification to NotifyMyWindowsPhone (%s). %s', (key, response[key]['message']))
|
||||
return False
|
||||
|
||||
return response
|
||||
|
||||
|
||||
config = [{
|
||||
'name': 'notifymywp',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'notifymywp',
|
||||
'label': 'Windows Phone',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'description': 'Multiple keys seperated by a comma. Maximum of 5.'
|
||||
},
|
||||
{
|
||||
'name': 'dev_key',
|
||||
'advanced': True,
|
||||
},
|
||||
{
|
||||
'name': 'priority',
|
||||
'default': 0,
|
||||
'type': 'dropdown',
|
||||
'values': [('Very Low', -2), ('Moderate', -1), ('Normal', 0), ('High', 1), ('Emergency', 2)],
|
||||
},
|
||||
{
|
||||
'name': 'on_snatch',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Also send message when movie is snatched.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -196,7 +196,7 @@ class Plugin(object):
|
||||
headers['Host'] = headers.get('Host', None)
|
||||
headers['User-Agent'] = headers.get('User-Agent', self.user_agent)
|
||||
headers['Accept-encoding'] = headers.get('Accept-encoding', 'gzip')
|
||||
headers['Connection'] = headers.get('Connection', 'keep-alive')
|
||||
headers['Connection'] = headers.get('Connection', 'close')
|
||||
headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0')
|
||||
|
||||
r = Env.get('http_opener')
|
||||
@@ -279,7 +279,7 @@ class Plugin(object):
|
||||
wait = (last_use - now) + self.http_time_between_calls
|
||||
|
||||
if wait > 0:
|
||||
log.debug('Waiting for %s, %d seconds', (self.getName(), wait))
|
||||
log.debug('Waiting for %s, %d seconds', (self.getName(), max(1, wait)))
|
||||
time.sleep(min(wait, 30))
|
||||
|
||||
def beforeCall(self, handler):
|
||||
@@ -370,14 +370,16 @@ class Plugin(object):
|
||||
|
||||
def cpTag(self, media, unique_tag = False):
|
||||
|
||||
identifier = getIdentifier(media) or ''
|
||||
unique_tag = ', ' + randomString() if unique_tag else ''
|
||||
tag = ''
|
||||
if Env.setting('enabled', 'renamer') or unique_tag:
|
||||
identifier = getIdentifier(media) or ''
|
||||
unique_tag = ', ' + randomString() if unique_tag else ''
|
||||
|
||||
tag = '.cp('
|
||||
tag += identifier
|
||||
tag += ', ' if unique_tag and identifier else ''
|
||||
tag += randomString() if unique_tag else ''
|
||||
tag += ')'
|
||||
tag = '.cp('
|
||||
tag += identifier
|
||||
tag += ', ' if unique_tag and identifier else ''
|
||||
tag += randomString() if unique_tag else ''
|
||||
tag += ')'
|
||||
|
||||
return tag if len(tag) > 7 else ''
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import ctypes
|
||||
import os
|
||||
import string
|
||||
import traceback
|
||||
import time
|
||||
|
||||
from couchpotato import CPLog
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.helpers.encoding import sp
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import sp, ss, toUnicode
|
||||
from couchpotato.core.helpers.variable import getUserDir
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import six
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
@@ -53,9 +59,9 @@ class FileBrowser(Plugin):
|
||||
dirs = []
|
||||
path = sp(path)
|
||||
for f in os.listdir(path):
|
||||
p = os.path.join(path, f)
|
||||
p = sp(os.path.join(path, f))
|
||||
if os.path.isdir(p) and ((self.is_hidden(p) and bool(int(show_hidden))) or not self.is_hidden(p)):
|
||||
dirs.append(p + os.path.sep)
|
||||
dirs.append(toUnicode('%s%s' % (p, os.path.sep)))
|
||||
|
||||
return sorted(dirs)
|
||||
|
||||
@@ -66,8 +72,8 @@ class FileBrowser(Plugin):
|
||||
|
||||
driveletters = []
|
||||
for drive in string.ascii_uppercase:
|
||||
if win32file.GetDriveType(drive + ":") in [win32file.DRIVE_FIXED, win32file.DRIVE_REMOTE, win32file.DRIVE_RAMDISK, win32file.DRIVE_REMOVABLE]:
|
||||
driveletters.append(drive + ":\\")
|
||||
if win32file.GetDriveType(drive + ':') in [win32file.DRIVE_FIXED, win32file.DRIVE_REMOTE, win32file.DRIVE_RAMDISK, win32file.DRIVE_REMOVABLE]:
|
||||
driveletters.append(drive + ':\\')
|
||||
|
||||
return driveletters
|
||||
|
||||
@@ -100,14 +106,19 @@ class FileBrowser(Plugin):
|
||||
|
||||
|
||||
def is_hidden(self, filepath):
|
||||
name = os.path.basename(os.path.abspath(filepath))
|
||||
name = ss(os.path.basename(os.path.abspath(filepath)))
|
||||
return name.startswith('.') or self.has_hidden_attribute(filepath)
|
||||
|
||||
def has_hidden_attribute(self, filepath):
|
||||
|
||||
result = False
|
||||
try:
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(six.text_type(filepath)) #@UndefinedVariable
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(sp(filepath)) #@UndefinedVariable
|
||||
assert attrs != -1
|
||||
result = bool(attrs & 2)
|
||||
except (AttributeError, AssertionError):
|
||||
result = False
|
||||
pass
|
||||
except:
|
||||
log.error('Failed getting hidden attribute: %s', traceback.format_exc())
|
||||
|
||||
return result
|
||||
|
||||
@@ -4,7 +4,7 @@ import traceback
|
||||
from couchpotato import get_db
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.encoding import toUnicode, ss, sp
|
||||
from couchpotato.core.helpers.variable import md5, getExt, isSubFolder
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
@@ -59,7 +59,7 @@ class FileManager(Plugin):
|
||||
log.error('Failed removing unused file: %s', traceback.format_exc())
|
||||
|
||||
def showCacheFile(self, route, **kwargs):
|
||||
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})])
|
||||
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': toUnicode(Env.get('cache_dir'))})])
|
||||
|
||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
|
||||
if not urlopen_kwargs: urlopen_kwargs = {}
|
||||
@@ -68,7 +68,9 @@ class FileManager(Plugin):
|
||||
urlopen_kwargs['stream'] = True
|
||||
|
||||
if not dest: # to Cache
|
||||
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
|
||||
dest = os.path.join(Env.get('cache_dir'), ss('%s.%s' % (md5(url), getExt(url))))
|
||||
|
||||
dest = sp(dest)
|
||||
|
||||
if not overwrite and os.path.isfile(dest):
|
||||
return dest
|
||||
|
||||
@@ -241,7 +241,7 @@ Running on: ...\n\
|
||||
'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md'
|
||||
}),
|
||||
new Element('span', {
|
||||
'text': ' before posting (kittens die if you don\'t), then copy the text below.'
|
||||
'html': ' before posting, then copy the text below and <strong>FILL IN</strong> the dots.'
|
||||
})
|
||||
),
|
||||
textarea = new Element('textarea', {
|
||||
|
||||
@@ -123,7 +123,7 @@ class Manage(Plugin):
|
||||
fireEvent('notify.frontend', type = 'manage.update', data = True, message = 'Scanning for movies in "%s"' % folder)
|
||||
|
||||
onFound = self.createAddToLibrary(folder, added_identifiers)
|
||||
fireEvent('scanner.scan', folder = folder, simple = True, newer_than = last_update if not full else 0, on_found = onFound, single = True)
|
||||
fireEvent('scanner.scan', folder = folder, simple = True, newer_than = last_update if not full else 0, check_file_date = False, on_found = onFound, single = True)
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
|
||||
@@ -86,6 +86,7 @@ class ProfilePlugin(Plugin):
|
||||
'label': toUnicode(kwargs.get('label')),
|
||||
'order': tryInt(kwargs.get('order', 999)),
|
||||
'core': kwargs.get('core', False),
|
||||
'minimum_score': tryInt(kwargs.get('minimum_score', 1)),
|
||||
'qualities': [],
|
||||
'wait_for': [],
|
||||
'stop_after': [],
|
||||
@@ -217,6 +218,7 @@ class ProfilePlugin(Plugin):
|
||||
'label': toUnicode(profile.get('label')),
|
||||
'order': order,
|
||||
'qualities': profile.get('qualities'),
|
||||
'minimum_score': 1,
|
||||
'finish': [],
|
||||
'wait_for': [],
|
||||
'stop_after': [],
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
|
||||
.profile .wait_for .minimum_score_input {
|
||||
width: 40px !important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.profile .types {
|
||||
padding: 0;
|
||||
margin: 0 20px 0 -4px;
|
||||
|
||||
@@ -53,12 +53,21 @@ var Profile = new Class({
|
||||
}),
|
||||
new Element('span', {'text':'day(s) for a better quality '}),
|
||||
new Element('span.advanced', {'text':'and keep searching'}),
|
||||
|
||||
// "After a checked quality is found and downloaded, continue searching for even better quality releases for the entered number of days."
|
||||
new Element('input.inlay.xsmall.stop_after_input.advanced', {
|
||||
'type':'text',
|
||||
'value': data.stop_after && data.stop_after.length > 0 ? data.stop_after[0] : 0
|
||||
}),
|
||||
new Element('span.advanced', {'text':'day(s) for a better (checked) quality.'})
|
||||
new Element('span.advanced', {'text':'day(s) for a better (checked) quality.'}),
|
||||
|
||||
// Minimum score of
|
||||
new Element('span.advanced', {'html':'<br/>Releases need a minimum score of'}),
|
||||
new Element('input.advanced.inlay.xsmall.minimum_score_input', {
|
||||
'size': 4,
|
||||
'type':'text',
|
||||
'value': data.minimum_score || 1
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -126,6 +135,7 @@ var Profile = new Class({
|
||||
'label' : self.el.getElement('.quality_label input').get('value'),
|
||||
'wait_for' : self.el.getElement('.wait_for_input').get('value'),
|
||||
'stop_after' : self.el.getElement('.stop_after_input').get('value'),
|
||||
'minimum_score' : self.el.getElement('.minimum_score_input').get('value'),
|
||||
'types': []
|
||||
};
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ class QualityPlugin(Plugin):
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'median_size': 4500, 'label': 'DVD-R', 'alternative': ['br2dvd', ('dvd', 'r')], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r'), 'dvd9']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'median_size': 1500, 'label': 'DVD-Rip', 'width': 720, 'alternative': [('dvd', 'rip')], 'allow': [], 'ext':['avi'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
||||
{'identifier': 'scr', 'size': (600, 1600), 'median_size': 700, 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr', 'webrip', ('web', 'rip')], 'allow': ['dvdr', 'dvdrip', '720p', '1080p'], 'ext':[], 'tags': []},
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'median_size': 700, 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr', '720p'], 'ext':[]},
|
||||
{'identifier': 'tc', 'size': (600, 1000), 'median_size': 700, 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': ['720p'], 'ext':[]},
|
||||
{'identifier': 'ts', 'size': (600, 1000), 'median_size': 700, 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': ['720p'], 'ext':[]},
|
||||
{'identifier': 'cam', 'size': (600, 1000), 'median_size': 700, 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': ['720p'], 'ext':[]}
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'median_size': 700, 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr', '720p', '1080p'], 'ext':[]},
|
||||
{'identifier': 'tc', 'size': (600, 1000), 'median_size': 700, 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': ['720p', '1080p'], 'ext':[]},
|
||||
{'identifier': 'ts', 'size': (600, 1000), 'median_size': 700, 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': ['720p', '1080p'], 'ext':[]},
|
||||
{'identifier': 'cam', 'size': (600, 1000), 'median_size': 700, 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': ['720p', '1080p'], 'ext':[]}
|
||||
]
|
||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||
threed_tags = {
|
||||
@@ -278,6 +278,8 @@ class QualityPlugin(Plugin):
|
||||
'ext': 5,
|
||||
}
|
||||
|
||||
scored_on = []
|
||||
|
||||
# Check alt and tags
|
||||
for tag_type in ['identifier', 'alternative', 'tags', 'label']:
|
||||
qualities = quality.get(tag_type, [])
|
||||
@@ -289,10 +291,13 @@ class QualityPlugin(Plugin):
|
||||
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
|
||||
score += points.get(tag_type)
|
||||
|
||||
if isinstance(alt, (str, unicode)) and ss(alt.lower()) in words:
|
||||
if isinstance(alt, (str, unicode)) and ss(alt.lower()) in words and ss(alt.lower()) not in scored_on:
|
||||
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
|
||||
score += points.get(tag_type)
|
||||
|
||||
# Don't score twice on same tag
|
||||
scored_on.append(ss(alt).lower())
|
||||
|
||||
# Check extention
|
||||
for ext in quality.get('ext', []):
|
||||
if ext == extension:
|
||||
@@ -485,6 +490,7 @@ class QualityPlugin(Plugin):
|
||||
'Movie Name (2015).mp4': {'size': 6500, 'quality': 'brrip'},
|
||||
'Movie Name.2014.720p Web-Dl Aac2.0 h264-ReleaseGroup': {'size': 3800, 'quality': 'brrip'},
|
||||
'Movie Name.2014.720p.WEBRip.x264.AC3-ReleaseGroup': {'size': 3000, 'quality': 'scr'},
|
||||
'Movie.Name.2014.1080p.HDCAM.-.ReleaseGroup': {'size': 5300, 'quality': 'cam'},
|
||||
}
|
||||
|
||||
correct = 0
|
||||
|
||||
@@ -187,7 +187,7 @@ class Release(Plugin):
|
||||
release['files'] = dict((k, [toUnicode(x) for x in v]) for k, v in group['files'].items() if v)
|
||||
db.update(release)
|
||||
|
||||
fireEvent('media.restatus', media['_id'], single = True)
|
||||
fireEvent('media.restatus', media['_id'], allowed_restatus = ['done'], single = True)
|
||||
|
||||
return True
|
||||
except:
|
||||
@@ -389,8 +389,8 @@ class Release(Plugin):
|
||||
log.info('Ignored: %s', rel['name'])
|
||||
continue
|
||||
|
||||
if rel['score'] <= 0:
|
||||
log.info('Ignored, score "%s" to low: %s', (rel['score'], rel['name']))
|
||||
if rel['score'] < quality_custom.get('minimum_score'):
|
||||
log.info('Ignored, score "%s" to low, need at least "%s": %s', (rel['score'], quality_custom.get('minimum_score'), rel['name']))
|
||||
continue
|
||||
|
||||
if rel['size'] <= 50:
|
||||
|
||||
@@ -222,8 +222,8 @@ class Renamer(Plugin):
|
||||
|
||||
cd_keys = ['<cd>','<cd_nr>', '<original>']
|
||||
if not any(x in folder_name for x in cd_keys) and not any(x in file_name for x in cd_keys):
|
||||
log.error('Missing `cd` or `cd_nr` in the renamer. This will cause multi-file releases of being renamed to the same file.'
|
||||
'Force adding it')
|
||||
log.error('Missing `cd` or `cd_nr` in the renamer. This will cause multi-file releases of being renamed to the same file. '
|
||||
'Please add it in the renamer settings. Force adding it for now.')
|
||||
file_name = '%s %s' % ('<cd>', file_name)
|
||||
|
||||
# Tag release folder as failed_rename in case no groups were found. This prevents check_snatched from removing the release from the downloader.
|
||||
@@ -373,10 +373,6 @@ class Renamer(Plugin):
|
||||
elif file_type is 'nfo':
|
||||
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True)
|
||||
|
||||
# Seperator replace
|
||||
if separator:
|
||||
final_file_name = final_file_name.replace(' ', separator)
|
||||
|
||||
# Move DVD files (no structure renaming)
|
||||
if group['is_dvd'] and file_type is 'movie':
|
||||
found = False
|
||||
@@ -545,12 +541,13 @@ class Renamer(Plugin):
|
||||
(not keep_original or self.fileIsAdded(current_file, group)):
|
||||
remove_files.append(current_file)
|
||||
|
||||
total_space, available_space = getFreeSpace(destination)
|
||||
renaming_size = getSize(rename_files.keys())
|
||||
if renaming_size > available_space:
|
||||
log.error('Not enough space left, need %s MB but only %s MB available', (renaming_size, available_space))
|
||||
self.tagRelease(group = group, tag = 'not_enough_space')
|
||||
continue
|
||||
if self.conf('check_space'):
|
||||
total_space, available_space = getFreeSpace(destination)
|
||||
renaming_size = getSize(rename_files.keys())
|
||||
if renaming_size > available_space:
|
||||
log.error('Not enough space left, need %s MB but only %s MB available', (renaming_size, available_space))
|
||||
self.tagRelease(group = group, tag = 'not_enough_space')
|
||||
continue
|
||||
|
||||
# Remove files
|
||||
delete_folders = []
|
||||
@@ -1393,6 +1390,14 @@ config = [{
|
||||
'label': 'Folder-Separator',
|
||||
'description': ('Replace all the spaces with a character.', 'Example: ".", "-" (without quotes). Leave empty to use spaces.'),
|
||||
},
|
||||
{
|
||||
'name': 'check_space',
|
||||
'label': 'Check space',
|
||||
'default': True,
|
||||
'type': 'bool',
|
||||
'description': ('Check if there\'s enough available space to rename the files', 'Disable when the filesystem doesn\'t return the proper value'),
|
||||
'advanced': True,
|
||||
},
|
||||
{
|
||||
'name': 'default_file_action',
|
||||
'label': 'Default File Action',
|
||||
|
||||
@@ -131,7 +131,7 @@ class Scanner(Plugin):
|
||||
addEvent('scanner.name_year', self.getReleaseNameYear)
|
||||
addEvent('scanner.partnumber', self.getPartNumber)
|
||||
|
||||
def scan(self, folder = None, files = None, release_download = None, simple = False, newer_than = 0, return_ignored = True, on_found = None):
|
||||
def scan(self, folder = None, files = None, release_download = None, simple = False, newer_than = 0, return_ignored = True, check_file_date = True, on_found = None):
|
||||
|
||||
folder = sp(folder)
|
||||
|
||||
@@ -145,7 +145,6 @@ class Scanner(Plugin):
|
||||
|
||||
# Scan all files of the folder if no files are set
|
||||
if not files:
|
||||
check_file_date = True
|
||||
try:
|
||||
files = []
|
||||
for root, dirs, walk_files in os.walk(folder, followlinks=True):
|
||||
|
||||
Reference in New Issue
Block a user