Downloading of single releases and more

This commit is contained in:
Ruud
2011-09-11 00:58:26 +02:00
parent 4617750277
commit cb5fc694ab
31 changed files with 457 additions and 112 deletions
+17 -6
View File
@@ -4,7 +4,9 @@ from couchpotato.core.helpers.variable import getExt
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from flask.helpers import send_from_directory
from libs.multipartpost import MultipartPostHandler
from urlparse import urlparse
import cookielib
import glob
import math
import os.path
@@ -80,7 +82,7 @@ class Plugin(object):
return False
# http request
def urlopen(self, url, timeout = 10, params = {}, headers = {}):
def urlopen(self, url, timeout = 10, params = {}, headers = {}, multipart = False):
socket.setdefaulttimeout(timeout)
@@ -88,15 +90,24 @@ class Plugin(object):
self.wait(host)
try:
log.info('Opening url: %s, params: %s' % (url, params))
data = urllib.urlencode(params) if len(params) > 0 else None
request = urllib2.Request(url, data, headers)
if multipart:
log.info('Opening multipart url: %s, params: %s' % (url, params.iterkeys()))
request = urllib2.Request(url, params, headers)
data = urllib2.urlopen(request).read()
cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
data = opener.open(request).read()
else:
log.info('Opening url: %s, params: %s' % (url, params))
data = urllib.urlencode(params) if len(params) > 0 else None
request = urllib2.Request(url, data, headers)
data = urllib2.urlopen(request).read()
except IOError, e:
log.error('Failed opening url, %s: %s' % (url, e))
data = None
raise
self.http_last_use[host] = time.time()
@@ -1,4 +1,7 @@
/* @override http://localhost:5000/static/movie_plugin/movie.css */
/* @override
http://localhost:5000/static/movie_plugin/movie.css
http://192.168.1.20:5000/static/movie_plugin/movie.css
*/
.movies {
padding: 20px 0;
@@ -79,13 +82,21 @@
float: left;
width: 5%;
padding: 0 0 0 3%;
background: url('../images/rating.png') no-repeat left center;
}
.movies .info .description {
clear: both;
width: 95%;
}
.movies .data .quality span {
padding: 5px;
font-weight: bold;
}
.movies .data .quality .available { color: orange; }
.movies .data .quality .snatched { color: lightgreen; }
.movies .data .actions {
position: absolute;
right: 15px;
@@ -96,17 +107,14 @@
.movies .data:hover .action:hover { opacity: 1; }
.movies .data .action {
background: no-repeat center;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
width: 20px;
height: 20px;
padding: 3px;
opacity: 0;
}
.movies .data .action.refresh { background-image: url('../images/reload.png'); }
.movies .data .action.delete { background-image: url('../images/delete.png'); }
.movies .data .action.edit { background-image: url('../images/edit.png'); }
.movies .data .action.imdb { background-image: url('../images/imdb.png'); }
.movies .delete_container {
clear: both;
@@ -142,6 +150,65 @@
padding: 2%;
}
.movies .options .releases {
height: 157px;
overflow: auto;
margin: -20px -20px -20px 110px;
padding: 15px 0 5px;
}
.movies .options .releases .item {
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.movies .options .releases .item:last-child { border: 0; }
.movies .options .releases .item:nth-child(even) {
background: rgba(255,255,255,0.05);
}
.movies .options .releases .item:not(.head):hover {
background: rgba(255,255,255,0.03);
}
.movies .options .releases .item > * {
display: inline-block;
padding: 0 5px;
width: 50px;
min-height: 24px;
white-space: nowrap;
text-overflow: ellipsis;
-moz-text-overflow: ellipsis;
text-align: center;
vertical-align: top;
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
.movies .options .releases .item > *:first-child {
border: 0;
}
.movies .options .releases .provider {
width: 120px;
}
.movies .options .releases .name {
width: 420px;
overflow: hidden;
text-align: left;
padding: 0 10px;
}
.movies .options .releases a {
width: 16px !important;
height: 16px;
opacity: 0.8;
}
.movies .options .releases a:hover {
opacity: 1;
}
.movies .options .releases .head > * {
font-weight: bold;
font-size: 14px;
padding-top: 4px;
padding-bottom: 4px;
height: auto;
}
.movies .alph_nav ul {
list-style: none;
padding: 0;
+76 -8
View File
@@ -32,7 +32,7 @@ var Movie = new Class({
self.year = new Element('div.year', {
'text': self.data.library.year || 'Unknown'
}),
self.rating = new Element('div.rating', {
self.rating = new Element('div.rating.icon', {
'text': self.data.library.rating
}),
self.description = new Element('div.description', {
@@ -47,11 +47,18 @@ var Movie = new Class({
);
self.profile.get('types').each(function(type){
// Check if quality is snatched
var is_snatched = self.data.releases.filter(function(release){
return release.quality_id == type.quality_id && release.status.identifier == 'snatched'
}).pick();
var q = Quality.getQuality(type.quality_id);
new Element('span', {
'text': ' '+q.label
'text': q.label,
'class': is_snatched ? 'snatched' : ''
}).inject(self.quality);
})
});
Object.each(self.options.actions, function(action, key){
self.actions.adopt(
@@ -127,7 +134,7 @@ var Movie = new Class({
var MovieAction = new Class({
class_name: 'action',
class_name: 'action icon',
initialize: function(movie){
var self = this;
@@ -193,7 +200,7 @@ var ReleaseAction = new Class({
self.id = self.movie.get('identifier');
self.el = new Element('a.releases', {
self.el = new Element('a.releases.icon.download', {
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
'events': {
'click': self.show.bind(self)
@@ -211,16 +218,77 @@ var ReleaseAction = new Class({
$(self.movie.thumbnail).clone(),
self.release_container = new Element('div.releases')
).inject(self.movie, 'top');
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'Release name'}),
new Element('span.quality', {'text': 'Quality'}),
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
Array.each(self.movie.data.releases, function(release){
p(release);
new Element('div', {
'text': release.title
}).inject(self.release_container)
'class': 'item ' + release.status.identifier
}).adopt(
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
new Element('span.quality', {'text': release.quality.label}),
new Element('span.age', {'text': self.get(release, 'age')}),
new Element('span.score', {'text': self.get(release, 'score')}),
new Element('span.provider', {'text': self.get(release, 'provider')}),
new Element('a.download.icon', {
'events': {
'click': function(e){
(e).stop();
self.download(release);
}
}
}),
new Element('a.delete.icon', {
'events': {
'click': function(e){
(e).stop();
self.delete(release);
}
}
})
).inject(self.release_container)
});
}
self.movie.slide('in');
},
download: function(release){
var self = this;
p(release)
Api.request('release.download', {
'data': {
'id': release.id
}
});
},
delete: function(release){
var self = this;
Api.request('release.delete', {
'data': {
'id': release.id
}
})
},
get: function(release, type){
var self = this;
return (release.info.filter(function(info){
return type == info.identifier
}).pick() || {}).value
}
});
@@ -29,7 +29,7 @@ Block.Search = new Class({
}).adopt(
new Element('div.pointer'),
self.results = new Element('div.results')
).fade('hide')
).hide()
);
self.spinner = new Spinner(self.result_container);
@@ -52,7 +52,7 @@ Block.Search = new Class({
if(self.hidden == bool) return;
self.result_container.fade(bool ? 0 : 1)
self.result_container[bool ? 'hide' : 'show']();
if(bool){
History.removeEvent('change', self.hideResults.bind(self, !bool));
+1 -1
View File
@@ -13,7 +13,7 @@ log = CPLog(__name__)
class QualityPlugin(Plugin):
qualities = [
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': []},
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate']},
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
+45 -10
View File
@@ -1,8 +1,10 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.request import getParam, jsonified
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import File, Release, Movie
from couchpotato.core.settings.model import File, Release as Relea, Movie
from sqlalchemy.sql.expression import and_, or_
log = CPLog(__name__)
@@ -13,6 +15,8 @@ class Release(Plugin):
def __init__(self):
addEvent('release.add', self.add)
addApiView('release.download', self.download)
def add(self, group):
db = get_session()
@@ -30,22 +34,22 @@ class Release(Plugin):
db.add(movie)
db.commit()
# Add release
# Add Release
snatched_status = fireEvent('status.get', 'snatched', single = True)
release = db.query(Release).filter(
rel = db.query(Relea).filter(
or_(
Release.identifier == identifier,
and_(Release.identifier.startswith(group['library']['identifier'], Release.status_id == snatched_status.get('id')))
Relea.identifier == identifier,
and_(Relea.identifier.startswith(group['library']['identifier'], Relea.status_id == snatched_status.get('id')))
)
).first()
if not release:
release = Release(
if not rel:
rel = Relea(
identifier = identifier,
movie = movie,
quality_id = group['meta_data']['quality'].get('id'),
status_id = done_status.get('id')
)
db.add(release)
db.add(rel)
db.commit()
# Add each file type
@@ -54,10 +58,10 @@ class Release(Plugin):
added_file = self.saveFile(file, type = type, include_media_info = type is 'movie')
try:
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
release.files.append(added_file)
Relea.files.append(added_file)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s' % (file, e))
log.debug('Failed to attach "%s" to Relea: %s' % (file, e))
db.remove()
@@ -73,3 +77,34 @@ class Release(Plugin):
# Check database and update/insert if necessary
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
def download(self):
db = get_session()
id = getParam('id')
rel = db.query(Relea).filter_by(id = id).first()
if rel:
item = {}
for info in rel.info:
item[info.identifier] = info.value
# Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], single = True)
item['download'] = provider.download
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
}))
return jsonified({
'success': True
})
else:
log.error('Couldn\'t find release with id: %s' % id)
return jsonified({
'success': False
})
+40 -7
View File
@@ -4,7 +4,7 @@ from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getExt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library
from couchpotato.core.settings.model import Library, Movie
import os.path
import re
import shutil
@@ -134,8 +134,18 @@ class Renamer(Plugin):
# Move DVD files (no structure renaming)
if group['is_dvd'] and file_type is 'movie':
structure_dir = file.split(group['dirname'])[-1].lstrip(os.path.sep)
rename_files[file] = os.path.join(destination, final_folder_name, structure_dir)
found = False
for top_dir in ['video_ts', 'audio_ts', 'bdmv', 'certificate']:
has_string = file.lower().find(os.path.sep + top_dir + os.path.sep)
if has_string >= 0:
structure_dir = file[has_string:].lstrip(os.path.sep)
rename_files[file] = os.path.join(destination, final_folder_name, structure_dir)
found = True
break
if not found:
log.error('Could not determin dvd structure for: %s' % file)
# Do rename others
else:
rename_files[file] = os.path.join(destination, final_folder_name, final_file_name)
@@ -162,15 +172,41 @@ class Renamer(Plugin):
# Before renaming, remove the lower quality files
db = get_session()
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
done_status = fireEvent('status.get', 'done', single = True)
active_status = fireEvent('status.get', 'active', single = True)
for movie in library.movies:
# Mark movie "done" onces it found the quality with the finish check
try:
if movie.status_id == active_status.get('id'):
for type in movie.profile.types:
if type.quality_id == group['meta_data']['quality']['id'] and type.finish:
movie.status_id = done_status.get('id')
db.commit()
except Exception, e:
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
# Go over current movie releases
for release in movie.releases:
# This is where CP removes older, lesser quality releases
if release.quality.order > group['meta_data']['quality']['order']:
log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
for file in release.files:
log.info('Removing (not really) "%s"' % file.path)
# When a release already exists
elif release.status_id is done_status.get('id'):
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
if release.quality.order is group['meta_data']['quality']['order']:
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
else:
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
@@ -190,10 +226,7 @@ class Renamer(Plugin):
break
for file in release.files:
log.info('Removing (not really) "%s"' % file.path)
# Rename
# Rename all files marked
for src in rename_files:
if rename_files[src]:
+8 -8
View File
@@ -23,11 +23,11 @@ class Scanner(Plugin):
'trailer': 1048576, # 1MB
}
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts']
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img'],
'dvd': ['vts_*', 'vob'],
'nfo': ['nfo', 'txt', 'tag'],
'nfo': ['nfo', 'nfo-orig', 'txt', 'tag'],
'subtitle': ['sub', 'srt', 'ssa', 'ass'],
'subtitle_extra': ['idx'],
'trailer': ['mov', 'mp4', 'flv']
@@ -210,13 +210,13 @@ class Scanner(Plugin):
group['parentdir'] = os.path.dirname(movie_file)
group['dirname'] = None
folders = group['parentdir'].replace(folder, '').split(os.path.sep)
folders.reverse()
folder_names = group['parentdir'].replace(folder, '').split(os.path.sep)
folder_names.reverse()
# Try and get a proper dirname, so no "A", "Movie", "Download" etc
for folder in folders:
if folder.lower() not in self.ignore_names and len(folder) > 2:
group['dirname'] = folder
for folder_name in folder_names:
if folder_name.lower() not in self.ignore_names and len(folder_name) > 2:
group['dirname'] = folder_name
break
break
@@ -426,7 +426,7 @@ class Scanner(Plugin):
if list(set(file.lower().split(os.path.sep)) & set(['video_ts', 'audio_ts'])):
return True
for needle in ['vts_', 'video_ts', 'audio_ts']:
for needle in ['vts_', 'video_ts', 'audio_ts', 'bdmv', 'certificate']:
if needle in file.lower():
return True
+32 -31
View File
@@ -7,6 +7,7 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
from couchpotato.environment import Env
import re
import traceback
log = CPLog(__name__)
@@ -17,6 +18,7 @@ class Searcher(Plugin):
addEvent('searcher.all', self.all)
addEvent('searcher.single', self.single)
addEvent('searcher.correct_movie', self.correctMovie)
addEvent('searcher.download', self.download)
# Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
@@ -34,7 +36,7 @@ class Searcher(Plugin):
for movie in movies:
self.single(movie.to_dict(deep = {
self.single(movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
@@ -47,11 +49,8 @@ class Searcher(Plugin):
def single(self, movie):
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
available_status = fireEvent('status.get', 'available', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = False
for type in movie['profile']['types']:
has_better_quality = 0
@@ -85,37 +84,19 @@ class Searcher(Plugin):
db.commit()
for info in nzb:
rls_info = ReleaseInfo(
identifier = info,
value = nzb[info]
)
rls.info.append(rls_info)
try:
rls_info = ReleaseInfo(
identifier = info,
value = nzb[info]
)
rls.info.append(rls_info)
except Exception:
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
db.commit()
for nzb in sorted_results:
successful = fireEvent('download', data = nzb, movie = movie, single = True)
if successful:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
rls.status_id = snatched_status.get('id')
db.commit()
# Mark movie snatched if quality is finish-checked
if type['finish']:
mvie = db.query(Movie).filter_by(id = movie['id']).first()
mvie.status_id = snatched_status.get('id')
db.commit()
log.info('Downloading of %s successful.' % nzb.get('name'))
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % nzb.get('name'), data = rls.to_dict())
return True
return False
return self.download(data = nzb, movie = movie)
else:
log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title))
break
@@ -126,6 +107,26 @@ class Searcher(Plugin):
return False
def download(self, data, movie):
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = fireEvent('download', data = data, movie = movie, single = True)
if successful:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
rls.status_id = snatched_status.get('id')
db.commit()
log.info('Downloading of %s successful.' % data.get('name'))
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % data.get('name'), data = rls.to_dict())
return True
return False
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):