Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_tvdb
This commit is contained in:
@@ -1,31 +1,47 @@
|
||||
from couchpotato.api import api_docs, api_docs_missing, api
|
||||
from couchpotato.core.auth import requires_auth
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.core.helpers.variable import md5, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from sqlalchemy.engine import create_engine
|
||||
from sqlalchemy.orm import scoped_session
|
||||
from sqlalchemy.orm.session import sessionmaker
|
||||
from tornado import template
|
||||
from tornado.web import RequestHandler
|
||||
from tornado.web import RequestHandler, authenticated
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
views = {}
|
||||
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
|
||||
|
||||
|
||||
class BaseHandler(RequestHandler):
|
||||
|
||||
def get_current_user(self):
|
||||
username = Env.setting('username')
|
||||
password = Env.setting('password')
|
||||
|
||||
if username or password:
|
||||
return self.get_secure_cookie('user')
|
||||
else: # Login when no username or password are set
|
||||
return True
|
||||
|
||||
# Main web handler
|
||||
@requires_auth
|
||||
class WebHandler(RequestHandler):
|
||||
class WebHandler(BaseHandler):
|
||||
|
||||
@authenticated
|
||||
def get(self, route, *args, **kwargs):
|
||||
route = route.strip('/')
|
||||
if not views.get(route):
|
||||
page_not_found(self)
|
||||
return
|
||||
self.write(views[route]())
|
||||
|
||||
try:
|
||||
self.write(views[route]())
|
||||
except:
|
||||
log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
|
||||
self.write({'success': False, 'error': 'Failed returning results'})
|
||||
|
||||
def addView(route, func, static = False):
|
||||
views[route] = func
|
||||
@@ -58,16 +74,54 @@ addView('docs', apiDocs)
|
||||
class KeyHandler(RequestHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
api = None
|
||||
|
||||
try:
|
||||
username = Env.setting('username')
|
||||
password = Env.setting('password')
|
||||
|
||||
if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
|
||||
api = Env.setting('api_key')
|
||||
|
||||
self.write({
|
||||
'success': api is not None,
|
||||
'api_key': api
|
||||
})
|
||||
except:
|
||||
log.error('Failed doing key request: %s', (traceback.format_exc()))
|
||||
self.write({'success': False, 'error': 'Failed returning results'})
|
||||
|
||||
|
||||
class LoginHandler(BaseHandler):
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
|
||||
if self.get_current_user():
|
||||
self.redirect(Env.get('web_base'))
|
||||
else:
|
||||
self.write(template_loader.load('login.html').generate(sep = os.sep, fireEvent = fireEvent, Env = Env))
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
|
||||
api = None
|
||||
|
||||
username = Env.setting('username')
|
||||
password = Env.setting('password')
|
||||
|
||||
if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
|
||||
if (self.get_argument('username') == username or not username) and (md5(self.get_argument('password')) == password or not password):
|
||||
api = Env.setting('api_key')
|
||||
|
||||
self.write({
|
||||
'success': api is not None,
|
||||
'api_key': api
|
||||
})
|
||||
if api:
|
||||
remember_me = tryInt(self.get_argument('remember_me', default = 0))
|
||||
self.set_secure_cookie('user', api, expires_days = 30 if remember_me > 0 else None)
|
||||
|
||||
self.redirect(Env.get('web_base'))
|
||||
|
||||
class LogoutHandler(BaseHandler):
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
self.clear_cookie('user')
|
||||
self.redirect('%slogin/' % Env.get('web_base'))
|
||||
|
||||
|
||||
def page_not_found(rh):
|
||||
index_url = Env.get('web_base')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from couchpotato.core.helpers.request import getParams
|
||||
from couchpotato.core.logger import CPLog
|
||||
from functools import wraps
|
||||
from threading import Thread
|
||||
from tornado.gen import coroutine
|
||||
@@ -6,8 +7,12 @@ from tornado.web import RequestHandler, asynchronous
|
||||
import json
|
||||
import threading
|
||||
import tornado
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
api = {}
|
||||
api_locks = {}
|
||||
api_nonblock = {}
|
||||
@@ -41,7 +46,11 @@ class NonBlockHandler(RequestHandler):
|
||||
if self.request.connection.stream.closed():
|
||||
return
|
||||
|
||||
self.write(response)
|
||||
try:
|
||||
self.finish(response)
|
||||
except:
|
||||
log.error('Failed doing nonblock request: %s', (traceback.format_exc()))
|
||||
self.finish({'success': False, 'error': 'Failed returning results'})
|
||||
|
||||
def on_connection_close(self):
|
||||
|
||||
@@ -70,33 +79,43 @@ class ApiHandler(RequestHandler):
|
||||
|
||||
api_locks[route].acquire()
|
||||
|
||||
kwargs = {}
|
||||
for x in self.request.arguments:
|
||||
kwargs[x] = urllib.unquote(self.get_argument(x))
|
||||
try:
|
||||
|
||||
# Split array arguments
|
||||
kwargs = getParams(kwargs)
|
||||
kwargs = {}
|
||||
for x in self.request.arguments:
|
||||
kwargs[x] = urllib.unquote(self.get_argument(x))
|
||||
|
||||
# Remove t random string
|
||||
try: del kwargs['t']
|
||||
except: pass
|
||||
# Split array arguments
|
||||
kwargs = getParams(kwargs)
|
||||
|
||||
# Add async callback handler
|
||||
@run_async
|
||||
def run_handler(callback):
|
||||
result = api[route](**kwargs)
|
||||
callback(result)
|
||||
result = yield tornado.gen.Task(run_handler)
|
||||
# Remove t random string
|
||||
try: del kwargs['t']
|
||||
except: pass
|
||||
|
||||
# Check JSONP callback
|
||||
jsonp_callback = self.get_argument('callback_func', default = None)
|
||||
# Add async callback handler
|
||||
@run_async
|
||||
def run_handler(callback):
|
||||
try:
|
||||
result = api[route](**kwargs)
|
||||
callback(result)
|
||||
except:
|
||||
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
||||
callback({'success': False, 'error': 'Failed returning results'})
|
||||
result = yield tornado.gen.Task(run_handler)
|
||||
|
||||
if jsonp_callback:
|
||||
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||
elif isinstance(result, (tuple)) and result[0] == 'redirect':
|
||||
self.redirect(result[1])
|
||||
else:
|
||||
self.write(result)
|
||||
# Check JSONP callback
|
||||
jsonp_callback = self.get_argument('callback_func', default = None)
|
||||
|
||||
if jsonp_callback:
|
||||
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||
elif isinstance(result, tuple) and result[0] == 'redirect':
|
||||
self.redirect(result[1])
|
||||
else:
|
||||
self.write(result)
|
||||
|
||||
except:
|
||||
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
||||
self.write({'success': False, 'error': 'Failed returning results'})
|
||||
|
||||
api_locks[route].release()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from minify.cssmin import cssmin
|
||||
from minify.jsmin import jsmin
|
||||
from tornado.web import StaticFileHandler
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
@@ -80,7 +81,7 @@ class ClientScript(Plugin):
|
||||
for static_type in self.core_static:
|
||||
for rel_path in self.core_static.get(static_type):
|
||||
file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path)
|
||||
core_url = 'api/%s/static/%s?%s' % (Env.setting('api_key'), rel_path, tryInt(os.path.getmtime(file_path)))
|
||||
core_url = 'static/%s' % rel_path
|
||||
|
||||
if static_type == 'script':
|
||||
self.registerScript(core_url, file_path, position = 'front')
|
||||
@@ -90,6 +91,13 @@ class ClientScript(Plugin):
|
||||
|
||||
def minify(self):
|
||||
|
||||
# Create cache dir
|
||||
cache = Env.get('cache_dir')
|
||||
parent_dir = os.path.join(cache, 'minified')
|
||||
self.makeDir(parent_dir)
|
||||
|
||||
Env.get('app').add_handlers(".*$", [(Env.get('web_base') + 'minified/(.*)', StaticFileHandler, {'path': parent_dir})])
|
||||
|
||||
for file_type in ['style', 'script']:
|
||||
ext = 'js' if file_type is 'script' else 'css'
|
||||
positions = self.paths.get(file_type, {})
|
||||
@@ -100,8 +108,8 @@ class ClientScript(Plugin):
|
||||
def _minify(self, file_type, files, position, out):
|
||||
|
||||
cache = Env.get('cache_dir')
|
||||
out_name = 'minified_' + out
|
||||
out = os.path.join(cache, out_name)
|
||||
out_name = out
|
||||
out = os.path.join(cache, 'minified', out_name)
|
||||
|
||||
raw = []
|
||||
for file_path in files:
|
||||
@@ -111,7 +119,7 @@ class ClientScript(Plugin):
|
||||
data = jsmin(f)
|
||||
else:
|
||||
data = self.prefix(f)
|
||||
data = cssmin(f)
|
||||
data = cssmin(data)
|
||||
data = data.replace('../images/', '../static/images/')
|
||||
data = data.replace('../fonts/', '../static/fonts/')
|
||||
data = data.replace('../../static/', '../static/') # Replace inside plugins
|
||||
@@ -131,7 +139,7 @@ class ClientScript(Plugin):
|
||||
if not self.minified[file_type].get(position):
|
||||
self.minified[file_type][position] = []
|
||||
|
||||
minified_url = 'api/%s/file.cache/%s?%s' % (Env.setting('api_key'), out_name, tryInt(os.path.getmtime(out)))
|
||||
minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out)))
|
||||
self.minified[file_type][position].append(minified_url)
|
||||
|
||||
def getStyles(self, *args, **kwargs):
|
||||
@@ -165,6 +173,8 @@ class ClientScript(Plugin):
|
||||
|
||||
def register(self, api_path, file_path, type, location):
|
||||
|
||||
api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path)))
|
||||
|
||||
if not self.urls[type].get(location):
|
||||
self.urls[type][location] = []
|
||||
self.urls[type][location].append(api_path)
|
||||
|
||||
@@ -132,6 +132,7 @@ class BaseUpdater(Plugin):
|
||||
update_failed = False
|
||||
update_version = None
|
||||
last_check = 0
|
||||
auto_register_static = False
|
||||
|
||||
def doUpdate(self):
|
||||
pass
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.environment import Env
|
||||
import base64
|
||||
|
||||
def check_auth(username, password):
|
||||
return username == Env.setting('username') and password == Env.setting('password')
|
||||
|
||||
def requires_auth(handler_class):
|
||||
|
||||
def wrap_execute(handler_execute):
|
||||
|
||||
def require_basic_auth(handler, kwargs):
|
||||
if Env.setting('username') and Env.setting('password'):
|
||||
|
||||
auth_header = handler.request.headers.get('Authorization')
|
||||
auth_decoded = base64.decodestring(auth_header[6:]) if auth_header else None
|
||||
if auth_decoded:
|
||||
username, password = auth_decoded.split(':', 2)
|
||||
|
||||
if auth_header is None or not auth_header.startswith('Basic ') or (not check_auth(username.decode('latin'), md5(password.decode('latin')))):
|
||||
handler.set_status(401)
|
||||
handler.set_header('WWW-Authenticate', 'Basic realm="CouchPotato Login"')
|
||||
handler._transforms = []
|
||||
handler.finish()
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, transforms, *args, **kwargs):
|
||||
|
||||
if not require_basic_auth(self, kwargs):
|
||||
return False
|
||||
return handler_execute(self, transforms, *args, **kwargs)
|
||||
|
||||
return _execute
|
||||
|
||||
handler_class._execute = wrap_execute(handler_class._execute)
|
||||
|
||||
return handler_class
|
||||
@@ -49,7 +49,10 @@ class Downloader(Provider):
|
||||
|
||||
return []
|
||||
|
||||
def _download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
def _download(self, data = None, movie = None, manual = False, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
if self.isDisabled(manual, data):
|
||||
return
|
||||
return self.download(data = data, movie = movie, filedata = filedata)
|
||||
@@ -119,7 +122,7 @@ class Downloader(Provider):
|
||||
except:
|
||||
log.debug('Torrent hash "%s" wasn\'t found on: %s', (torrent_hash, source))
|
||||
|
||||
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
||||
log.error('Failed converting magnet url to torrent: %s', torrent_hash)
|
||||
return False
|
||||
|
||||
def downloadReturnId(self, download_id):
|
||||
@@ -128,18 +131,24 @@ class Downloader(Provider):
|
||||
'id': download_id
|
||||
}
|
||||
|
||||
def isDisabled(self, manual, data):
|
||||
def isDisabled(self, manual = False, data = None):
|
||||
if not data: data = {}
|
||||
|
||||
return not self.isEnabled(manual, data)
|
||||
|
||||
def _isEnabled(self, manual, data = {}):
|
||||
def _isEnabled(self, manual, data = None):
|
||||
if not data: data = {}
|
||||
|
||||
if not self.isEnabled(manual, data):
|
||||
return
|
||||
return True
|
||||
|
||||
def isEnabled(self, manual, data = {}):
|
||||
def isEnabled(self, manual = False, data = None):
|
||||
if not data: data = {}
|
||||
|
||||
d_manual = self.conf('manual', default = False)
|
||||
return super(Downloader, self).isEnabled() and \
|
||||
((d_manual and manual) or (d_manual is False)) and \
|
||||
(d_manual and manual or d_manual is False) and \
|
||||
(not data or self.isCorrectProtocol(data.get('protocol')))
|
||||
|
||||
def _pause(self, item, pause = True):
|
||||
|
||||
@@ -12,7 +12,9 @@ class Blackhole(Downloader):
|
||||
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
directory = self.conf('directory')
|
||||
if not directory or not os.path.isdir(directory):
|
||||
@@ -62,7 +64,8 @@ class Blackhole(Downloader):
|
||||
else:
|
||||
return ['nzb']
|
||||
|
||||
def isEnabled(self, manual, data = {}):
|
||||
def isEnabled(self, manual = False, data = None):
|
||||
if not data: data = {}
|
||||
for_protocol = ['both']
|
||||
if data and 'torrent' in data.get('protocol'):
|
||||
for_protocol.append('torrent')
|
||||
|
||||
@@ -54,7 +54,7 @@ class Deluge(Downloader):
|
||||
|
||||
if self.conf('completed_directory'):
|
||||
if os.path.isdir(self.conf('completed_directory')):
|
||||
options['move_completed'] = 1
|
||||
options['move_completed'] = 1
|
||||
options['move_completed_path'] = self.conf('completed_directory')
|
||||
else:
|
||||
log.error('Download directory from Deluge settings: %s doesn\'t exist', self.conf('directory'))
|
||||
@@ -96,7 +96,7 @@ class Deluge(Downloader):
|
||||
|
||||
queue = self.drpc.get_alltorrents()
|
||||
|
||||
if not (queue):
|
||||
if not queue:
|
||||
log.debug('Nothing in queue or error')
|
||||
return False
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ class NZBGet(Downloader):
|
||||
|
||||
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
if not filedata:
|
||||
log.error('Unable to get NZB file: %s', traceback.format_exc())
|
||||
@@ -140,7 +142,7 @@ class NZBGet(Downloader):
|
||||
statuses.append({
|
||||
'id': nzb_id,
|
||||
'name': item['NZBFilename'],
|
||||
'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
|
||||
'status': 'completed' if item['ParStatus'] in ['SUCCESS','NONE'] and item['ScriptStatus'] in ['SUCCESS','NONE'] else 'failed',
|
||||
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
|
||||
'timeleft': str(timedelta(seconds = 0)),
|
||||
'folder': ss(item['DestDir'])
|
||||
@@ -172,11 +174,16 @@ class NZBGet(Downloader):
|
||||
|
||||
try:
|
||||
history = rpc.history()
|
||||
nzb_id = None
|
||||
path = None
|
||||
|
||||
for hist in history:
|
||||
if hist['Parameters'] and hist['Parameters']['couchpotato'] and hist['Parameters']['couchpotato'] == item['id']:
|
||||
nzb_id = hist['ID']
|
||||
path = hist['DestDir']
|
||||
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
|
||||
for param in hist['Parameters']:
|
||||
if param['Name'] == 'couchpotato' and param['Value'] == item['id']:
|
||||
nzb_id = hist['ID']
|
||||
path = hist['DestDir']
|
||||
|
||||
if nzb_id and path and rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
|
||||
shutil.rmtree(path, True)
|
||||
except:
|
||||
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||
|
||||
@@ -23,7 +23,9 @@ class NZBVortex(Downloader):
|
||||
api_level = None
|
||||
session_id = None
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
# Send the nzb
|
||||
try:
|
||||
@@ -97,9 +99,10 @@ class NZBVortex(Downloader):
|
||||
return False
|
||||
|
||||
|
||||
def call(self, call, parameters = {}, repeat = False, auth = True, *args, **kwargs):
|
||||
def call(self, call, parameters = None, repeat = False, auth = True, *args, **kwargs):
|
||||
|
||||
# Login first
|
||||
if not parameters: parameters = {}
|
||||
if not self.session_id and auth:
|
||||
self.login()
|
||||
|
||||
@@ -122,7 +125,7 @@ class NZBVortex(Downloader):
|
||||
# Try login and do again
|
||||
if not repeat:
|
||||
self.login()
|
||||
return self.call(call, parameters = parameters, repeat = True, *args, **kwargs)
|
||||
return self.call(call, parameters = parameters, repeat = True, **kwargs)
|
||||
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
except:
|
||||
@@ -148,7 +151,8 @@ class NZBVortex(Downloader):
|
||||
|
||||
return self.api_level
|
||||
|
||||
def isEnabled(self, manual, data):
|
||||
def isEnabled(self, manual = False, data = None):
|
||||
if not data: data = {}
|
||||
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ class Pneumatic(Downloader):
|
||||
protocol = ['nzb']
|
||||
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
directory = self.conf('directory')
|
||||
if not directory or not os.path.isdir(directory):
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
from base64 import b16encode, b32decode
|
||||
from datetime import timedelta
|
||||
from hashlib import sha1
|
||||
import shutil
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
from rtorrent.err import MethodError
|
||||
|
||||
from bencode import bencode, bdecode
|
||||
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
from couchpotato.core.logger import CPLog
|
||||
from datetime import timedelta
|
||||
from hashlib import sha1
|
||||
from rtorrent import RTorrent
|
||||
|
||||
from rtorrent.err import MethodError
|
||||
import shutil
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class rTorrent(Downloader):
|
||||
|
||||
protocol = ['torrent', 'torrent_magnet']
|
||||
rt = None
|
||||
|
||||
@@ -194,7 +193,7 @@ class rTorrent(Downloader):
|
||||
if torrent is None:
|
||||
return False
|
||||
|
||||
torrent.erase() # just removes the torrent, doesn't delete data
|
||||
torrent.erase() # just removes the torrent, doesn't delete data
|
||||
|
||||
if delete_files:
|
||||
shutil.rmtree(item['folder'], True)
|
||||
|
||||
@@ -15,7 +15,9 @@ class Sabnzbd(Downloader):
|
||||
|
||||
protocol = ['nzb']
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||
|
||||
@@ -26,9 +28,10 @@ class Sabnzbd(Downloader):
|
||||
'priority': self.conf('priority'),
|
||||
}
|
||||
|
||||
nzb_filename = None
|
||||
if filedata:
|
||||
if len(filedata) < 50:
|
||||
log.error('No proper nzb available: %s', (filedata))
|
||||
log.error('No proper nzb available: %s', filedata)
|
||||
return False
|
||||
|
||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||
@@ -38,7 +41,7 @@ class Sabnzbd(Downloader):
|
||||
req_params['name'] = data.get('url')
|
||||
|
||||
try:
|
||||
if req_params.get('mode') is 'addfile':
|
||||
if nzb_filename and req_params.get('mode') is 'addfile':
|
||||
sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
|
||||
else:
|
||||
sab_data = self.call(req_params)
|
||||
|
||||
@@ -12,7 +12,9 @@ class Synology(Downloader):
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
log = CPLog(__name__)
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
response = False
|
||||
log.error('Sending "%s" (%s) to Synology.', (data['name'], data['protocol']))
|
||||
@@ -49,7 +51,9 @@ class Synology(Downloader):
|
||||
else:
|
||||
return ['nzb']
|
||||
|
||||
def isEnabled(self, manual, data = {}):
|
||||
def isEnabled(self, manual = False, data = None):
|
||||
if not data: data = {}
|
||||
|
||||
for_protocol = ['both']
|
||||
if data and 'torrent' in data.get('protocol'):
|
||||
for_protocol.append('torrent')
|
||||
@@ -61,7 +65,7 @@ class Synology(Downloader):
|
||||
|
||||
class SynologyRPC(object):
|
||||
|
||||
'''SynologyRPC lite library'''
|
||||
"""SynologyRPC lite library"""
|
||||
|
||||
def __init__(self, host = 'localhost', port = 5000, username = None, password = None):
|
||||
|
||||
@@ -98,7 +102,7 @@ class SynologyRPC(object):
|
||||
req = requests.post(url, data = args, files = files)
|
||||
req.raise_for_status()
|
||||
response = json.loads(req.text)
|
||||
if response['success'] == True:
|
||||
if response['success']:
|
||||
log.info('Synology action successfull')
|
||||
return response
|
||||
except requests.ConnectionError, err:
|
||||
@@ -111,11 +115,11 @@ class SynologyRPC(object):
|
||||
return response
|
||||
|
||||
def create_task(self, url = None, filename = None, filedata = None):
|
||||
''' Creates new download task in Synology DownloadStation. Either specify
|
||||
""" Creates new download task in Synology DownloadStation. Either specify
|
||||
url or pair (filename, filedata).
|
||||
|
||||
Returns True if task was created, False otherwise
|
||||
'''
|
||||
"""
|
||||
result = False
|
||||
# login
|
||||
if self._login():
|
||||
|
||||
@@ -44,8 +44,9 @@ class Transmission(Downloader):
|
||||
return False
|
||||
|
||||
# Set parameters for adding torrent
|
||||
params = {}
|
||||
params['paused'] = self.conf('paused', default = False)
|
||||
params = {
|
||||
'paused': self.conf('paused', default = False)
|
||||
}
|
||||
|
||||
if self.conf('directory'):
|
||||
if os.path.isdir(self.conf('directory')):
|
||||
@@ -135,11 +136,11 @@ class Transmission(Downloader):
|
||||
|
||||
def removeFailed(self, item):
|
||||
log.info('%s failed downloading, deleting...', item['name'])
|
||||
return self.trpc.remove_torrent(self, item['hashString'], True)
|
||||
return self.trpc.remove_torrent(item['hashString'], True)
|
||||
|
||||
def processComplete(self, item, delete_files = False):
|
||||
log.debug('Requesting Transmission to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
|
||||
return self.trpc.remove_torrent(self, item['hashString'], delete_files)
|
||||
return self.trpc.remove_torrent(item['hashString'], delete_files)
|
||||
|
||||
class TransmissionRPC(object):
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from base64 import b16encode, b32decode
|
||||
from bencode import bencode, bdecode
|
||||
from bencode import bencode as benc, bdecode
|
||||
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||
from couchpotato.core.helpers.encoding import isInt, ss
|
||||
from couchpotato.core.helpers.variable import tryInt, tryFloat
|
||||
@@ -36,7 +36,9 @@ class uTorrent(Downloader):
|
||||
|
||||
return self.utorrent_api
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('protocol')))
|
||||
|
||||
@@ -74,7 +76,7 @@ class uTorrent(Downloader):
|
||||
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
|
||||
else:
|
||||
info = bdecode(filedata)["info"]
|
||||
torrent_hash = sha1(bencode(info)).hexdigest().upper()
|
||||
torrent_hash = sha1(benc(info)).hexdigest().upper()
|
||||
torrent_filename = self.createFileName(data, filedata, movie)
|
||||
|
||||
if data.get('seed_ratio'):
|
||||
@@ -280,7 +282,9 @@ class uTorrentAPI(object):
|
||||
|
||||
return settings_dict
|
||||
|
||||
def set_settings(self, settings_dict = {}):
|
||||
def set_settings(self, settings_dict = None):
|
||||
if not settings_dict: settings_dict = {}
|
||||
|
||||
for key in settings_dict:
|
||||
if isinstance(settings_dict[key], bool):
|
||||
settings_dict[key] = 1 if settings_dict[key] else 0
|
||||
|
||||
@@ -21,9 +21,11 @@ def addEvent(name, handler, priority = 100):
|
||||
|
||||
def createHandle(*args, **kwargs):
|
||||
|
||||
h = None
|
||||
try:
|
||||
# Open handler
|
||||
has_parent = hasattr(handler, 'im_self')
|
||||
parent = None
|
||||
if has_parent:
|
||||
parent = handler.im_self
|
||||
bc = hasattr(parent, 'beforeCall')
|
||||
@@ -33,7 +35,7 @@ def addEvent(name, handler, priority = 100):
|
||||
h = runHandler(name, handler, *args, **kwargs)
|
||||
|
||||
# Close handler
|
||||
if has_parent:
|
||||
if parent and has_parent:
|
||||
ac = hasattr(parent, 'afterCall')
|
||||
if ac: parent.afterCall(handler)
|
||||
except:
|
||||
@@ -53,11 +55,6 @@ def removeEvent(name, handler):
|
||||
def fireEvent(name, *args, **kwargs):
|
||||
if not events.has_key(name): return
|
||||
|
||||
e = Event(name = name, threads = 10, asynch = kwargs.get('async', False), exc_info = True, traceback = True, lock = threading.RLock())
|
||||
|
||||
for event in events[name]:
|
||||
e.handle(event['handler'], priority = event['priority'])
|
||||
|
||||
#log.debug('Firing event %s', name)
|
||||
try:
|
||||
|
||||
@@ -67,7 +64,6 @@ def fireEvent(name, *args, **kwargs):
|
||||
'single': False, # Return single handler
|
||||
'merge': False, # Merge items
|
||||
'in_order': False, # Fire them in specific order, waits for the other to finish
|
||||
'async': False
|
||||
}
|
||||
|
||||
# Do options
|
||||
@@ -78,12 +74,32 @@ def fireEvent(name, *args, **kwargs):
|
||||
options[x] = val
|
||||
except: pass
|
||||
|
||||
# Make sure only 1 event is fired at a time when order is wanted
|
||||
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
|
||||
kwargs['event_return_on_result'] = options['single']
|
||||
if len(events[name]) == 1:
|
||||
|
||||
# Fire
|
||||
result = e(*args, **kwargs)
|
||||
single = None
|
||||
try:
|
||||
single = events[name][0]['handler'](*args, **kwargs)
|
||||
except:
|
||||
log.error('Failed running single event: %s', traceback.format_exc())
|
||||
|
||||
# Don't load thread for single event
|
||||
result = {
|
||||
'single': (single is not None, single),
|
||||
}
|
||||
|
||||
else:
|
||||
|
||||
e = Event(name = name, threads = 10, exc_info = True, traceback = True, lock = threading.RLock())
|
||||
|
||||
for event in events[name]:
|
||||
e.handle(event['handler'], priority = event['priority'])
|
||||
|
||||
# Make sure only 1 event is fired at a time when order is wanted
|
||||
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
|
||||
kwargs['event_return_on_result'] = options['single']
|
||||
|
||||
# Fire
|
||||
result = e(*args, **kwargs)
|
||||
|
||||
if options['single'] and not options['merge']:
|
||||
results = None
|
||||
|
||||
@@ -63,7 +63,7 @@ def stripAccents(s):
|
||||
|
||||
def tryUrlencode(s):
|
||||
new = u''
|
||||
if isinstance(s, (dict)):
|
||||
if isinstance(s, dict):
|
||||
for key, value in s.iteritems():
|
||||
new += u'&%s=%s' % (key, tryUrlencode(value))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ def getParams(params):
|
||||
|
||||
reg = re.compile('^[a-z0-9_\.]+$')
|
||||
|
||||
current = temp = {}
|
||||
temp = {}
|
||||
for param, value in sorted(params.iteritems()):
|
||||
|
||||
nest = re.split("([\[\]]+)", param)
|
||||
|
||||
@@ -6,7 +6,7 @@ log = CPLog(__name__)
|
||||
class RSS(object):
|
||||
|
||||
def getTextElements(self, xml, path):
|
||||
''' Find elements and return tree'''
|
||||
""" Find elements and return tree"""
|
||||
|
||||
textelements = []
|
||||
try:
|
||||
@@ -28,7 +28,7 @@ class RSS(object):
|
||||
return elements
|
||||
|
||||
def getElement(self, xml, path):
|
||||
''' Find element and return text'''
|
||||
""" Find element and return text"""
|
||||
|
||||
try:
|
||||
return xml.find(path)
|
||||
@@ -36,7 +36,7 @@ class RSS(object):
|
||||
return
|
||||
|
||||
def getTextElement(self, xml, path):
|
||||
''' Find element and return text'''
|
||||
""" Find element and return text"""
|
||||
|
||||
try:
|
||||
return xml.find(path).text
|
||||
|
||||
@@ -106,6 +106,11 @@ def md5(text):
|
||||
def sha1(text):
|
||||
return hashlib.sha1(text).hexdigest()
|
||||
|
||||
def isLocalIP(ip):
|
||||
ip = ip.lstrip('htps:/')
|
||||
regex = '/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1)$/'
|
||||
return re.search(regex, ip) is not None or 'localhost' in ip or ip[:4] == '127.'
|
||||
|
||||
def getExt(filename):
|
||||
return os.path.splitext(filename)[1][1:]
|
||||
|
||||
@@ -113,8 +118,8 @@ def cleanHost(host):
|
||||
if not host.startswith(('http://', 'https://')):
|
||||
host = 'http://' + host
|
||||
|
||||
if not host.endswith('/'):
|
||||
host += '/'
|
||||
host = host.rstrip('/')
|
||||
host += '/'
|
||||
|
||||
return host
|
||||
|
||||
@@ -174,11 +179,11 @@ def getTitle(library_dict):
|
||||
|
||||
def possibleTitles(raw_title):
|
||||
|
||||
titles = []
|
||||
|
||||
titles.append(toSafeString(raw_title).lower())
|
||||
titles.append(raw_title.lower())
|
||||
titles.append(simplifyString(raw_title))
|
||||
titles = [
|
||||
toSafeString(raw_title).lower(),
|
||||
raw_title.lower(),
|
||||
simplifyString(raw_title)
|
||||
]
|
||||
|
||||
# replace some chars
|
||||
new_title = raw_title.replace('&', 'and')
|
||||
|
||||
@@ -66,7 +66,7 @@ class Loader(object):
|
||||
self.loadPlugins(m, plugin.get('name'))
|
||||
except ImportError as e:
|
||||
# todo:: subclass ImportError for missing requirements.
|
||||
if (e.message.lower().startswith("missing")):
|
||||
if e.message.lower().startswith("missing"):
|
||||
log.error(e.message)
|
||||
pass
|
||||
# todo:: this needs to be more descriptive.
|
||||
@@ -91,7 +91,7 @@ class Loader(object):
|
||||
|
||||
for cur_file in glob.glob(os.path.join(dir_name, '*')):
|
||||
name = os.path.basename(cur_file)
|
||||
if os.path.isdir(os.path.join(dir_name, name)) and name != 'static':
|
||||
if os.path.isdir(os.path.join(dir_name, name)) and name != 'static' and os.path.isfile(os.path.join(cur_file, '__init__.py')):
|
||||
module_name = '%s.%s' % (module, name)
|
||||
self.addModule(priority, plugin_type, module_name, name)
|
||||
|
||||
@@ -122,7 +122,7 @@ class Loader(object):
|
||||
try:
|
||||
module.start()
|
||||
return True
|
||||
except Exception, e:
|
||||
except:
|
||||
log.error('Failed loading plugin "%s": %s', (module.__file__, traceback.format_exc()))
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .main import Searcher
|
||||
import random
|
||||
|
||||
def start():
|
||||
return Searcher()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
@@ -19,12 +18,10 @@ class SearcherBase(Plugin):
|
||||
|
||||
self.initCron()
|
||||
|
||||
|
||||
""" Set the searcher cronjob
|
||||
Make sure to reset cronjob after setting has changed
|
||||
|
||||
"""
|
||||
def initCron(self):
|
||||
""" Set the searcher cronjob
|
||||
Make sure to reset cronjob after setting has changed
|
||||
"""
|
||||
|
||||
_type = self.getType()
|
||||
|
||||
@@ -38,14 +35,11 @@ class SearcherBase(Plugin):
|
||||
addEvent('setting.save.%s_searcher.cron_hour.after' % _type, setCrons)
|
||||
addEvent('setting.save.%s_searcher.cron_minute.after' % _type, setCrons)
|
||||
|
||||
|
||||
""" Return progress of current searcher
|
||||
|
||||
"""
|
||||
def getProgress(self, **kwargs):
|
||||
""" Return progress of current searcher"""
|
||||
|
||||
progress = {}
|
||||
progress[self.getType()] = self.in_progress
|
||||
progress = {
|
||||
self.getType(): self.in_progress
|
||||
}
|
||||
|
||||
return progress
|
||||
|
||||
|
||||
@@ -51,6 +51,10 @@ class Searcher(SearcherBase):
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
if not data.get('protocol'):
|
||||
data['protocol'] = data['type']
|
||||
data['type'] = 'movie'
|
||||
|
||||
# Test to see if any downloaders are enabled for this type
|
||||
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
|
||||
|
||||
@@ -122,7 +126,7 @@ class Searcher(SearcherBase):
|
||||
|
||||
return True
|
||||
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('protocol', '')))
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('protocol')))
|
||||
|
||||
return False
|
||||
|
||||
@@ -146,7 +150,8 @@ class Searcher(SearcherBase):
|
||||
|
||||
return search_protocols
|
||||
|
||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}):
|
||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
|
||||
if not preferred_quality: preferred_quality = {}
|
||||
|
||||
name = nzb['name']
|
||||
size = nzb.get('size', 0)
|
||||
@@ -173,10 +178,10 @@ class Searcher(SearcherBase):
|
||||
year_name = fireEvent('scanner.name_year', name, single = True)
|
||||
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
|
||||
if size > 3000: # Assume dvdr
|
||||
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size))
|
||||
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
|
||||
found['dvdr'] = True
|
||||
else: # Assume dvdrip
|
||||
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size))
|
||||
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', size)
|
||||
found['dvdrip'] = True
|
||||
|
||||
# Allow other qualities
|
||||
@@ -191,6 +196,7 @@ class Searcher(SearcherBase):
|
||||
if not isinstance(haystack, (list, tuple, set)):
|
||||
haystack = [haystack]
|
||||
|
||||
year_name = {}
|
||||
for string in haystack:
|
||||
|
||||
year_name = fireEvent('scanner.name_year', string, single = True)
|
||||
|
||||
@@ -2,7 +2,8 @@ from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt, \
|
||||
mergeDicts
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie import MovieTypeBase
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Media, \
|
||||
@@ -167,19 +168,33 @@ class MovieBase(MovieTypeBase):
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
# query movie ids
|
||||
q = db.query(Media) \
|
||||
.outerjoin(Media.releases, Media.library, Library.titles) \
|
||||
.filter(LibraryTitle.default == True) \
|
||||
.with_entities(Media.id) \
|
||||
.group_by(Media.id)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
q = q.filter(or_(*[Media.status.has(identifier = s) for s in status]))
|
||||
statuses = fireEvent('status.get', status, single = len(status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
|
||||
q = q.join(Media.releases)
|
||||
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Release.status_id.in_(statuses))
|
||||
|
||||
# Only join when searching / ordering
|
||||
if starts_with or search or order != 'release_order':
|
||||
q = q.join(Media.library, Library.titles) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
# Add search filters
|
||||
filter_or = []
|
||||
if starts_with:
|
||||
starts_with = toUnicode(starts_with.lower())
|
||||
@@ -194,48 +209,79 @@ class MovieBase(MovieTypeBase):
|
||||
if search:
|
||||
filter_or.append(LibraryTitle.simple_title.like('%%' + search + '%%'))
|
||||
|
||||
if filter_or:
|
||||
if len(filter_or) > 0:
|
||||
q = q.filter(or_(*filter_or))
|
||||
|
||||
total_count = q.count()
|
||||
if total_count == 0:
|
||||
return 0, []
|
||||
|
||||
if order == 'release_order':
|
||||
q = q.order_by(desc(Release.last_edit))
|
||||
else:
|
||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||
|
||||
q = q.subquery()
|
||||
q2 = db.query(Media).join((q, q.c.id == Media.id)) \
|
||||
.options(joinedload_all('releases.files')) \
|
||||
.options(joinedload_all('releases.info')) \
|
||||
.options(joinedload_all('profile.types')) \
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q = q.limit(limit).offset(offset)
|
||||
|
||||
# Get all movie_ids in sorted order
|
||||
movie_ids = [m.id for m in q.all()]
|
||||
|
||||
# List release statuses
|
||||
releases = db.query(Release) \
|
||||
.filter(Release.movie_id.in_(movie_ids)) \
|
||||
.all()
|
||||
|
||||
release_statuses = dict((m, set()) for m in movie_ids)
|
||||
releases_count = dict((m, 0) for m in movie_ids)
|
||||
for release in releases:
|
||||
release_statuses[release.movie_id].add('%d,%d' % (release.status_id, release.quality_id))
|
||||
releases_count[release.movie_id] += 1
|
||||
|
||||
# Get main movie data
|
||||
q2 = db.query(Media) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('status')) \
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q2 = q2.limit(limit).offset(offset)
|
||||
q2 = q2.filter(Media.id.in_(movie_ids))
|
||||
|
||||
results = q2.all()
|
||||
movies = []
|
||||
|
||||
# Create dict by movie id
|
||||
movie_dict = {}
|
||||
for movie in results:
|
||||
movies.append(movie.to_dict({
|
||||
'profile': {'types': {}},
|
||||
'releases': {'files':{}, 'info': {}},
|
||||
movie_dict[movie.id] = movie
|
||||
|
||||
# List movies based on movie_ids order
|
||||
movies = []
|
||||
for movie_id in movie_ids:
|
||||
|
||||
releases = []
|
||||
for r in release_statuses.get(movie_id):
|
||||
x = splitString(r)
|
||||
releases.append({'status_id': x[0], 'quality_id': x[1]})
|
||||
|
||||
# Merge releases with movie dict
|
||||
movies.append(mergeDicts(movie_dict[movie_id].to_dict({
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
}), {
|
||||
'releases': releases,
|
||||
'releases_count': releases_count.get(movie_id),
|
||||
}))
|
||||
|
||||
db.expire_all()
|
||||
return (total_count, movies)
|
||||
return total_count, movies
|
||||
|
||||
def availableChars(self, status = None, release_status = None):
|
||||
|
||||
chars = ''
|
||||
status = status or []
|
||||
release_status = release_status or []
|
||||
|
||||
db = get_session()
|
||||
|
||||
@@ -245,38 +291,53 @@ class MovieBase(MovieTypeBase):
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
q = db.query(Media) \
|
||||
.outerjoin(Media.releases, Media.library, Library.titles, Media.status) \
|
||||
.options(joinedload_all('library.titles'))
|
||||
q = db.query(Media)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
q = q.filter(or_(*[Media.status.has(identifier = s) for s in status]))
|
||||
statuses = fireEvent('status.get', status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
|
||||
|
||||
results = q.all()
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
for movie in results:
|
||||
if movie.library.titles:
|
||||
char = movie.library.titles[0].simple_title[0]
|
||||
q = q.join(Media.releases) \
|
||||
.filter(Release.status_id.in_(statuses))
|
||||
|
||||
q = q.join(Library, LibraryTitle) \
|
||||
.with_entities(LibraryTitle.simple_title) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
titles = q.all()
|
||||
|
||||
chars = set()
|
||||
for title in titles:
|
||||
try:
|
||||
char = title[0][0]
|
||||
char = char if char in ascii_lowercase else '#'
|
||||
if char not in chars:
|
||||
chars += str(char)
|
||||
chars.add(str(char))
|
||||
except:
|
||||
log.error('Failed getting title for %s', title.libraries_id)
|
||||
|
||||
if len(chars) == 25:
|
||||
break
|
||||
|
||||
db.expire_all()
|
||||
return ''.join(sorted(chars, key = str.lower))
|
||||
return ''.join(sorted(chars))
|
||||
|
||||
def listView(self, **kwargs):
|
||||
|
||||
status = splitString(kwargs.get('status', None))
|
||||
release_status = splitString(kwargs.get('release_status', None))
|
||||
limit_offset = kwargs.get('limit_offset', None)
|
||||
starts_with = kwargs.get('starts_with', None)
|
||||
search = kwargs.get('search', None)
|
||||
order = kwargs.get('order', None)
|
||||
status = splitString(kwargs.get('status'))
|
||||
release_status = splitString(kwargs.get('release_status'))
|
||||
limit_offset = kwargs.get('limit_offset')
|
||||
starts_with = kwargs.get('starts_with')
|
||||
search = kwargs.get('search')
|
||||
order = kwargs.get('order')
|
||||
|
||||
total_movies, movies = self.list(
|
||||
status = status,
|
||||
@@ -347,7 +408,9 @@ class MovieBase(MovieTypeBase):
|
||||
'movies': movies,
|
||||
}
|
||||
|
||||
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||
def add(self, params = None, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||
if not params: params = {}
|
||||
|
||||
if not params.get('identifier'):
|
||||
msg = 'Can\'t add movie without imdb identifier.'
|
||||
log.error(msg)
|
||||
@@ -372,7 +435,7 @@ class MovieBase(MovieTypeBase):
|
||||
fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True)
|
||||
|
||||
default_profile = fireEvent('profile.default', single = True)
|
||||
cat_id = params.get('category_id', None)
|
||||
cat_id = params.get('category_id')
|
||||
|
||||
db = get_session()
|
||||
m = db.query(Media).filter_by(library_id = library.get('id')).first()
|
||||
@@ -463,7 +526,7 @@ class MovieBase(MovieTypeBase):
|
||||
|
||||
m.profile_id = kwargs.get('profile_id')
|
||||
|
||||
cat_id = kwargs.get('category_id', None)
|
||||
cat_id = kwargs.get('category_id')
|
||||
if cat_id is not None:
|
||||
m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
|
||||
|
||||
@@ -518,7 +581,7 @@ class MovieBase(MovieTypeBase):
|
||||
total_deleted = 0
|
||||
new_movie_status = None
|
||||
for release in movie.releases:
|
||||
if delete_from in ['wanted', 'snatched']:
|
||||
if delete_from in ['wanted', 'snatched', 'late']:
|
||||
if release.status_id != done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
@@ -559,7 +622,7 @@ class MovieBase(MovieTypeBase):
|
||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||
return False
|
||||
|
||||
log.debug('Changing status for %s', (m.library.titles[0].title))
|
||||
log.debug('Changing status for %s', m.library.titles[0].title)
|
||||
if not m.profile:
|
||||
m.status_id = done_status.get('id')
|
||||
else:
|
||||
|
||||
@@ -273,8 +273,25 @@ var MovieList = new Class({
|
||||
})
|
||||
).addClass('search');
|
||||
|
||||
var available_chars;
|
||||
self.filter_menu.addEvent('open', function(){
|
||||
self.navigation_search_input.focus();
|
||||
|
||||
// Get available chars and highlight
|
||||
if(!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible()))
|
||||
Api.request('movie.available_chars', {
|
||||
'data': Object.merge({
|
||||
'status': self.options.status
|
||||
}, self.filter),
|
||||
'onSuccess': function(json){
|
||||
available_chars = json.chars
|
||||
|
||||
json.chars.split('').each(function(c){
|
||||
self.letters[c.capitalize()].addClass('available')
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.filter_menu.addLink(
|
||||
@@ -311,21 +328,6 @@ var MovieList = new Class({
|
||||
}).inject(self.navigation_alpha);
|
||||
});
|
||||
|
||||
// Get available chars and highlight
|
||||
if(self.navigation.isDisplayed() || self.navigation.isVisible())
|
||||
Api.request('movie.available_chars', {
|
||||
'data': Object.merge({
|
||||
'status': self.options.status
|
||||
}, self.filter),
|
||||
'onSuccess': function(json){
|
||||
|
||||
json.chars.split('').each(function(c){
|
||||
self.letters[c.capitalize()].addClass('available')
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Add menu or hide
|
||||
if (self.options.menu.length > 0)
|
||||
self.options.menu.each(function(menu_item){
|
||||
@@ -566,7 +568,7 @@ var MovieList = new Class({
|
||||
}
|
||||
|
||||
self.store(json.movies);
|
||||
self.addMovies(json.movies, json.total);
|
||||
self.addMovies(json.movies, json.total || json.movies.length);
|
||||
if(self.scrollspy) {
|
||||
self.load_more.set('text', 'load more movies');
|
||||
self.scrollspy.start();
|
||||
|
||||
@@ -124,6 +124,46 @@ MA.Release = new Class({
|
||||
else
|
||||
self.showHelper();
|
||||
|
||||
App.addEvent('movie.searcher.ended.'+self.movie.data.id, function(notification){
|
||||
self.releases = null;
|
||||
if(self.options_container){
|
||||
self.options_container.destroy();
|
||||
self.options_container = null;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
show: function(e){
|
||||
var self = this;
|
||||
if(e)
|
||||
(e).preventDefault();
|
||||
|
||||
if(self.releases)
|
||||
self.createReleases();
|
||||
else {
|
||||
|
||||
self.movie.busy(true);
|
||||
|
||||
Api.request('release.for_movie', {
|
||||
'data': {
|
||||
'id': self.movie.data.id
|
||||
},
|
||||
'onComplete': function(json){
|
||||
self.movie.busy(false, 1);
|
||||
|
||||
if(json && json.releases){
|
||||
self.releases = json.releases;
|
||||
self.createReleases();
|
||||
}
|
||||
else
|
||||
alert('Something went wrong, check the logs.');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
createReleases: function(){
|
||||
@@ -145,7 +185,7 @@ MA.Release = new Class({
|
||||
new Element('span.provider', {'text': 'Provider'})
|
||||
).inject(self.release_container)
|
||||
|
||||
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
||||
self.releases.each(function(release){
|
||||
|
||||
var status = Status.get(release.status_id),
|
||||
quality = Quality.getProfile(release.quality_id) || {},
|
||||
@@ -211,13 +251,11 @@ MA.Release = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
if(self.last_release){
|
||||
if(self.last_release)
|
||||
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
|
||||
}
|
||||
|
||||
if(self.next_release){
|
||||
if(self.next_release)
|
||||
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
|
||||
}
|
||||
|
||||
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
|
||||
|
||||
@@ -230,7 +268,9 @@ MA.Release = new Class({
|
||||
self.last_release ? new Element('a.button.orange', {
|
||||
'text': 'the same release again',
|
||||
'events': {
|
||||
'click': self.trySameRelease.bind(self)
|
||||
'click': function(){
|
||||
self.download(self.last_release);
|
||||
}
|
||||
}
|
||||
}) : null,
|
||||
self.next_release && self.last_release ? new Element('span.or', {
|
||||
@@ -239,7 +279,9 @@ MA.Release = new Class({
|
||||
self.next_release ? [new Element('a.button.green', {
|
||||
'text': self.last_release ? 'another release' : 'the best release',
|
||||
'events': {
|
||||
'click': self.tryNextRelease.bind(self)
|
||||
'click': function(){
|
||||
self.download(self.next_release);
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('span.or', {
|
||||
@@ -248,18 +290,15 @@ MA.Release = new Class({
|
||||
)
|
||||
}
|
||||
|
||||
self.last_release = null;
|
||||
self.next_release = null;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
show: function(e){
|
||||
var self = this;
|
||||
if(e)
|
||||
(e).preventDefault();
|
||||
|
||||
self.createReleases();
|
||||
// Show it
|
||||
self.options_container.inject(self.movie, 'top');
|
||||
self.movie.slide('in', self.options_container);
|
||||
|
||||
},
|
||||
|
||||
showHelper: function(e){
|
||||
@@ -267,15 +306,29 @@ MA.Release = new Class({
|
||||
if(e)
|
||||
(e).preventDefault();
|
||||
|
||||
self.createReleases();
|
||||
var has_available = false,
|
||||
has_snatched = false;
|
||||
|
||||
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
|
||||
self.movie.data.releases.each(function(release){
|
||||
if(has_available && has_snatched) return;
|
||||
|
||||
var status = Status.get(release.status_id);
|
||||
|
||||
if(['snatched', 'downloaded', 'seeding'].contains(status.identifier))
|
||||
has_snatched = true;
|
||||
|
||||
if(['available'].contains(status.identifier))
|
||||
has_available = true;
|
||||
|
||||
});
|
||||
|
||||
if(has_available || has_snatched){
|
||||
|
||||
self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container);
|
||||
|
||||
self.trynext_container.adopt(
|
||||
self.next_release ? [new Element('a.icon2.readd', {
|
||||
'text': self.last_release ? 'Download another release' : 'Download the best release',
|
||||
has_available ? [new Element('a.icon2.readd', {
|
||||
'text': has_snatched ? 'Download another release' : 'Download the best release',
|
||||
'events': {
|
||||
'click': self.tryNextRelease.bind(self)
|
||||
}
|
||||
@@ -291,24 +344,7 @@ MA.Release = new Class({
|
||||
new Element('a.icon2.completed', {
|
||||
'text': 'mark this movie done',
|
||||
'events': {
|
||||
'click': function(){
|
||||
Api.request('movie.delete', {
|
||||
'data': {
|
||||
'id': self.movie.get('id'),
|
||||
'delete_from': 'wanted'
|
||||
},
|
||||
'onComplete': function(){
|
||||
var movie = $(self.movie);
|
||||
movie.set('tween', {
|
||||
'duration': 300,
|
||||
'onComplete': function(){
|
||||
self.movie.destroy()
|
||||
}
|
||||
});
|
||||
movie.tween('height', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
'click': self.markMovieDone.bind(self)
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -326,14 +362,14 @@ MA.Release = new Class({
|
||||
var release_el = self.release_container.getElement('#release_'+release.id),
|
||||
icon = release_el.getElement('.download.icon2');
|
||||
|
||||
self.movie.busy(true);
|
||||
icon.addClass('icon spinner').removeClass('download');
|
||||
|
||||
Api.request('release.download', {
|
||||
'data': {
|
||||
'id': release.id
|
||||
},
|
||||
'onComplete': function(json){
|
||||
self.movie.busy(false);
|
||||
icon.removeClass('icon spinner');
|
||||
|
||||
if(json.success)
|
||||
icon.addClass('completed');
|
||||
@@ -365,24 +401,36 @@ MA.Release = new Class({
|
||||
|
||||
},
|
||||
|
||||
tryNextRelease: function(movie_id){
|
||||
markMovieDone: function(){
|
||||
var self = this;
|
||||
|
||||
self.createReleases();
|
||||
|
||||
if(self.last_release)
|
||||
self.ignore(self.last_release);
|
||||
|
||||
if(self.next_release)
|
||||
self.download(self.next_release);
|
||||
Api.request('movie.delete', {
|
||||
'data': {
|
||||
'id': self.movie.get('id'),
|
||||
'delete_from': 'wanted'
|
||||
},
|
||||
'onComplete': function(){
|
||||
var movie = $(self.movie);
|
||||
movie.set('tween', {
|
||||
'duration': 300,
|
||||
'onComplete': function(){
|
||||
self.movie.destroy()
|
||||
}
|
||||
});
|
||||
movie.tween('height', 0);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
trySameRelease: function(movie_id){
|
||||
tryNextRelease: function(movie_id){
|
||||
var self = this;
|
||||
|
||||
if(self.last_release)
|
||||
self.download(self.last_release);
|
||||
Api.request('movie.searcher.try_next', {
|
||||
'data': {
|
||||
'id': self.movie.get('id')
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -581,7 +629,7 @@ MA.Edit = new Class({
|
||||
'text': profile.label ? profile.label : profile.data.label
|
||||
}).inject(self.profile_select);
|
||||
|
||||
if(self.movie.profile && self.movie.profile.data && self.movie.profile.data.id == profile_id)
|
||||
if(self.movie.get('profile_id') == profile_id)
|
||||
self.profile_select.set('value', profile_id);
|
||||
|
||||
});
|
||||
@@ -780,16 +828,45 @@ MA.Files = new Class({
|
||||
self.el = new Element('a.directory', {
|
||||
'title': 'Available files',
|
||||
'events': {
|
||||
'click': self.showFiles.bind(self)
|
||||
'click': self.show.bind(self)
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
showFiles: function(e){
|
||||
show: function(e){
|
||||
var self = this;
|
||||
(e).preventDefault();
|
||||
|
||||
if(self.releases)
|
||||
self.showFiles();
|
||||
else {
|
||||
|
||||
self.movie.busy(true);
|
||||
|
||||
Api.request('release.for_movie', {
|
||||
'data': {
|
||||
'id': self.movie.data.id
|
||||
},
|
||||
'onComplete': function(json){
|
||||
self.movie.busy(false, 1);
|
||||
|
||||
if(json && json.releases){
|
||||
self.releases = json.releases;
|
||||
self.showFiles();
|
||||
}
|
||||
else
|
||||
alert('Something went wrong, check the logs.');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
showFiles: function(){
|
||||
var self = this;
|
||||
|
||||
if(!self.options_container){
|
||||
self.options_container = new Element('div.options').adopt(
|
||||
self.files_container = new Element('div.files.table')
|
||||
@@ -802,7 +879,7 @@ MA.Files = new Class({
|
||||
new Element('span.is_available', {'text': 'Available'})
|
||||
).inject(self.files_container)
|
||||
|
||||
Array.each(self.movie.data.releases, function(release){
|
||||
Array.each(self.releases, function(release){
|
||||
|
||||
var rel = new Element('div.release').inject(self.files_container);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ var Movie = new Class({
|
||||
})
|
||||
},
|
||||
|
||||
busy: function(set_busy){
|
||||
busy: function(set_busy, timeout){
|
||||
var self = this;
|
||||
|
||||
if(!set_busy){
|
||||
@@ -72,9 +72,9 @@ var Movie = new Class({
|
||||
self.spinner.el.destroy();
|
||||
self.spinner = null;
|
||||
self.mask = null;
|
||||
}, 400);
|
||||
}, timeout || 400);
|
||||
}
|
||||
}, 1000)
|
||||
}, timeout || 1000)
|
||||
}
|
||||
else if(!self.spinner) {
|
||||
self.createMask();
|
||||
@@ -179,20 +179,21 @@ var Movie = new Class({
|
||||
});
|
||||
|
||||
// Add releases
|
||||
self.data.releases.each(function(release){
|
||||
|
||||
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
||||
status = Status.get(release.status_id);
|
||||
|
||||
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
||||
var q = self.addQuality(release.quality_id)
|
||||
|
||||
if (status && q && !q.hasClass(status.identifier)){
|
||||
q.addClass(status.identifier);
|
||||
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
|
||||
}
|
||||
|
||||
});
|
||||
if(self.data.releases)
|
||||
self.data.releases.each(function(release){
|
||||
|
||||
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
||||
status = Status.get(release.status_id);
|
||||
|
||||
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
||||
var q = self.addQuality(release.quality_id)
|
||||
|
||||
if (status && q && !q.hasClass(status.identifier)){
|
||||
q.addClass(status.identifier);
|
||||
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Object.each(self.options.actions, function(action, key){
|
||||
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
|
||||
|
||||
@@ -326,10 +326,10 @@ Block.Search.Item = new Class({
|
||||
|
||||
self.options_el.grab(
|
||||
new Element('div', {
|
||||
'class': self.info.in_wanted && self.info.in_wanted.profile || in_library ? 'in_library_wanted' : ''
|
||||
'class': self.info.in_wanted && self.info.in_wanted.profile_id || in_library ? 'in_library_wanted' : ''
|
||||
}).adopt(
|
||||
self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
|
||||
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||
self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', {
|
||||
'text': 'Already in wanted list: ' + Quality.getProfile(self.info.in_wanted.profile_id).get('label')
|
||||
}) : (in_library ? new Element('span.in_library', {
|
||||
'text': 'Already in library: ' + in_library.join(', ')
|
||||
}) : null),
|
||||
@@ -390,7 +390,7 @@ Block.Search.Item = new Class({
|
||||
self.options_el.addClass('set');
|
||||
|
||||
if(categories.length == 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
|
||||
!(self.info.in_wanted && self.info.in_wanted.profile || in_library))
|
||||
!(self.info.in_wanted && self.info.in_wanted.profile_id || in_library))
|
||||
self.add();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, File
|
||||
from couchpotato.core.media._base.library import LibraryBase
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, File
|
||||
from string import ascii_letters
|
||||
import time
|
||||
import traceback
|
||||
@@ -71,6 +71,7 @@ class MovieLibraryPlugin(LibraryBase):
|
||||
library = db.query(Library).filter_by(identifier = identifier).first()
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
library_dict = None
|
||||
if library:
|
||||
library_dict = library.to_dict(self.default_dict)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
self.in_progress = False
|
||||
|
||||
def single(self, movie, search_protocols = None):
|
||||
def single(self, movie, search_protocols = None, manual = False):
|
||||
|
||||
# Find out search type
|
||||
try:
|
||||
@@ -126,7 +126,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
if not movie['profile'] or movie['status_id'] == done_status.get('id'):
|
||||
if not movie['profile'] or (movie['status_id'] == done_status.get('id') and not manual):
|
||||
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
|
||||
return
|
||||
|
||||
@@ -237,7 +237,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
log.info('Ignored, score to low: %s', nzb['name'])
|
||||
continue
|
||||
|
||||
downloaded = fireEvent('searcher.download', data = nzb, movie = movie, single = True)
|
||||
downloaded = fireEvent('searcher.download', data = nzb, movie = movie, manual = manual, single = True)
|
||||
if downloaded is True:
|
||||
ret = True
|
||||
break
|
||||
@@ -403,7 +403,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
def tryNextReleaseView(self, id = None, **kwargs):
|
||||
|
||||
trynext = self.tryNextRelease(id)
|
||||
trynext = self.tryNextRelease(id, manual = True)
|
||||
|
||||
return {
|
||||
'success': trynext
|
||||
@@ -411,14 +411,14 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
def tryNextRelease(self, movie_id, manual = False):
|
||||
|
||||
snatched_status, ignored_status = fireEvent('status.get', ['snatched', 'ignored'], single = True)
|
||||
snatched_status, done_status, ignored_status = fireEvent('status.get', ['snatched', 'done', 'ignored'], single = True)
|
||||
|
||||
try:
|
||||
db = get_session()
|
||||
rels = db.query(Release).filter_by(
|
||||
status_id = snatched_status.get('id'),
|
||||
movie_id = movie_id
|
||||
).all()
|
||||
rels = db.query(Release) \
|
||||
.filter_by(movie_id = movie_id) \
|
||||
.filter(Release.status_id.in_([snatched_status.get('id'), done_status.get('id')])) \
|
||||
.all()
|
||||
|
||||
for rel in rels:
|
||||
rel.status_id = ignored_status.get('id')
|
||||
@@ -426,7 +426,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
||||
fireEvent('movie.searcher.single', movie_dict)
|
||||
fireEvent('movie.searcher.single', movie_dict, manual = manual)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ class Notification(Provider):
|
||||
addEvent(listener, self.createNotifyHandler(listener))
|
||||
|
||||
def createNotifyHandler(self, listener):
|
||||
def notify(message = None, group = {}, data = None):
|
||||
def notify(message = None, group = None, data = None):
|
||||
if not group: group = {}
|
||||
|
||||
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
||||
return
|
||||
return self._notify(message = message, data = data if data else group, listener = listener)
|
||||
@@ -45,9 +47,10 @@ class Notification(Provider):
|
||||
def _notify(self, *args, **kwargs):
|
||||
if self.isEnabled():
|
||||
return self.notify(*args, **kwargs)
|
||||
return False
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
pass
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
def test(self, **kwargs):
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ class Boxcar(Notification):
|
||||
|
||||
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
try:
|
||||
message = message.strip()
|
||||
|
||||
@@ -128,7 +128,8 @@ class CoreNotifier(Notification):
|
||||
|
||||
Env.prop(prop_name, value = last_check)
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
db = get_session()
|
||||
|
||||
@@ -149,7 +150,8 @@ class CoreNotifier(Notification):
|
||||
|
||||
return True
|
||||
|
||||
def frontend(self, type = 'notification', data = {}, message = None):
|
||||
def frontend(self, type = 'notification', data = None, message = None):
|
||||
if not data: data = {}
|
||||
|
||||
log.debug('Notifying frontend')
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ log = CPLog(__name__)
|
||||
|
||||
class Email(Notification):
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
# Extract all the settings from settings
|
||||
from_address = self.conf('from')
|
||||
|
||||
@@ -43,7 +43,8 @@ class Growl(Notification):
|
||||
else:
|
||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
self.register()
|
||||
|
||||
|
||||
@@ -23,16 +23,15 @@ class NMJ(Notification):
|
||||
|
||||
def autoConfig(self, host = 'localhost', **kwargs):
|
||||
|
||||
database = ''
|
||||
mount = ''
|
||||
|
||||
try:
|
||||
terminal = telnetlib.Telnet(host)
|
||||
except Exception:
|
||||
log.error('Warning: unable to get a telnet session to %s', (host))
|
||||
log.error('Warning: unable to get a telnet session to %s', host)
|
||||
return self.failed()
|
||||
|
||||
log.debug('Connected to %s via telnet', (host))
|
||||
log.debug('Connected to %s via telnet', host)
|
||||
terminal.read_until('sh-3.00# ')
|
||||
terminal.write('cat /tmp/source\n')
|
||||
terminal.write('cat /tmp/netshare\n')
|
||||
@@ -46,7 +45,7 @@ class NMJ(Notification):
|
||||
device = match.group(2)
|
||||
log.info('Found NMJ database %s on device %s', (database, device))
|
||||
else:
|
||||
log.error('Could not get current NMJ database on %s, NMJ is probably not running!', (host))
|
||||
log.error('Could not get current NMJ database on %s, NMJ is probably not running!', host)
|
||||
return self.failed()
|
||||
|
||||
if device.startswith('NETWORK_SHARE/'):
|
||||
@@ -54,7 +53,7 @@ class NMJ(Notification):
|
||||
|
||||
if match:
|
||||
mount = match.group().replace('127.0.0.1', host)
|
||||
log.info('Found mounting url on the Popcorn Hour in configuration: %s', (mount))
|
||||
log.info('Found mounting url on the Popcorn Hour in configuration: %s', mount)
|
||||
else:
|
||||
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
||||
return self.failed()
|
||||
@@ -65,17 +64,18 @@ class NMJ(Notification):
|
||||
'mount': mount,
|
||||
}
|
||||
|
||||
def addToLibrary(self, message = None, group = {}):
|
||||
def addToLibrary(self, message = None, group = None):
|
||||
if self.isDisabled(): return
|
||||
if not group: group = {}
|
||||
|
||||
host = self.conf('host')
|
||||
mount = self.conf('mount')
|
||||
database = self.conf('database')
|
||||
|
||||
if mount:
|
||||
log.debug('Try to mount network drive via url: %s', (mount))
|
||||
log.debug('Try to mount network drive via url: %s', mount)
|
||||
try:
|
||||
data = self.urlopen(mount)
|
||||
self.urlopen(mount)
|
||||
except:
|
||||
return False
|
||||
|
||||
@@ -98,11 +98,11 @@ class NMJ(Notification):
|
||||
et = etree.fromstring(response)
|
||||
result = et.findtext('returnValue')
|
||||
except SyntaxError, e:
|
||||
log.error('Unable to parse XML returned from the Popcorn Hour: %s', (e))
|
||||
log.error('Unable to parse XML returned from the Popcorn Hour: %s', e)
|
||||
return False
|
||||
|
||||
if int(result) > 0:
|
||||
log.error('Popcorn Hour returned an errorcode: %s', (result))
|
||||
log.error('Popcorn Hour returned an errorcode: %s', result)
|
||||
return False
|
||||
else:
|
||||
log.info('NMJ started background scan')
|
||||
|
||||
@@ -12,7 +12,8 @@ class Notifo(Notification):
|
||||
|
||||
url = 'https://api.notifo.com/v1/send_notification'
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
try:
|
||||
params = {
|
||||
|
||||
@@ -8,19 +8,17 @@ log = CPLog(__name__)
|
||||
|
||||
class NotifyMyAndroid(Notification):
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
nma = pynma.PyNMA()
|
||||
keys = splitString(self.conf('api_key'))
|
||||
nma.addkey(keys)
|
||||
nma.developerkey(self.conf('dev_key'))
|
||||
|
||||
# hacky fix for the event type
|
||||
# as it seems to be part of the message now
|
||||
self.event = message.split(' ')[0]
|
||||
response = nma.push(
|
||||
application = self.default_title,
|
||||
event = self.event,
|
||||
event = message.split(' ')[0],
|
||||
description = message,
|
||||
priority = self.conf('priority'),
|
||||
batch_mode = len(keys) > 1
|
||||
|
||||
@@ -17,8 +17,9 @@ class Plex(Notification):
|
||||
super(Plex, self).__init__()
|
||||
addEvent('renamer.after', self.addToLibrary)
|
||||
|
||||
def addToLibrary(self, message = None, group = {}):
|
||||
def addToLibrary(self, message = None, group = None):
|
||||
if self.isDisabled(): return
|
||||
if not group: group = {}
|
||||
|
||||
log.info('Sending notification to Plex')
|
||||
hosts = self.getHosts(port = 32400)
|
||||
@@ -37,7 +38,7 @@ class Plex(Notification):
|
||||
for s in sections:
|
||||
if s.getAttribute('type') in source_type:
|
||||
url = refresh_url % s.getAttribute('key')
|
||||
x = self.urlopen(url)
|
||||
self.urlopen(url)
|
||||
|
||||
except:
|
||||
log.error('Plex library update failed for %s, Media Server not running: %s', (host, traceback.format_exc(1)))
|
||||
@@ -45,7 +46,8 @@ class Plex(Notification):
|
||||
|
||||
return True
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
hosts = self.getHosts(port = 3000)
|
||||
successful = 0
|
||||
|
||||
@@ -12,7 +12,8 @@ class Prowl(Notification):
|
||||
'api': 'https://api.prowlapp.com/publicapi/add'
|
||||
}
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
data = {
|
||||
'apikey': self.conf('api_key'),
|
||||
|
||||
@@ -11,7 +11,8 @@ class Pushalot(Notification):
|
||||
'api': 'https://pushalot.com/api/sendmessage'
|
||||
}
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
data = {
|
||||
'AuthorizationToken': self.conf('auth_token'),
|
||||
|
||||
@@ -11,7 +11,8 @@ class Pushover(Notification):
|
||||
|
||||
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
http_handler = HTTPSConnection("api.pushover.net:443")
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@ class Synoindex(Notification):
|
||||
super(Synoindex, self).__init__()
|
||||
addEvent('renamer.after', self.addToLibrary)
|
||||
|
||||
def addToLibrary(self, message = None, group = {}):
|
||||
def addToLibrary(self, message = None, group = None):
|
||||
if self.isDisabled(): return
|
||||
if not group: group = {}
|
||||
|
||||
command = [self.index_path, '-A', group.get('destination_dir')]
|
||||
log.info('Executing synoindex command: %s ', command)
|
||||
@@ -27,9 +28,8 @@ class Synoindex(Notification):
|
||||
return True
|
||||
except OSError, e:
|
||||
log.error('Unable to run synoindex: %s', e)
|
||||
return False
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def test(self, **kwargs):
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,8 @@ class Toasty(Notification):
|
||||
'api': 'http://api.supertoasty.com/notify/%s?%s'
|
||||
}
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
data = {
|
||||
'title': self.default_title,
|
||||
|
||||
@@ -13,7 +13,8 @@ class Trakt(Notification):
|
||||
|
||||
listen_to = ['movie.downloaded']
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
post_data = {
|
||||
'username': self.conf('automation_username'),
|
||||
|
||||
@@ -4,7 +4,8 @@ from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.environment import Env
|
||||
from pytwitter import Api, parse_qsl
|
||||
from pytwitter import Api
|
||||
from urlparse import parse_qsl
|
||||
import oauth2
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -29,7 +30,8 @@ class Twitter(Notification):
|
||||
addApiView('notify.%s.auth_url' % self.getName().lower(), self.getAuthorizationUrl)
|
||||
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class XBMC(Notification):
|
||||
use_json_notifications = {}
|
||||
http_time_between_calls = 0
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
hosts = splitString(self.conf('host'))
|
||||
|
||||
@@ -53,9 +54,9 @@ class XBMC(Notification):
|
||||
|
||||
try:
|
||||
for result in response:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
if result.get('result') and result['result'] == 'OK':
|
||||
successful += 1
|
||||
elif (result.get('error')):
|
||||
elif result.get('error'):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
|
||||
except:
|
||||
@@ -72,7 +73,7 @@ class XBMC(Notification):
|
||||
('JSONRPC.Version', {})
|
||||
])
|
||||
for result in response:
|
||||
if (result.get('result') and type(result['result']['version']).__name__ == 'int'):
|
||||
if result.get('result') and type(result['result']['version']).__name__ == 'int':
|
||||
# only v2 and v4 return an int object
|
||||
# v6 (as of XBMC v12(Frodo)) is required to send notifications
|
||||
xbmc_rpc_version = str(result['result']['version'])
|
||||
@@ -85,15 +86,15 @@ class XBMC(Notification):
|
||||
# send the text message
|
||||
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
||||
for result in resp:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
if result.get('result') and result['result'] == 'OK':
|
||||
log.debug('Message delivered successfully!')
|
||||
success = True
|
||||
break
|
||||
elif (result.get('error')):
|
||||
elif result.get('error'):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
break
|
||||
|
||||
elif (result.get('result') and type(result['result']['version']).__name__ == 'dict'):
|
||||
elif result.get('result') and type(result['result']['version']).__name__ == 'dict':
|
||||
# XBMC JSON-RPC v6 returns an array object containing
|
||||
# major, minor and patch number
|
||||
xbmc_rpc_version = str(result['result']['version']['major'])
|
||||
@@ -108,16 +109,16 @@ class XBMC(Notification):
|
||||
# send the text message
|
||||
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
|
||||
for result in resp:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
if result.get('result') and result['result'] == 'OK':
|
||||
log.debug('Message delivered successfully!')
|
||||
success = True
|
||||
break
|
||||
elif (result.get('error')):
|
||||
elif result.get('error'):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
break
|
||||
|
||||
# error getting version info (we do have contact with XBMC though)
|
||||
elif (result.get('error')):
|
||||
elif result.get('error'):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
|
||||
log.debug('Use JSON notifications: %s ', self.use_json_notifications)
|
||||
|
||||
@@ -2,7 +2,7 @@ from StringIO import StringIO
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString, \
|
||||
toUnicode
|
||||
from couchpotato.core.helpers.variable import getExt, md5
|
||||
from couchpotato.core.helpers.variable import getExt, md5, isLocalIP
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from multipartpost import MultipartPostHandler
|
||||
@@ -26,11 +26,13 @@ log = CPLog(__name__)
|
||||
class Plugin(object):
|
||||
|
||||
_class_name = None
|
||||
plugin_path = None
|
||||
|
||||
enabled_option = 'enabled'
|
||||
auto_register_static = True
|
||||
|
||||
_needs_shutdown = False
|
||||
_running = None
|
||||
|
||||
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20130519 Firefox/24.0'
|
||||
http_last_use = {}
|
||||
@@ -81,7 +83,7 @@ class Plugin(object):
|
||||
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
|
||||
# View path
|
||||
path = 'api/%s/static/%s/' % (Env.setting('api_key'), class_name)
|
||||
path = 'static/plugin/%s/' % (class_name)
|
||||
|
||||
# Add handler to Tornado
|
||||
Env.get('app').add_handlers(".*$", [(Env.get('web_base') + path + '(.*)', StaticFileHandler, {'path': static_folder})])
|
||||
@@ -140,7 +142,7 @@ class Plugin(object):
|
||||
if self.http_failed_disabled[host] > (time.time() - 900):
|
||||
log.info2('Disabled calls to %s for 15 minutes because so many failed requests.', host)
|
||||
if not show_error:
|
||||
raise
|
||||
raise Exception('Disabled calls to %s for 15 minutes because so many failed requests')
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
@@ -203,7 +205,7 @@ class Plugin(object):
|
||||
self.http_failed_request[host] += 1
|
||||
|
||||
# Disable temporarily
|
||||
if self.http_failed_request[host] > 5:
|
||||
if self.http_failed_request[host] > 5 and not isLocalIP(host):
|
||||
self.http_failed_disabled[host] = time.time()
|
||||
|
||||
except:
|
||||
@@ -257,8 +259,8 @@ class Plugin(object):
|
||||
|
||||
|
||||
def getCache(self, cache_key, url = None, **kwargs):
|
||||
cache_key = md5(ss(cache_key))
|
||||
cache = Env.get('cache').get(cache_key)
|
||||
cache_key_md5 = md5(ss(cache_key))
|
||||
cache = Env.get('cache').get(cache_key_md5)
|
||||
if cache:
|
||||
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
|
||||
return cache
|
||||
@@ -282,8 +284,9 @@ class Plugin(object):
|
||||
return ''
|
||||
|
||||
def setCache(self, cache_key, value, timeout = 300):
|
||||
cache_key_md5 = md5(ss(cache_key))
|
||||
log.debug('Setting cache %s', cache_key)
|
||||
Env.get('cache').set(cache_key, value, timeout)
|
||||
Env.get('cache').set(cache_key_md5, value, timeout)
|
||||
return value
|
||||
|
||||
def createNzbName(self, data, movie):
|
||||
@@ -292,9 +295,9 @@ class Plugin(object):
|
||||
|
||||
def createFileName(self, data, filedata, movie):
|
||||
name = os.path.join(self.createNzbName(data, movie))
|
||||
if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
|
||||
if data.get('protocol') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
|
||||
return '%s.%s' % (name, 'rar')
|
||||
return '%s.%s' % (name, data.get('type'))
|
||||
return '%s.%s' % (name, data.get('protocol'))
|
||||
|
||||
def cpTag(self, movie):
|
||||
if Env.setting('enabled', 'renamer'):
|
||||
@@ -306,4 +309,4 @@ class Plugin(object):
|
||||
return not self.isEnabled()
|
||||
|
||||
def isEnabled(self):
|
||||
return self.conf(self.enabled_option) or self.conf(self.enabled_option) == None
|
||||
return self.conf(self.enabled_option) or self.conf(self.enabled_option) is None
|
||||
|
||||
@@ -12,7 +12,7 @@ if os.name == 'nt':
|
||||
except:
|
||||
# todo:: subclass ImportError for missing dependencies, vs. broken plugins?
|
||||
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
|
||||
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/");
|
||||
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/")
|
||||
else:
|
||||
import win32file #@UnresolvedImport
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.variable import splitString, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Media
|
||||
from couchpotato.core.settings.model import Media, Library, LibraryTitle
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import asc
|
||||
import random as rndm
|
||||
import time
|
||||
|
||||
@@ -40,67 +41,81 @@ class Dashboard(Plugin):
|
||||
|
||||
profile_pre[profile.get('id')] = contains
|
||||
|
||||
# Get all active movies
|
||||
active_status, snatched_status, downloaded_status, available_status = fireEvent('status.get', ['active', 'snatched', 'downloaded', 'available'], single = True)
|
||||
subq = db.query(Media).filter(Media.status_id == active_status.get('id')).subquery()
|
||||
|
||||
q = db.query(Media).join((subq, subq.c.id == Media.id)) \
|
||||
.options(joinedload_all('releases')) \
|
||||
.options(joinedload_all('profile')) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('status')) \
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
# Add limit
|
||||
limit = 12
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
limit = tryInt(splt[0])
|
||||
|
||||
all_movies = q.all()
|
||||
# Get all active movies
|
||||
active_status = fireEvent('status.get', ['active'], single = True)
|
||||
q = db.query(Media) \
|
||||
.join(Library) \
|
||||
.filter(Media.status_id == active_status.get('id')) \
|
||||
.with_entities(Media.id, Media.profile_id, Library.info, Library.year) \
|
||||
.group_by(Media.id)
|
||||
|
||||
if random:
|
||||
rndm.shuffle(all_movies)
|
||||
if not random:
|
||||
q = q.join(LibraryTitle) \
|
||||
.filter(LibraryTitle.default == True) \
|
||||
.order_by(asc(LibraryTitle.simple_title))
|
||||
|
||||
active = q.all()
|
||||
movies = []
|
||||
for movie in all_movies:
|
||||
pp = profile_pre.get(movie.profile.id)
|
||||
eta = movie.library.info.get('release_date', {}) or {}
|
||||
coming_soon = False
|
||||
|
||||
# Theater quality
|
||||
if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, movie.library.year, single = True):
|
||||
coming_soon = True
|
||||
if pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, movie.library.year, single = True):
|
||||
coming_soon = True
|
||||
if len(active) > 0:
|
||||
|
||||
# Skip if movie is snatched/downloaded/available
|
||||
skip = False
|
||||
for release in movie.releases:
|
||||
if release.status_id in [snatched_status.get('id'), downloaded_status.get('id'), available_status.get('id')]:
|
||||
skip = True
|
||||
break
|
||||
if skip:
|
||||
continue
|
||||
# Do the shuffle
|
||||
if random:
|
||||
rndm.shuffle(active)
|
||||
|
||||
if coming_soon:
|
||||
temp = movie.to_dict({
|
||||
'profile': {'types': {}},
|
||||
'releases': {'files':{}, 'info': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
})
|
||||
movie_ids = []
|
||||
for movie in active:
|
||||
movie_id, profile_id, info, year = movie
|
||||
|
||||
# Don't list older movies
|
||||
if ((not late and ((not eta.get('dvd') and not eta.get('theater')) or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
|
||||
(late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
|
||||
movies.append(temp)
|
||||
pp = profile_pre.get(profile_id)
|
||||
if not pp: continue
|
||||
|
||||
if len(movies) >= limit:
|
||||
break
|
||||
eta = info.get('release_date', {}) or {}
|
||||
coming_soon = False
|
||||
|
||||
# Theater quality
|
||||
if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, year, single = True):
|
||||
coming_soon = True
|
||||
elif pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, year, single = True):
|
||||
coming_soon = True
|
||||
|
||||
if coming_soon:
|
||||
|
||||
# Don't list older movies
|
||||
if ((not late and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
|
||||
(late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
|
||||
movie_ids.append(movie_id)
|
||||
|
||||
if len(movie_ids) >= limit:
|
||||
break
|
||||
|
||||
if len(movie_ids) > 0:
|
||||
|
||||
# Get all movie information
|
||||
movies_raw = db.query(Movie) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('files')) \
|
||||
.filter(Movie.id.in_(movie_ids)) \
|
||||
.all()
|
||||
|
||||
# Create dict by movie id
|
||||
movie_dict = {}
|
||||
for movie in movies_raw:
|
||||
movie_dict[movie.id] = movie
|
||||
|
||||
for movie_id in movie_ids:
|
||||
movies.append(movie_dict[movie_id].to_dict({
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
}))
|
||||
|
||||
db.expire_all()
|
||||
return {
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
|
||||
@@ -71,7 +71,7 @@ class FileManager(Plugin):
|
||||
db = get_session()
|
||||
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
||||
for filename in walk_files:
|
||||
if root == python_cache or 'minified' in filename or 'version' in filename or 'temp_updater' in root: continue
|
||||
if root == python_cache or 'minified' in root or 'version' in filename or 'temp_updater' in root: continue
|
||||
file_path = os.path.join(root, filename)
|
||||
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
||||
if not f:
|
||||
@@ -83,7 +83,8 @@ class FileManager(Plugin):
|
||||
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})])
|
||||
|
||||
|
||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
|
||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
|
||||
if not urlopen_kwargs: urlopen_kwargs = {}
|
||||
|
||||
if not dest: # to Cache
|
||||
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
|
||||
@@ -100,7 +101,9 @@ class FileManager(Plugin):
|
||||
self.createFile(dest, filedata, binary = True)
|
||||
return dest
|
||||
|
||||
def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = {}):
|
||||
def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = None):
|
||||
if not properties: properties = {}
|
||||
|
||||
type_id = self.getType(type_tuple).get('id')
|
||||
db = get_session()
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ class Logging(Plugin):
|
||||
if not os.path.isfile(path):
|
||||
break
|
||||
|
||||
reversed_lines = []
|
||||
f = open(path, 'r')
|
||||
reversed_lines = toUnicode(f.read()).split('[0m\n')
|
||||
reversed_lines.reverse()
|
||||
|
||||
@@ -26,7 +26,8 @@ class Manage(Plugin):
|
||||
addEvent('manage.diskspace', self.getDiskSpace)
|
||||
|
||||
# Add files after renaming
|
||||
def after_rename(message = None, group = {}):
|
||||
def after_rename(message = None, group = None):
|
||||
if not group: group = {}
|
||||
return self.scanFilesToLibrary(folder = group['destination_dir'], files = group['renamed_files'])
|
||||
addEvent('renamer.after', after_rename, priority = 110)
|
||||
|
||||
@@ -117,7 +118,9 @@ class Manage(Plugin):
|
||||
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
|
||||
else:
|
||||
|
||||
for release in done_movie.get('releases', []):
|
||||
releases = fireEvent('release.for_movie', id = done_movie.get('id'), single = True)
|
||||
|
||||
for release in releases:
|
||||
if len(release.get('files', [])) == 0:
|
||||
fireEvent('release.delete', release['id'])
|
||||
else:
|
||||
@@ -128,9 +131,9 @@ class Manage(Plugin):
|
||||
break
|
||||
|
||||
# Check if there are duplicate releases (different quality) use the last one, delete the rest
|
||||
if len(done_movie.get('releases', [])) > 1:
|
||||
if len(releases) > 1:
|
||||
used_files = {}
|
||||
for release in done_movie.get('releases', []):
|
||||
for release in releases:
|
||||
|
||||
for release_file in release.get('files', []):
|
||||
already_used = used_files.get(release_file['path'])
|
||||
@@ -169,6 +172,7 @@ class Manage(Plugin):
|
||||
self.in_progress = False
|
||||
|
||||
def createAddToLibrary(self, folder, added_identifiers = []):
|
||||
|
||||
def addToLibrary(group, total_found, to_go):
|
||||
if self.in_progress[folder]['total'] is None:
|
||||
self.in_progress[folder] = {
|
||||
@@ -184,7 +188,7 @@ class Manage(Plugin):
|
||||
fireEvent('release.add', group = group)
|
||||
fireEventAsync('library.update.movie', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
|
||||
else:
|
||||
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
||||
self.in_progress[folder]['to_go'] -= 1
|
||||
|
||||
return addToLibrary
|
||||
|
||||
@@ -195,7 +199,7 @@ class Manage(Plugin):
|
||||
if not self.in_progress or self.shuttingDown():
|
||||
return
|
||||
|
||||
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
||||
self.in_progress[folder]['to_go'] -= 1
|
||||
total = self.in_progress[folder]['total']
|
||||
movie_dict = fireEvent('movie.get', identifier, single = True)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Profile, ProfileType, Media
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -55,7 +56,9 @@ class ProfilePlugin(Plugin):
|
||||
def all(self):
|
||||
|
||||
db = get_session()
|
||||
profiles = db.query(Profile).all()
|
||||
profiles = db.query(Profile) \
|
||||
.options(joinedload_all('types')) \
|
||||
.all()
|
||||
|
||||
temp = []
|
||||
for profile in profiles:
|
||||
@@ -104,7 +107,9 @@ class ProfilePlugin(Plugin):
|
||||
def default(self):
|
||||
|
||||
db = get_session()
|
||||
default = db.query(Profile).first()
|
||||
default = db.query(Profile) \
|
||||
.options(joinedload_all('types')) \
|
||||
.first()
|
||||
default_dict = default.to_dict(self.to_dict)
|
||||
|
||||
db.expire_all()
|
||||
@@ -155,7 +160,7 @@ class ProfilePlugin(Plugin):
|
||||
|
||||
def fill(self):
|
||||
|
||||
db = get_session();
|
||||
db = get_session()
|
||||
|
||||
profiles = [{
|
||||
'label': 'Best',
|
||||
|
||||
@@ -102,7 +102,7 @@ class QualityPlugin(Plugin):
|
||||
|
||||
def fill(self):
|
||||
|
||||
db = get_session();
|
||||
db = get_session()
|
||||
|
||||
order = 0
|
||||
for q in self.qualities:
|
||||
@@ -152,39 +152,41 @@ class QualityPlugin(Plugin):
|
||||
|
||||
return True
|
||||
|
||||
def guess(self, files, extra = {}):
|
||||
def guess(self, files, extra = None):
|
||||
if not extra: extra = {}
|
||||
|
||||
# Create hash for cache
|
||||
hash = md5(str([f.replace('.' + getExt(f), '') for f in files]))
|
||||
cached = self.getCache(hash)
|
||||
if cached and extra is {}: return cached
|
||||
cache_key = md5(str([f.replace('.' + getExt(f), '') for f in files]))
|
||||
cached = self.getCache(cache_key)
|
||||
if cached and len(extra) == 0: return cached
|
||||
|
||||
qualities = self.all()
|
||||
for cur_file in files:
|
||||
words = re.split('\W+', cur_file.lower())
|
||||
|
||||
found = {}
|
||||
for quality in self.all():
|
||||
for quality in qualities:
|
||||
contains = self.containsTag(quality, words, cur_file)
|
||||
if contains:
|
||||
found[quality['identifier']] = True
|
||||
|
||||
for quality in self.all():
|
||||
for quality in qualities:
|
||||
|
||||
# Check identifier
|
||||
if quality['identifier'] in words:
|
||||
if len(found) == 0 or len(found) == 1 and found.get(quality['identifier']):
|
||||
log.debug('Found via identifier "%s" in %s', (quality['identifier'], cur_file))
|
||||
return self.setCache(hash, quality)
|
||||
return self.setCache(cache_key, quality)
|
||||
|
||||
# Check alt and tags
|
||||
contains = self.containsTag(quality, words, cur_file)
|
||||
if contains:
|
||||
return self.setCache(hash, quality)
|
||||
return self.setCache(cache_key, quality)
|
||||
|
||||
# Try again with loose testing
|
||||
quality = self.guessLoose(hash, files = files, extra = extra)
|
||||
quality = self.guessLoose(cache_key, files = files, extra = extra)
|
||||
if quality:
|
||||
return self.setCache(hash, quality)
|
||||
return self.setCache(cache_key, quality)
|
||||
|
||||
log.debug('Could not identify quality for: %s', files)
|
||||
return None
|
||||
@@ -204,7 +206,7 @@ class QualityPlugin(Plugin):
|
||||
|
||||
return
|
||||
|
||||
def guessLoose(self, hash, files = None, extra = None):
|
||||
def guessLoose(self, cache_key, files = None, extra = None):
|
||||
|
||||
if extra:
|
||||
for quality in self.all():
|
||||
@@ -212,15 +214,15 @@ class QualityPlugin(Plugin):
|
||||
# Check width resolution, range 20
|
||||
if quality.get('width') and (quality.get('width') - 20) <= extra.get('resolution_width', 0) <= (quality.get('width') + 20):
|
||||
log.debug('Found %s via resolution_width: %s == %s', (quality['identifier'], quality.get('width'), extra.get('resolution_width', 0)))
|
||||
return self.setCache(hash, quality)
|
||||
return self.setCache(cache_key, quality)
|
||||
|
||||
# Check height resolution, range 20
|
||||
if quality.get('height') and (quality.get('height') - 20) <= extra.get('resolution_height', 0) <= (quality.get('height') + 20):
|
||||
log.debug('Found %s via resolution_height: %s == %s', (quality['identifier'], quality.get('height'), extra.get('resolution_height', 0)))
|
||||
return self.setCache(hash, quality)
|
||||
return self.setCache(cache_key, quality)
|
||||
|
||||
if 480 <= extra.get('resolution_width', 0) <= 720:
|
||||
log.debug('Found as dvdrip')
|
||||
return self.setCache(hash, self.single('dvdrip'))
|
||||
return self.setCache(cache_key, self.single('dvdrip'))
|
||||
|
||||
return None
|
||||
|
||||
@@ -6,8 +6,10 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.plugins.scanner.main import Scanner
|
||||
from couchpotato.core.settings.model import File, Release as Relea, Media
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
import os
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -35,7 +37,14 @@ class Release(Plugin):
|
||||
'id': {'type': 'id', 'desc': 'ID of the release object in release-table'}
|
||||
}
|
||||
})
|
||||
addApiView('release.for_movie', self.forMovieView, docs = {
|
||||
'desc': 'Returns all releases for a movie. Ordered by score(desc)',
|
||||
'params': {
|
||||
'id': {'type': 'id', 'desc': 'ID of the movie'}
|
||||
}
|
||||
})
|
||||
|
||||
addEvent('release.for_movie', self.forMovie)
|
||||
addEvent('release.delete', self.delete)
|
||||
addEvent('release.clean', self.clean)
|
||||
|
||||
@@ -88,8 +97,8 @@ class Release(Plugin):
|
||||
added_files = db.query(File).filter(or_(*[File.id == x for x in added_files])).all()
|
||||
rel.files.extend(added_files)
|
||||
db.commit()
|
||||
except Exception, e:
|
||||
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
|
||||
except:
|
||||
log.debug('Failed to attach "%s" to release: %s', (added_files, traceback.format_exc()))
|
||||
|
||||
fireEvent('movie.restatus', movie.id)
|
||||
|
||||
@@ -174,7 +183,11 @@ class Release(Plugin):
|
||||
# Get matching provider
|
||||
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
||||
|
||||
if item['protocol'] != 'torrent_magnet':
|
||||
if not item.get('protocol'):
|
||||
item['protocol'] = item['type']
|
||||
item['type'] = 'movie'
|
||||
|
||||
if item.get('protocol') != 'torrent_magnet':
|
||||
item['download'] = provider.loginDownload if provider.urls.get('login') else provider.download
|
||||
|
||||
success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
||||
@@ -203,3 +216,28 @@ class Release(Plugin):
|
||||
return {
|
||||
'success': False
|
||||
}
|
||||
|
||||
def forMovie(self, id = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
releases_raw = db.query(Relea) \
|
||||
.options(joinedload_all('info')) \
|
||||
.options(joinedload_all('files')) \
|
||||
.filter(Relea.movie_id == id) \
|
||||
.all()
|
||||
|
||||
releases = [r.to_dict({'info':{}, 'files':{}}) for r in releases_raw]
|
||||
releases = sorted(releases, key = lambda k: k['info'].get('score', 0), reverse = True)
|
||||
|
||||
return releases
|
||||
|
||||
def forMovieView(self, id = None, **kwargs):
|
||||
|
||||
releases = self.forMovie(id)
|
||||
|
||||
return {
|
||||
'releases': releases,
|
||||
'success': True
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library, File, Profile, Release, \
|
||||
ReleaseInfo
|
||||
from couchpotato.environment import Env
|
||||
from unrar2 import RarFile, RarInfo
|
||||
from unrar2.rar_exceptions import *
|
||||
from unrar2 import RarFile
|
||||
import errno
|
||||
import fnmatch
|
||||
import os
|
||||
@@ -62,10 +61,10 @@ class Renamer(Plugin):
|
||||
|
||||
def scanView(self, **kwargs):
|
||||
|
||||
async = tryInt(kwargs.get('async', None))
|
||||
movie_folder = kwargs.get('movie_folder', None)
|
||||
downloader = kwargs.get('downloader', None)
|
||||
download_id = kwargs.get('download_id', None)
|
||||
async = tryInt(kwargs.get('async', 0))
|
||||
movie_folder = kwargs.get('movie_folder')
|
||||
downloader = kwargs.get('downloader')
|
||||
download_id = kwargs.get('download_id')
|
||||
|
||||
download_info = {'folder': movie_folder} if movie_folder else None
|
||||
if download_info:
|
||||
@@ -98,7 +97,7 @@ class Renamer(Plugin):
|
||||
elif self.conf('from') in self.conf('to'):
|
||||
log.error('The "to" can\'t be inside of the "from" folder. You\'ll get an infinite loop.')
|
||||
return
|
||||
elif (movie_folder and movie_folder in [self.conf('to'), self.conf('from')]):
|
||||
elif movie_folder and movie_folder in [self.conf('to'), self.conf('from')]:
|
||||
log.error('The "to" and "from" folders can\'t be inside of or the same as the provided movie folder.')
|
||||
return
|
||||
|
||||
@@ -131,8 +130,8 @@ class Renamer(Plugin):
|
||||
# Unpack any archives
|
||||
extr_files = None
|
||||
if self.conf('unrar'):
|
||||
folder, movie_folder, files, extr_files = self.extractFiles(folder = folder, movie_folder = movie_folder, files = files, \
|
||||
cleanup = self.conf('cleanup') and not self.downloadIsTorrent(download_info))
|
||||
folder, movie_folder, files, extr_files = self.extractFiles(folder = folder, movie_folder = movie_folder, files = files,
|
||||
cleanup = self.conf('cleanup') and not self.downloadIsTorrent(download_info))
|
||||
|
||||
groups = fireEvent('scanner.scan', folder = folder if folder else self.conf('from'),
|
||||
files = files, download_info = download_info, return_ignored = False, single = True)
|
||||
@@ -347,7 +346,7 @@ class Renamer(Plugin):
|
||||
profile = db.query(Profile).filter_by(core = True, label = group['meta_data']['quality']['label']).first()
|
||||
fireEvent('movie.add', params = {'identifier': group['library']['identifier'], 'profile_id': profile.id}, search_after = False)
|
||||
db.expire_all()
|
||||
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||
library_ent = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||
|
||||
for movie in library_ent.media:
|
||||
|
||||
@@ -496,7 +495,9 @@ class Renamer(Plugin):
|
||||
|
||||
self.renaming_started = False
|
||||
|
||||
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = '', remove_multiple = False):
|
||||
def getRenameExtras(self, extra_type = '', replacements = None, folder_name = '', file_name = '', destination = '', group = None, current_file = '', remove_multiple = False):
|
||||
if not group: group = {}
|
||||
if not replacements: replacements = {}
|
||||
|
||||
replacements = replacements.copy()
|
||||
rename_files = {}
|
||||
@@ -517,7 +518,7 @@ class Renamer(Plugin):
|
||||
def tagDir(self, group, tag):
|
||||
|
||||
ignore_file = None
|
||||
if isinstance(group, (dict)):
|
||||
if isinstance(group, dict):
|
||||
for movie_file in sorted(list(group['files']['movie'])):
|
||||
ignore_file = '%s.%s.ignore' % (os.path.splitext(movie_file)[0], tag)
|
||||
break
|
||||
@@ -603,9 +604,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
return True
|
||||
|
||||
def doReplace(self, string, replacements, remove_multiple = False):
|
||||
'''
|
||||
"""
|
||||
replace confignames with the real thing
|
||||
'''
|
||||
"""
|
||||
|
||||
replacements = replacements.copy()
|
||||
if remove_multiple:
|
||||
@@ -844,11 +845,12 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
|
||||
def statusInfoComplete(self, item):
|
||||
return item['id'] and item['downloader'] and item['folder']
|
||||
|
||||
|
||||
def movieInFromFolder(self, movie_folder):
|
||||
return movie_folder and self.conf('from') in movie_folder or not movie_folder
|
||||
|
||||
def extractFiles(self, folder = None, movie_folder = None, files = [], cleanup = False):
|
||||
def extractFiles(self, folder = None, movie_folder = None, files = None, cleanup = False):
|
||||
if not files: files = []
|
||||
|
||||
# RegEx for finding rar files
|
||||
archive_regex = '(?P<file>^(?P<base>(?:(?!\.part\d+\.rar$).)*)\.(?:(?:part0*1\.)?rar)$)'
|
||||
@@ -873,7 +875,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
#Extract all found archives
|
||||
for archive in archives:
|
||||
# Check if it has already been processed by CPS
|
||||
if (self.hastagDir(os.path.dirname(archive['file']))):
|
||||
if self.hastagDir(os.path.dirname(archive['file'])):
|
||||
continue
|
||||
|
||||
# Find all related archive files
|
||||
@@ -942,7 +944,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
self.makeDir(os.path.dirname(move_to))
|
||||
self.moveFile(leftoverfile, move_to, cleanup)
|
||||
except Exception, e:
|
||||
log.error('Failed moving left over file %s to %s: %s %s',(leftoverfile, move_to, e, traceback.format_exc()))
|
||||
log.error('Failed moving left over file %s to %s: %s %s', (leftoverfile, move_to, e, traceback.format_exc()))
|
||||
# As we probably tried to overwrite the nfo file, check if it exists and then remove the original
|
||||
if os.path.isfile(move_to):
|
||||
if cleanup:
|
||||
@@ -965,9 +967,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
if extr_files:
|
||||
files.extend(extr_files)
|
||||
|
||||
# Cleanup files and folder if movie_folder was not provided
|
||||
# Cleanup files and folder if movie_folder was not provided
|
||||
if not movie_folder:
|
||||
files = []
|
||||
folder = None
|
||||
|
||||
return (folder, movie_folder, files, extr_files)
|
||||
return folder, movie_folder, files, extr_files
|
||||
|
||||
@@ -429,7 +429,7 @@ class Scanner(Plugin):
|
||||
if len(processed_movies) > 0:
|
||||
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
||||
else:
|
||||
log.debug('Found no movies in the folder %s', (folder))
|
||||
log.debug('Found no movies in the folder %s', folder)
|
||||
|
||||
return processed_movies
|
||||
|
||||
@@ -508,6 +508,7 @@ class Scanner(Plugin):
|
||||
detected_languages = {}
|
||||
|
||||
# Subliminal scanner
|
||||
paths = None
|
||||
try:
|
||||
paths = group['files']['movie']
|
||||
scan_result = []
|
||||
@@ -560,12 +561,14 @@ class Scanner(Plugin):
|
||||
break
|
||||
|
||||
# Check and see if nfo contains the imdb-id
|
||||
nfo_file = None
|
||||
if not imdb_id:
|
||||
try:
|
||||
for nfo_file in files['nfo']:
|
||||
imdb_id = getImdb(nfo_file)
|
||||
for nf in files['nfo']:
|
||||
imdb_id = getImdb(nf)
|
||||
if imdb_id:
|
||||
log.debug('Found movie via nfo file: %s', nfo_file)
|
||||
log.debug('Found movie via nfo file: %s', nf)
|
||||
nfo_file = nf
|
||||
break
|
||||
except:
|
||||
pass
|
||||
@@ -585,26 +588,16 @@ class Scanner(Plugin):
|
||||
# Check if path is already in db
|
||||
if not imdb_id:
|
||||
db = get_session()
|
||||
for cur_file in files['movie']:
|
||||
f = db.query(File).filter_by(path = toUnicode(cur_file)).first()
|
||||
for cf in files['movie']:
|
||||
f = db.query(File).filter_by(path = toUnicode(cf)).first()
|
||||
try:
|
||||
imdb_id = f.library[0].identifier
|
||||
log.debug('Found movie via database: %s', cur_file)
|
||||
log.debug('Found movie via database: %s', cf)
|
||||
cur_file = cf
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
# Search based on OpenSubtitleHash
|
||||
if not imdb_id and not group['is_dvd']:
|
||||
for cur_file in files['movie']:
|
||||
movie = fireEvent('movie.by_hash', file = cur_file, merge = True)
|
||||
|
||||
if len(movie) > 0:
|
||||
imdb_id = movie[0].get('imdb')
|
||||
if imdb_id:
|
||||
log.debug('Found movie via OpenSubtitleHash: %s', cur_file)
|
||||
break
|
||||
|
||||
# Search based on identifiers
|
||||
if not imdb_id:
|
||||
for identifier in group['identifiers']:
|
||||
@@ -691,10 +684,9 @@ class Scanner(Plugin):
|
||||
return getExt(s.lower()) in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tbn']
|
||||
files = set(filter(test, files))
|
||||
|
||||
images = {}
|
||||
|
||||
# Fanart
|
||||
images['backdrop'] = set(filter(lambda s: re.search('(^|[\W_])fanart|backdrop\d*[\W_]', s.lower()) and self.filesizeBetween(s, 0, 5), files))
|
||||
images = {
|
||||
'backdrop': set(filter(lambda s: re.search('(^|[\W_])fanart|backdrop\d*[\W_]', s.lower()) and self.filesizeBetween(s, 0, 5), files))
|
||||
}
|
||||
|
||||
# Rest
|
||||
images['rest'] = files - images['backdrop']
|
||||
|
||||
@@ -17,7 +17,7 @@ class Score(Plugin):
|
||||
addEvent('score.calculate', self.calculate)
|
||||
|
||||
def calculate(self, nzb, movie):
|
||||
''' Calculate the score of a NZB, used for sorting later '''
|
||||
""" Calculate the score of a NZB, used for sorting later """
|
||||
|
||||
# Merge global and category
|
||||
preferred_words = splitString(Env.setting('preferred_words', section = 'searcher').lower())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString
|
||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.environment import Env
|
||||
import re
|
||||
|
||||
@@ -24,7 +24,7 @@ name_scores = [
|
||||
|
||||
|
||||
def nameScore(name, year, preferred_words):
|
||||
''' Calculate score for words in the NZB name '''
|
||||
""" Calculate score for words in the NZB name """
|
||||
|
||||
score = 0
|
||||
name = name.lower()
|
||||
@@ -34,11 +34,11 @@ def nameScore(name, year, preferred_words):
|
||||
v = value.split(':')
|
||||
add = int(v.pop())
|
||||
if v.pop() in name:
|
||||
score = score + add
|
||||
score += add
|
||||
|
||||
# points if the year is correct
|
||||
if str(year) in name:
|
||||
score = score + 5
|
||||
score += 5
|
||||
|
||||
# Contains preferred word
|
||||
nzb_words = re.split('\W+', simplifyString(name))
|
||||
|
||||
@@ -75,7 +75,7 @@ class StatusPlugin(Plugin):
|
||||
|
||||
def get(self, identifiers):
|
||||
|
||||
if not isinstance(identifiers, (list)):
|
||||
if not isinstance(identifiers, list):
|
||||
identifiers = [identifiers]
|
||||
|
||||
db = get_session()
|
||||
|
||||
@@ -36,13 +36,12 @@ class Subtitle(Plugin):
|
||||
|
||||
files = []
|
||||
for file in release.files.filter(FileType.status.has(identifier = 'movie')).all():
|
||||
files.append(file.path);
|
||||
files.append(file.path)
|
||||
|
||||
# get subtitles for those files
|
||||
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
|
||||
|
||||
def searchSingle(self, group):
|
||||
|
||||
if self.isDisabled(): return
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
from couchpotato.core.helpers.variable import splitString, md5
|
||||
from couchpotato.core.helpers.variable import splitString
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Media
|
||||
from couchpotato.core.settings.model import Media, Library
|
||||
from couchpotato.environment import Env
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import or_
|
||||
|
||||
|
||||
class Suggestion(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
@@ -15,44 +16,53 @@ class Suggestion(Plugin):
|
||||
addApiView('suggestion.view', self.suggestView)
|
||||
addApiView('suggestion.ignore', self.ignoreView)
|
||||
|
||||
def suggestView(self, **kwargs):
|
||||
def suggestView(self, limit = 6, **kwargs):
|
||||
|
||||
movies = splitString(kwargs.get('movies', ''))
|
||||
ignored = splitString(kwargs.get('ignored', ''))
|
||||
limit = kwargs.get('limit', 6)
|
||||
|
||||
if not movies or len(movies) == 0:
|
||||
db = get_session()
|
||||
active_movies = db.query(Media) \
|
||||
.filter(or_(*[Media.status.has(identifier = s) for s in ['active', 'done']])).all()
|
||||
movies = [x.library.identifier for x in active_movies]
|
||||
|
||||
if not ignored or len(ignored) == 0:
|
||||
ignored = splitString(Env.prop('suggest_ignore', default = ''))
|
||||
seen = splitString(kwargs.get('seen', ''))
|
||||
|
||||
cached_suggestion = self.getCache('suggestion_cached')
|
||||
if cached_suggestion:
|
||||
suggestions = cached_suggestion
|
||||
else:
|
||||
|
||||
if not movies or len(movies) == 0:
|
||||
db = get_session()
|
||||
active_movies = db.query(Media) \
|
||||
.options(joinedload_all('library')) \
|
||||
.filter(or_(*[Media.status.has(identifier = s) for s in ['active', 'done']])).all()
|
||||
movies = [x.library.identifier for x in active_movies]
|
||||
|
||||
if not ignored or len(ignored) == 0:
|
||||
ignored = splitString(Env.prop('suggest_ignore', default = ''))
|
||||
if not seen or len(seen) == 0:
|
||||
movies.extend(splitString(Env.prop('suggest_seen', default = '')))
|
||||
|
||||
suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True)
|
||||
self.setCache(md5(ss('suggestion_cached')), suggestions, timeout = 6048000) # Cache for 10 weeks
|
||||
self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'count': len(suggestions),
|
||||
'suggestions': suggestions[:limit]
|
||||
'suggestions': suggestions[:int(limit)]
|
||||
}
|
||||
|
||||
def ignoreView(self, imdb = None, limit = 6, remove_only = False, **kwargs):
|
||||
def ignoreView(self, imdb = None, limit = 6, remove_only = False, mark_seen = False, **kwargs):
|
||||
|
||||
ignored = splitString(Env.prop('suggest_ignore', default = ''))
|
||||
seen = splitString(Env.prop('suggest_seen', default = ''))
|
||||
|
||||
new_suggestions = []
|
||||
if imdb:
|
||||
if not remove_only:
|
||||
if mark_seen:
|
||||
seen.append(imdb)
|
||||
Env.prop('suggest_seen', ','.join(set(seen)))
|
||||
elif not remove_only:
|
||||
ignored.append(imdb)
|
||||
Env.prop('suggest_ignore', ','.join(set(ignored)))
|
||||
|
||||
new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored)
|
||||
new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen)
|
||||
|
||||
return {
|
||||
'result': True,
|
||||
@@ -60,12 +70,13 @@ class Suggestion(Plugin):
|
||||
'suggestions': new_suggestions[limit - 1:limit]
|
||||
}
|
||||
|
||||
def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None):
|
||||
def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None):
|
||||
|
||||
# Combine with previous suggestion_cache
|
||||
cached_suggestion = self.getCache('suggestion_cached')
|
||||
new_suggestions = []
|
||||
ignored = [] if not ignored else ignored
|
||||
seen = [] if not seen else seen
|
||||
|
||||
if ignore_imdb:
|
||||
for cs in cached_suggestion:
|
||||
@@ -75,10 +86,15 @@ class Suggestion(Plugin):
|
||||
# Get new results and add them
|
||||
if len(new_suggestions) - 1 < limit:
|
||||
|
||||
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
|
||||
|
||||
db = get_session()
|
||||
active_movies = db.query(Media) \
|
||||
.filter(or_(*[Media.status.has(identifier = s) for s in ['active', 'done']])).all()
|
||||
movies = [x.library.identifier for x in active_movies]
|
||||
.join(Library) \
|
||||
.with_entities(Library.identifier) \
|
||||
.filter(Media.status_id.in_([active_status.get('id'), done_status.get('id')])).all()
|
||||
movies = [x[0] for x in active_movies]
|
||||
movies.extend(seen)
|
||||
|
||||
ignored.extend([x.get('imdb') for x in cached_suggestion])
|
||||
suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True)
|
||||
@@ -86,6 +102,6 @@ class Suggestion(Plugin):
|
||||
if suggestions:
|
||||
new_suggestions.extend(suggestions)
|
||||
|
||||
self.setCache(md5(ss('suggestion_cached')), new_suggestions, timeout = 6048000)
|
||||
self.setCache('suggestion_cached', new_suggestions, timeout = 6048000)
|
||||
|
||||
return new_suggestions
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
display: none;
|
||||
width: 120px;
|
||||
width: 140px;
|
||||
}
|
||||
.suggestions .movie_result:hover .actions {
|
||||
display: block;
|
||||
|
||||
@@ -26,6 +26,20 @@ var SuggestList = new Class({
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
|
||||
},
|
||||
'click:relay(a.eye-open)': function(e, el){
|
||||
(e).stop();
|
||||
|
||||
$(el).getParent('.movie_result').destroy();
|
||||
|
||||
Api.request('suggestion.ignore', {
|
||||
'data': {
|
||||
'imdb': el.get('data-seen'),
|
||||
'mark_seen': 1
|
||||
},
|
||||
'onComplete': self.fill.bind(self)
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}).grab(
|
||||
@@ -43,7 +57,7 @@ var SuggestList = new Class({
|
||||
fill: function(json){
|
||||
|
||||
var self = this;
|
||||
|
||||
|
||||
if(!json) return;
|
||||
|
||||
Object.each(json.suggestions, function(movie){
|
||||
@@ -69,6 +83,10 @@ var SuggestList = new Class({
|
||||
new Element('a.delete.icon2', {
|
||||
'title': 'Don\'t suggest this movie again',
|
||||
'data-ignore': movie.imdb
|
||||
}),
|
||||
new Element('a.eye-open.icon2', {
|
||||
'title': 'Seen it, like it, don\'t add',
|
||||
'data-seen': movie.imdb
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -89,6 +107,8 @@ var SuggestList = new Class({
|
||||
|
||||
});
|
||||
|
||||
self.fireEvent('loaded');
|
||||
|
||||
},
|
||||
|
||||
afterAdded: function(m, movie){
|
||||
|
||||
@@ -12,8 +12,8 @@ class Trailer(Plugin):
|
||||
def __init__(self):
|
||||
addEvent('renamer.after', self.searchSingle)
|
||||
|
||||
def searchSingle(self, message = None, group = {}):
|
||||
|
||||
def searchSingle(self, message = None, group = None):
|
||||
if not group: group = {}
|
||||
if self.isDisabled() or len(group['files']['trailer']) > 0: return
|
||||
|
||||
trailers = fireEvent('trailer.search', group = group, merge = True)
|
||||
@@ -40,4 +40,3 @@ class Trailer(Plugin):
|
||||
break
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class IMDBWatchlist(IMDBBase):
|
||||
break
|
||||
|
||||
except:
|
||||
log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc()))
|
||||
log.error('Failed loading IMDB watchlist: %s %s', (watchlist_url, traceback.format_exc()))
|
||||
|
||||
return movies
|
||||
|
||||
|
||||
@@ -80,7 +80,10 @@ class CouchPotatoApi(MovieProvider):
|
||||
|
||||
return dates
|
||||
|
||||
def getSuggestions(self, movies = [], ignore = []):
|
||||
def getSuggestions(self, movies = None, ignore = None):
|
||||
if not ignore: ignore = []
|
||||
if not movies: movies = []
|
||||
|
||||
suggestions = self.getJsonData(self.urls['suggest'], params = {
|
||||
'movies': ','.join(movies),
|
||||
'ignore': ','.join(ignore),
|
||||
|
||||
@@ -98,7 +98,7 @@ class OMDBAPI(MovieProvider):
|
||||
'mpaa': str(movie.get('Rated', '')),
|
||||
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
|
||||
'released': movie.get('Released'),
|
||||
'year': year if isinstance(year, (int)) else None,
|
||||
'year': year if isinstance(year, int) else None,
|
||||
'plot': movie.get('Plot'),
|
||||
'genres': splitString(movie.get('Genre', '')),
|
||||
'directors': splitString(movie.get('Director', '')),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.info.base import MovieProvider
|
||||
from themoviedb import tmdb
|
||||
import tmdb3
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -11,47 +12,16 @@ log = CPLog(__name__)
|
||||
class TheMovieDb(MovieProvider):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('movie.by_hash', self.byHash)
|
||||
addEvent('movie.search', self.search, priority = 2)
|
||||
addEvent('movie.info', self.getInfo, priority = 2)
|
||||
addEvent('movie.info_by_tmdb', self.getInfoByTMDBId)
|
||||
addEvent('movie.info_by_tmdb', self.getInfo)
|
||||
|
||||
# Use base wrapper
|
||||
tmdb.configure(self.conf('api_key'))
|
||||
|
||||
def byHash(self, file):
|
||||
''' Find movie by hash '''
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
cache_key = 'tmdb.cache.%s' % simplifyString(file)
|
||||
results = self.getCache(cache_key)
|
||||
|
||||
if not results:
|
||||
log.debug('Searching for movie by hash: %s', file)
|
||||
try:
|
||||
raw = tmdb.searchByHashingFile(file)
|
||||
|
||||
results = []
|
||||
if raw:
|
||||
try:
|
||||
results = self.parseMovie(raw)
|
||||
log.info('Found: %s', results['titles'][0] + ' (' + str(results.get('year', 0)) + ')')
|
||||
|
||||
self.setCache(cache_key, results)
|
||||
return results
|
||||
except SyntaxError, e:
|
||||
log.error('Failed to parse XML response: %s', e)
|
||||
return False
|
||||
except:
|
||||
log.debug('No movies known by hash for: %s', file)
|
||||
pass
|
||||
|
||||
return results
|
||||
# Configure TMDB settings
|
||||
tmdb3.set_key(self.conf('api_key'))
|
||||
tmdb3.set_cache('null')
|
||||
|
||||
def search(self, q, limit = 12):
|
||||
''' Find movie by name '''
|
||||
""" Find movie by name """
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
@@ -65,7 +35,7 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
raw = None
|
||||
try:
|
||||
raw = tmdb.search(search_string)
|
||||
raw = tmdb3.searchMovie(search_string)
|
||||
except:
|
||||
log.error('Failed searching TMDB for "%s": %s', (search_string, traceback.format_exc()))
|
||||
|
||||
@@ -75,7 +45,7 @@ class TheMovieDb(MovieProvider):
|
||||
nr = 0
|
||||
|
||||
for movie in raw:
|
||||
results.append(self.parseMovie(movie))
|
||||
results.append(self.parseMovie(movie, with_titles = False))
|
||||
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
@@ -100,117 +70,87 @@ class TheMovieDb(MovieProvider):
|
||||
result = self.getCache(cache_key)
|
||||
|
||||
if not result:
|
||||
result = {}
|
||||
movie = None
|
||||
|
||||
try:
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
movie = tmdb.imdbLookup(id = identifier)
|
||||
except:
|
||||
pass
|
||||
|
||||
if movie:
|
||||
result = self.parseMovie(movie[0])
|
||||
self.setCache(cache_key, result)
|
||||
|
||||
return result
|
||||
|
||||
def getInfoByTMDBId(self, id = None):
|
||||
|
||||
cache_key = 'tmdb.cache.%s' % id
|
||||
result = self.getCache(cache_key)
|
||||
|
||||
if not result:
|
||||
result = {}
|
||||
movie = None
|
||||
|
||||
try:
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
movie = tmdb.getMovieInfo(id = id)
|
||||
except:
|
||||
pass
|
||||
|
||||
if movie:
|
||||
movie = tmdb3.Movie(identifier)
|
||||
result = self.parseMovie(movie)
|
||||
self.setCache(cache_key, result)
|
||||
except:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def parseMovie(self, movie):
|
||||
def parseMovie(self, movie, with_titles = True):
|
||||
|
||||
# Images
|
||||
poster = self.getImage(movie, type = 'poster', size = 'cover')
|
||||
#backdrop = self.getImage(movie, type = 'backdrop', size = 'w1280')
|
||||
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
||||
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
||||
cache_key = 'tmdb.cache.%s' % movie.id
|
||||
movie_data = self.getCache(cache_key)
|
||||
|
||||
# Genres
|
||||
try:
|
||||
genres = self.getCategory(movie, 'genre')
|
||||
except:
|
||||
genres = []
|
||||
if not movie_data:
|
||||
|
||||
# 1900 is the same as None
|
||||
year = str(movie.get('released', 'none'))[:4]
|
||||
if year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
# Images
|
||||
poster = self.getImage(movie, type = 'poster', size = 'poster')
|
||||
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
||||
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
||||
|
||||
movie_data = {
|
||||
'via_tmdb': True,
|
||||
'tmdb_id': int(movie.get('id', 0)),
|
||||
'titles': [toUnicode(movie.get('name'))],
|
||||
'original_title': movie.get('original_name'),
|
||||
'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 [],
|
||||
},
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'mpaa': movie.get('certification', ''),
|
||||
'runtime': movie.get('runtime'),
|
||||
'released': movie.get('released'),
|
||||
'year': year,
|
||||
'plot': movie.get('overview'),
|
||||
'genres': genres,
|
||||
}
|
||||
# Genres
|
||||
try:
|
||||
genres = [genre.name for genre in movie.genres]
|
||||
except:
|
||||
genres = []
|
||||
|
||||
movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
|
||||
# 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
|
||||
|
||||
# Add alternative names
|
||||
for alt in ['original_name', 'alternative_name']:
|
||||
alt_name = toUnicode(movie.get(alt))
|
||||
if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name != None:
|
||||
movie_data['titles'].append(alt_name)
|
||||
movie_data = {
|
||||
'via_tmdb': True,
|
||||
'tmdb_id': movie.id,
|
||||
'titles': [toUnicode(movie.title)],
|
||||
'original_title': movie.originaltitle,
|
||||
'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 [],
|
||||
},
|
||||
'imdb': movie.imdb,
|
||||
'runtime': movie.runtime,
|
||||
'released': str(movie.releasedate),
|
||||
'year': year,
|
||||
'plot': movie.overview,
|
||||
'genres': genres,
|
||||
}
|
||||
|
||||
movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
|
||||
|
||||
# Add alternative names
|
||||
if with_titles:
|
||||
movie_data['titles'].append(movie.originaltitle)
|
||||
for alt in movie.alternate_titles:
|
||||
alt_name = alt.title
|
||||
if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
|
||||
movie_data['titles'].append(alt_name)
|
||||
|
||||
movie_data['titles'] = list(set(movie_data['titles']))
|
||||
|
||||
# Cache movie parsed
|
||||
self.setCache(cache_key, movie_data)
|
||||
|
||||
return movie_data
|
||||
|
||||
def getImage(self, movie, type = 'poster', size = 'cover'):
|
||||
def getImage(self, movie, type = 'poster', size = 'poster'):
|
||||
|
||||
image_url = ''
|
||||
for image in movie.get('images', []):
|
||||
if(image.get('type') == type) and image.get(size):
|
||||
image_url = image.get(size)
|
||||
break
|
||||
try:
|
||||
image_url = getattr(movie, type).geturl(size='original')
|
||||
except:
|
||||
log.debug('Failed getting %s.%s for "%s"', (type, size, movie.title))
|
||||
|
||||
return image_url
|
||||
|
||||
def getCategory(self, movie, type = 'genre'):
|
||||
|
||||
cats = movie.get('categories', {}).get(type)
|
||||
|
||||
categories = []
|
||||
for category in cats:
|
||||
try:
|
||||
categories.append(category)
|
||||
except:
|
||||
pass
|
||||
|
||||
return categories
|
||||
|
||||
def isDisabled(self):
|
||||
if self.conf('api_key') == '':
|
||||
log.error('No API key provided.')
|
||||
True
|
||||
else:
|
||||
False
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -17,8 +17,9 @@ class MetaDataBase(Plugin):
|
||||
def __init__(self):
|
||||
addEvent('renamer.after', self.create)
|
||||
|
||||
def create(self, message = None, group = {}):
|
||||
def create(self, message = None, group = None):
|
||||
if self.isDisabled(): return
|
||||
if not group: group = {}
|
||||
|
||||
log.info('Creating %s metadata.', self.getName())
|
||||
|
||||
@@ -48,6 +49,11 @@ class MetaDataBase(Plugin):
|
||||
log.debug('Creating %s file: %s', (file_type, name))
|
||||
if os.path.isfile(content):
|
||||
shutil.copy2(content, name)
|
||||
shutil.copyfile(content, name)
|
||||
|
||||
# Try and copy stats seperately
|
||||
try: shutil.copystat(content, name)
|
||||
except: pass
|
||||
else:
|
||||
self.createFile(name, content)
|
||||
group['renamed_files'].append(name)
|
||||
@@ -60,7 +66,8 @@ class MetaDataBase(Plugin):
|
||||
except:
|
||||
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
|
||||
|
||||
def getRootName(self, data = {}):
|
||||
def getRootName(self, data = None):
|
||||
if not data: data = {}
|
||||
return os.path.join(data['destination_dir'], data['filename'])
|
||||
|
||||
def getFanartName(self, name, root):
|
||||
@@ -72,13 +79,19 @@ class MetaDataBase(Plugin):
|
||||
def getNfoName(self, name, root):
|
||||
return
|
||||
|
||||
def getNfo(self, movie_info = {}, data = {}):
|
||||
return
|
||||
def getNfo(self, movie_info = None, data = None):
|
||||
if not data: data = {}
|
||||
if not movie_info: movie_info = {}
|
||||
|
||||
def getThumbnail(self, movie_info = {}, data = {}, wanted_file_type = 'poster_original'):
|
||||
def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original'):
|
||||
if not data: data = {}
|
||||
if not movie_info: movie_info = {}
|
||||
file_types = fireEvent('file.types', single = True)
|
||||
for file_type in file_types:
|
||||
if file_type.get('identifier') == wanted_file_type:
|
||||
file_type = {}
|
||||
|
||||
for ft in file_types:
|
||||
if ft.get('identifier') == wanted_file_type:
|
||||
file_type = ft
|
||||
break
|
||||
|
||||
# See if it is in current files
|
||||
@@ -94,5 +107,7 @@ class MetaDataBase(Plugin):
|
||||
except:
|
||||
pass
|
||||
|
||||
def getFanart(self, movie_info = {}, data = {}):
|
||||
def getFanart(self, movie_info = None, data = None):
|
||||
if not data: data = {}
|
||||
if not movie_info: movie_info = {}
|
||||
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original')
|
||||
|
||||
@@ -24,7 +24,9 @@ class XBMC(MetaDataBase):
|
||||
def createMetaName(self, basename, name, root):
|
||||
return os.path.join(root, basename.replace('%s', name))
|
||||
|
||||
def getNfo(self, movie_info = {}, data = {}):
|
||||
def getNfo(self, movie_info = None, data = None):
|
||||
if not data: data = {}
|
||||
if not movie_info: movie_info = {}
|
||||
|
||||
# return imdb url only
|
||||
if self.conf('meta_url_only'):
|
||||
|
||||
@@ -86,8 +86,10 @@ class BinSearch(NZBProvider):
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
|
||||
params = {'action': 'nzb'}
|
||||
params[nzb_id] = 'on'
|
||||
params = {
|
||||
'action': 'nzb',
|
||||
nzb_id: 'on'
|
||||
}
|
||||
|
||||
try:
|
||||
return self.urlopen(url, params = params, show_error = False)
|
||||
|
||||
@@ -118,7 +118,7 @@ class Newznab(NZBProvider, RSS):
|
||||
|
||||
return list
|
||||
|
||||
def belongsTo(self, url, provider = None):
|
||||
def belongsTo(self, url, provider = None, host = None):
|
||||
|
||||
hosts = self.getHosts()
|
||||
|
||||
|
||||
@@ -68,14 +68,21 @@ class PublicHD(TorrentMagnetProvider):
|
||||
|
||||
def getMoreInfo(self, item):
|
||||
|
||||
try:
|
||||
full_description = self.getCache('publichd.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||
html = BeautifulSoup(full_description)
|
||||
nfo_pre = html.find('div', attrs = {'id':'torrmain'})
|
||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||
except:
|
||||
log.error('Failed getting more info for %s', item['name'])
|
||||
description = ''
|
||||
cache_key = 'publichd.%s' % item['id']
|
||||
description = self.getCache(cache_key)
|
||||
|
||||
if not description:
|
||||
|
||||
try:
|
||||
full_description = self.urlopen(item['detail_url'])
|
||||
html = BeautifulSoup(full_description)
|
||||
nfo_pre = html.find('div', attrs = {'id':'torrmain'})
|
||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||
except:
|
||||
log.error('Failed getting more info for %s', item['name'])
|
||||
description = ''
|
||||
|
||||
self.setCache(cache_key, description, timeout = 25920000)
|
||||
|
||||
item['description'] = description
|
||||
return item
|
||||
|
||||
@@ -65,7 +65,7 @@ class SceneHD(TorrentProvider):
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
|
||||
def getLoginParams(self, params):
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
|
||||
@@ -86,10 +86,10 @@ class ThePirateBay(TorrentMagnetProvider):
|
||||
if link and download:
|
||||
|
||||
def extra_score(item):
|
||||
trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None]
|
||||
vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None]
|
||||
confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None]
|
||||
moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None]
|
||||
trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) is not None]
|
||||
vip = (0, 20)[result.find('img', alt = re.compile('VIP')) is not None]
|
||||
confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) is not None]
|
||||
moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) is not None]
|
||||
|
||||
return confirmed + trusted + vip + moderated
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@ class AlloCine(UserscriptBase):
|
||||
except:
|
||||
return
|
||||
|
||||
name = None
|
||||
year = None
|
||||
|
||||
try:
|
||||
start = data.find('<title>')
|
||||
end = data.find('</title>', start)
|
||||
|
||||
@@ -9,7 +9,7 @@ class TMDB(UserscriptBase):
|
||||
|
||||
def getMovie(self, url):
|
||||
match = re.search('(?P<id>\d+)', url)
|
||||
movie = fireEvent('movie.info_by_tmdb', id = match.group('id'), merge = True)
|
||||
movie = fireEvent('movie.info_by_tmdb', identifier = match.group('id'), merge = True)
|
||||
|
||||
if movie['imdb']:
|
||||
return self.getInfo(movie['imdb'])
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import isInt, toUnicode
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import mergeDicts, tryInt, tryFloat
|
||||
from couchpotato.core.settings.model import Properties
|
||||
import ConfigParser
|
||||
import os.path
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
class Settings(object):
|
||||
@@ -75,7 +72,9 @@ class Settings(object):
|
||||
addEvent('settings.register', self.registerDefaults)
|
||||
addEvent('settings.save', self.save)
|
||||
|
||||
def registerDefaults(self, section_name, options = {}, save = True):
|
||||
def registerDefaults(self, section_name, options = None, save = True):
|
||||
if not options: options = {}
|
||||
|
||||
self.addSection(section_name)
|
||||
|
||||
for option_name, option in options.iteritems():
|
||||
@@ -92,7 +91,7 @@ class Settings(object):
|
||||
self.setType(section_name, option_name, option.get('type'))
|
||||
|
||||
if save:
|
||||
self.save(self)
|
||||
self.save()
|
||||
|
||||
def set(self, section, option, value):
|
||||
return self.p.set(section, option, value)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from UserDict import DictMixin
|
||||
from collections import OrderedDict
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from elixir.entity import Entity
|
||||
from elixir.fields import Field
|
||||
@@ -5,9 +7,7 @@ from elixir.options import options_defaults, using_options
|
||||
from elixir.relationships import ManyToMany, OneToMany, ManyToOne
|
||||
from sqlalchemy.ext.mutable import Mutable
|
||||
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \
|
||||
TypeDecorator, Float, BLOB
|
||||
from UserDict import DictMixin
|
||||
from collections import OrderedDict
|
||||
TypeDecorator
|
||||
import json
|
||||
import time
|
||||
|
||||
@@ -240,7 +240,10 @@ class Release(Entity):
|
||||
files = ManyToMany('File')
|
||||
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
|
||||
|
||||
def to_dict(self, deep = {}, exclude = []):
|
||||
def to_dict(self, deep = None, exclude = None):
|
||||
if not exclude: exclude = []
|
||||
if not deep: deep = {}
|
||||
|
||||
orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude)
|
||||
|
||||
new_info = {}
|
||||
@@ -302,7 +305,10 @@ class Profile(Entity):
|
||||
media = OneToMany('Media')
|
||||
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
|
||||
|
||||
def to_dict(self, deep = {}, exclude = []):
|
||||
def to_dict(self, deep = None, exclude = None):
|
||||
if not exclude: exclude = []
|
||||
if not deep: deep = {}
|
||||
|
||||
orig_dict = super(Profile, self).to_dict(deep = deep, exclude = exclude)
|
||||
orig_dict['core'] = orig_dict.get('core') or False
|
||||
orig_dict['hide'] = orig_dict.get('hide') or False
|
||||
|
||||
@@ -74,7 +74,7 @@ class Env(object):
|
||||
s = Env.get('settings')
|
||||
|
||||
# Return setting
|
||||
if value == None:
|
||||
if value is None:
|
||||
return s.get(attr, default = default, section = section, type = type)
|
||||
|
||||
# Set setting
|
||||
@@ -86,7 +86,7 @@ class Env(object):
|
||||
@staticmethod
|
||||
def prop(identifier, value = None, default = None):
|
||||
s = Env.get('settings')
|
||||
if value == None:
|
||||
if value is None:
|
||||
v = s.getProperty(identifier)
|
||||
return v if v else default
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from argparse import ArgumentParser
|
||||
from cache import FileSystemCache
|
||||
from couchpotato import KeyHandler
|
||||
from couchpotato import KeyHandler, LoginHandler, LogoutHandler
|
||||
from couchpotato.api import NonBlockHandler, ApiHandler
|
||||
from couchpotato.core.event import fireEventAsync, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
@@ -91,7 +91,12 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
|
||||
for src_file in src_files:
|
||||
if os.path.isfile(src_file):
|
||||
shutil.copy2(src_file, toUnicode(os.path.join(new_backup, os.path.basename(src_file))))
|
||||
dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file)))
|
||||
shutil.copyfile(src_file, dst_file)
|
||||
|
||||
# Try and copy stats seperately
|
||||
try: shutil.copystat(src_file, dst_file)
|
||||
except: pass
|
||||
|
||||
# Remove older backups, keep backups 3 days or at least 3
|
||||
backups = []
|
||||
@@ -109,7 +114,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
try:
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
except Exception, e:
|
||||
except:
|
||||
raise
|
||||
|
||||
os.rmdir(backup)
|
||||
@@ -230,10 +235,11 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
log_function = lambda x : None,
|
||||
debug = config['use_reloader'],
|
||||
gzip = True,
|
||||
cookie_secret = api_key,
|
||||
login_url = '%slogin/' % web_base,
|
||||
)
|
||||
Env.set('app', application)
|
||||
|
||||
|
||||
# Request handlers
|
||||
application.add_handlers(".*$", [
|
||||
(r'%snonblock/(.*)(/?)' % api_base, NonBlockHandler),
|
||||
@@ -243,18 +249,22 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
(r'%sgetkey(/?)' % web_base, KeyHandler), # Get API key
|
||||
(r'%s' % api_base, RedirectHandler, {"url": web_base + 'docs/'}), # API docs
|
||||
|
||||
# Login handlers
|
||||
(r'%slogin(/?)' % web_base, LoginHandler),
|
||||
(r'%slogout(/?)' % web_base, LogoutHandler),
|
||||
|
||||
# Catch all webhandlers
|
||||
(r'%s(.*)(/?)' % web_base, WebHandler),
|
||||
(r'(.*)', WebHandler),
|
||||
])
|
||||
|
||||
# Static paths
|
||||
static_path = '%sstatic/' % api_base
|
||||
static_path = '%sstatic/' % web_base
|
||||
for dir_name in ['fonts', 'images', 'scripts', 'style']:
|
||||
application.add_handlers(".*$", [
|
||||
('%s%s/(.*)' % (static_path, dir_name), StaticFileHandler, {'path': toUnicode(os.path.join(base_path, 'couchpotato', 'static', dir_name))})
|
||||
])
|
||||
Env.set('static_path', static_path);
|
||||
Env.set('static_path', static_path)
|
||||
|
||||
|
||||
# Load configs & plugins
|
||||
|
||||
BIN
couchpotato/static/fonts/Lobster-webfont.eot
Executable file
BIN
couchpotato/static/fonts/Lobster-webfont.eot
Executable file
Binary file not shown.
244
couchpotato/static/fonts/Lobster-webfont.svg
Executable file
244
couchpotato/static/fonts/Lobster-webfont.svg
Executable file
@@ -0,0 +1,244 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="lobster_1.4regular" horiz-adv-x="1034" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="444" />
|
||||
<glyph unicode="fi" horiz-adv-x="1093" d="M-424 -283q0 53 21 96.5t51.5 72t82 55t93.5 41.5t106 34l228 1078q93 440 479 440q367 0 367 -236q0 -86 -46 -125.5t-116 -39.5q-52 0 -87.5 25t-35.5 85q0 37 19.5 68.5t49.5 31.5q15 0 17 -2q-1 39 -32 60.5t-81 21.5q-168 0 -217 -231l-35 -168h164l-16 -82h-164 l-234 -1106q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-114 0 -184 62t-70 167zM-311 -248q0 -32 17.5 -52t34 -25t32.5 -5q35 0 68 39.5t50 120.5l25 119q-102 -32 -164.5 -83.5t-62.5 -113.5zM530 233q0 51 17 134l139 657h295l-147 -696q-9 -40 -9 -66 q0 -42 20 -59t64 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -159 61t-61 184z" />
|
||||
<glyph unicode="fl" horiz-adv-x="1122" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t69 101t83 60.5t86.5 31t84.5 7.5q86 0 185.5 -22t170.5 -47.5t186 -71.5l-225 -1065q-8 -36 -8 -66q0 -42 20 -59t64 -17q59 0 109.5 51.5t74.5 129.5h86q-33 -95 -79 -167t-90.5 -111 t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184q0 56 16 134l213 1005q-35 15 -87.5 24t-79.5 9q-43 0 -82 -35t-58 -127l-45 -219h191l-17 -82h-190l-234 -1106q-18 -84 -49.5 -148t-68.5 -101.5t-83 -60.5t-86 -30.5t-85 -7.5q-97 0 -169.5 62t-72.5 159z M-315 -262q0 -23 25 -45.5t57 -22.5q36 0 69.5 38.5t50.5 121.5l25 119q-109 -36 -168 -85t-59 -126z" />
|
||||
<glyph unicode="ffi" horiz-adv-x="1564" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q170 0 252 -107q119 107 309 107q367 0 367 -236q0 -86 -46 -125.5t-116 -39.5q-52 0 -87.5 25t-35.5 85q0 37 19.5 68.5t49.5 31.5q15 0 17 -2 q-1 39 -32 60.5t-81 21.5q-168 0 -217 -231l-35 -168h543l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -159 61t-61 184q0 51 17 134l121 575h-246l-234 -1106 q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-75 0 -133.5 29t-89.5 80q-99 -109 -262 -109q-97 0 -169.5 62t-72.5 159zM-303 -262q0 -23 25 -45.5t57 -22.5q36 0 70 38.5t51 121.5l24 119q-109 -36 -168 -85t-59 -126zM145 -293q5 -36 29 -50.5t47 -14.5 q45 0 81.5 45.5t57.5 142.5l48 223l-183 -37l-39 -180q-13 -65 -41 -129zM238 74l180 30l176 838h-172zM438 1024h174l15 70q32 153 102 256q-31 34 -82 34q-78 0 -119.5 -56t-56.5 -136z" />
|
||||
<glyph unicode="ffl" horiz-adv-x="1597" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q152 0 235 -86q94 86 240 86q86 0 186 -22t171 -47.5t186 -71.5l-226 -1065q-8 -36 -8 -66q0 -42 20 -59t64 -17q59 0 109.5 51.5t74.5 129.5h86 q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184q0 51 17 134l213 1005q-35 15 -88 24t-80 9q-43 0 -82 -35t-58 -127l-45 -219h191l-17 -82h-190l-236 -1106q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-76 0 -134 29 t-89 82q-98 -111 -262 -111q-97 0 -169.5 62t-72.5 159zM-303 -262q0 -23 25 -45.5t57 -22.5q36 0 70 38.5t51 121.5l24 119q-109 -36 -168 -85t-59 -126zM145 -291q4 -37 28.5 -52t47.5 -15q45 0 81.5 45.5t57.5 142.5l48 223l-183 -37l-39 -180q-13 -63 -41 -127zM238 74 l180 30l178 838h-174zM438 1024h174l35 162q19 99 66 178q-27 20 -66 20q-78 0 -119.5 -56t-56.5 -136z" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph unicode="
" horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="444" />
|
||||
<glyph unicode="	" horiz-adv-x="444" />
|
||||
<glyph unicode=" " horiz-adv-x="444" />
|
||||
<glyph unicode="!" horiz-adv-x="526" d="M70 164q0 67 47.5 114.5t115.5 47.5q67 0 114.5 -47t47.5 -115t-47.5 -116t-114.5 -48t-115 48t-48 116zM201 473l145 961h295l-266 -961h-174z" />
|
||||
<glyph unicode=""" horiz-adv-x="860" d="M127 1149l125 387h215l-186 -387h-154zM449 1149l124 387h215l-186 -387h-153z" />
|
||||
<glyph unicode="#" horiz-adv-x="1349" d="M92 227l43 209h213l43 232h-209l43 209h203l107 593h180l-133 -593h215l106 593h180l-133 -593h213l-41 -209h-217l-51 -232h219l-41 -209h-225l-62 -282h-116l51 282h-242l-61 -282h-117l51 282h-219zM485 436h232l43 232h-223z" />
|
||||
<glyph unicode="$" horiz-adv-x="825" d="M49 455q0 78 43 127t105 49q41 0 64.5 -15t23.5 -36q-82 -14 -82 -152q0 -42 25 -75.5t61 -49.5l73 402q-16 14 -49 39.5t-51.5 41t-44 41t-39 46.5t-23.5 50.5t-10 61.5q0 109 98 186t218 82l39 217h118l-51 -223q178 -28 178 -170q0 -71 -26.5 -113t-71.5 -42 q-42 0 -80 47q49 28 49 106q0 26 -16.5 51.5t-48.5 38.5l-74 -329l46.5 -31t43.5 -31t46 -38t36.5 -41t33 -50t18 -55.5t8.5 -67.5q0 -142 -102 -221t-267 -82l-59 -274h-56l49 276q-103 8 -164 64t-61 170zM340 1024q0 -68 57 -125l49 277q-55 -11 -80.5 -55t-25.5 -97z M358 295q69 8 102.5 59.5t33.5 124.5q0 82 -60 156z" />
|
||||
<glyph unicode="%" horiz-adv-x="1318" d="M219 999q0 187 91 313t241 126q87 0 127.5 -58.5t40.5 -173.5q0 -68 -20.5 -142.5t-57 -142t-96 -111.5t-129.5 -44q-113 0 -155 57t-42 176zM248 0l790 1536h148l-791 -1536h-147zM367 975q0 -133 55 -133q53 0 93 56t60.5 145t20.5 194q0 62 -10.5 95.5t-40.5 33.5 q-50 0 -106.5 -83.5t-67.5 -219.5q-4 -50 -4 -88zM727 233q0 187 91 313t241 126q90 0 139 -60t49 -172q0 -55 -10.5 -112.5t-35 -117t-60.5 -106t-92.5 -75.5t-124.5 -29q-113 0 -155 57t-42 176zM864 209q0 -133 56 -133q53 0 91.5 52.5t57 137.5t18.5 191q0 139 -57 139 q-49 0 -100 -81t-62 -218q-4 -50 -4 -88z" />
|
||||
<glyph unicode="&" horiz-adv-x="1396" d="M23 401q0 102 39.5 193.5t125.5 163.5t205 96q-101 55 -152 137t-51 174q0 71 31 137t87 118.5t144 84t193 31.5q74 0 139.5 -15.5t120 -46.5t86 -85.5t31.5 -126.5q0 -87 -34.5 -138t-108.5 -51q-71 0 -117 53q35 10 62.5 67t27.5 101q0 15 -1 29t-9 38.5t-21 41.5 t-40 30t-64 13q-110 0 -180 -73.5t-70 -178.5q0 -96 66.5 -186.5t203.5 -151.5q-183 -75 -269.5 -185.5t-86.5 -226.5q0 -112 71 -192t169 -80q57 0 109.5 19t104 60.5t92.5 120t64 185.5q-18 1 -47.5 4.5t-53 5.5t-44.5 2q-134 0 -193 -92l-14 2q32 130 136.5 209.5 t258.5 79.5q29 0 80 -5t76 -5q61 0 115 22.5t79 65.5h13q0 -88 -93.5 -176t-218.5 -109q-25 -91 -56.5 -167t-63 -131.5t-71 -99.5t-73.5 -72t-78 -48.5t-76.5 -31t-77.5 -16t-73 -6.5t-70 -1q-103 0 -185 34.5t-133 93t-78 131.5t-27 154z" />
|
||||
<glyph unicode="'" horiz-adv-x="548" d="M209 1149l125 387h215l-187 -387h-153z" />
|
||||
<glyph unicode="(" horiz-adv-x="894" d="M283 205q0 200 48 406.5t133 386.5t211.5 323.5t274.5 214.5l27 -82q-148 -74 -275 -284t-198.5 -483t-71.5 -537q0 -381 150 -603l-68 -59q-116 128 -173.5 314t-57.5 403z" />
|
||||
<glyph unicode=")" horiz-adv-x="845" d="M-59 -430q148 74 274.5 283t198 482t71.5 539q0 382 -149 603l67 59q116 -128 174 -314t58 -403q0 -270 -81 -538t-235.5 -483t-351.5 -310z" />
|
||||
<glyph unicode="*" horiz-adv-x="1054" d="M176 1026l223 129l-223 129l62 107l219 -127v276h131v-276l219 127l61 -107l-223 -129l223 -129l-61 -106l-219 127v-277h-131v277l-219 -127z" />
|
||||
<glyph unicode="+" horiz-adv-x="1054" d="M109 420l49 229h262l55 250h232l-56 -250h260l-47 -229h-262l-55 -252h-232l56 252h-262z" />
|
||||
<glyph unicode="," horiz-adv-x="442" d="M49 154q0 68 47.5 115.5t114.5 47.5q66 0 102.5 -43t36.5 -112q0 -97 -72.5 -216.5t-193.5 -203.5q-18 13 -18 35q0 25 16.5 50t36.5 42.5t36.5 43t16.5 50.5q0 23 -27 41q-96 63 -96 150z" />
|
||||
<glyph unicode="-" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="." horiz-adv-x="538" d="M63 154q0 68 47.5 115.5t114.5 47.5q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="/" horiz-adv-x="636" d="M23 -512l434 2048h147l-434 -2048h-147z" />
|
||||
<glyph unicode="0" horiz-adv-x="1118" d="M61 520q0 167 31.5 320t92.5 279.5t146 219.5t198.5 145t242.5 52q188 0 276.5 -135.5t88.5 -399.5q0 -103 -19.5 -217t-56 -228.5t-93.5 -217.5t-127 -182.5t-162.5 -126.5t-193.5 -47q-104 0 -179 24t-122.5 66.5t-75 112t-37.5 148t-10 187.5zM375 463 q0 -309 121 -309q84 0 156 86t118 226t72 313.5t26 353.5q0 70 -4 118.5t-15 92t-33.5 65t-57.5 21.5q-47 0 -105.5 -56t-114 -152t-98.5 -244t-55 -312q-10 -138 -10 -203z" />
|
||||
<glyph unicode="1" horiz-adv-x="649" d="M43 0l270 1278h-194l24 94q112 0 233.5 31t287.5 129l-326 -1532h-295z" />
|
||||
<glyph unicode="2" horiz-adv-x="1007" d="M-23 61q0 91 37 178t96.5 160.5t131.5 147.5t144 151.5t131.5 159t96.5 184.5t37 213q0 72 -39 120t-86 48q-96 0 -131 -41t-35 -125q0 -43 19.5 -85.5t58.5 -53.5q-78 -69 -155 -69q-65 0 -106.5 48.5t-41.5 135.5q0 43 14 84.5t46 81.5t79.5 69.5t120.5 47.5t162 18 q389 0 389 -330q0 -94 -31 -180.5t-84 -157t-118 -134.5t-138 -123t-138.5 -112t-126 -113t-93.5 -114h10q56 0 183 -18.5t208 -18.5q30 0 54 2t47 11t36 13.5t35.5 23t30 24t35.5 33t37 35.5q2 -33 2 -90q0 -81 -10 -134t-36 -94.5t-74.5 -60.5t-123.5 -19q-113 0 -259 37 t-249 37q-21 0 -51 -10.5t-63.5 -26.5t-46.5 -20q-5 29 -5 67z" />
|
||||
<glyph unicode="3" horiz-adv-x="1052" d="M12 338q0 132 64.5 212.5t183.5 80.5q138 0 160 -105q-180 -14 -180 -221q0 -106 43 -144t112 -38q127 0 206 103t79 243q0 126 -68.5 232.5t-203.5 154.5q83 23 151.5 68.5t110.5 101.5t65 115t23 114q0 74 -40.5 121t-115.5 47q-73 0 -117.5 -43.5t-44.5 -115.5 q0 -48 19.5 -91.5t58.5 -54.5q-75 -69 -156 -69q-64 0 -105.5 46.5t-41.5 127.5q0 138 121.5 224.5t302.5 86.5t281 -86t100 -213q0 -106 -72.5 -209.5t-208.5 -171.5q129 -42 193.5 -132.5t64.5 -205.5q0 -74 -26 -149.5t-77 -143t-119.5 -120t-160.5 -83t-194 -30.5 q-45 0 -89 5.5t-105 27t-105.5 56.5t-76.5 102.5t-32 156.5z" />
|
||||
<glyph unicode="4" horiz-adv-x="974" d="M16 563q22 67 77 184.5t101 214.5t83.5 222.5t37.5 228.5q0 66 -14 123q95 0 161 -51.5t66 -143.5q0 -90 -33.5 -181.5t-75 -154t-101.5 -149t-89 -141.5h320l166 780l303 41l-176 -821h131l-41 -152h-123l-119 -563h-295l121 563h-500z" />
|
||||
<glyph unicode="5" horiz-adv-x="1058" d="M31 285q0 77 24 135.5t63.5 91t84.5 48t94 15.5q125 0 145 -104q-84 -9 -134 -59t-50 -138q0 -42 15 -72.5t41 -46t53.5 -22t58.5 -6.5q81 0 142 58t89.5 142t28.5 177q0 145 -60 221t-165 76q-122 0 -305 -115l168 817q38 -4 100 -12t97.5 -12t83 -7.5t87.5 -3.5 q164 0 332 66q4 -46 4 -68q0 -107 -56 -172t-179 -65q-64 0 -195.5 24.5t-146.5 26.5l-82 -371q134 64 256 64q90 0 160.5 -34t113 -93t64.5 -134.5t22 -162.5q0 -110 -35 -207.5t-104.5 -177.5t-186.5 -127t-268 -47q-360 0 -360 295z" />
|
||||
<glyph unicode="6" horiz-adv-x="1017" d="M33 395q0 49 2 76q12 187 47.5 347.5t96.5 293.5t144.5 226.5t196 145.5t246.5 52q109 0 172 -51.5t63 -118.5q0 -55 -46.5 -94t-135.5 -43q33 29 33 74q0 43 -29.5 76.5t-72.5 33.5q-11 0 -24.5 -2t-39 -12t-50.5 -26t-57.5 -48.5t-61.5 -76.5t-60.5 -112.5t-57 -152 t-48 -201t-36.5 -254.5q-10 -102 -10 -155q0 -74 11 -120t35.5 -68t50.5 -28.5t67 -6.5q65 0 130.5 61.5t108 163t42.5 207.5q0 49 -8 90t-34 74.5t-67 33.5q-104 0 -180 -155q-41 58 -41 118q0 88 85 146.5t204 58.5q70 0 125 -30t87 -79.5t48 -108t16 -120.5 q0 -130 -45.5 -246.5t-121 -197.5t-176.5 -128.5t-210 -47.5q-96 0 -167.5 22t-115 57.5t-70.5 89.5t-36.5 110t-9.5 126z" />
|
||||
<glyph unicode="7" horiz-adv-x="950" d="M68 256q0 70 19 146t47.5 145t80.5 154.5t98.5 152.5t122.5 162t132 161t145 169q-89 12 -174 12q-292 0 -424 -146q-17 73 -17 119q0 103 66 154t213 51h672q-124 -185 -352 -545.5t-265 -447.5q-65 -147 -65 -264q0 -62 22.5 -108t77.5 -91q-30 -80 -188 -80 q-103 0 -157 55t-54 201z" />
|
||||
<glyph unicode="8" horiz-adv-x="1021" d="M-16 401q0 149 95 276t273 177q-88 51 -131 123.5t-43 155.5q0 74 34 146t92.5 128.5t145.5 91.5t185 35q185 0 286 -85t101 -212q0 -107 -75 -211.5t-212 -171.5q125 -38 186.5 -123.5t61.5 -195.5q0 -94 -43 -190.5t-116 -175.5t-181 -129t-227 -50q-102 0 -185.5 34.5 t-136.5 92t-81.5 131t-28.5 153.5zM281 334q0 -88 37.5 -142.5t107.5 -54.5q101 0 168.5 103.5t67.5 240.5q0 87 -32.5 167t-97.5 136q-81 -54 -139.5 -134t-85 -160.5t-26.5 -155.5zM446 1167q0 -70 27.5 -134.5t81.5 -108.5q97 64 152 168.5t55 199.5q0 64 -26 104t-74 40 q-97 0 -156.5 -80.5t-59.5 -188.5z" />
|
||||
<glyph unicode="9" horiz-adv-x="1001" d="M59 184q0 69 38.5 110.5t138.5 41.5q-29 -29 -29 -70q0 -44 31.5 -79t76.5 -35q20 0 38.5 2.5t59 19t74.5 46.5t77 93t75 149.5t59.5 224.5t40.5 310q4 78 4 107q0 93 -12 153.5t-37.5 91t-55.5 41.5t-75 11q-65 0 -125.5 -66.5t-97.5 -170t-37 -208.5q0 -36 5 -70 t17 -67.5t36 -53.5t57 -20q61 0 103.5 27.5t68.5 95.5q43 -52 43 -102q0 -77 -80.5 -132.5t-194.5 -55.5q-73 0 -130.5 32.5t-91 85.5t-50 114.5t-16.5 125.5q0 167 70 305t191.5 216.5t268.5 78.5q92 0 162.5 -26t114 -68.5t70.5 -106t37.5 -131t10.5 -149.5 q0 -308 -82.5 -548.5t-241 -378.5t-370.5 -138q-111 0 -176.5 59t-65.5 135z" />
|
||||
<glyph unicode=":" horiz-adv-x="546" d="M84 276q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114t-116 -47t-115 47t-47 114zM186 768q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5t-115 47t-47 115z" />
|
||||
<glyph unicode=";" horiz-adv-x="565" d="M94 186q0 68 47.5 116t114.5 48q66 0 102.5 -43t36.5 -112q0 -97 -72.5 -216.5t-193.5 -203.5q-18 13 -18 35q0 25 16.5 50t36.5 42.5t36.5 43t16.5 50.5q0 23 -27 41q-96 63 -96 149zM193 678q0 68 47 116t114 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5 q-67 0 -114 47t-47 115z" />
|
||||
<glyph unicode="<" horiz-adv-x="735" d="M41 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117z" />
|
||||
<glyph unicode="=" horiz-adv-x="1054" d="M66 207l49 229h753l-47 -229h-755zM156 647l49 230h753l-47 -230h-755z" />
|
||||
<glyph unicode=">" horiz-adv-x="704" d="M31 207l28 117l492 196l-397 246l18 113l504 -308l-23 -110z" />
|
||||
<glyph unicode="?" horiz-adv-x="903" d="M98 164q0 67 48 114.5t116 47.5t115 -47t47 -115t-47.5 -116t-114.5 -48q-68 0 -116 48t-48 116zM113 1104q0 55 24.5 110.5t73.5 106t135.5 82t196.5 31.5q102 0 169 -18.5t100.5 -55t45.5 -77t12 -97.5q0 -81 -21.5 -147.5t-58.5 -113.5t-82 -88t-94 -79t-93.5 -77 t-83 -92t-58.5 -114h-135q0 58 20.5 113t53.5 99.5t72.5 88.5t79 88.5t72.5 91t53.5 105t20.5 121.5q0 71 -32 106t-80 35q-62 0 -113 -48.5t-51 -121.5q0 -42 19.5 -83t58.5 -52q-26 -42 -66 -64t-84 -22q-64 0 -109.5 45t-45.5 127z" />
|
||||
<glyph unicode="@" horiz-adv-x="1150" d="M-16 336q0 219 94.5 404.5t255 293t348.5 107.5q44 0 82.5 -6t85 -26.5t79 -53.5t55 -94t22.5 -142q0 -130 -50.5 -256t-132 -205t-166.5 -79q-64 0 -90 47q-58 -80 -121 -80q-131 0 -131 141q0 56 19 137t51.5 163.5t86.5 140t114 57.5q49 0 78 -37l8 33l112 -13 l-92 -393q-8 -32 -8 -47q0 -39 27 -39q39 0 77 92t59 198t21 169q0 162 -180 162q-87 0 -165 -39t-134 -104.5t-97.5 -151t-62 -179.5t-20.5 -188q0 -129 52.5 -185.5t197.5 -56.5q111 0 182.5 33t124.5 103q47 -20 47 -92q0 -99 -113 -163.5t-301 -64.5q-100 0 -173.5 21 t-129.5 67.5t-84 128t-28 197.5zM449 434q0 -63 36 -63q24 0 68 28q0 22 6 43l88 340q-14 17 -37 17q-42 0 -75 -44.5t-50.5 -107.5t-26.5 -119.5t-9 -93.5z" />
|
||||
<glyph unicode="A" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486z" />
|
||||
<glyph unicode="B" horiz-adv-x="1300" d="M12 993q0 104 62.5 204t164 174t238.5 119.5t279 45.5q109 0 194.5 -27t137 -73.5t78 -103.5t26.5 -122q0 -93 -54 -181t-151 -144q123 -25 182.5 -115t59.5 -215q0 -64 -13.5 -137.5t-47 -155t-82.5 -146.5t-128.5 -108t-177.5 -43q-37 0 -69.5 6.5t-64 22t-50 47.5 t-18.5 76q0 51 26 119q71 -74 137 -74q53 0 94.5 35t64 91.5t33.5 117.5t11 124q0 261 -194 261q-30 0 -95 -7l-168 -784h-294l278 1303l303 40l-96 -454h12q79 0 148 51t108 127.5t39 153.5q0 96 -66.5 158.5t-203.5 62.5q-73 0 -145 -19t-138.5 -59.5t-117.5 -98 t-81 -140.5t-30 -181q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-219 0 -219 188z" />
|
||||
<glyph unicode="C" horiz-adv-x="1017" d="M39 471q0 83 11 176t37 197.5t64 202t96.5 187t129.5 156.5t168.5 106.5t208.5 39.5q75 0 135.5 -15t107.5 -47t73 -85.5t26 -126.5q0 -87 -35 -138t-109 -51q-70 0 -116 53q37 19 65.5 75t28.5 114q0 55 -30 90t-95 35q-108 0 -208.5 -152t-159 -374t-58.5 -431 q0 -59 6.5 -105.5t23.5 -87.5t44.5 -68t70.5 -42.5t101 -15.5q119 0 223.5 54.5t175.5 150.5l47 -21q-39 -98 -110 -174.5t-154 -119.5t-164.5 -65t-155.5 -22q-232 0 -340 116.5t-108 387.5z" />
|
||||
<glyph unicode="D" horiz-adv-x="1357" d="M12 995q0 104 61 203t163 173t244 119.5t294 45.5q144 0 252 -47.5t171 -131t93.5 -186.5t30.5 -225q0 -174 -46.5 -350.5t-123 -313.5t-182 -223t-215.5 -86q-124 0 -250 109l-17 -82h-294l278 1303l303 40l-246 -1148q55 -41 117 -41q71 0 134.5 54t109.5 144.5 t80.5 206.5t51.5 243.5t17 252.5q0 201 -79 294t-220 93q-234 0 -385 -135t-151 -363q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-219 0 -219 190z" />
|
||||
<glyph unicode="E" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138z" />
|
||||
<glyph unicode="F" horiz-adv-x="940" d="M18 1004q0 84 23.5 160t77 144.5t133 118.5t199 79.5t266.5 29.5q35 0 152.5 -12.5t187.5 -12.5q105 0 184 25q-5 -14 -13.5 -45t-14.5 -50t-20 -46.5t-31 -45t-47 -34.5t-68 -25q-104 0 -287 39l-86 -407h299l-33 -152h-299l-166 -770h-295l295 1368q-63 -2 -109 -23 t-82.5 -67.5t-55.5 -130t-19 -203.5q0 -46 6.5 -76t14.5 -44.5t8 -18.5q-110 0 -165 46t-55 153z" />
|
||||
<glyph unicode="G" horiz-adv-x="1183" d="M39 748q0 118 31 232t93 214.5t147.5 176.5t203 120.5t250.5 44.5q74 0 140 -15.5t121 -46.5t87.5 -85.5t32.5 -126.5q0 -87 -35 -138t-109 -51q-95 0 -141 53q35 10 62.5 67t27.5 101q0 14 -2 28.5t-10.5 37t-22.5 39t-41.5 29t-64.5 12.5q-109 0 -216 -108.5 t-173 -274.5t-66 -326q0 -148 59 -243t179 -95q117 0 174 76l88 389l264 -2l-168 -770q-27 -125 -64 -213t-97 -158t-150 -104.5t-213 -34.5q-137 0 -211 83.5t-74 199.5q0 112 67.5 196t182.5 84q31 0 53 -1.5t52.5 -9t49 -21.5t32.5 -40.5t14 -64.5q0 -36 -12 -82 q-37 31 -64 42t-59 11q-67 0 -105.5 -46.5t-38.5 -108.5q0 -58 34 -99t95 -41q37 0 64.5 10t54 36.5t48 77.5t38.5 128l82 367q-54 -28 -113.5 -46.5t-90.5 -23t-46 -4.5q-121 0 -209 41.5t-137 116t-71.5 165t-22.5 202.5z" />
|
||||
<glyph unicode="H" horiz-adv-x="1361" d="M25 1004q0 132 107.5 256.5t274.5 200t336 75.5q28 0 82 -4l-131 -610h301l131 610h295l-325 -1532h-295l164 770h-301l-164 -770h-295l297 1393q-137 -58 -212 -173t-75 -276q0 -46 6.5 -76t14.5 -44.5t8 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="I" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="J" horiz-adv-x="1097" d="M35 -135q0 111 67 192.5t183 81.5q31 0 53 -1.5t52 -9t49 -21.5t32.5 -40.5t13.5 -64.5q0 -36 -12 -82q-37 31 -64 42t-59 11q-67 0 -105 -45t-38 -106q0 -72 43 -108t96 -36q50 0 82 18.5t61 74.5t52 159l321 1463q-136 -57 -212.5 -172.5t-76.5 -276.5q0 -46 6.5 -76 t14.5 -44.5t8 -18.5q-110 0 -164.5 46t-54.5 153q0 132 108 256.5t275 200t336 75.5h82l-307 -1454q-26 -123 -65.5 -212t-102 -157.5t-155.5 -102.5t-216 -34q-143 0 -223 79.5t-80 209.5z" />
|
||||
<glyph unicode="K" horiz-adv-x="1230" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-133 -627q60 81 174.5 234.5t202.5 270.5t88 116h215l-569 -656q14 0 37 1t34 1q140 0 197 -58t57 -161q0 -89 -50 -273.5t-50 -244.5q0 -46 22 -84t58 -51q-120 -35 -203 -35q-160 0 -160 137q0 65 54.5 261.5 t54.5 277.5q0 139 -123 139q-18 0 -68 -8l-164 -772h-294l296 1393q-137 -58 -211.5 -173t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="L" horiz-adv-x="776" d="M12 983q0 99 64.5 197.5t166 171.5t232 118.5t256.5 45.5q15 0 82 -5l-315 -1470q57 -13 127 -45.5t134 -68.5t132.5 -70.5t145 -57t149.5 -22.5q94 0 172 33q-15 -148 -72.5 -235t-140.5 -87q-80 0 -170.5 48t-175.5 116t-172.5 136.5t-191.5 116.5t-204 48 q-31 0 -47 -2l305 1419q-136 -57 -211 -171t-75 -275q0 -46 6.5 -76t14 -45t7.5 -19q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="M" horiz-adv-x="1611" d="M137 0l520 1407q-155 -52 -240 -170.5t-85 -290.5q0 -48 6.5 -78.5t14 -44.5t7.5 -18q-219 0 -219 186q0 128 97.5 255.5t250 208.5t304.5 81q76 0 155 -29l-47 -1079l451 1079h295l-215 -1507h-295l131 920l-385 -920h-281l41 924l-342 -924h-164z" />
|
||||
<glyph unicode="N" horiz-adv-x="1368" d="M12 1004q0 70 26 144.5t77.5 143.5t120 123.5t161.5 87.5t195 33q131 0 190 -29l162 -1085l223 1110h295l-325 -1532h-248l-174 1071l-228 -1071h-294l294 1391q-135 -56 -209.5 -171t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="O" horiz-adv-x="1361" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172z" />
|
||||
<glyph unicode="P" horiz-adv-x="1173" d="M12 1004q0 132 108 256.5t275 200t336 75.5q122 0 216.5 -33t151.5 -90t86 -128t29 -152q0 -99 -41.5 -196t-115 -176t-187.5 -128t-247 -49h-11l-125 -584h-294l278 1303l303 40l-145 -684q75 6 143 55t114 121t73.5 159t27.5 169q0 123 -63 201t-191 78 q-249 0 -389.5 -132.5t-140.5 -365.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Q" horiz-adv-x="1361" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -153 -39.5 -317.5t-131 -328.5t-220.5 -260q37 -52 65.5 -83t79 -56.5t111.5 -25.5q13 0 45 4q-37 -100 -88 -138t-123 -38q-52 0 -93 15t-66.5 36t-47.5 57.5t-32.5 65 t-24.5 73.5q-89 -28 -190 -28q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -61 4 -88q78 19 127 19q91 0 164 -56q106 134 179 366.5t73 450.5q0 95 -11 157.5 t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79l10 -31q-207 0 -207 172zM545 238q29 -84 98 -84q44 0 78 22q-43 70 -117 70q-28 0 -59 -8z" />
|
||||
<glyph unicode="R" horiz-adv-x="1230" d="M12 1004q0 132 108 256.5t275 200t336 75.5q117 0 208.5 -27.5t146 -74t82.5 -104t28 -122.5q0 -96 -59 -192.5t-162 -157.5q141 -40 141 -205q0 -89 -50 -273.5t-50 -244.5q0 -46 22 -84t58 -51q-120 -35 -203 -35q-160 0 -160 137q0 65 54.5 261.5t54.5 277.5 q0 139 -123 139q-14 0 -68 -8l-164 -772h-294l278 1303l303 40l-100 -473h16q81 0 149.5 57t105 138.5t36.5 161.5q0 94 -56 154.5t-169 60.5q-251 0 -402 -133.5t-151 -364.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="S" horiz-adv-x="1167" d="M55 309q0 80 26.5 145.5t70.5 106t96.5 62.5t107.5 22q30 0 58.5 -6.5t56.5 -21t46 -42t20 -65.5q-44 0 -85 -10.5t-76.5 -32.5t-57 -60.5t-21.5 -89.5q0 -82 52 -139t145 -57q105 0 171 73t66 199q0 96 -43.5 184t-105 155.5t-123 132.5t-105 143t-43.5 161 q0 158 136.5 262.5t336.5 104.5q37 0 72 -3.5t84.5 -18.5t85.5 -40t62 -72.5t26 -111.5q0 -90 -46 -148.5t-122 -58.5q-57 0 -94 43q41 24 66.5 74.5t25.5 102.5q0 60 -36 98.5t-115 38.5q-87 0 -138 -48.5t-51 -138.5q0 -61 25.5 -114.5t66 -96t89.5 -84.5t98.5 -90 t90 -102t66 -130.5t25.5 -166.5q0 -125 -48 -224.5t-129 -161t-183 -94t-216 -32.5q-90 0 -166.5 20.5t-137.5 61.5t-95.5 110t-34.5 160z" />
|
||||
<glyph unicode="T" horiz-adv-x="985" d="M12 1001q0 106 39 198.5t119.5 169t219 121t321.5 44.5q130 0 279.5 -23.5t244.5 -23.5q135 0 238 43q-6 -260 -256 -260q-118 0 -318 45l-283 -1315h-294l290 1356h-24q-69 0 -124 -11t-105 -40t-83.5 -74.5t-53 -118.5t-19.5 -170q0 -46 6.5 -76t14 -44.5t7.5 -18.5 q-110 0 -164.5 45.5t-54.5 152.5z" />
|
||||
<glyph unicode="U" horiz-adv-x="1466" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z " />
|
||||
<glyph unicode="V" horiz-adv-x="1259" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l18 -1260l431 1254h143l-569 -1526h-312v1399q-321 -101 -321 -455q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="W" horiz-adv-x="1970" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l18 -1260l431 1254h268v-1254l440 1254h144l-570 -1526h-303v1042l-405 -1042h-301v1399q-165 -52 -248.5 -168t-83.5 -287q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="X" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l88 -426l238 420h143l-344 -596l192 -930h-325l-109 512l-297 -512h-125l385 680l-151 719q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Y" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1110" d="M23 0l716 1425q-18 0 -82 2.5t-102 2.5q-188 0 -188 -46q13 0 23.5 -16t10.5 -49q0 -61 -37 -89t-83 -28q-54 0 -79.5 36t-25.5 85q0 83 68 148t184 65h702l-706 -1403q25 0 93 -2t104 -2q180 0 213 45q-13 0 -25 15.5t-12 48.5q0 59 37.5 88.5t85.5 29.5 q53 0 78.5 -34.5t25.5 -83.5q0 -86 -73.5 -162t-188.5 -76h-739z" />
|
||||
<glyph unicode="[" horiz-adv-x="819" d="M-49 -512l434 2048h395l-26 -127h-248l-381 -1794h248l-27 -127h-395z" />
|
||||
<glyph unicode="\" horiz-adv-x="741" d="M86 1536h147l435 -2048h-148z" />
|
||||
<glyph unicode="]" horiz-adv-x="819" d="M-49 -512l26 127h248l381 1794h-248l27 127h395l-434 -2048h-395z" />
|
||||
<glyph unicode="^" horiz-adv-x="862" d="M102 1149l435 387h43l122 -387h-65l-123 164l-321 -164h-91z" />
|
||||
<glyph unicode="_" horiz-adv-x="909" d="M74 0l37 162h716l-34 -162h-719z" />
|
||||
<glyph unicode="`" horiz-adv-x="411" d="M49 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="a" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="b" horiz-adv-x="909" d="M-14 250q0 57 14 117l219 1026l303 41l-98 -461q94 57 168 57q242 0 242 -350q0 -54 -8 -114t-27.5 -128t-48.5 -131t-74.5 -122t-102 -102t-134 -69t-167.5 -26q-139 0 -212.5 68.5t-73.5 193.5zM279 281q0 -95 108 -95q63 0 120 81t88.5 196.5t31.5 224.5 q0 81 -28.5 139t-80.5 58q-29 0 -67.5 -9.5t-53.5 -27.5l-112 -520q-6 -24 -6 -47z" />
|
||||
<glyph unicode="c" horiz-adv-x="786" d="M-27 313q0 54 8.5 115.5t28.5 133t49 138.5t74 130t99.5 109.5t130 74.5t161.5 28q125 0 176.5 -53t51.5 -135q0 -72 -31 -110.5t-78 -38.5q-35 0 -72 24q25 70 25 121q0 84 -57 84q-61 0 -124 -103.5t-101 -244t-38 -254.5q0 -100 35 -136t113 -36q77 0 144.5 29.5 t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79t-84.5 246z" />
|
||||
<glyph unicode="d" horiz-adv-x="1075" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-8l100 471l303 41l-235 -1106q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-89.5 -60t-83.5 -28t-75 -5.5 q-84 0 -136.5 45.5t-64.5 130.5q-122 -188 -303 -188q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q48 0 98.5 45.5t69.5 124.5v11l100 469q-4 26 -24 48.5t-60 22.5q-62 0 -118 -60t-91.5 -148.5t-56.5 -183 t-21 -169.5z" />
|
||||
<glyph unicode="e" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5z" />
|
||||
<glyph unicode="f" horiz-adv-x="546" d="M-426 -291q0 55 19 99t47.5 73t78.5 55.5t94 42.5t113 37l248 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q97 0 169 -62t72 -159q0 -47 -6 -84h-96q4 40 4 55q0 34 -22.5 52t-55.5 18q-43 0 -83 -38t-60 -130l-35 -162h150l-15 -82h-151l-236 -1106 q-72 -348 -366 -348q-97 0 -169.5 62t-72.5 159zM-313 -262q0 -23 25 -45.5t57 -22.5q80 0 116 160l27 119q-108 -36 -166.5 -85t-58.5 -126z" />
|
||||
<glyph unicode="g" horiz-adv-x="1038" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-12l22 106h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 108q-105 -112 -246 -112q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM246 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q45 0 94.5 43.5t69.5 116.5l104 485q-2 26 -21.5 51t-62.5 25q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="h" d="M-78 0l297 1393l303 41l-110 -516q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264 t46 258q0 105 -76 105q-53 0 -98.5 -63t-57.5 -117l-141 -666h-295z" />
|
||||
<glyph unicode="i" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM180 1296q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116 q0 -67 -48 -114t-116 -47t-115 47t-47 114z" />
|
||||
<glyph unicode="j" horiz-adv-x="491" d="M-414 -313q0 204 336 305l219 1032h295l-203 -950q98 35 158 100t101 193h86q-29 -96 -71.5 -167.5t-94 -113.5t-97 -64.5t-100.5 -37.5l-31 -148q-72 -348 -368 -348q-99 0 -164.5 52t-65.5 147zM-301 -285q0 -24 19.5 -45.5t50.5 -21.5q34 0 66.5 47.5t51.5 134.5 l13 59q-201 -75 -201 -174zM164 1298q0 68 47.5 116t114.5 48t115 -48t48 -116q0 -67 -47.5 -114t-115.5 -47t-115 47t-47 114z" />
|
||||
<glyph unicode="k" horiz-adv-x="1060" d="M-78 0l297 1393l303 41l-153 -721l395 311h203l-410 -285q52 9 68 9q109 0 167 -68t58 -172q0 -42 -8 -78l-23 -102q-10 -50 -10 -66q0 -76 80 -76q61 0 96.5 43t75.5 138h86q-33 -98 -75.5 -171t-80.5 -111.5t-82 -61.5t-74.5 -29t-64.5 -6q-126 0 -192 60t-66 173 q0 49 12 107l17 80q8 36 8 75q0 99 -74 99q-59 0 -151 -76l-107 -506h-295z" />
|
||||
<glyph unicode="l" horiz-adv-x="546" d="M-16 233q0 56 16 134l219 1026l303 41l-235 -1106q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="m" horiz-adv-x="1540" d="M-78 0l217 1024h295l-22 -106q102 116 250 116q178 0 210 -176q112 174 285 174q104 0 165.5 -56.5t61.5 -174.5q0 -89 -49 -283.5t-49 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5 t-58.5 164.5q0 71 46 264t46 258q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295l150 707q6 24 6 51q0 38 -15.5 64t-46.5 26q-56 0 -98 -53.5t-68 -137.5l-139 -657h-295z" />
|
||||
<glyph unicode="n" d="M-78 0l217 1024h295l-22 -106q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264t46 258 q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295z" />
|
||||
<glyph unicode="o" horiz-adv-x="899" d="M-29 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q263 0 263 -327q8 -4 22 -4q66 0 155 36.5t161 86.5l18 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM276 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 83 64 111q-3 65 -20.5 92t-57.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="p" horiz-adv-x="958" d="M-186 -512l342 1612h295l-33 -154q102 88 239 88q114 0 182 -75.5t68 -247.5q0 -74 -10 -150t-32 -159.5t-61.5 -156t-93 -131t-132.5 -92.5t-175 -34q-131 0 -174 73l-106 -499zM262 213q16 -47 78 -47q61 0 112 39t82.5 99t53 136t30.5 145.5t9 131.5q0 170 -103 170 q-35 0 -74 -26.5t-67 -74.5z" />
|
||||
<glyph unicode="q" horiz-adv-x="995" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -29t41 -79l20 98h295l-311 -1462l-310 -74l129 612q-105 -112 -243 -112q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q43 0 90 39t70 107l108 506q-4 25 -24.5 47t-59.5 22q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="r" horiz-adv-x="770" d="M-78 0l217 1024h295l-26 -127q49 42 76.5 62.5t76 42.5t94.5 22q66 0 104 -45.5t37 -108.5q0 -59 -38.5 -104t-108.5 -45q-32 0 -50.5 14t-24 33.5t-9 39.5t-11 34t-24.5 14q-42 0 -73 -18t-76 -60l-164 -778h-295z" />
|
||||
<glyph unicode="s" horiz-adv-x="782" d="M-66 250q0 66 32 117t77 73q138 244 252 607l303 40q11 -224 22.5 -387.5t17 -233.5t5.5 -124q0 -45 -8 -78q87 52 147 103h86q-127 -147 -313 -256q-60 -63 -147.5 -93t-174.5 -30q-75 0 -134.5 23t-94 61.5t-52.5 83.5t-18 94zM43 293q0 -73 29.5 -108t95.5 -35 q76 0 127 45.5t51 144.5q0 51 -14.5 186t-24.5 299q-51 -165 -166 -387q47 -21 47 -71q0 -39 -26 -70t-64 -31q-42 0 -55 27z" />
|
||||
<glyph unicode="t" horiz-adv-x="546" d="M-16 233q0 56 16 134l123 575h-68l17 82h67l62 283l303 41l-70 -324h123l-16 -82h-123l-131 -614q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="u" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184z" />
|
||||
<glyph unicode="v" horiz-adv-x="847" d="M-18 229q0 50 10 99l147 696h295l-147 -696q-11 -53 -11 -74q0 -68 70 -68q58 0 114 49.5t98 127t75 169.5t50 181t17 157q-4 -9 -26 -14.5t-39 -5.5q-34 0 -54 33t-20 71q0 50 32.5 80.5t94.5 30.5q68 0 98.5 -50.5t30.5 -127.5q0 -85 -14.5 -180.5t-43.5 -198 t-77.5 -194.5t-111 -165.5t-149.5 -117t-188 -43.5q-119 0 -185 61t-66 180z" />
|
||||
<glyph unicode="w" horiz-adv-x="1353" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q109 0 164 181l139 657h295l-147 -696q-11 -53 -11 -74q0 -68 70 -68q58 0 114 49.5t98 127t75 169.5t50 181t17 157q-4 -9 -26 -14.5t-39 -5.5q-34 0 -54 33t-20 71q0 50 32.5 80.5t94.5 30.5 q68 0 98.5 -50.5t30.5 -127.5q0 -85 -14.5 -180.5t-43.5 -198t-77.5 -194.5t-111 -165.5t-149.5 -117t-188 -43.5t-163.5 43.5t-81.5 128.5q-117 -172 -291 -172q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="x" horiz-adv-x="1069" d="M0 367q20 98 48 186.5t70.5 178.5t94 155t121.5 106t149 41q58 0 101.5 -22.5t67 -61t36 -76.5t17.5 -83l18 -156q173 194 178 295q-24 -4 -35 -4q-96 0 -96 114q0 50 39.5 75.5t99.5 25.5q53 0 84 -57t31 -130q0 -89 -60.5 -177.5t-224.5 -268.5l19 -143 q12 -98 42 -138.5t85 -40.5q59 0 109.5 51.5t74.5 129.5h86q-32 -91 -75 -160.5t-85.5 -110t-91 -66t-87.5 -34t-79 -8.5q-192 0 -250 356l-282 -344h-148l414 467l-33 229q-14 99 -31.5 134.5t-46.5 35.5q-66 0 -144.5 -131t-129.5 -368h-86z" />
|
||||
<glyph unicode="y" horiz-adv-x="995" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174z" />
|
||||
<glyph unicode="z" horiz-adv-x="876" d="M0 0l522 913h-114q-56 0 -84.5 -6t-28.5 -26q0 -3 5.5 -5t11 -10.5t5.5 -29.5q0 -53 -33 -81t-77 -28q-38 0 -66 24t-28 66q0 71 68 139t169 68h527l-502 -866q7 0 65 -6.5t90 -6.5q90 0 97 41q-39 3 -39 43q0 32 30.5 60t83.5 28q44 0 67.5 -27.5t23.5 -70.5 q0 -79 -70 -149t-180 -70h-543z" />
|
||||
<glyph unicode="{" horiz-adv-x="720" d="M61 -287q0 65 30.5 150t69 157t81 167t59.5 171q6 24 6 45q0 64 -84 64q-25 0 -65 -6l24 117q44 -7 64 -7q38 0 64.5 15.5t40.5 36.5t21.5 55t9 59.5t1.5 61.5q0 58 -7 166t-7 159q0 50 10 103t35 109t61 100t93.5 72t127.5 28h88l-18 -102q-72 0 -113.5 -10t-72.5 -44 q-52 -58 -72.5 -125t-22.5 -157l13 -350q-6 -164 -82 -228q24 -27 24 -74q0 -60 -38 -157t-83.5 -184t-83.5 -188.5t-38 -170.5q0 -164 174 -164l-19 -92h-18q-105 0 -151 14t-83 72q-39 58 -39 137z" />
|
||||
<glyph unicode="|" horiz-adv-x="1089" d="M449 -512v2050h190v-2050h-190z" />
|
||||
<glyph unicode="}" horiz-adv-x="720" d="M61 -510l19 102q72 0 113.5 10t72.5 44q52 58 72 124.5t22 157.5l-12 351q5 163 82 227q-24 27 -24 74q0 60 38 157t83.5 184t83.5 188.5t38 170.5q0 164 -174 164l19 92h18q105 0 150.5 -14t82.5 -72q39 -58 39 -137q0 -65 -30.5 -150t-69 -157t-80.5 -167t-59 -171 q-6 -24 -6 -45q0 -64 84 -64q25 0 65 6l-24 -116q-38 6 -64 6q-38 0 -64.5 -15.5t-40.5 -36.5t-21.5 -55t-9 -59.5t-1.5 -61.5q0 -58 7 -166t7 -159q0 -50 -10 -103t-35 -109t-61 -100t-93.5 -72t-127.5 -28h-89z" />
|
||||
<glyph unicode="~" horiz-adv-x="882" d="M100 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l13 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="¡" horiz-adv-x="448" d="M-125 -508l266 961h174l-145 -961h-295zM121 762q0 68 47.5 116t114.5 48t115 -48t48 -116q0 -67 -47.5 -114.5t-115.5 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="¢" horiz-adv-x="825" d="M63 647q0 141 48 262.5t146.5 206.5t231.5 103l46 251h118l-55 -249q81 -5 132.5 -38.5t51.5 -101.5q0 -33 -10 -67h-117q11 42 11 57q0 70 -76 70h-10l-156 -703q10 -2 31 -2q40 0 76 10.5t72.5 34t59 41.5t62.5 53q-45 -267 -332 -278l-78 -352h-55l64 354 q-112 16 -186.5 107t-74.5 241zM279 653q0 -55 18.5 -105t56.5 -79l113 625q-82 -64 -135 -190t-53 -251z" />
|
||||
<glyph unicode="£" horiz-adv-x="952" d="M51 686q67 136 215 182l148 605l307 63l-164 -678q8 -2 32.5 -10.5t35.5 -11.5t31.5 -8t38 -7t36.5 -2q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65h-33l-115 -473q32 4 101.5 6.5t111.5 7.5t115.5 36t150.5 88q-15 -148 -72 -236t-140 -88h-605l176 733q-5 0 -16.5 1 t-17.5 1q-80 0 -136 -53z" />
|
||||
<glyph unicode="¤" horiz-adv-x="1024" d="M139 1008l78 73l115 -114q83 61 186 61q101 0 182 -59l115 114l74 -73l-113 -113q62 -84 62 -188q0 -103 -62 -187l113 -112l-74 -74l-113 113q-83 -60 -184 -60q-104 0 -188 62l-115 -115l-74 74l115 114q-59 81 -59 183q0 103 59 184zM313 709q0 -85 60 -145t145 -60 t145 60t60 145t-60 144.5t-145 59.5t-145 -59.5t-60 -144.5z" />
|
||||
<glyph unicode="¥" horiz-adv-x="970" d="M39 291l29 102h196l29 133h-203l29 103h196l-229 907h336l180 -739l307 739h135l-401 -907h268l-28 -103h-262l-29 -133h268l-29 -102h-262l-61 -291h-328l62 291h-203z" />
|
||||
<glyph unicode="¦" horiz-adv-x="1089" d="M449 455h190v-967h-190v967zM449 616v922h190v-922h-190z" />
|
||||
<glyph unicode="§" horiz-adv-x="1110" d="M16 158q0 111 56.5 181.5t140.5 70.5q38 0 72 -18.5t49.5 -35.5t40.5 -49q-91 -7 -128.5 -42.5t-37.5 -112.5q0 -81 39.5 -120.5t128.5 -39.5q78 0 121 48.5t43 149.5q0 68 -20.5 125.5t-53 99t-71.5 78.5t-78.5 71t-72 69.5t-53 82t-20.5 99.5q0 95 59 170t154 111 q-10 45 -10 86q0 88 38.5 157.5t101.5 111.5t138.5 63.5t155.5 21.5q29 0 57.5 -2.5t68.5 -14.5t69 -31.5t50 -57.5t21 -89q0 -72 -36.5 -118.5t-98.5 -46.5q-46 0 -76 32q33 20 53.5 60.5t20.5 81.5q0 46 -29 78t-86 32q-82 0 -137 -65.5t-55 -153.5q0 -51 22.5 -96 t58 -79.5t79 -68t86.5 -71t78.5 -79t58 -99.5t22.5 -127q0 -79 -27 -150.5t-82 -124t-128 -67.5q0 -182 -118 -286t-327 -104q-43 0 -82 4.5t-88.5 21.5t-84.5 44.5t-59 78.5t-24 120zM389 924q0 -47 34.5 -94t87 -92t107 -94.5t102.5 -119.5t66 -149q39 48 39 129 q0 67 -29 123.5t-76.5 100.5t-98.5 82t-106 87t-87 98q-39 -20 -39 -71z" />
|
||||
<glyph unicode="¨" horiz-adv-x="802" d="M115 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM483 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="©" horiz-adv-x="1202" d="M104 678q0 144 67.5 265t181.5 190t247 69t247 -68.5t181.5 -189t67.5 -264.5t-67.5 -265t-181.5 -190t-247 -69t-247 69t-181.5 189.5t-67.5 263.5zM199 678q0 -183 119 -309.5t286 -126.5q166 0 285 126.5t119 309.5t-119 309.5t-285 126.5q-167 0 -286 -126.5 t-119 -309.5zM311 688q0 129 88.5 216t214.5 87q95 -4 164.5 -58t89.5 -136l-96 -37q-7 26 -14 41.5t-23.5 38t-46 34t-70.5 11.5q-82 0 -138 -48.5t-56 -138.5q0 -92 56 -155.5t138 -63.5q48 0 89.5 24.5t64.5 67.5l90 -59q-43 -65 -110 -102t-142 -37q-124 0 -211.5 93.5 t-87.5 221.5z" />
|
||||
<glyph unicode="ª" horiz-adv-x="706" d="M88 1096q0 60 9 116.5t32 112t57.5 96.5t89 66.5t123.5 25.5q40 0 80 -28l6 31h170l-86 -377q-6 -36 -6 -37q0 -41 43 -41q21 0 31 4q-15 -45 -38.5 -69t-41 -28.5t-43.5 -4.5q-99 0 -119 81q-72 -88 -162 -88q-77 0 -111 41t-34 99zM248 1126q0 -77 69 -77q35 0 74 53 l76 330q-23 20 -59 20q-42 0 -80 -50.5t-59 -125.5t-21 -150z" />
|
||||
<glyph unicode="«" horiz-adv-x="1206" d="M41 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117zM471 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117z" />
|
||||
<glyph unicode="¬" horiz-adv-x="909" d="M39 455l37 161h741l-104 -512h-232l72 351h-514z" />
|
||||
<glyph unicode="­" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="®" horiz-adv-x="1132" d="M70 684q0 108 39.5 205.5t106.5 167t158 110.5t191 41q133 0 247 -68.5t181.5 -189t67.5 -264.5t-67.5 -265t-181.5 -190t-247 -69q-100 0 -191 41t-158 110.5t-106.5 166.5t-39.5 204zM164 684q0 -183 119 -309.5t286 -126.5q166 0 285 126.5t119 309.5t-119 309.5 t-285 126.5q-167 0 -286 -126.5t-119 -309.5zM360 393l2 596h193q110 0 178 -45.5t68 -126.5q0 -57 -33.5 -100t-87.5 -58l133 -256l-96 -14l-127 256h-135v-252h-95zM455 723h106q36 0 65.5 6t55 27.5t25.5 56.5q0 49 -44.5 70.5t-105.5 21.5h-102v-182z" />
|
||||
<glyph unicode="°" horiz-adv-x="643" d="M76 999q0 187 91 313t241 126q87 0 127 -58.5t40 -173.5q0 -68 -20.5 -142.5t-57 -142t-96 -111.5t-129.5 -44q-113 0 -154.5 57t-41.5 176zM223 975q0 -133 56 -133q53 0 93 56t60.5 145t20.5 194q0 129 -52 129q-50 0 -106.5 -83.5t-67.5 -219.5q-4 -50 -4 -88z" />
|
||||
<glyph unicode="±" horiz-adv-x="1091" d="M63 139l37 162h752l-35 -162h-754zM170 633l49 229h262l56 250h231l-55 -250h260l-47 -229h-262l-56 -252h-231l55 252h-262z" />
|
||||
<glyph unicode="²" horiz-adv-x="614" d="M6 739q0 66 40.5 130t97.5 118t114.5 110.5t98 131t40.5 155.5q0 38 -22 64.5t-49 26.5q-56 0 -76.5 -22t-20.5 -67q0 -24 11 -47t34 -28q-45 -39 -92 -39q-37 0 -60.5 26t-23.5 74q0 65 61 113.5t185 48.5q225 0 225 -176q0 -70 -34 -132.5t-88 -111t-110 -90 t-111 -87.5t-81 -85h9q38 0 114 -10.5t117 -10.5q16 0 29.5 1.5t26 6t20 7.5t20 12.5t17.5 13.5t20 18t21 19q0 -37 -0.5 -57t-3.5 -47t-8.5 -40.5t-15.5 -29.5t-25 -23.5t-37 -12.5t-52 -5q-65 0 -149 20.5t-144 20.5q-13 0 -30.5 -6t-37 -15.5t-26.5 -11.5q-4 24 -4 37z " />
|
||||
<glyph unicode="³" horiz-adv-x="647" d="M27 879q0 79 36.5 123.5t104.5 44.5q82 0 96 -56q-34 -3 -56.5 -15.5t-32 -33t-12.5 -39t-3 -43.5q0 -52 26.5 -71t77.5 -19q63 0 104.5 58.5t41.5 134.5q0 68 -38.5 124.5t-115.5 81.5q96 23 149.5 88.5t53.5 131.5q0 38 -20 62t-56 24q-107 0 -107 -89q0 -24 11.5 -47 t34.5 -28q-45 -39 -93 -39q-37 0 -60.5 26t-23.5 74q0 64 66 113t190 49q98 0 153.5 -47t55.5 -115q0 -59 -40.5 -113.5t-120.5 -91.5q147 -44 147 -180q0 -105 -89.5 -196t-215.5 -91q-44 0 -83 6t-83.5 23t-71 55.5t-26.5 94.5z" />
|
||||
<glyph unicode="´" horiz-adv-x="647" d="M246 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="µ" d="M-150 -340l289 1364h295l-147 -696q-6 -24 -6 -52q0 -39 14.5 -64.5t46.5 -25.5q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-89.5 -60t-83.5 -28t-75 -5.5q-86 0 -139 47.5 t-62 136.5q-113 -180 -293 -184l-55 -266z" />
|
||||
<glyph unicode="¶" horiz-adv-x="1110" d="M147 1006q0 77 20.5 152.5t63 143.5t102 119.5t144 82t182.5 30.5q80 0 146 -18.5t119.5 -57t83.5 -105.5t30 -157q0 -62 -16 -147l-260 -1215h-193l166 778q-33 -11 -88 -22l-158 -756h-192l158 756q-148 29 -228 146t-80 270zM670 694h12q41 0 72 8l98 465 q12 59 12 101q0 85 -47 129z" />
|
||||
<glyph unicode="·" horiz-adv-x="888" d="M246 547q0 82 57 140t139 58t140.5 -58t58.5 -140t-58.5 -139.5t-140.5 -57.5t-139 57.5t-57 139.5z" />
|
||||
<glyph unicode="¸" horiz-adv-x="464" d="M0 -389q35 65 82 65q45 0 85.5 -27.5t65.5 -27.5q26 0 44 26.5t18 59.5q0 31 -20 52.5t-60 21.5q-48 0 -106 -25l108 303l72 -47l-60 -147q66 16 109 16q63 0 95 -27t32 -75q0 -104 -74.5 -164.5t-171.5 -60.5q-70 0 -117.5 14t-101.5 43z" />
|
||||
<glyph unicode="¹" horiz-adv-x="444" d="M45 707l158 690h-113l14 51q148 0 302 86l-189 -827h-172z" />
|
||||
<glyph unicode="º" horiz-adv-x="673" d="M86 1137q0 98 25.5 174t69.5 122t99 69.5t117 23.5q45 0 79 -14t34 -35q0 -37 -53 -54q-16 39 -54 39q-41 0 -73.5 -36t-50 -89t-26.5 -103t-9 -87q0 -47 22 -65.5t62 -18.5q50 0 80 65.5t41 155.5q-31 8 -31 45q0 30 25.5 57t54.5 27q22 0 30.5 -8t8.5 -31q14 8 23.5 17 t22.5 25t21 24l12 -23q-27 -87 -79 -117q-4 -82 -30.5 -150t-65 -109.5t-82.5 -64t-87 -22.5q-96 0 -141 43t-45 140z" />
|
||||
<glyph unicode="»" horiz-adv-x="1200" d="M31 207l28 117l492 196l-397 246l18 113l504 -308l-23 -110zM485 207l29 117l492 196l-398 246l19 113l503 -308l-22 -110z" />
|
||||
<glyph unicode="¼" horiz-adv-x="1411" d="M143 461l158 690h-113l15 51q147 0 301 86l-189 -827h-172zM285 -338l540 1874h148l-541 -1874h-147zM766 307q14 41 60 129t79 172t33 156q0 36 -8 67q56 0 94.5 -27t38.5 -77q0 -49 -20 -99t-44 -84t-58.5 -81t-51.5 -76h184l96 422l177 22l-103 -444h78l-25 -80h-71 l-70 -305h-170l70 305h-289z" />
|
||||
<glyph unicode="½" horiz-adv-x="1382" d="M143 461l158 690h-113l15 51q147 0 301 86l-189 -827h-172zM274 -338l541 1874h148l-541 -1874h-148zM750 43q0 49 21.5 96t56 86.5t76.5 79.5t83.5 82t76 86t56 99.5t21.5 115.5q0 38 -22 64t-50 26q-56 0 -76 -21.5t-20 -66.5q0 -24 11.5 -47.5t33.5 -28.5 q-45 -39 -92 -39q-37 0 -60.5 26.5t-23.5 74.5q0 66 60.5 114t184.5 48q226 0 226 -176q0 -70 -34 -132.5t-88 -111t-110 -90t-111 -87.5t-81 -85h8q37 0 114 -10.5t117 -10.5q16 0 29.5 1.5t26 6t20 7.5t20 12.5t17.5 13.5t20 18t21 19q0 -52 -1.5 -77.5t-8.5 -57.5 t-21.5 -46t-41.5 -24t-68 -10q-65 0 -149 20.5t-144 20.5q-13 0 -30.5 -6t-37 -15.5t-26.5 -11.5q-4 24 -4 37z" />
|
||||
<glyph unicode="¾" horiz-adv-x="1677" d="M125 633q0 79 36.5 123.5t104.5 44.5q82 0 96 -56q-34 -3 -56.5 -15.5t-32 -33t-12.5 -39t-3 -43.5q0 -52 26.5 -71t77.5 -19q63 0 104.5 58.5t41.5 134.5q0 68 -38.5 125t-115.5 82q96 23 149.5 88.5t53.5 130.5q0 38 -20 62t-56 24q-106 0 -106 -88q0 -24 11.5 -47.5 t33.5 -28.5q-45 -39 -92 -39q-37 0 -60.5 26t-23.5 74q0 64 66 113t190 49q98 0 153.5 -47t55.5 -115q0 -58 -41 -112.5t-121 -91.5q75 -22 111 -70t36 -111q0 -105 -89.5 -195.5t-215.5 -90.5q-264 0 -264 178zM522 -338l541 1874h147l-540 -1874h-148zM1010 307 q14 41 60 129t79 172t33 156q0 36 -8 67q56 0 94.5 -27t38.5 -77q0 -49 -20 -99t-44 -84t-58.5 -81t-51.5 -76h184l96 422l176 22l-102 -444h78l-25 -80h-72l-69 -305h-170l69 305h-288z" />
|
||||
<glyph unicode="¿" horiz-adv-x="899" d="M25 -262q0 78 20 142.5t55 112t77.5 88.5t91.5 80.5t93.5 79.5t87 95t68.5 117h135q0 -58 -20.5 -113t-53.5 -99.5t-72.5 -88.5t-79 -88.5t-72.5 -91t-53.5 -105t-20.5 -121.5q0 -71 32 -106t80 -35q62 0 113 48.5t51 121.5q0 42 -19.5 83t-58.5 52q26 42 66 64t84 22 q64 0 109.5 -45t45.5 -127q0 -55 -24.5 -110.5t-73.5 -106t-135.5 -82t-196.5 -31.5q-329 0 -329 244zM473 762q0 69 47.5 117.5t114.5 48.5q68 0 116 -48.5t48 -117.5q0 -67 -48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="À" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM895 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Á" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM995 1661l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="Â" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM834 1661l434 387h43l123 -387h-66l-123 164l-321 -164h-90z" />
|
||||
<glyph unicode="Ã" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM838 1704q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34 q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="Ä" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM881 1810q0 51 36.5 88t88.5 37q51 0 87.5 -37t36.5 -88 q0 -52 -36.5 -88t-87.5 -36q-53 0 -89 36t-36 88zM1249 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Å" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM993 1786q0 29 10.5 60.5t30 60t54.5 47t79 18.5q72 0 110 -44t38 -113 q0 -74 -44.5 -123.5t-133.5 -49.5q-64 0 -104 42t-40 102zM1075 1790q0 -76 70 -76q46 0 70 25t24 69q0 20 -1.5 32.5t-7.5 29.5t-21 26t-38 9q-49 0 -72.5 -35t-23.5 -80z" />
|
||||
<glyph unicode="Æ" horiz-adv-x="1564" d="M18 696q0 61 55.5 116t148 87.5t196.5 32.5q54 0 121 -6q81 129 166 236.5t177 193.5t187.5 134t183.5 48q53 0 173.5 -20.5t195.5 -20.5q119 0 240 39q-3 -9 -10.5 -34.5t-12 -38.5t-15 -36.5t-20 -36.5t-26 -30.5t-35 -26.5t-44.5 -15.5t-57 -6.5q-21 0 -40.5 1.5 t-41.5 6.5t-34.5 7.5t-37.5 11.5t-31 11t-36 14t-32 13l-111 -518h317l-41 -151h-309l-104 -490h458l-43 -217h-755l157 743q-151 66 -276 74q-116 -249 -190 -511.5t-74 -422.5q0 -36 4 -59t9 -30.5t9.5 -13.5t5.5 -11q-118 7 -163 24q-98 36 -109 165q-2 19 -2 40 q0 131 83.5 359t217.5 460q-100 -7 -160 -30.5t-80 -75.5q8 0 17.5 -21.5t9.5 -42.5q0 -35 -31.5 -57.5t-93.5 -22.5q-49 0 -83 32.5t-34 96.5zM723 903q146 -27 254 -69l119 557q-176 -96 -373 -488z" />
|
||||
<glyph unicode="Ç" horiz-adv-x="1017" d="M39 471q0 83 11 176t37 197.5t64 202t96.5 187t129.5 156.5t168.5 106.5t208.5 39.5q75 0 135.5 -15t107.5 -47t73 -85.5t26 -126.5q0 -87 -35 -138t-109 -51q-70 0 -116 53q37 19 65.5 75t28.5 114q0 55 -30 90t-95 35q-108 0 -208.5 -152t-159 -374t-58.5 -431 q0 -59 6.5 -105.5t23.5 -87.5t44.5 -68t70.5 -42.5t101 -15.5q119 0 223.5 54.5t175.5 150.5l47 -21q-35 -88 -96.5 -159t-134 -115t-146 -69.5t-145.5 -33.5l-43 -106q66 16 108 16q63 0 95 -27t32 -75q0 -104 -74 -164.5t-171 -60.5q-71 0 -118.5 14.5t-101.5 42.5 q35 65 82 65q45 0 86 -27.5t66 -27.5q26 0 43.5 26.5t17.5 59.5q0 31 -19.5 52.5t-59.5 21.5q-50 0 -107 -25l76 211q-218 6 -320 123t-102 381z" />
|
||||
<glyph unicode="È" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM516 2048h215l60 -387h-154z" />
|
||||
<glyph unicode="É" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM676 1661l186 387h215l-248 -387h-153z " />
|
||||
<glyph unicode="Ê" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM414 1661l434 387h43l123 -387h-66 l-123 164l-321 -164h-90z" />
|
||||
<glyph unicode="Ë" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM487 1810q0 51 36.5 88t88.5 37 q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM856 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Ì" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM350 2048h215l60 -387h-154z" />
|
||||
<glyph unicode="Í" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM551 1661l186 387h215l-247 -387h-154z" />
|
||||
<glyph unicode="Î" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM309 1661l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="Ï" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM342 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36 t-36 88zM711 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Ð" horiz-adv-x="1357" d="M12 1004q0 132 108 256.5t275 200t336 75.5q138 0 243 -30.5t169.5 -80.5t105.5 -123t56.5 -150t15.5 -169q0 -260 -78 -493.5t-209.5 -375t-279.5 -141.5q-124 0 -250 109l-17 -82h-294l151 707h-80l41 151h72l94 445l303 40l-104 -485h161l-40 -151h-154l-109 -512 q55 -41 117 -41q88 0 164 83t125 213t76.5 285t27.5 305q0 82 -6.5 141t-24.5 110.5t-49.5 83t-81.5 49.5t-120 18q-251 0 -402 -133.5t-151 -364.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Ñ" horiz-adv-x="1404" d="M12 1004q0 70 26 144.5t77.5 143.5t120 123.5t161.5 87.5t195 33q131 0 190 -29l162 -1085l223 1110h295l-325 -1532h-248l-174 1071l-228 -1071h-294l294 1391q-135 -56 -209.5 -171t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM631 1704 q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="Ò" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM598 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Ó" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM813 1661l186 387h215l-247 -387h-154z" />
|
||||
<glyph unicode="Ô" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM557 1661l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="Õ" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM561 1704q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65q-70 0 -180 38t-164 38q-80 0 -136 -53z" />
|
||||
<glyph unicode="Ö" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM590 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM958 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="×" horiz-adv-x="1054" d="M201 338l194 194l-194 195l133 131l192 -194l197 196l131 -133l-195 -195l195 -194l-131 -133l-197 194l-192 -192z" />
|
||||
<glyph unicode="Ø" horiz-adv-x="1347" d="M31 913q0 99 66.5 195.5t175 168.5t255.5 116.5t299 44.5q34 0 50 -2l22 100h107l-25 -115q313 -72 313 -475q0 -106 -21.5 -221.5t-70 -236.5t-118 -220.5t-174.5 -173.5t-230 -96l-31 -143h-106l28 133q-169 0 -263.5 92.5t-94.5 294.5q0 79 15.5 174t44.5 196 t75.5 195.5t103.5 169t134 119.5t162 45q49 0 92 -14l18 83q-40 7 -78 7q-153 0 -281.5 -59t-208 -173t-79.5 -262q0 -32 5 -54.5t10 -33t5 -14.5q-98 0 -149 37t-51 122zM506 367q0 -207 98 -224l234 1106q-83 -56 -161.5 -213t-124.5 -341t-46 -328zM719 180 q81 59 153.5 194.5t116 308t43.5 331.5q0 108 -17 177t-61 107z" />
|
||||
<glyph unicode="Ù" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M840 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Ú" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M924 1661l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="Û" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M743 1661l435 387h43l122 -387h-65l-123 164l-321 -164h-91z" />
|
||||
<glyph unicode="Ü" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M764 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM1133 1810q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88z" />
|
||||
<glyph unicode="Ý" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM928 1661l186 387h215l-248 -387h-153z " />
|
||||
<glyph unicode="Þ" horiz-adv-x="1193" d="M12 778q0 146 129.5 281t319.5 203l51 233l303 41l-49 -225q113 -5 200.5 -40t140.5 -91.5t80 -125.5t27 -147q0 -99 -41.5 -196t-115 -176t-187.5 -128t-247 -49h-60l-76 -358h-294l245 1151q-115 -60 -175 -170t-60 -262q0 -46 6.5 -76t14 -44.5t7.5 -18.5 q-110 0 -164.5 45.5t-54.5 152.5zM580 434h28q104 0 193 79.5t137.5 195t48.5 229.5q0 77 -23.5 137t-78.5 99.5t-137 42.5z" />
|
||||
<glyph unicode="ß" horiz-adv-x="921" d="M-174 -512l340 1597q18 84 50 148.5t68.5 101.5t82.5 60t86.5 31t85.5 8q135 0 207.5 -75.5t72.5 -184.5q0 -92 -49.5 -175.5t-138.5 -124.5q103 -35 150.5 -112.5t47.5 -177.5q0 -120 -26.5 -220.5t-68 -166t-94 -111.5t-103.5 -66t-97 -20q-21 0 -67 4l26 121 q60 0 106 51t70.5 131.5t36 161.5t11.5 158q0 70 -34 113.5t-114 64.5l37 119q73 30 121.5 87t48.5 140q0 58 -18.5 80t-59.5 22q-43 0 -83 -38t-60 -130l-326 -1523z" />
|
||||
<glyph unicode="à" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM442 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="á" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM502 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="â" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM299 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z " />
|
||||
<glyph unicode="ã" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM209 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="ä" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM369 1298q0 51 36.5 88t88.5 37q51 0 87.5 -37t36.5 -88 q0 -52 -36.5 -88t-87.5 -36q-53 0 -89 36t-36 88zM737 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="å" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM479 1274q0 29 10.5 60.5t30 60t54.5 47t79 18.5 q72 0 110 -44t38 -113q0 -74 -44.5 -123.5t-133.5 -49.5q-64 0 -104 42t-40 102zM561 1278q0 -76 70 -76q46 0 70 25t24 69q0 20 -1.5 32.5t-7.5 29.5t-21 26t-38 9q-49 0 -72.5 -35t-23.5 -80z" />
|
||||
<glyph unicode="æ" horiz-adv-x="1331" d="M-16 254q0 93 34 160t97 105.5t142.5 56.5t180.5 18q57 0 107 -8q43 91 43 180q0 65 -31.5 105t-93.5 40q-84 0 -140 -24.5t-108 -71.5l-57 59q55 56 104 90.5t98 48.5t82.5 17.5t87.5 3.5q166 0 267 -82q121 90 276 90q66 0 111.5 -15.5t69 -45t33 -63.5t9.5 -78 q0 -110 -66.5 -206.5t-174.5 -154.5t-230 -65q-4 -46 -4 -66q0 -108 31 -148t92 -40q71 0 125.5 13t102 45.5t77 61.5t82.5 87h70q-48 -77 -99 -137.5t-120.5 -117.5t-157.5 -88.5t-188 -31.5q-246 0 -301 192q-49 -93 -114 -144.5t-177 -51.5q-49 0 -91.5 11.5t-82.5 39.5 t-63 83t-23 132zM272 289q0 -33 10.5 -55.5t28 -31.5t30.5 -12.5t26 -3.5q55 0 94.5 61.5t68.5 219.5q-12 2 -34 2q-100 0 -162 -49.5t-62 -130.5zM840 512q119 7 209 108.5t90 223.5q0 90 -49 90q-50 0 -101.5 -65.5t-88.5 -159.5t-60 -197z" />
|
||||
<glyph unicode="ç" horiz-adv-x="786" d="M-27 313q0 54 8.5 115.5t28.5 133t49 138.5t74 130t99.5 109.5t130 74.5t161.5 28q125 0 176.5 -53t51.5 -135q0 -72 -31 -110.5t-78 -38.5q-35 0 -72 24q25 70 25 121q0 84 -57 84q-61 0 -124 -103.5t-101 -244t-38 -254.5q0 -100 35 -136t113 -36q77 0 144.5 29.5 t113 69.5t104.5 108h70q-181 -289 -410 -357l-57 -145q66 16 109 16q63 0 95 -27t32 -75q0 -104 -74.5 -164.5t-171.5 -60.5q-70 0 -117.5 14t-101.5 43q35 65 82 65q45 0 85.5 -27.5t65.5 -27.5q26 0 44 26.5t18 59.5q0 31 -20 52.5t-60 21.5q-49 0 -107 -25l84 236 q-20 -4 -59 -4q-151 0 -235.5 79t-84.5 246z" />
|
||||
<glyph unicode="è" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM354 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="é" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM397 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="ê" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM215 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5z" />
|
||||
<glyph unicode="ë" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM244 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM612 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88 t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ì" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM135 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="í" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM248 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="î" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM12 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="ï" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM35 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88 q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM403 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ð" horiz-adv-x="1101" d="M-2 317q0 71 14.5 149.5t42.5 159t73 153.5t101 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-12l17 75q19 91 22 156h-158l17 82h141q-12 104 -86 203h92q79 -44 133 -90t86 -113h129l-16 -82h-86q12 -50 12 -109q0 -77 -22 -190l-111 -522q-8 -36 -8 -66 q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-90 -60t-84 -28t-75 -5.5q-84 0 -136.5 45.5t-64.5 132.5q-123 -190 -305 -190q-55 0 -101.5 17.5t-86 55t-62 103t-22.5 153.5zM301 346q0 -45 7.5 -77t17 -47.5t26 -24t26.5 -10t25 -1.5 q49 0 101 49t69 132l99 462q0 26 -19.5 52t-64.5 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="ñ" d="M-78 0l217 1024h295l-22 -106q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264t46 258 q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295zM180 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="ò" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM264 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM367 1536h215l59 -387h-154z" />
|
||||
<glyph unicode="ó" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM264 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM449 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="ô" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM195 1149l434 387h43 l123 -387h-66l-123 164l-321 -164h-90zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="õ" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM82 1192 q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27 q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="ö" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM215 1298 q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM584 1298 q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="÷" horiz-adv-x="966" d="M104 422l37 162h717l-35 -162h-719zM301 164q0 68 47.5 116t114.5 48t115.5 -48t48.5 -116q0 -67 -48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115zM424 850q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5t-115 47t-47 115z" />
|
||||
<glyph unicode="ø" horiz-adv-x="876" d="M-29 315q0 52 8 112t27 130t47.5 136t72 128.5t97 109.5t127.5 76.5t158 32.5l41 199h106l-43 -209q170 -46 176 -299q70 3 156.5 41.5t147.5 81.5h65q-46 -66 -151 -131.5t-222 -95.5q-17 -251 -123.5 -417t-267.5 -206l-45 -215h-106l43 203q-145 3 -229.5 81 t-84.5 242zM276 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 142.5 124.5t90.5 307.5q-57 15 -57 94q0 41 17.5 69.5t46.5 39.5q-4 58 -22.5 82.5t-55.5 24.5q-68 0 -132 -103t-99.5 -236t-35.5 -237z" />
|
||||
<glyph unicode="ù" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM379 1536h215l59 -387h-153z" />
|
||||
<glyph unicode="ú" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM489 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="û" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM258 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="ü" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM279 1298q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88zM647 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ý" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM487 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="þ" horiz-adv-x="968" d="M-188 -512l319 1505q25 116 25 191q0 132 -88 250h92q47 -26 79 -47.5t68.5 -56.5t58.5 -73t37 -94t15 -123q0 -51 -8 -100q105 94 247 94q114 0 182 -75.5t68 -247.5q0 -74 -10 -150t-32 -159.5t-61.5 -156t-93 -131t-132.5 -92.5t-175 -34q-138 0 -176 75l-106 -501z M260 217q16 -51 80 -51q61 0 112 39t82.5 99t53 136t30.5 145.5t9 131.5q0 170 -103 170q-36 0 -76.5 -28.5t-66.5 -78.5z" />
|
||||
<glyph unicode="ÿ" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM252 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36 q-53 0 -89 36t-36 88zM621 1298q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88z" />
|
||||
<glyph unicode="Œ" horiz-adv-x="1732" d="M33 977q0 141 115 270.5t307 209t403 79.5q130 0 225 -39q109 39 265 39q35 0 152.5 -12.5t187.5 -12.5q105 0 184 25q-5 -14 -13.5 -45t-14.5 -50t-20.5 -46.5t-31.5 -45t-47 -34.5t-68 -25q-90 0 -232.5 34.5t-193.5 39.5q92 -131 92 -352q0 -66 -10 -154h273l-41 -151 h-260q-70 -301 -234 -490h498l-43 -217h-793q-86 -12 -145 -12q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5 t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79l10 -31q-207 0 -207 172z" />
|
||||
<glyph unicode="œ" horiz-adv-x="1357" d="M-14 324q0 183 44 324t119.5 226t170.5 127.5t204 42.5q182 0 230 -174q128 172 327 172q127 0 194.5 -53.5t67.5 -159.5q0 -89 -44 -166t-114.5 -127.5t-148.5 -82t-155 -41.5q-7 -58 -7 -88q0 -93 49 -135t119 -42q79 0 185.5 80.5t158.5 190.5h93 q-37 -66 -79.5 -124.5t-98.5 -113.5t-116 -94t-133.5 -62.5t-150.5 -23.5q-101 0 -171.5 48.5t-104.5 129.5q-66 -93 -151.5 -141.5t-174.5 -48.5q-166 0 -239.5 77.5t-73.5 258.5zM258 342q0 -87 35.5 -121.5t101.5 -34.5q45 0 83 33.5t64.5 90.5t44 125.5t27.5 146.5 q-42 0 -64 26.5t-22 65.5q0 46 25 87t65 56q-9 107 -86 107q-68 0 -123.5 -67t-86.5 -164t-47.5 -190t-16.5 -161zM897 510q90 1 165.5 48t116.5 117.5t42 143.5q0 27 -4.5 50t-22 44t-45.5 21q-54 0 -106.5 -68t-87.5 -159.5t-58 -196.5z" />
|
||||
<glyph unicode="Ÿ" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM776 1810q0 51 36.5 88t88.5 37 q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM1145 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ˆ" horiz-adv-x="600" d="M0 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="˜" horiz-adv-x="825" d="M78 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode=" " horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="682" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode=" " horiz-adv-x="341" />
|
||||
<glyph unicode=" " horiz-adv-x="341" />
|
||||
<glyph unicode=" " horiz-adv-x="256" />
|
||||
<glyph unicode=" " horiz-adv-x="409" />
|
||||
<glyph unicode=" " horiz-adv-x="113" />
|
||||
<glyph unicode="‐" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="‑" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="‒" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="–" horiz-adv-x="720" d="M39 455l37 161h532l-35 -161h-534z" />
|
||||
<glyph unicode="—" horiz-adv-x="909" d="M39 455l37 161h717l-35 -161h-719z" />
|
||||
<glyph unicode="‘" horiz-adv-x="624" d="M172 1397q0 60 34.5 97.5t96.5 37.5q63 0 106.5 -42t43.5 -101q0 -74 -91 -129q-24 -15 -24 -35l100 -144q0 -18 -18 -28q-115 73 -181.5 168t-66.5 176z" />
|
||||
<glyph unicode="’" horiz-adv-x="624" d="M172 1389q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-19 11 -19 28l101 144q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="‚" horiz-adv-x="579" d="M156 156q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129z" />
|
||||
<glyph unicode="“" horiz-adv-x="1042" d="M156 1397q0 60 34.5 97.5t96.5 37.5q63 0 106 -42t43 -101q0 -74 -90 -129q-24 -15 -24 -35l100 -144q0 -17 -19 -28q-114 73 -180.5 167.5t-66.5 176.5zM606 1397q0 60 34.5 97.5t96.5 37.5q63 0 106.5 -42t43.5 -101q0 -74 -90 -129q-25 -16 -25 -35l100 -144 q0 -18 -18 -28q-115 73 -181.5 168t-66.5 176z" />
|
||||
<glyph unicode="”" horiz-adv-x="1042" d="M156 1389q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129zM606 1389q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144 q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="„" horiz-adv-x="1042" d="M156 156q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129zM606 156q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144 q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="•" horiz-adv-x="888" d="M246 547q0 82 57 140t139 58t140.5 -58t58.5 -140t-58.5 -139.5t-140.5 -57.5t-139 57.5t-57 139.5z" />
|
||||
<glyph unicode="…" horiz-adv-x="1437" d="M63 154q0 68 47.5 115.5t114.5 47.5q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115zM545 154q0 68 47.5 115.5t114.5 47.5t115 -47.5t48 -115.5q0 -67 -47.5 -114.5t-115.5 -47.5q-67 0 -114.5 47t-47.5 115zM1047 154q0 68 47 115.5t114 47.5 q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114 47t-47 115z" />
|
||||
<glyph unicode=" " horiz-adv-x="409" />
|
||||
<glyph unicode="‹" horiz-adv-x="735" d="M41 487l18 84l629 349l-20 -113l-494 -287l397 -237l-24 -117z" />
|
||||
<glyph unicode="›" horiz-adv-x="704" d="M31 166l28 117l492 237l-397 287l18 113l504 -349l-23 -110z" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode="€" horiz-adv-x="1173" d="M-6 582l29 102h151q4 43 16 133h-145l29 103h137q24 103 59.5 190.5t90.5 167.5t124.5 136t165 89t207.5 33q59 0 110.5 -9t97 -30t78 -52.5t51.5 -78t19 -104.5q0 -87 -34.5 -138t-108.5 -51q-71 0 -117 53q35 10 62.5 67t27.5 101q0 14 -2 28.5t-10.5 37t-22.5 39 t-41.5 29t-64.5 12.5q-83 0 -158 -75t-126 -189t-84 -256h413l-28 -103h-406q-12 -66 -18 -133h401l-29 -102h-379v-27q0 -84 7 -143t24.5 -107t48.5 -76t78 -42.5t113 -14.5q119 0 186.5 17.5t128.5 64.5l4 -59q-88 -116 -198.5 -161.5t-264.5 -45.5q-67 0 -127 13.5 t-120.5 50t-103.5 93t-70 150.5t-27 215v72h-174z" />
|
||||
<glyph unicode="™" horiz-adv-x="776" d="M-47 1188q0 68 51.5 114t157.5 46q8 0 63.5 -5.5t89.5 -5.5q51 0 76 11q-13 -80 -51 -80q-19 0 -63.5 8t-57.5 10l-86 -399h-88l88 407q-58 -2 -90.5 -30t-32.5 -95q0 -17 8 -41q-65 0 -65 60zM305 887l139 430l84 12v-346l136 356h96l-78 -452h-88l55 319l-129 -319h-84 v268l-82 -268h-49z" />
|
||||
<glyph unicode="" horiz-adv-x="1136" d="M0 0v1137h1137v-1137h-1137z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 85 KiB |
BIN
couchpotato/static/fonts/Lobster-webfont.ttf
Executable file
BIN
couchpotato/static/fonts/Lobster-webfont.ttf
Executable file
Binary file not shown.
BIN
couchpotato/static/fonts/Lobster-webfont.woff
Executable file
BIN
couchpotato/static/fonts/Lobster-webfont.woff
Executable file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
var ApiClass = new Class({
|
||||
|
||||
setup: function(options){
|
||||
var self = this
|
||||
var self = this;
|
||||
|
||||
self.options = options;
|
||||
},
|
||||
@@ -13,7 +13,7 @@ var ApiClass = new Class({
|
||||
return new Request[r_type](Object.merge({
|
||||
'callbackKey': 'callback_func',
|
||||
'method': 'get',
|
||||
'url': self.createUrl(type, {'t': randomString()}),
|
||||
'url': self.createUrl(type, {'t': randomString()})
|
||||
}, options)).send()
|
||||
},
|
||||
|
||||
@@ -26,4 +26,4 @@ var ApiClass = new Class({
|
||||
}
|
||||
|
||||
});
|
||||
window.Api = new ApiClass()
|
||||
window.Api = new ApiClass();
|
||||
@@ -36,4 +36,4 @@ var BlockBase = new Class({
|
||||
|
||||
});
|
||||
|
||||
var Block = BlockBase
|
||||
var Block = BlockBase;
|
||||
@@ -18,11 +18,11 @@ Block.Menu = new Class({
|
||||
self.button = new Element('a.button' + (self.options.button_class ? '.' + self.options.button_class : ''), {
|
||||
'events': {
|
||||
'click': function(){
|
||||
self.el.toggleClass('show')
|
||||
self.fireEvent(self.el.hasClass('show') ? 'open' : 'close')
|
||||
self.el.toggleClass('show');
|
||||
self.fireEvent(self.el.hasClass('show') ? 'open' : 'close');
|
||||
|
||||
if(self.el.hasClass('show')){
|
||||
self.el.addEvent('outerClick', self.removeOuterClick.bind(self))
|
||||
self.el.addEvent('outerClick', self.removeOuterClick.bind(self));
|
||||
this.addEvent('outerClick', function(e){
|
||||
if(e.target.get('tag') != 'input')
|
||||
self.removeOuterClick()
|
||||
@@ -41,7 +41,7 @@ Block.Menu = new Class({
|
||||
removeOuterClick: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.removeClass('show')
|
||||
self.el.removeClass('show');
|
||||
self.el.removeEvents('outerClick');
|
||||
|
||||
self.button.removeEvents('outerClick');
|
||||
@@ -49,8 +49,7 @@ Block.Menu = new Class({
|
||||
|
||||
addLink: function(tab, position){
|
||||
var self = this;
|
||||
var el = new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom');
|
||||
return el;
|
||||
return new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -5,7 +5,6 @@ Block.Navigation = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
var settings_added = false;
|
||||
self.el = new Element('div.navigation').adopt(
|
||||
self.foldout = new Element('a.foldout.icon2.menu', {
|
||||
'events': {
|
||||
@@ -28,7 +27,7 @@ Block.Navigation = new Class({
|
||||
'duration': 100
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
new ScrollSpy({
|
||||
min: 400,
|
||||
@@ -58,7 +57,7 @@ Block.Navigation = new Class({
|
||||
|
||||
},
|
||||
|
||||
toggleMenu: function(e){
|
||||
toggleMenu: function(){
|
||||
var self = this,
|
||||
body = $(document.body),
|
||||
html = body.getParent();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
|
||||
self.c = $(document.body)
|
||||
self.c = $(document.body);
|
||||
|
||||
self.route = new Route(self.defaults);
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
},
|
||||
|
||||
pushState: function(e){
|
||||
var self = this;
|
||||
if((!e.meta && Browser.Platform.mac) || (!e.control && !Browser.Platform.mac)){
|
||||
(e).preventDefault();
|
||||
var url = e.target.get('href');
|
||||
@@ -112,11 +111,11 @@
|
||||
'click': self.shutdownQA.bind(self)
|
||||
}
|
||||
})
|
||||
]
|
||||
];
|
||||
|
||||
setting_links.each(function(a){
|
||||
self.block.more.addLink(a)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
new ScrollSpy({
|
||||
@@ -134,7 +133,7 @@
|
||||
var self = this;
|
||||
|
||||
Object.each(Page, function(page_class, class_name){
|
||||
pg = new Page[class_name](self, {});
|
||||
var pg = new Page[class_name](self, {});
|
||||
self.pages[class_name] = pg;
|
||||
|
||||
$(pg).inject(self.content);
|
||||
@@ -157,7 +156,7 @@
|
||||
return;
|
||||
|
||||
if(self.current_page)
|
||||
self.current_page.hide()
|
||||
self.current_page.hide();
|
||||
|
||||
try {
|
||||
var page = self.pages[page_name] || self.pages.Home;
|
||||
@@ -191,7 +190,7 @@
|
||||
self.checkAvailable(1000);
|
||||
},
|
||||
|
||||
shutdownQA: function(e){
|
||||
shutdownQA: function(){
|
||||
var self = this;
|
||||
|
||||
var q = new Question('Are you sure you want to shutdown CouchPotato?', '', [{
|
||||
@@ -240,7 +239,7 @@
|
||||
checkForUpdate: function(onComplete){
|
||||
var self = this;
|
||||
|
||||
Updater.check(onComplete)
|
||||
Updater.check(onComplete);
|
||||
|
||||
self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates');
|
||||
self.checkAvailable(3000);
|
||||
@@ -258,7 +257,7 @@
|
||||
},
|
||||
'onSuccess': function(){
|
||||
if(onAvailable)
|
||||
onAvailable()
|
||||
onAvailable();
|
||||
self.unBlockPage();
|
||||
self.fireEvent('reload');
|
||||
}
|
||||
@@ -272,7 +271,6 @@
|
||||
|
||||
self.unBlockPage();
|
||||
|
||||
var body = $(document.body);
|
||||
self.mask = new Element('div.mask').adopt(
|
||||
new Element('div').adopt(
|
||||
new Element('h1', {'text': title || 'Unavailable'}),
|
||||
@@ -329,7 +327,7 @@
|
||||
'target': '',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop()
|
||||
(e).stop();
|
||||
alert('Drag it to your bookmark ;)')
|
||||
}
|
||||
}
|
||||
@@ -352,35 +350,35 @@ var Route = new Class({
|
||||
params: {},
|
||||
|
||||
initialize: function(defaults){
|
||||
var self = this
|
||||
var self = this;
|
||||
self.defaults = defaults
|
||||
},
|
||||
|
||||
parse: function(){
|
||||
var self = this;
|
||||
|
||||
var rep = function(pa){
|
||||
var rep = function (pa) {
|
||||
return pa.replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/')
|
||||
}
|
||||
};
|
||||
|
||||
var path = rep(History.getPath())
|
||||
var path = rep(History.getPath());
|
||||
if(path == '/' && location.hash){
|
||||
path = rep(location.hash.replace('#', '/'))
|
||||
}
|
||||
self.current = path.replace(/^\/+|\/+$/g, '')
|
||||
var url = self.current.split('/')
|
||||
self.current = path.replace(/^\/+|\/+$/g, '');
|
||||
var url = self.current.split('/');
|
||||
|
||||
self.page = (url.length > 0) ? url.shift() : self.defaults.page
|
||||
self.action = (url.length > 0) ? url.shift() : self.defaults.action
|
||||
self.page = (url.length > 0) ? url.shift() : self.defaults.page;
|
||||
self.action = (url.length > 0) ? url.shift() : self.defaults.action;
|
||||
|
||||
self.params = Object.merge({}, self.defaults.params);
|
||||
if(url.length > 1){
|
||||
var key
|
||||
var key;
|
||||
url.each(function(el, nr){
|
||||
if(nr%2 == 0)
|
||||
key = el
|
||||
key = el;
|
||||
else if(key) {
|
||||
self.params[key] = el
|
||||
self.params[key] = el;
|
||||
key = null
|
||||
}
|
||||
})
|
||||
@@ -488,8 +486,8 @@ function randomString(length, extra) {
|
||||
|
||||
var comparer = function(a, b) {
|
||||
for (var i = 0, l = keyPaths.length; i < l; i++) {
|
||||
aVal = valueOf(a, keyPaths[i].path);
|
||||
bVal = valueOf(b, keyPaths[i].path);
|
||||
var aVal = valueOf(a, keyPaths[i].path),
|
||||
bVal = valueOf(b, keyPaths[i].path);
|
||||
if (aVal > bVal) return keyPaths[i].sign;
|
||||
if (aVal < bVal) return -keyPaths[i].sign;
|
||||
}
|
||||
@@ -530,4 +528,4 @@ var createSpinner = function(target, options){
|
||||
}, options);
|
||||
|
||||
return new Spinner(opts).spin(target);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ var PageBase = new Class({
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
|
||||
self.setOptions(options)
|
||||
self.setOptions(options);
|
||||
|
||||
// Create main page container
|
||||
self.el = new Element('div.page.'+self.name);
|
||||
@@ -74,4 +74,4 @@ var PageBase = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
var Page = {}
|
||||
var Page = {};
|
||||
|
||||
@@ -13,7 +13,7 @@ var AboutSettingTab = new Class({
|
||||
addSettings: function(){
|
||||
var self = this;
|
||||
|
||||
self.settings = App.getPage('Settings')
|
||||
self.settings = App.getPage('Settings');
|
||||
self.settings.addEvent('create', function(){
|
||||
var tab = self.settings.createTab('about', {
|
||||
'label': 'About',
|
||||
@@ -72,7 +72,7 @@ var AboutSettingTab = new Class({
|
||||
);
|
||||
|
||||
if(!self.fillVersion(Updater.getInfo()))
|
||||
Updater.addEvent('loaded', self.fillVersion.bind(self))
|
||||
Updater.addEvent('loaded', self.fillVersion.bind(self));
|
||||
|
||||
self.settings.createGroup({
|
||||
'name': 'Help Support CouchPotato'
|
||||
|
||||
@@ -5,7 +5,7 @@ Page.Home = new Class({
|
||||
name: 'home',
|
||||
title: 'Manage new stuff for things and such',
|
||||
|
||||
indexAction: function(param){
|
||||
indexAction: function () {
|
||||
var self = this;
|
||||
|
||||
if(self.soon_list){
|
||||
@@ -14,10 +14,24 @@ Page.Home = new Class({
|
||||
self.available_list.update();
|
||||
self.late_list.update();
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Snatched
|
||||
self.chain = new Chain();
|
||||
self.chain.chain(
|
||||
self.createAvailable.bind(self),
|
||||
self.createSoon.bind(self),
|
||||
self.createSuggestions.bind(self),
|
||||
self.createLate.bind(self)
|
||||
);
|
||||
|
||||
self.chain.callChain();
|
||||
|
||||
},
|
||||
|
||||
createAvailable: function(){
|
||||
var self = this;
|
||||
|
||||
self.available_list = new MovieList({
|
||||
'navigation': false,
|
||||
'identifier': 'snatched',
|
||||
@@ -40,9 +54,19 @@ Page.Home = new Class({
|
||||
'filter': {
|
||||
'release_status': 'snatched,available'
|
||||
},
|
||||
'limit': null
|
||||
'limit': null,
|
||||
'onLoaded': function(){
|
||||
self.chain.callChain();
|
||||
}
|
||||
});
|
||||
|
||||
$(self.available_list).inject(self.el);
|
||||
|
||||
},
|
||||
|
||||
createSoon: function(){
|
||||
var self = this;
|
||||
|
||||
// Coming Soon
|
||||
self.soon_list = new MovieList({
|
||||
'navigation': false,
|
||||
@@ -50,10 +74,6 @@ Page.Home = new Class({
|
||||
'limit': 12,
|
||||
'title': 'Available soon',
|
||||
'description': 'These are being searched for and should be available soon as they will be released on DVD in the next few weeks.',
|
||||
'on_empty_element': new Element('div').adopt(
|
||||
new Element('h2', {'text': 'Available soon'}),
|
||||
new Element('span', {'text': 'There are no movies available soon. Add some movies, so you have something to watch later.'})
|
||||
),
|
||||
'filter': {
|
||||
'random': true
|
||||
},
|
||||
@@ -61,7 +81,10 @@ Page.Home = new Class({
|
||||
'load_more': false,
|
||||
'view': 'thumbs',
|
||||
'force_view': true,
|
||||
'api_call': 'dashboard.soon'
|
||||
'api_call': 'dashboard.soon',
|
||||
'onLoaded': function(){
|
||||
self.chain.callChain();
|
||||
}
|
||||
});
|
||||
|
||||
// Make all thumbnails the same size
|
||||
@@ -99,10 +122,30 @@ Page.Home = new Class({
|
||||
images.setStyle('height', highest);
|
||||
}).delay(300);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(self.soon_list).inject(self.el);
|
||||
|
||||
},
|
||||
|
||||
createSuggestions: function(){
|
||||
var self = this;
|
||||
|
||||
// Suggest
|
||||
self.suggestion_list = new SuggestList();
|
||||
self.suggestion_list = new SuggestList({
|
||||
'onLoaded': function(){
|
||||
self.chain.callChain();
|
||||
}
|
||||
});
|
||||
|
||||
$(self.suggestion_list).inject(self.el);
|
||||
|
||||
|
||||
},
|
||||
|
||||
createLate: function(){
|
||||
var self = this;
|
||||
|
||||
// Still not available
|
||||
self.late_list = new MovieList({
|
||||
@@ -110,7 +153,7 @@ Page.Home = new Class({
|
||||
'identifier': 'late',
|
||||
'limit': 50,
|
||||
'title': 'Still not available',
|
||||
'description': 'Try another quality profile or maybe add more providers in <a href="'+App.createUrl('settings/searcher/providers/')+'">Settings</a>.',
|
||||
'description': 'Try another quality profile or maybe add more providers in <a href="' + App.createUrl('settings/searcher/providers/') + '">Settings</a>.',
|
||||
'filter': {
|
||||
'late': true
|
||||
},
|
||||
@@ -118,25 +161,14 @@ Page.Home = new Class({
|
||||
'load_more': false,
|
||||
'view': 'list',
|
||||
'actions': [MA.IMDB, MA.Trailer, MA.Edit, MA.Refresh, MA.Delete],
|
||||
'api_call': 'dashboard.soon'
|
||||
'api_call': 'dashboard.soon',
|
||||
'onLoaded': function(){
|
||||
self.chain.callChain();
|
||||
}
|
||||
});
|
||||
|
||||
self.el.adopt(
|
||||
$(self.available_list),
|
||||
$(self.soon_list),
|
||||
$(self.suggestion_list),
|
||||
$(self.late_list)
|
||||
);
|
||||
|
||||
// Recent
|
||||
// Snatched
|
||||
// Renamed
|
||||
// Added
|
||||
|
||||
// Free space
|
||||
|
||||
// Shortcuts
|
||||
$(self.late_list).inject(self.el);
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
@@ -5,7 +5,7 @@ Page.Manage = new Class({
|
||||
name: 'manage',
|
||||
title: 'Do stuff to your existing movies!',
|
||||
|
||||
indexAction: function(param){
|
||||
indexAction: function(){
|
||||
var self = this;
|
||||
|
||||
if(!self.list){
|
||||
@@ -73,7 +73,7 @@ Page.Manage = new Class({
|
||||
'data': {
|
||||
'full': +full
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
self.startProgressInterval();
|
||||
|
||||
@@ -108,7 +108,7 @@ Page.Manage = new Class({
|
||||
return;
|
||||
|
||||
if(!self.progress_container)
|
||||
self.progress_container = new Element('div.progress').inject(self.list.navigation, 'after')
|
||||
self.progress_container = new Element('div.progress').inject(self.list.navigation, 'after');
|
||||
|
||||
self.progress_container.empty();
|
||||
|
||||
|
||||
@@ -46,16 +46,16 @@ Page.Settings = new Class({
|
||||
var t = self.tabs[tab_name] || self.tabs[self.action] || self.tabs.general;
|
||||
|
||||
// Subtab
|
||||
var subtab = null
|
||||
var subtab = null;
|
||||
Object.each(self.params, function(param, subtab_name){
|
||||
subtab = subtab_name;
|
||||
})
|
||||
});
|
||||
|
||||
self.el.getElements('li.'+c+' , .tab_content.'+c).each(function(active){
|
||||
active.removeClass(c);
|
||||
});
|
||||
|
||||
if (t.subtabs[subtab]){
|
||||
if(t.subtabs[subtab]){
|
||||
t.tab[a](c);
|
||||
t.subtabs[subtab].tab[a](c);
|
||||
t.subtabs[subtab].content[a](c);
|
||||
@@ -87,7 +87,7 @@ Page.Settings = new Class({
|
||||
self.data = json;
|
||||
onComplete(json);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return self.data;
|
||||
},
|
||||
@@ -139,7 +139,7 @@ Page.Settings = new Class({
|
||||
Object.each(json.options, function(section, section_name){
|
||||
section['section_name'] = section_name;
|
||||
options.include(section);
|
||||
})
|
||||
});
|
||||
|
||||
options.sort(function(a, b){
|
||||
return (a.order || 100) - (b.order || 100)
|
||||
@@ -156,13 +156,13 @@ Page.Settings = new Class({
|
||||
// Create tab
|
||||
if(!self.tabs[group.tab] || !self.tabs[group.tab].groups)
|
||||
self.createTab(group.tab, {});
|
||||
var content_container = self.tabs[group.tab].content
|
||||
var content_container = self.tabs[group.tab].content;
|
||||
|
||||
// Create subtab
|
||||
if(group.subtab){
|
||||
if (!self.tabs[group.tab].subtabs[group.subtab])
|
||||
if(!self.tabs[group.tab].subtabs[group.subtab])
|
||||
self.createSubTab(group.subtab, group, self.tabs[group.tab], group.tab);
|
||||
var content_container = self.tabs[group.tab].subtabs[group.subtab].content
|
||||
content_container = self.tabs[group.tab].subtabs[group.subtab].content
|
||||
}
|
||||
|
||||
if(group.list && !self.lists[group.list]){
|
||||
@@ -170,12 +170,10 @@ Page.Settings = new Class({
|
||||
}
|
||||
|
||||
// Create the group
|
||||
if(!self.tabs[group.tab].groups[group.name]){
|
||||
var group_el = self.createGroup(group)
|
||||
if(!self.tabs[group.tab].groups[group.name])
|
||||
self.tabs[group.tab].groups[group.name] = self.createGroup(group)
|
||||
.inject(group.list ? self.lists[group.list] : content_container)
|
||||
.addClass('section_'+section_name);
|
||||
self.tabs[group.tab].groups[group.name] = group_el;
|
||||
}
|
||||
|
||||
// Create list if needed
|
||||
if(group.type && group.type == 'list'){
|
||||
@@ -208,9 +206,9 @@ Page.Settings = new Class({
|
||||
var self = this;
|
||||
|
||||
if(self.tabs[tab_name] && self.tabs[tab_name].tab)
|
||||
return self.tabs[tab_name].tab
|
||||
return self.tabs[tab_name].tab;
|
||||
|
||||
var label = tab.label || (tab.name || tab_name).capitalize()
|
||||
var label = tab.label || (tab.name || tab_name).capitalize();
|
||||
var tab_el = new Element('li.t_'+tab_name).adopt(
|
||||
new Element('a', {
|
||||
'href': App.createUrl(self.name+'/'+tab_name),
|
||||
@@ -221,14 +219,14 @@ Page.Settings = new Class({
|
||||
if(!self.tabs[tab_name])
|
||||
self.tabs[tab_name] = {
|
||||
'label': label
|
||||
}
|
||||
};
|
||||
|
||||
self.tabs[tab_name] = Object.merge(self.tabs[tab_name], {
|
||||
'tab': tab_el,
|
||||
'subtabs': {},
|
||||
'content': new Element('div.tab_content.tab_'+tab_name).inject(self.containers),
|
||||
'content': new Element('div.tab_content.tab_' + tab_name).inject(self.containers),
|
||||
'groups': {}
|
||||
})
|
||||
});
|
||||
|
||||
return self.tabs[tab_name]
|
||||
|
||||
@@ -238,12 +236,12 @@ Page.Settings = new Class({
|
||||
var self = this;
|
||||
|
||||
if(parent_tab.subtabs[tab_name])
|
||||
return parent_tab.subtabs[tab_name]
|
||||
return parent_tab.subtabs[tab_name];
|
||||
|
||||
if(!parent_tab.subtabs_el)
|
||||
parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab);
|
||||
|
||||
var label = tab.subtab_label || tab_name.replace('_', ' ').capitalize()
|
||||
var label = tab.subtab_label || tab_name.replace('_', ' ').capitalize();
|
||||
var tab_el = new Element('li.t_'+tab_name).adopt(
|
||||
new Element('a', {
|
||||
'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name),
|
||||
@@ -254,7 +252,7 @@ Page.Settings = new Class({
|
||||
if(!parent_tab.subtabs[tab_name])
|
||||
parent_tab.subtabs[tab_name] = {
|
||||
'label': label
|
||||
}
|
||||
};
|
||||
|
||||
parent_tab.subtabs[tab_name] = Object.merge(parent_tab.subtabs[tab_name], {
|
||||
'tab': tab_el,
|
||||
@@ -267,21 +265,17 @@ Page.Settings = new Class({
|
||||
},
|
||||
|
||||
createGroup: function(group){
|
||||
var self = this;
|
||||
|
||||
var group_el = new Element('fieldset', {
|
||||
return new Element('fieldset', {
|
||||
'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '')
|
||||
}).adopt(
|
||||
new Element('h2', {
|
||||
'text': group.label || (group.name).capitalize()
|
||||
}).adopt(
|
||||
new Element('span.hint', {
|
||||
'html': group.description || ''
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return group_el
|
||||
new Element('h2', {
|
||||
'text': group.label || (group.name).capitalize()
|
||||
}).adopt(
|
||||
new Element('span.hint', {
|
||||
'html': group.description || ''
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
createList: function(content_container){
|
||||
@@ -299,12 +293,12 @@ var OptionBase = new Class({
|
||||
Implements: [Options, Events],
|
||||
|
||||
klass: 'textInput',
|
||||
focused_class : 'focused',
|
||||
focused_class: 'focused',
|
||||
save_on_change: true,
|
||||
|
||||
initialize: function(section, name, value, options){
|
||||
var self = this
|
||||
self.setOptions(options)
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
|
||||
self.section = section;
|
||||
self.name = name;
|
||||
@@ -330,10 +324,11 @@ var OptionBase = new Class({
|
||||
*/
|
||||
createBase: function(){
|
||||
var self = this;
|
||||
self.el = new Element('div.ctrlHolder.'+self.section + '_' + self.name)
|
||||
self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name)
|
||||
},
|
||||
|
||||
create: function(){},
|
||||
create: function(){
|
||||
},
|
||||
|
||||
createLabel: function(){
|
||||
var self = this;
|
||||
@@ -343,7 +338,7 @@ var OptionBase = new Class({
|
||||
},
|
||||
|
||||
setAdvanced: function(){
|
||||
this.el.addClass(this.options.advanced ? 'advanced': '')
|
||||
this.el.addClass(this.options.advanced ? 'advanced' : '')
|
||||
},
|
||||
|
||||
createHint: function(){
|
||||
@@ -354,7 +349,8 @@ var OptionBase = new Class({
|
||||
}).inject(self.el);
|
||||
},
|
||||
|
||||
afterInject: function(){},
|
||||
afterInject: function(){
|
||||
},
|
||||
|
||||
// Element has changed, do something
|
||||
changed: function(){
|
||||
@@ -407,7 +403,7 @@ var OptionBase = new Class({
|
||||
|
||||
postName: function(){
|
||||
var self = this;
|
||||
return self.section +'['+self.name+']';
|
||||
return self.section + '[' + self.name + ']';
|
||||
},
|
||||
|
||||
getValue: function(){
|
||||
@@ -427,16 +423,16 @@ var OptionBase = new Class({
|
||||
toElement: function(){
|
||||
return this.el;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var Option = {}
|
||||
var Option = {};
|
||||
Option.String = new Class({
|
||||
Extends: OptionBase,
|
||||
|
||||
type: 'string',
|
||||
|
||||
create: function(){
|
||||
var self = this
|
||||
var self = this;
|
||||
|
||||
self.el.adopt(
|
||||
self.createLabel(),
|
||||
@@ -458,21 +454,21 @@ Option.Dropdown = new Class({
|
||||
Extends: OptionBase,
|
||||
|
||||
create: function(){
|
||||
var self = this
|
||||
var self = this;
|
||||
|
||||
self.el.adopt(
|
||||
self.createLabel(),
|
||||
self.input = new Element('select', {
|
||||
'name': self.postName()
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
Object.each(self.options.values, function(value){
|
||||
new Element('option', {
|
||||
'text': value[0],
|
||||
'value': value[1]
|
||||
}).inject(self.input)
|
||||
})
|
||||
});
|
||||
|
||||
self.input.set('value', self.getSettingValue());
|
||||
|
||||
@@ -491,7 +487,7 @@ Option.Checkbox = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
var randomId = 'r-'+randomString()
|
||||
var randomId = 'r-' + randomString();
|
||||
|
||||
self.el.adopt(
|
||||
self.createLabel().set('for', randomId),
|
||||
@@ -520,8 +516,8 @@ Option.Password = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.parent()
|
||||
self.input.set('type', 'password')
|
||||
self.parent();
|
||||
self.input.set('type', 'password');
|
||||
|
||||
self.input.addEvent('focus', function(){
|
||||
self.input.set('value', '')
|
||||
@@ -570,9 +566,9 @@ Option.Enabler = new Class({
|
||||
afterInject: function(){
|
||||
var self = this;
|
||||
|
||||
self.parentFieldset = self.el.getParent('fieldset').addClass('enabler')
|
||||
self.parentFieldset = self.el.getParent('fieldset').addClass('enabler');
|
||||
self.parentList = self.parentFieldset.getParent('.option_list');
|
||||
self.el.inject(self.parentFieldset, 'top')
|
||||
self.el.inject(self.parentFieldset, 'top');
|
||||
self.checkState()
|
||||
}
|
||||
|
||||
@@ -622,7 +618,7 @@ Option.Directory = new Class({
|
||||
self.getDirs()
|
||||
},
|
||||
|
||||
previousDirectory: function(e){
|
||||
previousDirectory: function(){
|
||||
var self = this;
|
||||
|
||||
self.selectDirectory(self.getParentDir())
|
||||
@@ -697,8 +693,8 @@ Option.Directory = new Class({
|
||||
|
||||
self.initial_directory = self.input.get('text');
|
||||
|
||||
self.getDirs()
|
||||
self.browser.show()
|
||||
self.getDirs();
|
||||
self.browser.show();
|
||||
self.el.addEvent('outerClick', self.hideBrowser.bind(self))
|
||||
},
|
||||
|
||||
@@ -707,11 +703,11 @@ Option.Directory = new Class({
|
||||
(e).preventDefault();
|
||||
|
||||
if(save)
|
||||
self.save()
|
||||
self.save();
|
||||
else
|
||||
self.input.set('text', self.initial_directory);
|
||||
|
||||
self.browser.hide()
|
||||
self.browser.hide();
|
||||
self.el.removeEvents('outerClick')
|
||||
|
||||
},
|
||||
@@ -732,11 +728,11 @@ Option.Directory = new Class({
|
||||
var prev_dirname = self.getCurrentDirname(previous_dir);
|
||||
if(previous_dir == json.home)
|
||||
prev_dirname = 'Home';
|
||||
else if (previous_dir == '/' && json.platform == 'nt')
|
||||
else if(previous_dir == '/' && json.platform == 'nt')
|
||||
prev_dirname = 'Computer';
|
||||
|
||||
self.back_button.set('data-value', previous_dir)
|
||||
self.back_button.set('html', '« '+prev_dirname)
|
||||
self.back_button.set('data-value', previous_dir);
|
||||
self.back_button.set('html', '« ' + prev_dirname);
|
||||
self.back_button.show()
|
||||
}
|
||||
else {
|
||||
@@ -798,8 +794,6 @@ Option.Directory = new Class({
|
||||
},
|
||||
|
||||
getCurrentDirname: function(dir){
|
||||
var self = this;
|
||||
|
||||
var dir_split = dir.split(Api.getOption('path_sep'));
|
||||
|
||||
return dir_split[dir_split.length-2] || Api.getOption('path_sep')
|
||||
@@ -848,7 +842,7 @@ Option.Directories = new Class({
|
||||
var parent = self.el.getParent('fieldset');
|
||||
var dirs = parent.getElements('.multi_directory');
|
||||
if(dirs.length == 0)
|
||||
$(dir).inject(parent)
|
||||
$(dir).inject(parent);
|
||||
else
|
||||
$(dir).inject(dirs.getLast(), 'after');
|
||||
|
||||
@@ -885,7 +879,7 @@ Option.Directories = new Class({
|
||||
saveItems: function(){
|
||||
var self = this;
|
||||
|
||||
var dirs = []
|
||||
var dirs = [];
|
||||
self.directories.each(function(dir){
|
||||
if(dir.getValue()){
|
||||
$(dir).removeClass('is_empty');
|
||||
@@ -957,7 +951,7 @@ Option.Choice = new Class({
|
||||
}).inject(self.input, 'after');
|
||||
self.el.addClass('tag_input');
|
||||
|
||||
var mtches = []
|
||||
var mtches = [];
|
||||
if(matches)
|
||||
matches.each(function(match, mnr){
|
||||
var pos = value.indexOf(match),
|
||||
@@ -1037,7 +1031,7 @@ Option.Choice = new Class({
|
||||
|
||||
var prev_index = self.tags.indexOf(from_tag)-1;
|
||||
if(prev_index >= 0)
|
||||
self.tags[prev_index].selectFrom('right')
|
||||
self.tags[prev_index].selectFrom('right');
|
||||
else
|
||||
from_tag.focus();
|
||||
|
||||
@@ -1049,7 +1043,7 @@ Option.Choice = new Class({
|
||||
|
||||
var next_index = self.tags.indexOf(from_tag)+1;
|
||||
if(next_index < self.tags.length)
|
||||
self.tags[next_index].selectFrom('left')
|
||||
self.tags[next_index].selectFrom('left');
|
||||
else
|
||||
from_tag.focus();
|
||||
},
|
||||
@@ -1139,7 +1133,7 @@ Option.Choice.Tag = new Class({
|
||||
if(e.key == 'left' && current_caret_pos == self.last_caret_pos){
|
||||
self.fireEvent('goLeft');
|
||||
}
|
||||
else if (e.key == 'right' && self.last_caret_pos === current_caret_pos){
|
||||
else if(e.key == 'right' && self.last_caret_pos === current_caret_pos){
|
||||
self.fireEvent('goRight');
|
||||
}
|
||||
self.last_caret_pos = self.input.getCaretPosition();
|
||||
@@ -1195,11 +1189,11 @@ Option.Choice.Tag = new Class({
|
||||
self.fireEvent('goRight');
|
||||
this.destroy();
|
||||
}
|
||||
else if (e.key == 'left'){
|
||||
else if(e.key == 'left'){
|
||||
self.fireEvent('goLeft');
|
||||
this.destroy();
|
||||
}
|
||||
else if (e.key == 'backspace'){
|
||||
else if(e.key == 'backspace'){
|
||||
self.del();
|
||||
this.destroy();
|
||||
self.fireEvent('goLeft');
|
||||
@@ -1213,7 +1207,7 @@ Option.Choice.Tag = new Class({
|
||||
'top': -200
|
||||
}
|
||||
});
|
||||
self.el.adopt(temp_input)
|
||||
self.el.adopt(temp_input);
|
||||
temp_input.focus();
|
||||
}
|
||||
},
|
||||
@@ -1266,10 +1260,10 @@ Option.Combined = new Class({
|
||||
|
||||
self.fieldset = self.input.getParent('fieldset');
|
||||
self.combined_list = new Element('div.combined_table').inject(self.fieldset.getElement('h2'), 'after');
|
||||
self.values = {}
|
||||
self.inputs = {}
|
||||
self.items = []
|
||||
self.labels = {}
|
||||
self.values = {};
|
||||
self.inputs = {};
|
||||
self.items = [];
|
||||
self.labels = {};
|
||||
|
||||
self.options.combine.each(function(name){
|
||||
|
||||
@@ -1277,7 +1271,7 @@ Option.Combined = new Class({
|
||||
var values = self.inputs[name].get('value').split(',');
|
||||
|
||||
values.each(function(value, nr){
|
||||
if (!self.values[nr]) self.values[nr] = {};
|
||||
if(!self.values[nr]) self.values[nr] = {};
|
||||
self.values[nr][name] = value.trim();
|
||||
});
|
||||
|
||||
@@ -1286,19 +1280,18 @@ Option.Combined = new Class({
|
||||
|
||||
});
|
||||
|
||||
var head = new Element('div.head').inject(self.combined_list)
|
||||
var head = new Element('div.head').inject(self.combined_list);
|
||||
|
||||
Object.each(self.inputs, function(input, name){
|
||||
self.labels[name] = input.getPrevious().get('text')
|
||||
self.labels[name] = input.getPrevious().get('text');
|
||||
new Element('abbr', {
|
||||
'class': name,
|
||||
'text': self.labels[name],
|
||||
//'title': input.getNext().get('text')
|
||||
'text': self.labels[name]
|
||||
}).inject(head)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Object.each(self.values, function(item, nr){
|
||||
Object.each(self.values, function(item){
|
||||
self.createItem(item);
|
||||
});
|
||||
|
||||
@@ -1316,7 +1309,7 @@ Option.Combined = new Class({
|
||||
self.items.each(function(ctrl_holder){
|
||||
var empty_count = 0;
|
||||
self.options.combine.each(function(name){
|
||||
var input = ctrl_holder.getElement('input.'+name)
|
||||
var input = ctrl_holder.getElement('input.' + name);
|
||||
if(input.get('value') == '' || input.get('type') == 'checkbox')
|
||||
empty_count++
|
||||
});
|
||||
@@ -1338,7 +1331,7 @@ Option.Combined = new Class({
|
||||
value_empty = 0;
|
||||
|
||||
self.options.combine.each(function(name){
|
||||
var value = values[name] || ''
|
||||
var value = values[name] || '';
|
||||
|
||||
if(name.indexOf('use') != -1){
|
||||
var checkbox = new Element('input[type=checkbox].inlay.'+name, {
|
||||
@@ -1375,7 +1368,7 @@ Option.Combined = new Class({
|
||||
'events': {
|
||||
'click': self.deleteCombinedItem.bind(self)
|
||||
}
|
||||
}).inject(item)
|
||||
}).inject(item);
|
||||
|
||||
self.items.include(item);
|
||||
|
||||
@@ -1386,7 +1379,7 @@ Option.Combined = new Class({
|
||||
var self = this;
|
||||
|
||||
|
||||
var temp = {}
|
||||
var temp = {};
|
||||
self.items.each(function(item, nr){
|
||||
self.options.combine.each(function(name){
|
||||
var input = item.getElement('input.'+name);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user