Browse Source

Merge branch 'develop'

pull/7245/merge
Ruud 8 years ago
parent
commit
dae7327d35
  1. 2
      .gitignore
  2. 15
      couchpotato/core/downloaders/transmission.py
  3. 41
      couchpotato/core/media/_base/providers/torrent/bithdtv.py
  4. 10
      couchpotato/core/media/_base/providers/torrent/passthepopcorn.py
  5. 32
      couchpotato/core/media/_base/providers/torrent/torrentz.py
  6. 4
      couchpotato/core/media/_base/providers/torrent/yts.py
  7. 11
      couchpotato/core/media/movie/_base/static/details.js
  8. 22
      couchpotato/core/media/movie/providers/automation/letterboxd.py
  9. 11
      couchpotato/core/media/movie/providers/metadata/xbmc.py
  10. 4
      couchpotato/core/media/movie/providers/nzb/binsearch.py
  11. 2
      couchpotato/core/media/movie/providers/torrent/alpharatio.py
  12. 8
      couchpotato/core/media/movie/providers/torrent/torrentleech.py
  13. 2
      couchpotato/core/media/movie/providers/torrent/torrentz.py
  14. 9
      couchpotato/core/media/movie/providers/userscript/filmstarts.py
  15. 2
      couchpotato/core/plugins/log/static/log.js
  16. 2
      couchpotato/core/plugins/subtitle.py
  17. 1
      couchpotato/static/images/icons/dark/safari.svg
  18. 1
      couchpotato/static/images/icons/safari.svg
  19. 11
      couchpotato/static/scripts/combined.plugins.min.js
  20. 3
      couchpotato/templates/index.html
  21. 48
      libs/rtorrent/__init__.py
  22. 28
      libs/rtorrent/file.py
  23. 26
      libs/rtorrent/peer.py
  24. 6
      libs/rtorrent/rpc/__init__.py
  25. 168
      libs/rtorrent/torrent.py
  26. 22
      libs/rtorrent/tracker.py
  27. 2
      libs/subliminal/core.py
  28. 18
      libs/subliminal/services/__init__.py
  29. 153
      libs/subliminal/services/subscenter.py
  30. 56
      libs/subliminal/services/wizdom.py
  31. 8
      libs/xmpp/transports.py

2
.gitignore

@ -14,3 +14,5 @@ nosetests.xml
# Visual Studio # Visual Studio
/.vs /.vs
.DS_Store

15
couchpotato/core/downloaders/transmission.py

@ -143,12 +143,21 @@ class Transmission(DownloaderBase):
log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / isStalled=%s / eta=%s / uploadRatio=%s / isFinished=%s / incomplete-dir-enabled=%s / incomplete-dir=%s', log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / isStalled=%s / eta=%s / uploadRatio=%s / isFinished=%s / incomplete-dir-enabled=%s / incomplete-dir=%s',
(torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished'], session['incomplete-dir-enabled'], session['incomplete-dir'])) (torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished'], session['incomplete-dir-enabled'], session['incomplete-dir']))
"""
https://trac.transmissionbt.com/browser/branches/2.8x/libtransmission/transmission.h#L1853
0 = Torrent is stopped
1 = Queued to check files
2 = Checking files
3 = Queued to download
4 = Downloading
5 = Queued to seed
6 = Seeding
"""
status = 'busy' status = 'busy'
if torrent.get('isStalled') and not torrent['percentDone'] == 1 and self.conf('stalled_as_failed'): if torrent.get('isStalled') and not torrent['percentDone'] == 1 and self.conf('stalled_as_failed'):
status = 'failed' status = 'failed'
elif torrent['status'] == 0 and torrent['percentDone'] == 1: elif torrent['status'] == 0 and torrent['percentDone'] == 1 and torrent['isFinished']:
status = 'completed'
elif torrent['status'] == 16 and torrent['percentDone'] == 1:
status = 'completed' status = 'completed'
elif torrent['status'] in [5, 6]: elif torrent['status'] in [5, 6]:
status = 'seeding' status = 'seeding'

41
couchpotato/core/media/_base/providers/torrent/bithdtv.py

@ -13,9 +13,6 @@ log = CPLog(__name__)
class Base(TorrentProvider): class Base(TorrentProvider):
urls = { urls = {
'test': 'https://www.bit-hdtv.com/',
'login': 'https://www.bit-hdtv.com/takelogin.php',
'login_check': 'https://www.bit-hdtv.com/messages.php',
'detail': 'https://www.bit-hdtv.com/details.php?id=%s', 'detail': 'https://www.bit-hdtv.com/details.php?id=%s',
'search': 'https://www.bit-hdtv.com/torrents.php?', 'search': 'https://www.bit-hdtv.com/torrents.php?',
'download': 'https://www.bit-hdtv.com/download.php?id=%s', 'download': 'https://www.bit-hdtv.com/download.php?id=%s',
@ -31,7 +28,7 @@ class Base(TorrentProvider):
url = "%s&%s" % (self.urls['search'], query) url = "%s&%s" % (self.urls['search'], query)
data = self.getHTMLData(url) data = self.getHTMLData(url, headers = self.getRequestHeaders())
if data: if data:
# Remove BiT-HDTV's output garbage so outdated BS4 versions successfully parse the HTML # Remove BiT-HDTV's output garbage so outdated BS4 versions successfully parse the HTML
@ -42,11 +39,12 @@ class Base(TorrentProvider):
html = BeautifulSoup(data, 'html.parser') html = BeautifulSoup(data, 'html.parser')
try: try:
result_tables = html.find_all('table', attrs = {'width': '750', 'class': ''}) result_tables = html.find_all('table', attrs = {'width': '800', 'class': ''})
if result_tables is None: if result_tables is None:
return return
result_table = result_tables[1] # Take first result
result_table = result_tables[0]
if result_table is None: if result_table is None:
return return
@ -72,10 +70,10 @@ class Base(TorrentProvider):
except: except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
def getLoginParams(self): def getRequestHeaders(self):
cookies = 'h_sl={};h_sp={};h_su={}'.format(self.conf('cookiesettingsl') or '', self.conf('cookiesettingsp') or '', self.conf('cookiesettingsu') or '')
return { return {
'username': self.conf('username'), 'Cookie': cookies
'password': self.conf('password'),
} }
def getMoreInfo(self, item): def getMoreInfo(self, item):
@ -87,11 +85,13 @@ class Base(TorrentProvider):
item['description'] = description item['description'] = description
return item return item
def loginSuccess(self, output): def download(self, url = '', nzb_id = ''):
return 'logout.php' in output.lower() try:
return self.urlopen(url, headers=self.getRequestHeaders())
loginCheckSuccess = loginSuccess except:
log.error('Failed getting release from %s: %s', (self.getName(), traceback.format_exc()))
return 'try_next'
config = [{ config = [{
'name': 'bithdtv', 'name': 'bithdtv',
@ -110,13 +110,22 @@ config = [{
'default': False, 'default': False,
}, },
{ {
'name': 'username', 'name': 'cookiesettingsl',
'label': 'Cookies (h_sl)',
'default': '',
'description': 'Cookie h_sl from session',
},
{
'name': 'cookiesettingsp',
'label': 'Cookies (h_sp)',
'default': '', 'default': '',
'description': 'Cookie h_sp from session',
}, },
{ {
'name': 'password', 'name': 'cookiesettingsu',
'label': 'Cookies (h_su)',
'default': '', 'default': '',
'type': 'password', 'description': 'Cookie h_su from session',
}, },
{ {
'name': 'seed_ratio', 'name': 'seed_ratio',

10
couchpotato/core/media/_base/providers/torrent/passthepopcorn.py

@ -73,6 +73,8 @@ class Base(TorrentProvider):
torrentdesc += ' Scene' torrentdesc += ' Scene'
if self.conf('prefer_scene'): if self.conf('prefer_scene'):
torrentscore += 2000 torrentscore += 2000
if self.conf('no_scene'):
torrentscore -= 2000
if 'RemasterTitle' in torrent and torrent['RemasterTitle']: if 'RemasterTitle' in torrent and torrent['RemasterTitle']:
torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle']) torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle'])
@ -258,6 +260,14 @@ config = [{
'description': 'Favors scene-releases over non-scene releases.' 'description': 'Favors scene-releases over non-scene releases.'
}, },
{ {
'name': 'no_scene',
'advanced': True,
'type': 'bool',
'label': 'Reject scene',
'default': 0,
'description': 'Reject scene-releases over non-scene releases.'
},
{
'name': 'require_approval', 'name': 'require_approval',
'advanced': True, 'advanced': True,
'type': 'bool', 'type': 'bool',

32
couchpotato/core/media/_base/providers/torrent/torrentz.py

@ -15,25 +15,19 @@ log = CPLog(__name__)
class Base(TorrentMagnetProvider, RSS): class Base(TorrentMagnetProvider, RSS):
urls = { urls = {
'detail': 'https://torrentz.eu/%s', 'detail': 'https://torrentz2.eu/%s',
'search': 'https://torrentz.eu/feed?q=%s', 'search': 'https://torrentz2.eu/feed?f=%s'
'verified_search': 'https://torrentz.eu/feed_verified?q=%s'
} }
http_time_between_calls = 0 http_time_between_calls = 0
def _searchOnTitle(self, title, media, quality, results): def _searchOnTitle(self, title, media, quality, results):
search_url = self.urls['verified_search'] if self.conf('verified_only') else self.urls['search'] search_url = self.urls['search']
# Create search parameters # Create search parameters
search_params = self.buildUrl(title, media, quality) search_params = self.buildUrl(title, media, quality)
smin = quality.get('size_min')
smax = quality.get('size_max')
if smin and smax:
search_params += ' size %sm - %sm' % (smin, smax)
min_seeds = tryInt(self.conf('minimal_seeds')) min_seeds = tryInt(self.conf('minimal_seeds'))
if min_seeds: if min_seeds:
search_params += ' seed > %s' % (min_seeds - 1) search_params += ' seed > %s' % (min_seeds - 1)
@ -52,17 +46,24 @@ class Base(TorrentMagnetProvider, RSS):
magnet = splitString(detail_url, '/')[-1] magnet = splitString(detail_url, '/')[-1]
magnet_url = 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (magnet.upper(), tryUrlencode(name), tryUrlencode('udp://tracker.openbittorrent.com/announce')) magnet_url = 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (magnet.upper(), tryUrlencode(name), tryUrlencode('udp://tracker.openbittorrent.com/announce'))
reg = re.search('Size: (?P<size>\d+) MB Seeds: (?P<seeds>[\d,]+) Peers: (?P<peers>[\d,]+)', six.text_type(description)) reg = re.search('Size: (?P<size>\d+) (?P<unit>[KMG]B) Seeds: (?P<seeds>[\d,]+) Peers: (?P<peers>[\d,]+)', six.text_type(description))
size = reg.group('size') size = reg.group('size')
unit = reg.group('unit')
seeds = reg.group('seeds').replace(',', '') seeds = reg.group('seeds').replace(',', '')
peers = reg.group('peers').replace(',', '') peers = reg.group('peers').replace(',', '')
multiplier = 1
if unit == 'GB':
multiplier = 1000
elif unit == 'KB':
multiplier = 0
results.append({ results.append({
'id': magnet, 'id': magnet,
'name': six.text_type(name), 'name': six.text_type(name),
'url': magnet_url, 'url': magnet_url,
'detail_url': detail_url, 'detail_url': detail_url,
'size': tryInt(size), 'size': tryInt(size)*multiplier,
'seeders': tryInt(seeds), 'seeders': tryInt(seeds),
'leechers': tryInt(peers), 'leechers': tryInt(peers),
}) })
@ -78,7 +79,7 @@ config = [{
'tab': 'searcher', 'tab': 'searcher',
'list': 'torrent_providers', 'list': 'torrent_providers',
'name': 'Torrentz', 'name': 'Torrentz',
'description': 'Torrentz is a free, fast and powerful meta-search engine. <a href="https://torrentz.eu/" target="_blank">Torrentz</a>', 'description': 'Torrentz.eu was a free, fast and powerful meta-search engine combining results from dozens of search engines, Torrentz2.eu is trying to replace it. <a href="https://torrentz2.eu/" target="_blank">Torrentz2</a>',
'wizard': True, 'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQklEQVQ4y2NgAALjtJn/ycEMlGiGG0IVAxiwAKzOxaKGARcgxgC8YNSAwWoAzuRMjgsIugqfAUR5CZcBRIcHsWEAADSA96Ig020yAAAAAElFTkSuQmCC', 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQklEQVQ4y2NgAALjtJn/ycEMlGiGG0IVAxiwAKzOxaKGARcgxgC8YNSAwWoAzuRMjgsIugqfAUR5CZcBRIcHsWEAADSA96Ig020yAAAAAElFTkSuQmCC',
'options': [ 'options': [
@ -88,13 +89,6 @@ config = [{
'default': True 'default': True
}, },
{ {
'name': 'verified_only',
'type': 'bool',
'default': True,
'advanced': True,
'description': 'Only search verified releases',
},
{
'name': 'minimal_seeds', 'name': 'minimal_seeds',
'type': 'int', 'type': 'int',
'default': 1, 'default': 1,

4
couchpotato/core/media/_base/providers/torrent/yts.py

@ -11,8 +11,8 @@ class Base(TorrentMagnetProvider):
# Only qualities allowed: 720p/1080p/3D - the rest will fail. # Only qualities allowed: 720p/1080p/3D - the rest will fail.
# All YTS.ag torrents are verified # All YTS.ag torrents are verified
urls = { urls = {
'detail': 'https://yts.ag/api#list_movies', 'detail': 'https://yts.am/api#list_movies',
'search': 'https://yts.ag/api/v2/list_movies.json?query_term=%s&limit=%s&page=%s' 'search': 'https://yts.am/api/v2/list_movies.json?query_term=%s&limit=%s&page=%s'
} }
def _search(self, movie, quality, results): def _search(self, movie, quality, results):

11
couchpotato/core/media/movie/_base/static/details.js

@ -77,7 +77,6 @@ var MovieDetails = new Class({
'class': parent.get('title') == t ? 'icon-ok' : '' 'class': parent.get('title') == t ? 'icon-ok' : ''
})); }));
}); });
}, },
addSection: function(name, section_el){ addSection: function(name, section_el){
@ -101,7 +100,7 @@ var MovieDetails = new Class({
var self = this; var self = this;
self.el.addClass('show'); self.el.addClass('show');
document.onkeyup = self.keyup.bind(self);
//if(!App.mobile_screen){ //if(!App.mobile_screen){
// $(self.content).getElements('> .head, > .section').each(function(section, nr){ // $(self.content).getElements('> .head, > .section').each(function(section, nr){
// dynamics.css(section, { // dynamics.css(section, {
@ -130,12 +129,19 @@ var MovieDetails = new Class({
}, },
keyup: function(e) {
if (e.keyCode == 27 /* Esc */) {
this.close();
}
},
close: function(){ close: function(){
var self = this; var self = this;
var ended = function() { var ended = function() {
self.el.dispose(); self.el.dispose();
self.overlay.removeEventListener('transitionend', ended); self.overlay.removeEventListener('transitionend', ended);
document.onkeyup = null;
}; };
self.overlay.addEventListener('transitionend', ended, false); self.overlay.addEventListener('transitionend', ended, false);
@ -165,5 +171,4 @@ var MovieDetails = new Class({
App.removeEvent('history.push', self.outer_click); App.removeEvent('history.push', self.outer_click);
} }
}); });

22
couchpotato/core/media/movie/providers/automation/letterboxd.py

@ -13,7 +13,7 @@ autoload = 'Letterboxd'
class Letterboxd(Automation): class Letterboxd(Automation):
url = 'http://letterboxd.com/%s/watchlist/' url = 'http://letterboxd.com/%s/watchlist/page/%d/'
pattern = re.compile(r'(.*)\((\d*)\)') pattern = re.compile(r'(.*)\((\d*)\)')
interval = 1800 interval = 1800
@ -46,10 +46,23 @@ class Letterboxd(Automation):
if not enablers[index]: if not enablers[index]:
continue continue
soup = BeautifulSoup(self.getHTMLData(self.url % username)) soup = BeautifulSoup(self.getHTMLData(self.url % (username, 1)))
for movie in soup.find_all('li', attrs = {'class': 'poster-container'}): pagination = soup.find_all('li', attrs={'class': 'paginate-page'})
img = movie.find('img', movie) number_of_pages = tryInt(pagination[-1].find('a').get_text()) if pagination else 1
pages = range(1, number_of_pages)
for page in pages:
soup = BeautifulSoup(self.getHTMLData(self.url % (username, page)))
movies += self.getMoviesFromHTML(soup)
return movies
def getMoviesFromHTML(self, html):
movies = []
for movie in html.find_all('li', attrs={'class': 'poster-container'}):
img = movie.find('img')
title = img.get('alt') title = img.get('alt')
movies.append({ movies.append({
@ -58,7 +71,6 @@ class Letterboxd(Automation):
return movies return movies
config = [{ config = [{
'name': 'letterboxd', 'name': 'letterboxd',
'groups': [ 'groups': [

11
couchpotato/core/media/movie/providers/metadata/xbmc.py

@ -3,6 +3,7 @@ import os
import re import re
import traceback import traceback
import xml.dom.minidom import xml.dom.minidom
import time
from couchpotato.core.media.movie.providers.metadata.base import MovieMetaData from couchpotato.core.media.movie.providers.metadata.base import MovieMetaData
from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.encoding import toUnicode
@ -92,7 +93,7 @@ class XBMC(MovieMetaData):
pass pass
# Other values # Other values
types = ['year', 'originaltitle:original_title', 'outline', 'plot', 'tagline', 'premiered:released'] types = ['year', 'originaltitle:original_title', 'outline', 'plot', 'tagline']
for type in types: for type in types:
if ':' in type: if ':' in type:
@ -107,6 +108,14 @@ class XBMC(MovieMetaData):
except: except:
pass pass
# Release date
try:
if movie_info.get('released'):
el = SubElement(nfoxml, 'premiered')
el.text = time.strftime('%Y:%m:%d', time.strptime(movie_info.get('released'), '%d %b %Y'))
except:
log.debug('Failed to parse release date %s: %s', movie_info.get('released'), traceback.format_exc())
# Rating # Rating
for rating_type in ['imdb', 'rotten', 'tmdb']: for rating_type in ['imdb', 'rotten', 'tmdb']:
try: try:

4
couchpotato/core/media/movie/providers/nzb/binsearch.py

@ -21,7 +21,7 @@ class BinSearch(MovieProvider, Base):
'adv_sort': 'date', 'adv_sort': 'date',
'adv_col': 'on', 'adv_col': 'on',
'adv_nfo': 'on', 'adv_nfo': 'on',
'minsize': quality.get('size_min'), 'xminsize': quality.get('size_min'),
'maxsize': quality.get('size_max'), 'xmaxsize': quality.get('size_max'),
}) })
return query return query

2
couchpotato/core/media/movie/providers/torrent/alpharatio.py

@ -19,7 +19,7 @@ class AlphaRatio(MovieProvider, Base):
cat_ids = [ cat_ids = [
([7, 9], ['bd50']), ([7, 9], ['bd50']),
([7, 9], ['720p', '1080p']), ([7, 9], ['720p', '1080p', '2160p']),
([6, 8], ['dvdr']), ([6, 8], ['dvdr']),
([6, 8], ['brrip', 'dvdrip']), ([6, 8], ['brrip', 'dvdrip']),
] ]

8
couchpotato/core/media/movie/providers/torrent/torrentleech.py

@ -11,12 +11,14 @@ autoload = 'TorrentLeech'
class TorrentLeech(MovieProvider, Base): class TorrentLeech(MovieProvider, Base):
cat_ids = [ cat_ids = [
([13], ['720p', '1080p', 'bd50']), ([41, 47], ['2160p']),
([13, 14, 37, 43], ['720p', '1080p']),
([13], ['bd50']),
([8], ['cam']), ([8], ['cam']),
([9], ['ts', 'tc']), ([9], ['ts', 'tc']),
([10], ['r5', 'scr']), ([10, 11, 37], ['r5', 'scr']),
([11], ['dvdrip']), ([11], ['dvdrip']),
([13, 14], ['brrip']), ([13, 14, 37, 43], ['brrip']),
([12], ['dvdr']), ([12], ['dvdr']),
] ]

2
couchpotato/core/media/movie/providers/torrent/torrentz.py

@ -11,4 +11,4 @@ autoload = 'Torrentz'
class Torrentz(MovieProvider, Base): class Torrentz(MovieProvider, Base):
def buildUrl(self, title, media, quality): def buildUrl(self, title, media, quality):
return tryUrlencode('"%s %s"' % (title, media['info']['year'])) return tryUrlencode('%s %s' % (title, media['info']['year']))

9
couchpotato/core/media/movie/providers/userscript/filmstarts.py

@ -1,5 +1,6 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from couchpotato.core.media._base.providers.userscript.base import UserscriptBase from couchpotato.core.media._base.providers.userscript.base import UserscriptBase
import re
autoload = 'Filmstarts' autoload = 'Filmstarts'
@ -15,16 +16,16 @@ class Filmstarts(UserscriptBase):
return return
html = BeautifulSoup(data) html = BeautifulSoup(data)
table = html.find("table", attrs={"class": "table table-standard thead-standard table-striped_2 fs11"}) table = html.find("section", attrs={"class": "section ovw ovw-synopsis", "id": "synopsis-details"})
if table.find(text='Originaltitel'): if table.find(text=re.compile('Originaltitel')): #some trailing whitespaces on some pages
# Get original film title from the table specified above # Get original film title from the table specified above
name = table.find("div", text="Originaltitel").parent.parent.parent.td.text name = name = table.find("span", text=re.compile("Originaltitel")).findNext('h2').text
else: else:
# If none is available get the title from the meta data # If none is available get the title from the meta data
name = html.find("meta", {"property":"og:title"})['content'] name = html.find("meta", {"property":"og:title"})['content']
# Year of production is not available in the meta data, so get it from the table # Year of production is not available in the meta data, so get it from the table
year = table.find(text="Produktionsjahr").parent.parent.next_sibling.text year = table.find("span", text=re.compile("Produktionsjahr")).findNext('span').text
return self.search(name, year) return self.search(name, year)

2
couchpotato/core/plugins/log/static/log.js

@ -250,7 +250,7 @@ Page.Log = new Class({
new Element('a.button', { new Element('a.button', {
'target': '_blank', 'target': '_blank',
'text': 'the contributing guide', 'text': 'the contributing guide',
'href': 'https://github.com/CouchPotato/CouchPotatoServer/blob/develop/contributing.md' 'href': 'https://github.com/CouchPotato/CouchPotatoServer/wiki/Developer-branch'
}), }),
new Element('span', { new Element('span', {
'html': ' before posting, then copy the text below and <strong>FILL IN</strong> the dots.' 'html': ' before posting, then copy the text below and <strong>FILL IN</strong> the dots.'

2
couchpotato/core/plugins/subtitle.py

@ -16,7 +16,7 @@ autoload = 'Subtitle'
class Subtitle(Plugin): class Subtitle(Plugin):
services = ['opensubtitles', 'thesubdb', 'subswiki', 'subscenter', 'thewiz'] services = ['opensubtitles', 'thesubdb', 'subswiki', 'subscenter', 'wizdom']
def __init__(self): def __init__(self):
addEvent('renamer.before', self.searchSingle) addEvent('renamer.before', self.searchSingle)

1
couchpotato/static/images/icons/dark/safari.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 16 16"><g><path d="m5.3009374 1.8040626c-3.8007467 0-5.06281241 4.9177307-5.06281241 7.5434374 0 2.6112 1.03020071 3.568437 3.17718751 3.568437 1.3201067 0 3.3655217-0.754232 4.134375-2.698125l-0.3337501-0.145c-0.6673065 0.899413-1.6972299 1.450625-2.8287499 1.450625-1.3926401 0-1.7409374-0.928511-1.7409376-2.2631245 0-2.9593602 1.4217667-6.7746875 3.0175002-6.7746875 0.6527998 0 0.8849998 0.4207867 0.885 0.885 0 0.5512532-0.3193402 1.1606075-0.6675002 1.3346875C6.1568766 5.0244592 6.4760182 5.0825 6.708125 5.0825c0.2872515 0 0.5038558-0.086278 0.6621875-0.235625-0.080472 0.2391332-0.1240626 0.4831773-0.1240626 0.72875 0 1.04448 0.5512275 1.4071875 1.5521875 1.4071875-0.014507-0.13056-0.2031249-0.2467224-0.2031249-0.9865626 0-2.2050133 1.4074195-3.5249998 3.7574995-3.525 1.276588 0 1.798751 0.8993192 1.798751 1.9728127 0 1.5522132-1.073578 3.4526966-2.538751 3.5687499l1.03-4.8453125-2.146875 0.2903125-1.9731245 9.2262505h2.0890625l0.885-4.1343755h0.0725c2.6112 0 4.1925-2.0454657 4.1925-3.8878125 0-1.4941867-1.044656-2.8578124-3.42375-2.8578124-1.828958 0-3.7977012 1.0501832-4.653125 2.395 0.02578-0.1395922 0.03875-0.2906221 0.03875-0.4512501 0-1.4071467-1.0591859-1.9437499-2.4228126-1.9437499z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
couchpotato/static/images/icons/safari.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 16 16"><g><path d="m5.3009374 1.8040626c-3.8007467 0-5.06281241 4.9177307-5.06281241 7.5434374 0 2.6112 1.03020071 3.568437 3.17718751 3.568437 1.3201067 0 3.3655217-0.754232 4.134375-2.698125l-0.3337501-0.145c-0.6673065 0.899413-1.6972299 1.450625-2.8287499 1.450625-1.3926401 0-1.7409374-0.928511-1.7409376-2.2631245 0-2.9593602 1.4217667-6.7746875 3.0175002-6.7746875 0.6527998 0 0.8849998 0.4207867 0.885 0.885 0 0.5512532-0.3193402 1.1606075-0.6675002 1.3346875C6.1568766 5.0244592 6.4760182 5.0825 6.708125 5.0825c0.2872515 0 0.5038558-0.086278 0.6621875-0.235625-0.080472 0.2391332-0.1240626 0.4831773-0.1240626 0.72875 0 1.04448 0.5512275 1.4071875 1.5521875 1.4071875-0.014507-0.13056-0.2031249-0.2467224-0.2031249-0.9865626 0-2.2050133 1.4074195-3.5249998 3.7574995-3.525 1.276588 0 1.798751 0.8993192 1.798751 1.9728127 0 1.5522132-1.073578 3.4526966-2.538751 3.5687499l1.03-4.8453125-2.146875 0.2903125-1.9731245 9.2262505h2.0890625l0.885-4.1343755h0.0725c2.6112 0 4.1925-2.0454657 4.1925-3.8878125 0-1.4941867-1.044656-2.8578124-3.42375-2.8578124-1.828958 0-3.7977012 1.0501832-4.653125 2.395 0.02578-0.1395922 0.03875-0.2906221 0.03875-0.4512501 0-1.4071467-1.0591859-1.9437499-2.4228126-1.9437499z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

11
couchpotato/static/scripts/combined.plugins.min.js

@ -382,16 +382,23 @@ var MovieDetails = new Class({
open: function() { open: function() {
var self = this; var self = this;
self.el.addClass("show"); self.el.addClass("show");
document.onkeyup = self.keyup.bind(self);
self.outer_click = function() { self.outer_click = function() {
self.close(); self.close();
}; };
App.addEvent("history.push", self.outer_click); App.addEvent("history.push", self.outer_click);
}, },
keyup: function(e) {
if (e.keyCode == 27) {
this.close();
}
},
close: function() { close: function() {
var self = this; var self = this;
var ended = function() { var ended = function() {
self.el.dispose(); self.el.dispose();
self.overlay.removeEventListener("transitionend", ended); self.overlay.removeEventListener("transitionend", ended);
document.onkeyup = null;
}; };
self.overlay.addEventListener("transitionend", ended, false); self.overlay.addEventListener("transitionend", ended, false);
self.el.removeClass("show"); self.el.removeClass("show");
@ -3093,7 +3100,7 @@ Page.Log = new Class({
}), new Element("a.button", { }), new Element("a.button", {
target: "_blank", target: "_blank",
text: "the contributing guide", text: "the contributing guide",
href: "https://github.com/CouchPotato/CouchPotatoServer/blob/develop/contributing.md" href: "https://github.com/CouchPotato/CouchPotatoServer/wiki/Developer-branch"
}), new Element("span", { }), new Element("span", {
html: " before posting, then copy the text below and <strong>FILL IN</strong> the dots." html: " before posting, then copy the text below and <strong>FILL IN</strong> the dots."
})), textarea = new Element("textarea", { })), textarea = new Element("textarea", {
@ -3416,7 +3423,7 @@ var QualityBase = new Class({
try { try {
return this.qualities.filter(function(q) { return this.qualities.filter(function(q) {
return q.identifier == identifier; return q.identifier == identifier;
}).pick(); }).pick() || {};
} catch (e) {} } catch (e) {}
return {}; return {};
}, },

3
couchpotato/templates/index.html

@ -15,6 +15,9 @@
<!-- IOS --> <!-- IOS -->
<link rel="apple-touch-icon-precomposed" href="{{ themed_icon_path }}ios.png" /> <link rel="apple-touch-icon-precomposed" href="{{ themed_icon_path }}ios.png" />
<!-- Safari Pinned Tab Icon -->
<link rel="mask-icon" href="{{ themed_icon_path }}safari.svg" color="#AC0000">
<!-- Android --> <!-- Android -->
<link rel="icon" type="image/png" href="{{ themed_icon_path }}android.png" sizes="192x192"> <link rel="icon" type="image/png" href="{{ themed_icon_path }}android.png" sizes="192x192">

48
libs/rtorrent/__init__.py

@ -74,7 +74,9 @@ class RTorrent:
if m.is_retriever() and m.is_available(self)] if m.is_retriever() and m.is_available(self)]
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
m.add("d.multicall", view, "d.get_hash=", # multicall2 wants .. something .. as its first argument. It accepts a blank string, so let's go with that.
MCFirstArg = ""
m.add("d.multicall2", MCFirstArg, view, "d.hash=",
*[method.rpc_call + "=" for method in retriever_methods]) *[method.rpc_call + "=" for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result results = m.call()[0] # only sent one call, only need first result
@ -116,7 +118,7 @@ class RTorrent:
elif verbose: elif verbose:
func_name = "load.verbose" func_name = "load.verbose"
else: else:
func_name = "load" func_name = "load.normal"
elif file_type in ["file", "raw"]: elif file_type in ["file", "raw"]:
if start and verbose: if start and verbose:
func_name = "load.raw_start_verbose" func_name = "load.raw_start_verbose"
@ -137,31 +139,49 @@ class RTorrent:
func_name = self._get_load_function("url", start, verbose) func_name = self._get_load_function("url", start, verbose)
# rtorrent > 0.9.6 requires first parameter @target
target = ""
# load magnet # load magnet
getattr(p, func_name)(magneturl) getattr(p, func_name)(target, magneturl)
if verify_load: if verify_load:
magnet = False
i = 0 i = 0
while i < verify_retries: while i < verify_retries:
for torrent in self.get_torrents(): for m in self.get_torrents():
if torrent.info_hash != info_hash: # This block finds the magnet that was just added, starts it, breaks
continue # out of the for loop, and then out of the while loop.
# If it can't find the magnet, magnet won't get defined.
if m.info_hash == info_hash:
magnet = m
magnet.start()
i += 999
break
# If torrent hasn't been defined, sleep for a second and check again.
if not magnet:
time.sleep(1) time.sleep(1)
i += 1 i += 1
# Resolve magnet to torrent # This bit waits for the magnet to be resolved into an actual
# torrent, and then starts it.
torrent = False
i = 0
while i < verify_retries:
for t in self.get_torrents():
if t.info_hash == info_hash:
if str(info_hash) not in str(t.name):
torrent = t
torrent.start() torrent.start()
i += 999
break
if not torrent:
time.sleep(1)
i += 1
assert info_hash in [t.info_hash for t in self.torrents],\ assert info_hash in [t.info_hash for t in self.torrents],\
"Adding magnet was unsuccessful." "Adding magnet was unsuccessful."
i = 0
while i < verify_retries:
for torrent in self.get_torrents():
if torrent.info_hash == info_hash:
if str(info_hash) not in str(torrent.name):
time.sleep(1)
i += 1
return(torrent) return(torrent)

28
libs/rtorrent/file.py

@ -59,29 +59,29 @@ class File:
methods = [ methods = [
# RETRIEVERS # RETRIEVERS
Method(File, 'get_last_touched', 'f.get_last_touched'), Method(File, 'get_last_touched', 'f.last_touched'),
Method(File, 'get_range_second', 'f.get_range_second'), Method(File, 'get_range_second', 'f.range_second'),
Method(File, 'get_size_bytes', 'f.get_size_bytes'), Method(File, 'get_size_bytes', 'f.size_bytes'),
Method(File, 'get_priority', 'f.get_priority'), Method(File, 'get_priority', 'f.priority'),
Method(File, 'get_match_depth_next', 'f.get_match_depth_next'), Method(File, 'get_match_depth_next', 'f.match_depth_next'),
Method(File, 'is_resize_queued', 'f.is_resize_queued', Method(File, 'is_resize_queued', 'f.is_resize_queued',
boolean=True, boolean=True,
), ),
Method(File, 'get_range_first', 'f.get_range_first'), Method(File, 'get_range_first', 'f.range_first'),
Method(File, 'get_match_depth_prev', 'f.get_match_depth_prev'), Method(File, 'get_match_depth_prev', 'f.match_depth_prev'),
Method(File, 'get_path', 'f.get_path'), Method(File, 'get_path', 'f.path'),
Method(File, 'get_completed_chunks', 'f.get_completed_chunks'), Method(File, 'get_completed_chunks', 'f.completed_chunks'),
Method(File, 'get_path_components', 'f.get_path_components'), Method(File, 'get_path_components', 'f.path_components'),
Method(File, 'is_created', 'f.is_created', Method(File, 'is_created', 'f.is_created',
boolean=True, boolean=True,
), ),
Method(File, 'is_open', 'f.is_open', Method(File, 'is_open', 'f.is_open',
boolean=True, boolean=True,
), ),
Method(File, 'get_size_chunks', 'f.get_size_chunks'), Method(File, 'get_size_chunks', 'f.size_chunks'),
Method(File, 'get_offset', 'f.get_offset'), Method(File, 'get_offset', 'f.offset'),
Method(File, 'get_frozen_path', 'f.get_frozen_path'), Method(File, 'get_frozen_path', 'f.frozen_path'),
Method(File, 'get_path_depth', 'f.get_path_depth'), Method(File, 'get_path_depth', 'f.path_depth'),
Method(File, 'is_create_queued', 'f.is_create_queued', Method(File, 'is_create_queued', 'f.is_create_queued',
boolean=True, boolean=True,
), ),

26
libs/rtorrent/peer.py

@ -60,39 +60,39 @@ methods = [
Method(Peer, 'is_preferred', 'p.is_preferred', Method(Peer, 'is_preferred', 'p.is_preferred',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_down_rate', 'p.get_down_rate'), Method(Peer, 'get_down_rate', 'p.down_rate'),
Method(Peer, 'is_unwanted', 'p.is_unwanted', Method(Peer, 'is_unwanted', 'p.is_unwanted',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_peer_total', 'p.get_peer_total'), Method(Peer, 'get_peer_total', 'p.peer_total'),
Method(Peer, 'get_peer_rate', 'p.get_peer_rate'), Method(Peer, 'get_peer_rate', 'p.peer_rate'),
Method(Peer, 'get_port', 'p.get_port'), Method(Peer, 'get_port', 'p.port'),
Method(Peer, 'is_snubbed', 'p.is_snubbed', Method(Peer, 'is_snubbed', 'p.is_snubbed',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_id_html', 'p.get_id_html'), Method(Peer, 'get_id_html', 'p.id_html'),
Method(Peer, 'get_up_rate', 'p.get_up_rate'), Method(Peer, 'get_up_rate', 'p.up_rate'),
Method(Peer, 'is_banned', 'p.banned', Method(Peer, 'is_banned', 'p.banned',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_completed_percent', 'p.get_completed_percent'), Method(Peer, 'get_completed_percent', 'p.completed_percent'),
Method(Peer, 'completed_percent', 'p.completed_percent'), Method(Peer, 'completed_percent', 'p.completed_percent'),
Method(Peer, 'get_id', 'p.get_id'), Method(Peer, 'get_id', 'p.id'),
Method(Peer, 'is_obfuscated', 'p.is_obfuscated', Method(Peer, 'is_obfuscated', 'p.is_obfuscated',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_down_total', 'p.get_down_total'), Method(Peer, 'get.down.total', 'p.down_total'),
Method(Peer, 'get_client_version', 'p.get_client_version'), Method(Peer, 'get_client_version', 'p.client_version'),
Method(Peer, 'get_address', 'p.get_address'), Method(Peer, 'get_address', 'p.address'),
Method(Peer, 'is_incoming', 'p.is_incoming', Method(Peer, 'is_incoming', 'p.is_incoming',
boolean=True, boolean=True,
), ),
Method(Peer, 'is_encrypted', 'p.is_encrypted', Method(Peer, 'is_encrypted', 'p.is_encrypted',
boolean=True, boolean=True,
), ),
Method(Peer, 'get_options_str', 'p.get_options_str'), Method(Peer, 'get_options_str', 'p.options_str'),
Method(Peer, 'get_client_version', 'p.client_version'), Method(Peer, 'get_client_version', 'p.client_version'),
Method(Peer, 'get_up_total', 'p.get_up_total'), Method(Peer, 'get_up_total', 'p.up_total'),
# MODIFIERS # MODIFIERS
] ]

6
libs/rtorrent/rpc/__init__.py

@ -38,13 +38,13 @@ def get_varname(rpc_call):
r = re.search( r = re.search(
"([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I) "([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I)
if r: if r:
return(r.groups()[-1]) return(r.groups()[-1].replace(".","_"))
else: else:
return(None) return(None)
def _handle_unavailable_rpc_method(method, rt_obj): def _handle_unavailable_rpc_method(method, rt_obj):
msg = "Method isn't available." msg = "Method " + str(method) + " isn't available."
if rt_obj.connection._get_client_version_tuple() < method.min_version: if rt_obj.connection._get_client_version_tuple() < method.min_version:
msg = "This method is only available in " \ msg = "This method is only available in " \
"RTorrent version v{0} or later".format( "RTorrent version v{0} or later".format(
@ -91,7 +91,7 @@ class Method:
def _get_method_type(self): def _get_method_type(self):
"""Determine whether method is a modifier or a retriever""" """Determine whether method is a modifier or a retriever"""
if self.method_name[:4] == "set_": return('m') # modifier if self.method_name[:4] == "set_" or self.method_name[-4:] == ".set": return('m') # modifier
else: else:
return('r') # retriever return('r') # retriever

168
libs/rtorrent/torrent.py

@ -139,7 +139,7 @@ class Torrent:
results = m.call()[0] # only sent one call, only need first result results = m.call()[0] # only sent one call, only need first result
offset_method_index = retriever_methods.index( offset_method_index = retriever_methods.index(
rtorrent.rpc.find_method("f.get_offset")) rtorrent.rpc.find_method("f.offset"))
# make a list of the offsets of all the files, sort appropriately # make a list of the offsets of all the files, sort appropriately
offset_list = sorted([r[offset_method_index] for r in results]) offset_list = sorted([r[offset_method_index] for r in results])
@ -168,7 +168,7 @@ class Torrent:
""" """
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_stop") self.multicall_add(m, "d.try_stop")
self.multicall_add(m, "d.set_directory", d) self.multicall_add(m, "d.directory.set", d)
self.directory = m.call()[-1] self.directory = m.call()[-1]
@ -181,7 +181,7 @@ class Torrent:
""" """
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_stop") self.multicall_add(m, "d.try_stop")
self.multicall_add(m, "d.set_directory_base", d) self.multicall_add(m, "d.directory_base.set", d)
def start(self): def start(self):
"""Start the torrent""" """Start the torrent"""
@ -304,7 +304,7 @@ class Torrent:
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
field = "custom{0}".format(key) field = "custom{0}".format(key)
self.multicall_add(m, "d.get_{0}".format(field)) self.multicall_add(m, "d.{0}".format(field))
setattr(self, field, m.call()[-1]) setattr(self, field, m.call()[-1])
return (getattr(self, field)) return (getattr(self, field))
@ -326,7 +326,7 @@ class Torrent:
self._assert_custom_key_valid(key) self._assert_custom_key_valid(key)
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.set_custom{0}".format(key), value) self.multicall_add(m, "d.custom{0}.set".format(key), value)
return(m.call()[-1]) return(m.call()[-1])
@ -355,7 +355,7 @@ class Torrent:
@note: Variable where the result for this method is stored Torrent.hash_checking_queued""" @note: Variable where the result for this method is stored Torrent.hash_checking_queued"""
m = rtorrent.rpc.Multicall(self) m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.get_hashing") self.multicall_add(m, "d.hashing")
self.multicall_add(m, "d.is_hash_checking") self.multicall_add(m, "d.is_hash_checking")
results = m.call() results = m.call()
@ -397,86 +397,86 @@ methods = [
Method(Torrent, 'is_hash_checking', 'd.is_hash_checking', Method(Torrent, 'is_hash_checking', 'd.is_hash_checking',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_peers_max', 'd.get_peers_max'), Method(Torrent, 'get_peers_max', 'd.peers_max'),
Method(Torrent, 'get_tracker_focus', 'd.get_tracker_focus'), Method(Torrent, 'get_tracker_focus', 'd.tracker_focus'),
Method(Torrent, 'get_skip_total', 'd.get_skip_total'), Method(Torrent, 'get_skip_total', 'd.skip.total'),
Method(Torrent, 'get_state', 'd.get_state'), Method(Torrent, 'get_state', 'd.state'),
Method(Torrent, 'get_peer_exchange', 'd.get_peer_exchange'), Method(Torrent, 'get_peer_exchange', 'd.peer_exchange'),
Method(Torrent, 'get_down_rate', 'd.get_down_rate'), Method(Torrent, 'get_down_rate', 'd.down.rate'),
Method(Torrent, 'get_connection_seed', 'd.get_connection_seed'), Method(Torrent, 'get_connection_seed', 'd.connection_seed'),
Method(Torrent, 'get_uploads_max', 'd.get_uploads_max'), Method(Torrent, 'get_uploads_max', 'd.uploads_max'),
Method(Torrent, 'get_priority_str', 'd.get_priority_str'), Method(Torrent, 'get_priority_str', 'd.priority_str'),
Method(Torrent, 'is_open', 'd.is_open', Method(Torrent, 'is_open', 'd.is_open',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_peers_min', 'd.get_peers_min'), Method(Torrent, 'get_peers_min', 'd.peers_min'),
Method(Torrent, 'get_peers_complete', 'd.get_peers_complete'), Method(Torrent, 'get_peers_complete', 'd.peers_complete'),
Method(Torrent, 'get_tracker_numwant', 'd.get_tracker_numwant'), Method(Torrent, 'get_tracker_numwant', 'd.tracker_numwant'),
Method(Torrent, 'get_connection_current', 'd.get_connection_current'), Method(Torrent, 'get_connection_current', 'd.connection_current'),
Method(Torrent, 'is_complete', 'd.get_complete', Method(Torrent, 'is_complete', 'd.complete',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_peers_connected', 'd.get_peers_connected'), Method(Torrent, 'get_peers_connected', 'd.peers_connected'),
Method(Torrent, 'get_chunk_size', 'd.get_chunk_size'), Method(Torrent, 'get_chunk_size', 'd.chunk_size'),
Method(Torrent, 'get_state_counter', 'd.get_state_counter'), Method(Torrent, 'get_state_counter', 'd.state_counter'),
Method(Torrent, 'get_base_filename', 'd.get_base_filename'), Method(Torrent, 'get_base_filename', 'd.base_filename'),
Method(Torrent, 'get_state_changed', 'd.get_state_changed'), Method(Torrent, 'get_state_changed', 'd.state_changed'),
Method(Torrent, 'get_peers_not_connected', 'd.get_peers_not_connected'), Method(Torrent, 'get_peers_not_connected', 'd.peers_not_connected'),
Method(Torrent, 'get_directory', 'd.get_directory'), Method(Torrent, 'get_directory', 'd.directory'),
Method(Torrent, 'is_incomplete', 'd.incomplete', Method(Torrent, 'is_incomplete', 'd.incomplete',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_tracker_size', 'd.get_tracker_size'), Method(Torrent, 'get_tracker_size', 'd.tracker_size'),
Method(Torrent, 'is_multi_file', 'd.is_multi_file', Method(Torrent, 'is_multi_file', 'd.is_multi_file',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_local_id', 'd.get_local_id'), Method(Torrent, 'get_local_id', 'd.local_id'),
Method(Torrent, 'get_ratio', 'd.get_ratio', Method(Torrent, 'get_ratio', 'd.ratio',
post_process_func=lambda x: x / 1000.0, post_process_func=lambda x: x / 1000.0,
), ),
Method(Torrent, 'get_loaded_file', 'd.get_loaded_file'), Method(Torrent, 'get_loaded_file', 'd.loaded_file'),
Method(Torrent, 'get_max_file_size', 'd.get_max_file_size'), Method(Torrent, 'get_max_file_size', 'd.max_file_size'),
Method(Torrent, 'get_size_chunks', 'd.get_size_chunks'), Method(Torrent, 'get_size_chunks', 'd.size_chunks'),
Method(Torrent, 'is_pex_active', 'd.is_pex_active', Method(Torrent, 'is_pex_active', 'd.is_pex_active',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_hashing', 'd.get_hashing'), Method(Torrent, 'get_hashing', 'd.hashing'),
Method(Torrent, 'get_bitfield', 'd.get_bitfield'), Method(Torrent, 'get_bitfield', 'd.bitfield'),
Method(Torrent, 'get_local_id_html', 'd.get_local_id_html'), Method(Torrent, 'get_local_id_html', 'd.local_id_html'),
Method(Torrent, 'get_connection_leech', 'd.get_connection_leech'), Method(Torrent, 'get_connection_leech', 'd.connection_leech'),
Method(Torrent, 'get_peers_accounted', 'd.get_peers_accounted'), Method(Torrent, 'get_peers_accounted', 'd.peers_accounted'),
Method(Torrent, 'get_message', 'd.get_message'), Method(Torrent, 'get_message', 'd.message'),
Method(Torrent, 'is_active', 'd.is_active', Method(Torrent, 'is_active', 'd.is_active',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_size_bytes', 'd.get_size_bytes'), Method(Torrent, 'get_size_bytes', 'd.size_bytes'),
Method(Torrent, 'get_ignore_commands', 'd.get_ignore_commands'), Method(Torrent, 'get_ignore_commands', 'd.ignore_commands'),
Method(Torrent, 'get_creation_date', 'd.get_creation_date'), Method(Torrent, 'get_creation_date', 'd.creation_date'),
Method(Torrent, 'get_base_path', 'd.get_base_path'), Method(Torrent, 'get_base_path', 'd.base_path'),
Method(Torrent, 'get_left_bytes', 'd.get_left_bytes'), Method(Torrent, 'get_left_bytes', 'd.left_bytes'),
Method(Torrent, 'get_size_files', 'd.get_size_files'), Method(Torrent, 'get_size_files', 'd.size_files'),
Method(Torrent, 'get_size_pex', 'd.get_size_pex'), Method(Torrent, 'get_size_pex', 'd.size_pex'),
Method(Torrent, 'is_private', 'd.is_private', Method(Torrent, 'is_private', 'd.is_private',
boolean=True, boolean=True,
), ),
Method(Torrent, 'get_max_size_pex', 'd.get_max_size_pex'), Method(Torrent, 'get_max_size_pex', 'd.max_size_pex'),
Method(Torrent, 'get_num_chunks_hashed', 'd.get_chunks_hashed', Method(Torrent, 'get_num_chunks_hashed', 'd.chunks_hashed',
aliases=("get_chunks_hashed",)), aliases=("get_chunks_hashed",)),
Method(Torrent, 'get_num_chunks_wanted', 'd.wanted_chunks'), Method(Torrent, 'get_num_chunks_wanted', 'd.wanted_chunks'),
Method(Torrent, 'get_priority', 'd.get_priority'), Method(Torrent, 'get_priority', 'd.priority'),
Method(Torrent, 'get_skip_rate', 'd.get_skip_rate'), Method(Torrent, 'get_skip_rate', 'd.skip.rate'),
Method(Torrent, 'get_completed_bytes', 'd.get_completed_bytes'), Method(Torrent, 'get_completed_bytes', 'd.completed_bytes'),
Method(Torrent, 'get_name', 'd.get_name'), Method(Torrent, 'get_name', 'd.name'),
Method(Torrent, 'get_completed_chunks', 'd.get_completed_chunks'), Method(Torrent, 'get_completed_chunks', 'd.completed_chunks'),
Method(Torrent, 'get_throttle_name', 'd.get_throttle_name'), Method(Torrent, 'get_throttle_name', 'd.throttle_name'),
Method(Torrent, 'get_free_diskspace', 'd.get_free_diskspace'), Method(Torrent, 'get_free_diskspace', 'd.free_diskspace'),
Method(Torrent, 'get_directory_base', 'd.get_directory_base'), Method(Torrent, 'get_directory_base', 'd.directory_base'),
Method(Torrent, 'get_hashing_failed', 'd.get_hashing_failed'), Method(Torrent, 'get_hashing_failed', 'd.hashing_failed'),
Method(Torrent, 'get_tied_to_file', 'd.get_tied_to_file'), Method(Torrent, 'get_tied_to_file', 'd.tied_to_file'),
Method(Torrent, 'get_down_total', 'd.get_down_total'), Method(Torrent, 'get_down_total', 'd.down.total'),
Method(Torrent, 'get_bytes_done', 'd.get_bytes_done'), Method(Torrent, 'get_bytes_done', 'd.bytes_done'),
Method(Torrent, 'get_up_rate', 'd.get_up_rate'), Method(Torrent, 'get_up_rate', 'd.up.rate'),
Method(Torrent, 'get_up_total', 'd.get_up_total'), Method(Torrent, 'get_up_total', 'd.up.total'),
Method(Torrent, 'is_accepting_seeders', 'd.accepting_seeders', Method(Torrent, 'is_accepting_seeders', 'd.accepting_seeders',
boolean=True, boolean=True,
), ),
@ -490,28 +490,28 @@ methods = [
boolean=True, boolean=True,
), ),
Method(Torrent, "get_time_started", "d.timestamp.started"), Method(Torrent, "get_time_started", "d.timestamp.started"),
Method(Torrent, "get_custom1", "d.get_custom1"), Method(Torrent, "get_custom1", "d.custom1"),
Method(Torrent, "get_custom2", "d.get_custom2"), Method(Torrent, "get_custom2", "d.custom2"),
Method(Torrent, "get_custom3", "d.get_custom3"), Method(Torrent, "get_custom3", "d.custom3"),
Method(Torrent, "get_custom4", "d.get_custom4"), Method(Torrent, "get_custom4", "d.custom4"),
Method(Torrent, "get_custom5", "d.get_custom5"), Method(Torrent, "get_custom5", "d.custom5"),
# MODIFIERS # MODIFIERS
Method(Torrent, 'set_uploads_max', 'd.set_uploads_max'), Method(Torrent, 'set_uploads_max', 'd.uploads_max.set'),
Method(Torrent, 'set_tied_to_file', 'd.set_tied_to_file'), Method(Torrent, 'set_tied_to_file', 'd.tied_to_file.set'),
Method(Torrent, 'set_tracker_numwant', 'd.set_tracker_numwant'), Method(Torrent, 'set_tracker_numwant', 'd.tracker_numwant.set'),
Method(Torrent, 'set_priority', 'd.set_priority'), Method(Torrent, 'set_priority', 'd.priority.set'),
Method(Torrent, 'set_peers_max', 'd.set_peers_max'), Method(Torrent, 'set_peers_max', 'd.peers_max.set'),
Method(Torrent, 'set_hashing_failed', 'd.set_hashing_failed'), Method(Torrent, 'set_hashing_failed', 'd.hashing_failed.set'),
Method(Torrent, 'set_message', 'd.set_message'), Method(Torrent, 'set_message', 'd.message.set'),
Method(Torrent, 'set_throttle_name', 'd.set_throttle_name'), Method(Torrent, 'set_throttle_name', 'd.throttle_name.set'),
Method(Torrent, 'set_peers_min', 'd.set_peers_min'), Method(Torrent, 'set_peers_min', 'd.peers_min.set'),
Method(Torrent, 'set_ignore_commands', 'd.set_ignore_commands'), Method(Torrent, 'set_ignore_commands', 'd.ignore_commands.set'),
Method(Torrent, 'set_max_file_size', 'd.set_max_file_size'), Method(Torrent, 'set_max_file_size', 'd.max_file_size.set'),
Method(Torrent, 'set_custom5', 'd.set_custom5'), Method(Torrent, 'set_custom5', 'd.custom5.set'),
Method(Torrent, 'set_custom4', 'd.set_custom4'), Method(Torrent, 'set_custom4', 'd.custom4.set'),
Method(Torrent, 'set_custom2', 'd.set_custom2'), Method(Torrent, 'set_custom2', 'd.custom2.set'),
Method(Torrent, 'set_custom1', 'd.set_custom1'), Method(Torrent, 'set_custom1', 'd.custom1.set'),
Method(Torrent, 'set_custom3', 'd.set_custom3'), Method(Torrent, 'set_custom3', 'd.custom3.set'),
Method(Torrent, 'set_connection_current', 'd.set_connection_current'), Method(Torrent, 'set_connection_current', 'd.connection_current.set'),
] ]

22
libs/rtorrent/tracker.py

@ -70,17 +70,17 @@ class Tracker:
methods = [ methods = [
# RETRIEVERS # RETRIEVERS
Method(Tracker, 'is_enabled', 't.is_enabled', boolean=True), Method(Tracker, 'is_enabled', 't.is_enabled', boolean=True),
Method(Tracker, 'get_id', 't.get_id'), Method(Tracker, 'get_id', 't.id'),
Method(Tracker, 'get_scrape_incomplete', 't.get_scrape_incomplete'), Method(Tracker, 'get_scrape_incomplete', 't.scrape_incomplete'),
Method(Tracker, 'is_open', 't.is_open', boolean=True), Method(Tracker, 'is_open', 't.is_open', boolean=True),
Method(Tracker, 'get_min_interval', 't.get_min_interval'), Method(Tracker, 'get_min_interval', 't.min_interval'),
Method(Tracker, 'get_scrape_downloaded', 't.get_scrape_downloaded'), Method(Tracker, 'get_scrape_downloaded', 't.scrape_downloaded'),
Method(Tracker, 'get_group', 't.get_group'), Method(Tracker, 'get_group', 't.group'),
Method(Tracker, 'get_scrape_time_last', 't.get_scrape_time_last'), Method(Tracker, 'get_scrape_time_last', 't.scrape_time_last'),
Method(Tracker, 'get_type', 't.get_type'), Method(Tracker, 'get_type', 't.type'),
Method(Tracker, 'get_normal_interval', 't.get_normal_interval'), Method(Tracker, 'get_normal_interval', 't.normal_interval'),
Method(Tracker, 'get_url', 't.get_url'), Method(Tracker, 'get_url', 't.url'),
Method(Tracker, 'get_scrape_complete', 't.get_scrape_complete', Method(Tracker, 'get_scrape_complete', 't.scrape_complete',
min_version=(0, 8, 9), min_version=(0, 8, 9),
), ),
Method(Tracker, 'get_activity_time_last', 't.activity_time_last', Method(Tracker, 'get_activity_time_last', 't.activity_time_last',
@ -134,5 +134,5 @@ methods = [
), ),
# MODIFIERS # MODIFIERS
Method(Tracker, 'set_enabled', 't.set_enabled'), Method(Tracker, 'set_enabled', 't.is_enabled.set'),
] ]

2
libs/subliminal/core.py

@ -33,7 +33,7 @@ __all__ = ['SERVICES', 'LANGUAGE_INDEX', 'SERVICE_INDEX', 'SERVICE_CONFIDENCE',
'key_subtitles', 'group_by_video'] 'key_subtitles', 'group_by_video']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SERVICES = ['opensubtitles', 'bierdopje', 'subswiki', 'subtitulos', 'thesubdb', 'addic7ed', 'tvsubtitles', SERVICES = ['opensubtitles', 'bierdopje', 'subswiki', 'subtitulos', 'thesubdb', 'addic7ed', 'tvsubtitles',
'subscenter', 'thewiz'] 'subscenter', 'wizdom']
LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE = range(4) LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE = range(4)

18
libs/subliminal/services/__init__.py

@ -183,16 +183,21 @@ class ServiceBase(object):
return False return False
return True return True
def download_file(self, url, filepath): def download_file(self, url, filepath, data=None):
"""Attempt to download a file and remove it in case of failure """Attempt to download a file and remove it in case of failure
:param string url: URL to download :param string url: URL to download
:param string filepath: destination path :param string filepath: destination path
:param string data: data to add to the post request
""" """
logger.info(u'Downloading %s in %s' % (url, filepath)) logger.info(u'Downloading %s in %s' % (url, filepath))
try: try:
r = self.session.get(url, timeout = 10, headers = {'Referer': url, 'User-Agent': self.user_agent}) headers = {'Referer': url, 'User-Agent': self.user_agent}
if data:
r = self.session.post(url, data=data, timeout=10, headers=headers)
else:
r = self.session.get(url, timeout=10, headers=headers)
with open(filepath, 'wb') as f: with open(filepath, 'wb') as f:
f.write(r.content) f.write(r.content)
except Exception as e: except Exception as e:
@ -202,18 +207,23 @@ class ServiceBase(object):
raise DownloadFailedError(str(e)) raise DownloadFailedError(str(e))
logger.debug(u'Download finished') logger.debug(u'Download finished')
def download_zip_file(self, url, filepath): def download_zip_file(self, url, filepath, data=None):
"""Attempt to download a zip file and extract any subtitle file from it, if any. """Attempt to download a zip file and extract any subtitle file from it, if any.
This cleans up after itself if anything fails. This cleans up after itself if anything fails.
:param string url: URL of the zip file to download :param string url: URL of the zip file to download
:param string filepath: destination path for the subtitle :param string filepath: destination path for the subtitle
:param string data: data to add to the post request
""" """
logger.info(u'Downloading %s in %s' % (url, filepath)) logger.info(u'Downloading %s in %s' % (url, filepath))
try: try:
zippath = filepath + '.zip' zippath = filepath + '.zip'
r = self.session.get(url, timeout = 10, headers = {'Referer': url, 'User-Agent': self.user_agent}) headers = {'Referer': url, 'User-Agent': self.user_agent}
if data:
r = self.session.post(url, data=data, timeout=10, headers=headers)
else:
r = self.session.get(url, timeout=10, headers=headers)
with open(zippath, 'wb') as f: with open(zippath, 'wb') as f:
f.write(r.content) f.write(r.content)
if not zipfile.is_zipfile(zippath): if not zipfile.is_zipfile(zippath):

153
libs/subliminal/services/subscenter.py

@ -16,105 +16,127 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..exceptions import DownloadFailedError, ServiceError from ..exceptions import ServiceError
from ..language import language_set from ..language import language_set
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..videos import Episode, Movie from ..videos import Episode, Movie
from ..utils import to_unicode, get_keywords from ..utils import to_unicode
from bs4 import BeautifulSoup
import bisect import bisect
import json
import logging import logging
from urllib import urlencode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Subscenter(ServiceBase): class Subscenter(ServiceBase):
server = 'http://www.subscenter.org/he/' server = 'http://www.cinemast.org/he/cinemast/api/'
api_based = False api_based = True
languages = language_set(['he']) languages = language_set(['he'])
videos = [Episode, Movie] videos = [Episode, Movie]
require_video = False require_video = False
def _search_url_title(self, title, kind): default_username = 'subliminal@gmail.com'
"""Search the URL title for the given `title`. default_password = 'subliminal'
:param str title: title to search for.
:param str kind: kind of the title, ``movie`` or ``series``.
:return: the URL version of the title.
:rtype: str or None
"""
# make the search
logger.info('Searching title name for %r', title)
r = self.session.get(self.server + 'subtitle/search/', params={'q': title}, allow_redirects=False, timeout=10)
r.raise_for_status()
# if redirected, get the url title from the Location header def __init__(self, config=None):
if r.is_redirect: super(Subscenter, self).__init__(config)
parts = r.headers['Location'].split('/') self.token = None
self.user_id = None
# check kind def init(self):
if parts[-3] == kind: super(Subscenter, self).init()
return parts[-2] logger.debug('Logging in')
url = self.server_url + 'login/'
return None # actual login
data = {'username': self.default_username, 'password': self.default_password}
r = self.session.post(url, data=urlencode(data), allow_redirects=False, timeout=10)
# otherwise, get the first valid suggestion if r.status_code != 200:
soup = BeautifulSoup(r.content, ['lxml', 'html.parser']) raise ServiceError('Login failed')
suggestions = soup.select('#processes div.generalWindowTop a')
logger.debug('Found %d suggestions', len(suggestions)) try:
for suggestion in suggestions: result = r.json()
parts = suggestion.attrs['href'].split('/') if 'token' not in result:
raise ServiceError('Login failed')
# check kind logger.info('Logged in')
if parts[-3] == kind: self.user_id = r.json().get('user')
return parts[-2] self.token = r.json().get('token')
except ValueError:
raise ServiceError('Login failed')
def terminate(self):
super(Subscenter, self).terminate()
if self.token or self.user_id:
logger.info('Logged out')
self.token = None
self.user_id = None
def list_checked(self, video, languages): def list_checked(self, video, languages):
series = None series = None
season = None season = None
episode = None episode = None
title = video.title title = video.title
year = video.year
if isinstance(video, Episode): if isinstance(video, Episode):
series = video.series series = video.series
season = video.season season = video.season
episode = video.episode episode = video.episode
return self.query(video.path or video.release, languages, get_keywords(video.guess), series, season, return self.query(video.path or video.release, languages, series, season, episode, title, year)
episode, title)
def query(self, filepath, languages=None, keywords=None, series=None, season=None, episode=None, title=None): def query(self, filepath, languages=None, series=None, season=None, episode=None, title=None, year=None):
logger.debug(u'Getting subtitles for {0} season {1} episode {2} with languages {3}'.format( logger.debug(u'Getting subtitles for {0} season {1} episode {2} with languages {3}'.format(
series, season, episode, languages)) series, season, episode, languages))
# Set the correct parameters depending on the kind.
if series and season and episode: query = {
url_series = self._search_url_title(series, 'series') 'user': self.user_id,
url = self.server + 'cst/data/series/sb/{}/{}/{}/'.format(url_series, season, episode) 'token': self.token
}
# episode
if season and episode:
query['q'] = series
query['type'] = 'series'
query['season'] = season
query['episode'] = episode
elif title: elif title:
url_title = self._search_url_title(title, 'movie') query['q'] = title
url = self.server + 'cst/data/movie/sb/{}/'.format(url_title) query['type'] = 'movies'
if year:
query['year_start'] = year - 1
query['year_end'] = year
else: else:
raise ServiceError('One or more parameters are missing') raise ServiceError('One or more parameters are missing')
logger.debug('Searching subtitles for title {0}, season {1}, episode {2}'.format(title, season, episode))
response = self.session.get(url) # get the list of subtitles
if response.status_code != 200: logger.debug('Getting the list of subtitles')
raise ServiceError('Request failed with status code {0}'.format(response.status_code)) url = self.server_url + 'search/'
# Loop over results. r = self.session.post(url, data=urlencode(query))
subtitles = dict() r.raise_for_status()
response_json = json.loads(response.content)
for language_code, language_data in response_json.items(): try:
results = r.json()
except ValueError:
return {}
# loop over results
subtitles = {}
for group_data in results.get('data', []):
for language_code, subtitles_data in group_data.get('subtitles', {}).items():
language_object = self.get_language(language_code) language_object = self.get_language(language_code)
if language_object in self.languages and language_object in languages:
for quality_data in language_data.values(): for subtitle_item in subtitles_data:
for quality, subtitles_data in quality_data.items(): # read the item
for subtitle_item in subtitles_data.values():
# Read the item.
subtitle_id = subtitle_item['id'] subtitle_id = subtitle_item['id']
subtitle_key = subtitle_item['key'] subtitle_key = subtitle_item['key']
subtitle_version = subtitle_item['h_version'] release = subtitle_item['version']
release = subtitle_item['subtitle_version']
subtitle_path = get_subtitle_path(filepath, language_object, self.config.multi) subtitle_path = get_subtitle_path(filepath, language_object, self.config.multi)
download_link = self.server_url + 'subtitle/download/{0}/{1}/?v={2}&key={3}'.format( download_link = self.server_url + 'subtitle/download/{0}/?v={1}&key={2}&sub_id={3}'.format(
language_code, subtitle_id, subtitle_version, subtitle_key) language_code, release, subtitle_key, subtitle_id)
# Add the release and increment downloaded count if we already have the subtitle. # Add the release and increment downloaded count if we already have the subtitle.
if subtitle_id in subtitles: if subtitle_id in subtitles:
logger.debug('Found additional release {0} for subtitle {1}'.format( logger.debug('Found additional release {0} for subtitle {1}'.format(
@ -126,14 +148,15 @@ class Subscenter(ServiceBase):
download_link, release=to_unicode(release)) download_link, release=to_unicode(release))
logger.debug('Found subtitle %r', subtitle) logger.debug('Found subtitle %r', subtitle)
subtitles[subtitle_id] = subtitle subtitles[subtitle_id] = subtitle
return subtitles.values() return subtitles.values()
def download(self, subtitle): def download(self, subtitle):
try: data = {
self.download_zip_file(subtitle.link, subtitle.path) 'user': self.user_id,
except DownloadFailedError: 'token': self.token
# If no zip file was retrieved, daily downloads limit has exceeded. }
raise ServiceError('Daily limit exceeded') self.download_zip_file(subtitle.link, subtitle.path, data=urlencode(data))
return subtitle return subtitle

56
libs/subliminal/services/thewiz.py → libs/subliminal/services/wizdom.py

@ -23,53 +23,47 @@ from ..videos import Episode, Movie
from ..utils import to_unicode from ..utils import to_unicode
import bisect import bisect
import logging import logging
import os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TheWiz(ServiceBase): class Wizdom(ServiceBase):
server = 'http://subs.thewiz.info/' server = 'http://wizdom.xyz'
api_based = True api_based = True
languages = language_set(['he']) languages = language_set(['he'])
videos = [Episode, Movie] videos = [Episode, Movie]
require_video = False require_video = False
_tmdb_api_key = 'f7f51775877e0bb6703520952b3c7840' _tmdb_api_key = 'a51ee051bcd762543373903de296e0a3'
def _search_imdb_id(self, title, year, is_movie): def _search_imdb_id(self, title, year, is_movie):
"""Search the IMDB ID for the given `title` and `year`. """Search the IMDB ID for the given `title` and `year`.
:param str title: title to search for. :param str title: title to search for.
:param int year: year to search for (or 0 if not relevant). :param int year: year to search for (or 0 if not relevant).
:param bool is_movie: If True, IMDB ID will be searched for in TMDB instead of TheWiz. :param bool is_movie: If True, IMDB ID will be searched for in TMDB instead of Wizdom.
:return: the IMDB ID for the given title and year (or None if not found). :return: the IMDB ID for the given title and year (or None if not found).
:rtype: str :rtype: str
""" """
# make the search # make the search
logger.info('Searching IMDB ID for %r%r', title, '' if not year else ' ({})'.format(year)) logger.info('Searching IMDB ID for %r%r', title, '' if not year else ' ({})'.format(year))
category = 'movie' if is_movie else 'tv'
title = title.replace('\'', '') title = title.replace('\'', '')
if is_movie:
# get TMDB ID first # get TMDB ID first
r = self.session.get('http://api.tmdb.org/3/search/movie?api_key={}&query={}{}&language=en'.format( r = self.session.get('http://api.tmdb.org/3/search/{}?api_key={}&query={}{}&language=en'.format(
self._tmdb_api_key, title, '' if not year else '&year={}'.format(year))) category, self._tmdb_api_key, title, '' if not year else '&year={}'.format(year)))
r.raise_for_status() r.raise_for_status()
tmdb_results = r.json().get('results') tmdb_results = r.json().get('results')
if tmdb_results: if tmdb_results:
tmdb_id = tmdb_results[0].get('id') tmdb_id = tmdb_results[0].get('id')
if tmdb_id: if tmdb_id:
# get actual IMDB ID from TMDB # get actual IMDB ID from TMDB
r = self.session.get('http://api.tmdb.org/3/movie/{}?api_key={}&language=en'.format( r = self.session.get('http://api.tmdb.org/3/{}/{}{}?api_key={}&language=en'.format(
tmdb_id, self._tmdb_api_key)) category, tmdb_id, '' if is_movie else '/external_ids', self._tmdb_api_key))
r.raise_for_status() r.raise_for_status()
return str(r.json().get('imdb_id', '')) or None return str(r.json().get('imdb_id', '')) or None
return None return None
# handle TV series
r = self.session.get(self.server_url + 'search.tv.php', params={'name': title}, timeout=10)
r.raise_for_status()
return r.text or None
def list_checked(self, video, languages): def list_checked(self, video, languages):
series = None series = None
season = None season = None
@ -84,40 +78,44 @@ class TheWiz(ServiceBase):
return self.query(video.path or video.release, languages, series, season, return self.query(video.path or video.release, languages, series, season,
episode, title, imdb_id, year) episode, title, imdb_id, year)
def query(self, filepath, languages=None, series=None, season=None, episode=None, title=None, imdbid=None, year=None): def query(self, filepath, languages=None, series=None, season=None, episode=None, title=None, imdbid=None,
year=None):
logger.debug(u'Getting subtitles for {0} season {1} episode {2} with languages {3}'.format( logger.debug(u'Getting subtitles for {0} season {1} episode {2} with languages {3}'.format(
series, season, episode, languages)) series, season, episode, languages))
# search for the IMDB ID if needed # search for the IMDB ID if needed
is_movie = not (series and season and episode) is_movie = not (series and season and episode)
if is_movie and not title: if is_movie and not title:
raise ServiceError('One or more parameters are missing') raise ServiceError('One or more parameters are missing')
# for tv series, we need the series IMDB ID, and not the specific episode ID # for TV series, we need the series IMDB ID, and not the specific episode ID
imdb_id = (is_movie and imdbid) or self._search_imdb_id(title, year, is_movie) imdb_id = imdbid or self._search_imdb_id(title, year, is_movie)
# get search parameters
season = season or 0
episode = episode or 0
version = os.path.splitext(os.path.basename(filepath))[0] if filepath else 0
# search # search
logger.debug(u'Using IMDB ID {0}'.format(imdb_id)) logger.debug(u'Using IMDB ID {0}'.format(imdb_id))
url = 'http://subs.thewiz.info/search.id.php?imdb={}&season={}&episode={}&version={}'.format( url = 'http://json.{}/{}.json'.format(self.server_url, imdb_id)
imdb_id, season, episode, version)
# get the list of subtitles # get the list of subtitles
logger.debug(u'Getting the list of subtitles') logger.debug('Getting the list of subtitles')
r = self.session.get(url) r = self.session.get(url)
r.raise_for_status() r.raise_for_status()
try:
results = r.json() results = r.json()
except ValueError:
return {}
# loop over results # filter irrelevant results
if not is_movie:
results = results.get('subs', {}).get(str(season), {}).get(str(episode), [])
else:
results = results.get('subs', [])
# loop over results
subtitles = dict() subtitles = dict()
for result in results: for result in results:
language_object = self.get_language('heb') language_object = self.get_language('heb')
subtitle_id = result['id'] subtitle_id = result['id']
release = result['versioname'] release = result['version']
subtitle_path = get_subtitle_path(filepath, language_object, self.config.multi) subtitle_path = get_subtitle_path(filepath, language_object, self.config.multi)
download_link = self.server_url + 'zip/{0}.zip'.format(subtitle_id) download_link = 'http://zip.{}/{}.zip'.format(self.server_url, subtitle_id)
# add the release and increment downloaded count if we already have the subtitle # add the release and increment downloaded count if we already have the subtitle
if subtitle_id in subtitles: if subtitle_id in subtitles:
logger.debug(u'Found additional release {0} for subtitle {1}'.format(release, subtitle_id)) logger.debug(u'Found additional release {0} for subtitle {1}'.format(release, subtitle_id))
@ -137,4 +135,4 @@ class TheWiz(ServiceBase):
return subtitle return subtitle
Service = TheWiz Service = Wizdom

8
libs/xmpp/transports.py

@ -27,7 +27,7 @@ Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as mo
Also exception 'error' is defined to allow capture of this module specific exceptions. Also exception 'error' is defined to allow capture of this module specific exceptions.
""" """
import socket, select, base64, dispatcher, sys import socket, ssl, select, base64, dispatcher, sys
from simplexml import ustr from simplexml import ustr
from client import PlugIn from client import PlugIn
from protocol import * from protocol import *
@ -312,9 +312,9 @@ class TLS(PlugIn):
""" Immidiatedly switch socket to TLS mode. Used internally.""" """ Immidiatedly switch socket to TLS mode. Used internally."""
""" Here we should switch pending_data to hint mode.""" """ Here we should switch pending_data to hint mode."""
tcpsock = self._owner.Connection tcpsock = self._owner.Connection
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) tcpsock._sslObj = ssl.wrap_socket(tcpsock._sock, None, None)
tcpsock._sslIssuer = tcpsock._sslObj.issuer() tcpsock._sslIssuer = tcpsock._sslObj.getpeercert().get('issuer')
tcpsock._sslServer = tcpsock._sslObj.server() tcpsock._sslServer = tcpsock._sslObj.getpeercert().get('server')
tcpsock._recv = tcpsock._sslObj.read tcpsock._recv = tcpsock._sslObj.read
tcpsock._send = tcpsock._sslObj.write tcpsock._send = tcpsock._sslObj.write

Loading…
Cancel
Save