Compare commits

..

213 Commits

Author SHA1 Message Date
Ruud cd55966575 One up! 2014-11-09 13:50:52 +01:00
Ruud 5873a5c8e2 Force include lxml 2014-11-09 13:50:43 +01:00
Ruud 32163b3951 Merge branch 'master' into desktop 2014-11-09 10:27:35 +01:00
Ruud e0cc86b51c Merge branch 'develop' 2014-11-09 10:27:12 +01:00
Ruud 7ec64e202b Merge branch 'develop' 2014-10-20 21:05:14 +02:00
Ruud da97b62c44 Merge branch 'develop' 2014-10-19 22:42:00 +02:00
Ruud 1fb031ff40 Merge branch 'develop' 2014-10-12 16:41:56 +02:00
Ruud 86edf5eb04 Exclude libs 2014-10-12 16:19:27 +02:00
Ruud 92f9743d3c Merge branch 'master' into desktop 2014-10-11 23:01:02 +02:00
Ruud 1b151fbd97 Merge branch 'develop' 2014-10-11 22:59:53 +02:00
Ruud 0567504394 One up! 2014-10-11 22:46:45 +02:00
Ruud c8a3b64624 Set black icon for mac 2014-10-11 22:45:31 +02:00
Ruud c657d6d70b Merge branch 'master' into desktop
Conflicts:
	couchpotato/core/database.py
2014-10-11 19:34:46 +02:00
Ruud d307d343e5 Merge branch 'develop' 2014-10-11 19:33:06 +02:00
Ruud f2ab59e384 Merge branch 'develop' 2014-10-11 14:38:21 +02:00
Ruud 55f201040b Merge branch 'develop' 2014-10-09 23:10:52 +02:00
Ruud 476a5cc3dd Merge branch 'develop' 2014-10-08 23:08:33 +02:00
Ruud 342a4ad885 Merge branch 'develop' 2014-10-08 23:00:37 +02:00
Ruud 12159a1b7b Merge branch 'develop' 2014-10-08 22:54:41 +02:00
Ruud b773f7b71c Merge branch 'develop' 2014-10-07 23:09:44 +02:00
Ruud 41aba6b19c Merge branch 'develop' 2014-10-07 09:30:07 +02:00
Ruud 96def8563b Merge branch 'develop' 2014-10-05 11:16:06 +02:00
Ruud bf46a937c0 Merge branch 'develop' 2014-10-04 22:57:51 +02:00
Ruud 2edb6caa97 Merge branch 'develop' 2014-10-04 22:19:37 +02:00
Ruud 9e125a361a Merge branch 'develop' 2014-10-04 15:43:01 +02:00
Ruud 2252ed710c Merge branch 'develop' 2014-09-29 16:25:19 +02:00
Ruud 07a790e9b2 Merge branch 'develop' 2014-09-25 22:32:38 +02:00
Ruud bb6fefd010 Merge branch 'develop' 2014-09-23 14:54:21 +02:00
Ruud 55e489cc51 Mark faulty movies done 2014-09-19 00:14:52 +02:00
Ruud 7fe5a271dc Make sure original_folder isn't empty
fix #3747
2014-09-18 22:00:59 +02:00
Ruud ea92c503bb Merge branch 'develop' 2014-09-18 21:44:35 +02:00
Ruud 6942126b7f Merge branch 'develop' 2014-09-18 17:58:48 +02:00
Ruud a6d37bf9c2 Merge branch 'develop' 2014-09-18 17:51:25 +02:00
Ruud 37c6bc7612 Merge branch 'develop' 2014-09-18 17:48:04 +02:00
Ruud d6a264aaed Merge branch 'develop' 2014-09-03 16:47:48 +02:00
Ruud 108f3292c3 Merge branch 'develop' 2014-09-01 23:07:12 +02:00
Ruud fc60727e82 Merge branch 'develop' 2014-09-01 13:40:19 +02:00
Ruud 49cd8fbc2c Merge branch 'develop' 2014-09-01 13:32:39 +02:00
Ruud 1991792291 Remove ending seperator 2014-08-27 22:04:33 +02:00
Ruud 29290022e6 Merge branch 'develop' 2014-08-27 19:56:17 +02:00
Ruud 04aa2e5fa4 Merge branch 'develop'
Conflicts:
	couchpotato/core/database.py
2014-08-16 13:30:37 +02:00
Ruud 6772b9d965 Don't migrate when db is closed 2014-07-17 23:09:26 +02:00
Ruud 5df14d67e1 One up 2014-07-17 22:28:32 +02:00
Ruud 73abd1f022 Merge branch 'refs/heads/master' into desktop 2014-07-17 22:27:23 +02:00
Ruud e75a8529c9 Try fix migration failure from 2.5.1 2014-07-17 22:26:23 +02:00
Ruud 07a7f8cbcf Change fanart api url 2014-07-16 10:32:02 +02:00
Ruud 9b35a0fb20 Only trigger onClose when it's set 2014-07-08 21:21:22 +02:00
Ruud 0622e6e5ab One up 2014-06-29 23:16:09 +02:00
Ruud f16931906f Don't remove pyc files when using desktop updater 2014-06-29 23:15:36 +02:00
Ruud 68dcba8853 One up 2/2 2014-06-29 21:56:51 +02:00
Ruud ae8f66df1a Exit main loop on crash 2014-06-29 21:56:39 +02:00
Ruud 5237ead5cb Merge branch 'refs/heads/develop' into desktop 2014-06-29 17:01:47 +02:00
Ruud 45b2dff6d2 Merge branch 'refs/heads/develop' 2014-06-29 11:01:09 +02:00
Ruud 30d56b5d2c Merge branch 'refs/heads/develop' 2014-06-29 00:02:55 +02:00
Ruud 5ff6824ae9 Merge branch 'refs/heads/develop' 2014-06-25 18:26:10 +02:00
Ruud 0210859155 Merge branch 'refs/heads/develop' 2014-06-25 09:17:12 +02:00
Ruud 665478db13 Merge branch 'refs/heads/develop' 2014-06-23 23:45:03 +02:00
Ruud 84c366ab54 Merge branch 'master' of github.com:RuudBurger/CouchPotatoServer 2014-06-23 20:47:30 +02:00
Ruud 908e5eae77 Merge branch 'refs/heads/develop' 2014-06-23 20:47:06 +02:00
Ruud c4aaa10308 One up 2014-06-23 20:00:06 +02:00
Ruud d10536a829 Remove path from getOptions 2014-06-23 20:00:00 +02:00
Ruud 1e7fa82e11 Merge branch 'refs/heads/develop' into desktop 2014-06-23 19:01:58 +02:00
Ruud 1d448f3d9c Merge branch 'refs/heads/develop' 2014-06-23 14:29:20 +02:00
Ruud 338b5f427a Merge branch 'refs/heads/develop' 2014-06-23 13:37:50 +02:00
Ruud 59e3e73c4c Merge branch 'refs/heads/develop' 2014-06-23 01:19:05 +02:00
Ruud cb2614127c Merge branch 'refs/heads/develop' 2014-06-22 21:14:44 +02:00
Ruud fdbd826917 Merge branch 'refs/heads/develop' 2014-06-22 20:35:30 +02:00
Ruud 31daf4915e Merge branch 'refs/heads/develop' 2014-06-20 21:31:48 +02:00
Ruud 4ca7691afd Merge branch 'refs/heads/develop' 2014-06-20 21:08:33 +02:00
Ruud 64d3ecd9b8 Merge branch 'refs/heads/develop' 2014-06-20 14:52:15 +02:00
Ruud d55df3240f Merge branch 'refs/heads/develop' 2014-06-20 14:14:26 +02:00
Ruud 52214e4938 Merge branch 'refs/heads/develop' 2014-06-20 12:22:13 +02:00
Ruud b45307e493 Merge branch 'refs/heads/develop' 2014-06-11 23:51:05 +02:00
Ruud 4320369448 Merge branch 'refs/heads/develop' 2014-06-11 10:15:31 +02:00
Ruud f560dc093c Merge branch 'refs/heads/develop' 2014-06-10 22:54:14 +02:00
Ruud d26a2b1480 Merge branch 'refs/heads/develop' 2014-06-07 20:44:49 +02:00
Ruud e11b07b559 Don't save profile order twice 2014-06-06 17:26:45 +02:00
Ruud b6ee8ef4d4 Merge branch 'refs/heads/develop' 2014-06-06 11:24:24 +02:00
Ruud f80559d380 Merge branch 'refs/heads/develop' 2014-06-03 22:31:20 +02:00
Ruud 8530b00e7b Merge branch 'refs/heads/develop' 2014-06-03 17:18:11 +02:00
Ruud 5851e1e69f Merge branch 'refs/heads/develop' 2014-06-02 23:51:01 +02:00
Ruud 686bfd62eb Merge branch 'refs/heads/develop' 2014-06-02 15:10:29 +02:00
Ruud 9b82603c26 Merge branch 'refs/heads/develop' 2014-06-02 14:20:50 +02:00
Ruud f41792915f Merge branch 'refs/heads/develop' 2014-06-02 12:59:47 +02:00
Ruud 2fa77fb610 Merge branch 'refs/heads/develop' 2014-06-02 10:40:07 +02:00
Ruud e64d0e33fc Merge branch 'refs/heads/develop' 2014-06-01 14:31:39 +02:00
Ruud b168643600 Merge branch 'refs/heads/develop'
Conflicts:
	couchpotato/core/helpers/variable.py
2014-05-31 22:50:02 +02:00
Ruud 240283405e variable 'year' referenced before assignment 2014-05-07 11:50:36 +02:00
Ruud b69f8b7ed5 Files not properly send to sabnzbd 2014-03-19 22:33:14 +01:00
Ruud fbccba77a7 64Bit installer setup 2014-03-16 13:00:09 +01:00
Ruud d3efda74b2 One up 2014-03-16 09:44:44 +01:00
Ruud 66b849cb29 Merge branch 'refs/heads/master' into desktop
Conflicts:
	version.py
2014-03-16 09:43:32 +01:00
Ruud b19f98ef5b Merge branch 'refs/heads/develop' 2014-03-15 12:35:28 +01:00
Ruud c389790cf2 Merge branch 'refs/heads/develop' 2014-03-03 22:19:29 +01:00
Ruud d7445dfa80 Merge branch 'refs/heads/develop' 2014-02-26 14:00:56 +01:00
Ruud 36782768a4 Merge branch 'refs/heads/develop' 2014-02-25 21:37:29 +01:00
Ruud 2c9d487614 Update build url 2014-02-25 21:20:59 +01:00
Ruud b9a724c8bb Merge branch 'refs/heads/develop' 2014-02-16 09:43:03 +01:00
Ruud 68d826ca1c Merge branch 'refs/heads/develop' 2014-02-15 19:48:07 +01:00
Ruud d6921882e1 Merge branch 'refs/heads/develop' 2014-02-14 19:39:47 +01:00
Ruud 2cfff73486 Merge branch 'refs/heads/develop' 2014-01-18 19:54:32 +01:00
Ruud 0c7dda8d44 Merge branch 'refs/heads/develop' 2014-01-17 23:17:41 +01:00
Ruud dbaa377770 version.master 2014-01-17 16:29:29 +01:00
Ruud 47d2b81d1c Merge branch 'refs/heads/develop' 2014-01-17 16:28:59 +01:00
Ruud f79fcda27f Small one up 2013-11-17 21:22:24 +01:00
Ruud cdbcad2238 Merge branch 'refs/heads/develop' into desktop 2013-11-17 21:20:30 +01:00
Ruud 5d913e87c3 One up! 2013-11-17 20:20:18 +01:00
Ruud 16f02bda27 Merge branch 'refs/heads/develop' into desktop 2013-11-17 20:03:22 +01:00
Ruud 8d108b92bf One Up 2013-09-23 21:48:12 +02:00
Ruud 46783028b1 Merge branch 'refs/heads/develop' into desktop 2013-09-23 21:36:45 +02:00
Ruud d08c7c57a8 One up! 2013-09-20 17:46:54 +02:00
Ruud eeeb845ef3 Simplify string before checking on imdb 2013-09-20 17:30:11 +02:00
Ruud 651a063f94 Fix about submenu 2013-09-20 16:33:01 +02:00
Ruud f20aaa2d9d Hide IE clear button on search 2013-09-20 16:23:42 +02:00
Ruud ba925ec191 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/plugins/suggestion/main.py
2013-09-20 16:12:40 +02:00
Ruud 3b7376fd18 One up 2013-07-06 01:01:26 +02:00
Ruud c31b10c798 Ignore current suggested results 2013-07-06 00:49:11 +02:00
Ruud acda664686 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2013-07-05 22:43:54 +02:00
Ruud e2852407ea One up 2013-06-03 22:22:44 +02:00
Ruud 88e738c6cd Don't show double updater name 2013-06-03 22:22:35 +02:00
Ruud eaae8bdb0b Merge branch 'refs/heads/develop' into desktop 2013-06-03 22:00:21 +02:00
Ruud 821f68909d One up 2013-05-05 21:19:10 +02:00
Ruud 2b8dfed475 Merge branch 'refs/heads/master' into desktop
Conflicts:
	version.py
2013-05-05 20:31:28 +02:00
Ruud 607b5ea766 Run exe after install 2013-03-19 21:22:07 +01:00
Ruud 88579cd71a One up 2013-03-19 20:52:07 +01:00
Ruud 6c57316ce6 Use https for changelog 2013-03-19 20:46:00 +01:00
Ruud 6702683da3 Merge branch 'refs/heads/develop' into desktop 2013-03-19 20:34:38 +01:00
Ruud 1ed58586a1 Force install install in AppData
Add images to installer
2013-03-18 23:56:54 +01:00
Ruud f08ccd4fd8 One up installer 2013-03-17 22:34:04 +01:00
Ruud 312562a9f5 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2013-03-17 16:42:53 +01:00
Ruud 9e260a89af One up 2013-01-26 14:51:39 +01:00
Ruud d233e4d22e Merge branch 'refs/heads/develop' into desktop 2013-01-26 13:54:56 +01:00
Ruud 23893dbcb9 Merge branch 'refs/heads/develop' into desktop 2013-01-25 20:13:58 +01:00
Ruud 506871b506 One up 2013-01-23 23:10:55 +01:00
Ruud 6115917660 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2013-01-23 22:57:07 +01:00
Ruud 21df8819d3 Merge branch 'refs/heads/develop' into desktop 2013-01-23 22:55:09 +01:00
Ruud fb3f3e11f6 Merge branch 'refs/heads/develop' into desktop 2013-01-22 21:40:40 +01:00
Ruud 178c8942c3 Merge branch 'refs/heads/develop' into desktop 2013-01-14 19:54:22 +01:00
Ruud 51e747049d One up 2013-01-07 23:10:42 +01:00
Ruud 0582f7d694 Urlencode spotweb id. fix #1213 2013-01-07 23:10:06 +01:00
Ruud fa7cac7538 Merge branch 'refs/heads/develop' into desktop 2013-01-07 22:41:55 +01:00
Ruud 9a314cfbc4 One up 2012-12-29 00:03:45 +01:00
Ruud 5941d0bf77 Add version to update url 2012-12-29 00:03:36 +01:00
Ruud d326c1c25c Merge branch 'refs/heads/master' into desktop
Conflicts:
	version.py
2012-12-28 23:31:08 +01:00
Ruud 96472a9a8f One up 2012-12-16 23:51:58 +01:00
Ruud 27252561e2 Merge branch 'refs/heads/develop' into desktop 2012-12-16 23:51:24 +01:00
Ruud c9e732651f One up 2012-12-01 12:16:58 +01:00
Ruud 7849e7170d Uninstall only create files, no wildcard *.* 2012-12-01 12:16:51 +01:00
Ruud 087894eb4e Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-12-01 11:50:08 +01:00
Ruud 25f1b8c7a7 Fedora init fix #1009 2012-11-02 18:32:15 +01:00
Ruud e71da1f14d Use proper description for binary build. fix #1005 2012-11-02 18:24:13 +01:00
Ruud 938b14ba18 One up installer 2012-10-29 20:45:17 +01:00
Ruud d6522d8f38 One up installer 2012-10-27 18:49:44 +02:00
Ruud 78eab890e7 Merge branch 'refs/heads/develop' into desktop 2012-10-27 18:25:36 +02:00
Ruud 1a56191f83 Don't unzip 2012-10-27 18:22:50 +02:00
Ruud 41c0f34d95 Properly restart 2012-10-27 18:22:40 +02:00
Ruud 37bf205d7a Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-10-27 11:56:57 +02:00
Ruud aa1fa3eb9a Add description 2012-09-19 15:42:33 +02:00
Ruud 0e2f8a612c Extract zip after build, for testing 2012-09-19 15:29:07 +02:00
Ruud 465e7b2abc Merge branch 'refs/heads/develop' into desktop 2012-09-16 12:36:17 +02:00
Ruud 578fb45785 Installer 1 up 2012-09-16 11:35:56 +02:00
Ruud 96995bbbe5 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-09-16 10:45:19 +02:00
Ruud 4cfdafebbc Merge branch 'refs/heads/develop' into desktop 2012-09-14 13:15:47 +02:00
Ruud b97acb8ef5 Merge branch 'refs/heads/develop' into desktop 2012-09-14 13:08:19 +02:00
Ruud d68d2dfdb6 Updated installer 2012-09-09 21:48:38 +02:00
Ruud 39b269a454 Merge branch 'refs/heads/develop' into desktop 2012-09-09 17:32:47 +02:00
Ruud ac081d3e10 Getting ready for build 2012-09-09 17:28:23 +02:00
Ruud 5d4efb60cf Merge branch 'refs/heads/develop' into desktop 2012-09-08 16:01:49 +02:00
Ruud cc408b980c Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-08-05 16:18:35 +02:00
Ruud 59590b3ac9 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-07-14 00:35:00 +02:00
Ruud ff759dacf3 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-07-11 22:43:45 +02:00
Ruud a328e44130 Merge branch 'desktop' of github.com:RuudBurger/CouchPotatoServer into desktop 2012-05-15 23:23:56 +02:00
Ruud 7924cac5f9 Update installer version 2012-05-15 23:21:24 +02:00
Ruud 1cef3b0c93 remove --nogit tag 2012-05-15 23:21:24 +02:00
Ruud 3cd59edc8b Import errors
File icon
2012-05-15 23:21:24 +02:00
Ruud 0d624af01d Working PNG 2012-05-15 23:21:24 +02:00
Ruud a09132570c Change branch to desktop 2012-05-15 23:21:14 +02:00
Ruud ee3fc38432 Better setup 2012-05-15 23:21:14 +02:00
Ruud dbf0192c8e Inno setup, start 2012-05-15 23:21:14 +02:00
Ruud 6962cfc3f5 new Desktop runner 2012-05-15 23:21:14 +02:00
Ruud e096ec3b5b Desktop files 2012-05-15 23:20:05 +02:00
Ruud b30a74ae0c Merge branch 'refs/heads/develop' into desktop 2012-05-15 23:15:17 +02:00
Ruud 978eeb16c9 Update installer version 2012-05-15 23:14:20 +02:00
Ruud e5c9d91657 Merge branch 'refs/heads/develop' into desktop 2012-05-15 22:27:22 +02:00
Ruud fa81c3a07a Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-05-14 22:00:02 +02:00
Ruud 9cdd520d41 Merge branch 'refs/heads/develop' into desktop 2012-05-14 20:22:55 +02:00
Ruud 55d7898771 Merge branch 'refs/heads/develop' into desktop 2012-05-13 12:56:45 +02:00
Ruud b8256bef97 Merge branch 'refs/heads/develop' into desktop 2012-05-12 00:35:52 +02:00
Ruud 5be9dc0b4a Merge branch 'refs/heads/develop' into desktop 2012-05-09 22:20:53 +02:00
Ruud 7d0be0cefb remove --nogit tag 2012-05-07 22:55:54 +02:00
Ruud f7ce1edb13 Merge branch 'refs/heads/develop' into desktop 2012-05-07 22:44:01 +02:00
Ruud 5ad9280b60 Merge branch 'refs/heads/develop' into desktop 2012-05-07 22:27:55 +02:00
Ruud 2b353f1b20 Merge branch 'refs/heads/develop' into desktop 2012-05-04 17:29:15 +02:00
Ruud 75ab90b87b Merge branch 'refs/heads/develop' into desktop 2012-05-02 21:40:19 +02:00
Ruud 0219296120 Import errors
File icon
2012-05-02 21:34:45 +02:00
Ruud 20032b3a31 Working PNG 2012-05-01 07:35:44 +02:00
Ruud ea9e9a8c90 Updater base 2012-05-01 07:35:27 +02:00
Ruud f7b0ee145b Change branch to desktop 2012-04-30 21:37:04 +02:00
Ruud cc866738ee Merge branch 'refs/heads/develop' into desktop 2012-04-30 21:32:56 +02:00
Ruud eadccf6e33 Merge branch 'refs/heads/develop' into desktop 2012-04-29 00:00:25 +02:00
Ruud b70b66e567 Merge branch 'refs/heads/develop' into desktop 2012-04-28 23:14:59 +02:00
Ruud 5b6792dc20 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	CouchPotato.py
	couchpotato/core/plugins/renamer/main.py
	couchpotato/core/plugins/trailer/__init__.py
2012-04-07 21:35:36 +02:00
Ruud f498e7343a Better setup 2012-02-25 01:48:58 +01:00
Ruud 6962f441e6 Inno setup, start 2012-02-21 18:50:34 +01:00
Ruud 1def62b1b1 new Desktop runner 2012-02-19 17:13:37 +01:00
Ruud a4a4a6a185 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	CouchPotato.py
2012-02-19 13:14:56 +01:00
Ruud d4c9469c1a Remove nfo when not renaming as .orig.nfo 2012-02-19 12:53:55 +01:00
Ruud 3e2d4c5d7b Initial trailer support 2012-02-19 12:48:54 +01:00
Ruud d03f711d69 kwargs in file.download for urlopen 2012-02-19 12:45:22 +01:00
Ruud 44dd8d9b96 Merge lists, not overwrite 2012-02-19 12:37:25 +01:00
Ruud 549a3be0d8 Merge branch 'refs/heads/develop' into desktop 2012-02-12 00:10:56 +01:00
Ruud 1bb2edf8ec Merge branch 'refs/heads/develop' into desktop 2012-02-11 23:33:14 +01:00
Ruud 84c6f36315 Desktop files 2012-02-11 23:06:14 +01:00
207 changed files with 7324 additions and 9556 deletions
-2
View File
@@ -3,5 +3,3 @@
/_source/
.project
.pydevproject
node_modules
.tmp
+241
View File
@@ -0,0 +1,241 @@
from esky.util import appdir_from_executable #@UnresolvedImport
from threading import Thread
from version import VERSION
from wx.lib.softwareupdate import SoftwareUpdate
import os
import sys
import time
import webbrowser
import wx
# Include proper dirs
if hasattr(sys, 'frozen'):
import libs
base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__)))
else:
base_path = os.path.dirname(os.path.abspath(__file__))
def icon():
icon = 'icon_windows.png'
if os.path.isfile('icon_mac.png'):
icon = 'icon_mac.png'
return wx.Icon(icon, wx.BITMAP_TYPE_PNG)
lib_dir = os.path.join(base_path, 'libs')
sys.path.insert(0, base_path)
sys.path.insert(0, lib_dir)
from couchpotato.environment import Env
class TaskBarIcon(wx.TaskBarIcon):
TBMENU_OPEN = wx.NewId()
TBMENU_SETTINGS = wx.NewId()
TBMENU_EXIT = wx.ID_EXIT
closed = False
menu = False
enabled = False
def __init__(self, frame):
wx.TaskBarIcon.__init__(self)
self.frame = frame
self.SetIcon(icon())
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN)
self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS)
self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT)
def OnTaskBarClick(self, evt):
menu = self.CreatePopupMenu()
self.PopupMenu(menu)
menu.Destroy()
def enable(self):
self.enabled = True
if self.menu:
self.open_menu.Enable(True)
self.setting_menu.Enable(True)
self.open_menu.SetText('Open')
def CreatePopupMenu(self):
if not self.menu:
self.menu = wx.Menu()
self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open')
self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About')
self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit')
if not self.enabled:
self.open_menu.Enable(False)
self.setting_menu.Enable(False)
self.open_menu.SetText('Loading...')
return self.menu
def onOpen(self, event):
url = self.frame.parent.getSetting('base_url')
webbrowser.open(url)
def onSettings(self, event):
url = self.frame.parent.getSetting('base_url') + 'settings/about/'
webbrowser.open(url)
def onTaskBarClose(self, evt):
if self.closed:
return
self.closed = True
self.RemoveIcon()
wx.CallAfter(self.frame.Close)
def makeIcon(self, img):
if "wxMSW" in wx.PlatformInfo:
img = img.Scale(16, 16)
elif "wxGTK" in wx.PlatformInfo:
img = img.Scale(22, 22)
icon = wx.IconFromBitmap(img.CopyFromBitmap())
return icon
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, None, style = wx.FRAME_NO_TASKBAR)
self.parent = parent
self.tbicon = TaskBarIcon(self)
class WorkerThread(Thread):
def __init__(self, desktop):
Thread.__init__(self)
self.daemon = True
self._desktop = desktop
self.start()
def run(self):
# Get options via arg
from couchpotato.runner import getOptions
args = ['--quiet']
self.options = getOptions(args)
# Load settings
settings = Env.get('settings')
settings.setFile(self.options.config_file)
# Create data dir if needed
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
if self.data_dir == '':
from couchpotato.core.helpers.variable import getDataDir
self.data_dir = getDataDir()
if not os.path.isdir(self.data_dir):
os.makedirs(self.data_dir)
# Create logging dir
self.log_dir = os.path.join(self.data_dir, 'logs');
if not os.path.isdir(self.log_dir):
os.mkdir(self.log_dir)
try:
from couchpotato.runner import runCouchPotato
runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop)
except:
pass
self._desktop.frame.Close()
self._desktop.ExitMainLoop()
class CouchPotatoApp(wx.App, SoftwareUpdate):
settings = {}
events = {}
restart = False
closing = False
triggered_onClose = False
def OnInit(self):
# Updater
base_url = 'https://api.couchpota.to/updates/%s'
self.InitUpdates(base_url % VERSION + '/', 'https://couchpota.to/updates/%s' % 'changelog.html',
icon = icon())
self.frame = MainFrame(self)
self.frame.Bind(wx.EVT_CLOSE, self.onClose)
# CouchPotato thread
self.worker = WorkerThread(self)
return True
def onAppLoad(self):
self.frame.tbicon.enable()
def setSettings(self, settings = {}):
self.settings = settings
def getSetting(self, name):
return self.settings.get(name)
def addEvents(self, events = {}):
for name in events.iterkeys():
self.events[name] = events[name]
def onClose(self, event):
if not self.closing:
self.closing = True
self.frame.tbicon.onTaskBarClose(event)
onClose = self.events.get('onClose')
if onClose and not self.triggered_onClose:
self.triggered_onClose = True
onClose(event)
def afterShutdown(self, restart = False):
self.frame.Destroy()
self.restart = restart
self.ExitMainLoop()
if __name__ == '__main__':
app = CouchPotatoApp(redirect = False)
app.MainLoop()
time.sleep(1)
if app.restart:
def appexe_from_executable(exepath):
appdir = appdir_from_executable(exepath)
exename = os.path.basename(exepath)
if sys.platform == "darwin":
if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")):
return os.path.join(appdir, "Contents", "MacOS", exename)
return os.path.join(appdir, exename)
exe = appexe_from_executable(sys.executable)
os.chdir(os.path.dirname(exe))
os.execv(exe, [exe] + sys.argv[1:])
-121
View File
@@ -1,121 +0,0 @@
'use strict';
module.exports = function(grunt){
require('time-grunt')(grunt);
// Configurable paths
var config = {
tmp: '.tmp',
base: 'couchpotato',
css_dest: 'couchpotato/static/style/combined.min.css'
};
grunt.initConfig({
// Project settings
config: config,
// Make sure code styles are up to par and there are no obvious mistakes
jshint: {
options: {
reporter: require('jshint-stylish'),
unused: false,
camelcase: false,
devel: true
},
all: [
'<%= config.base %>/{,**/}*.js',
'!<%= config.base %>/static/scripts/vendor/{,**/}*.js'
]
},
// Compiles Sass to CSS and generates necessary files if requested
sass: {
options: {
compass: true,
update: true
},
server: {
files: [{
expand: true,
cwd: '<%= config.base %>/',
src: ['**/*.scss'],
dest: '<%= config.tmp %>/styles/',
ext: '.css'
}]
}
},
// Add vendor prefixed styles
autoprefixer: {
options: {
browsers: ['> 1%', 'Android >= 2.1', 'Chrome >= 21', 'Explorer >= 7', 'Firefox >= 17', 'Opera >= 12.1', 'Safari >= 6.0']
},
dist: {
files: [{
expand: true,
cwd: '<%= config.tmp %>/styles/',
src: '{,**/}*.css',
dest: '<%= config.tmp %>/styles/'
}]
}
},
cssmin: {
dist: {
files: {
'<%= config.css_dest %>': ['<%= config.tmp %>/styles/**/*.css']
}
}
},
shell: {
runCouchPotato: {
command: 'python CouchPotato.py'
}
},
// COOL TASKS ==============================================================
watch: {
scss: {
files: ['<%= config.base %>/**/*.{scss,sass}'],
tasks: ['sass:server', 'autoprefixer', 'cssmin']
},
js: {
files: [
'<%= config.base %>/**/*.js'
],
tasks: ['jshint']
},
livereload: {
options: {
livereload: 35729
},
files: [
'<%= config.css_dest %>'
]
}
},
concurrent: {
options: {
logConcurrentOutput: true
},
tasks: ['shell:runCouchPotato', 'sass:server', 'autoprefixer', 'cssmin', 'watch']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
//grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('default', ['concurrent']);
};
-45
View File
@@ -1,45 +0,0 @@
# First, require any additional compass plugins installed on your system.
# require 'zen-grids'
require 'susy'
# require 'breakpoint'
# Toggle this between :development and :production when deploying the CSS to the
# live server. Development mode will retain comments and spacing from the
# original Sass source and adds line numbering comments for easier debugging.
environment = :development
# environment = :development
# In development, we can turn on the FireSass-compatible debug_info.
firesass = false
# firesass = true
# Location of the your project's resources.
# Set this to the root of your project. All resource locations above are
# considered to be relative to this path.
http_path = "/"
# To use relative paths to assets in your compiled CSS files, set this to true.
# relative_assets = true
##
## You probably don't need to edit anything below this.
##
sass_dir = "./"
css_dir = "./static/style_compiled"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
output_style = (environment == :development) ? :expanded : :compressed
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# Pass options to sass. For development, we turn on the FireSass-compatible
# debug_info if the firesass config variable above is true.
sass_options = (environment == :development && firesass == true) ? {:debug_info => true} : {}
-9
View File
@@ -40,8 +40,6 @@ class WebHandler(BaseHandler):
return
try:
if route == 'robots.txt':
self.set_header('Content-Type', 'text/plain')
self.write(views[route]())
except:
log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
@@ -62,13 +60,6 @@ def index():
addView('', index)
# Web view
def robots():
return 'User-agent: * \n' \
'Disallow: /'
addView('robots.txt', robots)
# API docs
def apiDocs():
routes = list(api.keys())
+22 -26
View File
@@ -7,7 +7,6 @@ import urllib
from couchpotato.core.helpers.request import getParams
from couchpotato.core.logger import CPLog
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, asynchronous
@@ -51,22 +50,24 @@ class NonBlockHandler(RequestHandler):
start, stop = api_nonblock[route]
self.stopper = stop
start(self.sendData, last_id = self.get_argument('last_id', None))
start(self.onNewMessage, last_id = self.get_argument('last_id', None))
def sendData(self, response):
if not self.request.connection.stream.closed():
try:
self.finish(response)
except:
log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
try: self.finish({'success': False, 'error': 'Failed returning results'})
except: pass
def onNewMessage(self, response):
if self.request.connection.stream.closed():
self.on_connection_close()
return
self.removeStopper()
try:
self.finish(response)
except:
log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
try: self.finish({'success': False, 'error': 'Failed returning results'})
except: pass
def on_connection_close(self):
def removeStopper(self):
if self.stopper:
self.stopper(self.sendData)
self.stopper(self.onNewMessage)
self.stopper = None
@@ -82,11 +83,10 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
# Blocking API handler
class ApiHandler(RequestHandler):
route = None
@asynchronous
def get(self, route, *args, **kwargs):
self.route = route = route.strip('/')
route = route.strip('/')
if not api.get(route):
self.write('API call doesn\'t seem to exist')
self.finish()
@@ -123,15 +123,11 @@ class ApiHandler(RequestHandler):
except:
log.error('Failed write error "%s": %s', (route, traceback.format_exc()))
self.unlock()
api_locks[route].release()
post = get
def taskFinished(self, result, route):
IOLoop.current().add_callback(self.sendData, result, route)
self.unlock()
def sendData(self, result, route):
if not self.request.connection.stream.closed():
try:
@@ -139,12 +135,14 @@ class ApiHandler(RequestHandler):
jsonp_callback = self.get_argument('callback_func', default = None)
if jsonp_callback:
self.set_header('Content-Type', 'text/javascript')
self.finish(str(jsonp_callback) + '(' + json.dumps(result) + ')')
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
self.set_header("Content-Type", "text/javascript")
self.finish()
elif isinstance(result, tuple) and result[0] == 'redirect':
self.redirect(result[1])
else:
self.finish(result)
self.write(result)
self.finish()
except UnicodeDecodeError:
log.error('Failed proper encode: %s', traceback.format_exc())
except:
@@ -152,9 +150,7 @@ class ApiHandler(RequestHandler):
try: self.finish({'success': False, 'error': 'Failed returning results'})
except: pass
def unlock(self):
try: api_locks[self.route].release()
except: pass
api_locks[route].release()
def addApiView(route, func, static = False, docs = None, **kwargs):
+93 -46
View File
@@ -1,5 +1,6 @@
import os
import re
import traceback
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import ss
@@ -7,6 +8,8 @@ from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
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
@@ -19,26 +22,30 @@ class ClientScript(Plugin):
core_static = {
'style': [
'style/combined.min.css',
'style/main.css',
'style/uniform.generic.css',
'style/uniform.css',
'style/settings.css',
],
'script': [
'scripts/vendor/mootools.js',
'scripts/vendor/mootools_more.js',
'scripts/vendor/form_replacement/form_check.js',
'scripts/vendor/form_replacement/form_radio.js',
'scripts/vendor/form_replacement/form_dropdown.js',
'scripts/vendor/form_replacement/form_selectoption.js',
'scripts/vendor/Array.stableSort.js',
'scripts/vendor/history.js',
'scripts/library/mootools.js',
'scripts/library/mootools_more.js',
'scripts/library/uniform.js',
'scripts/library/form_replacement/form_check.js',
'scripts/library/form_replacement/form_radio.js',
'scripts/library/form_replacement/form_dropdown.js',
'scripts/library/form_replacement/form_selectoption.js',
'scripts/library/question.js',
'scripts/library/scrollspy.js',
'scripts/library/spin.js',
'scripts/library/Array.stableSort.js',
'scripts/library/async.js',
'scripts/couchpotato.js',
'scripts/api.js',
'scripts/library/history.js',
'scripts/page.js',
'scripts/block.js',
'scripts/block/navigation.js',
'scripts/block/header.js',
'scripts/block/footer.js',
'scripts/block/menu.js',
'scripts/page/home.js',
@@ -47,9 +54,8 @@ class ClientScript(Plugin):
],
}
watches = {}
original_paths = {'style': {}, 'script': {}}
urls = {'style': {}, 'script': {}}
minified = {'style': {}, 'script': {}}
paths = {'style': {}, 'script': {}}
comment = {
'style': '/*** %s:%d ***/\n',
@@ -68,7 +74,8 @@ class ClientScript(Plugin):
addEvent('clientscript.get_styles', self.getStyles)
addEvent('clientscript.get_scripts', self.getScripts)
addEvent('app.load', self.compile)
if not Env.get('dev'):
addEvent('app.load', self.minify)
self.addCore()
@@ -84,7 +91,7 @@ class ClientScript(Plugin):
else:
self.registerStyle(core_url, file_path, position = 'front')
def compile(self):
def minify(self):
# Create cache dir
cache = Env.get('cache_dir')
@@ -95,43 +102,47 @@ class ClientScript(Plugin):
for file_type in ['style', 'script']:
ext = 'js' if file_type is 'script' else 'css'
positions = self.original_paths.get(file_type, {})
positions = self.paths.get(file_type, {})
for position in positions:
files = positions.get(position)
self._compile(file_type, files, position, position + '.' + ext)
self._minify(file_type, files, position, position + '.' + ext)
def _compile(self, file_type, paths, position, out):
def _minify(self, file_type, files, position, out):
cache = Env.get('cache_dir')
out_name = out
minified_dir = os.path.join(cache, 'minified')
data_combined = ''
new_paths = []
for x in paths:
file_path, url_path = x
out = os.path.join(cache, 'minified', out_name)
raw = []
for file_path in files:
f = open(file_path, 'r').read()
if not Env.get('dev'):
data = f
data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path)))
data_combined += data + '\n\n'
if file_type == 'script':
data = jsmin(f)
else:
new_paths.append(x)
data = self.prefix(f)
data = cssmin(data)
data = data.replace('../images/', '../static/images/')
data = data.replace('../fonts/', '../static/fonts/')
data = data.replace('../../static/', '../static/') # Replace inside plugins
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
# Combine all files together with some comments
if not Env.get('dev'):
data = ''
for r in raw:
data += self.comment.get(file_type) % (ss(r.get('file')), r.get('date'))
data += r.get('data') + '\n\n'
out_path = os.path.join(minified_dir, out_name)
self.createFile(out_path, data_combined.strip())
self.createFile(out, data.strip())
minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out)))
new_paths.append((out_path, {'url': minified_url}))
if not self.minified.get(file_type):
self.minified[file_type] = {}
if not self.minified[file_type].get(position):
self.minified[file_type][position] = []
self.paths[file_type][position] = new_paths
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):
return self.get('style', *args, **kwargs)
@@ -139,12 +150,22 @@ class ClientScript(Plugin):
def getScripts(self, *args, **kwargs):
return self.get('script', *args, **kwargs)
def get(self, type, location = 'head'):
if type in self.paths and location in self.paths[type]:
paths = self.paths[type][location]
return [x[1] for x in paths]
def get(self, type, as_html = False, location = 'head'):
return []
data = '' if as_html else []
try:
try:
if not Env.get('dev'):
return self.minified[type][location]
except:
pass
return self.urls[type][location]
except:
log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc()))
return data
def registerStyle(self, api_path, file_path, position = 'head'):
self.register(api_path, file_path, 'style', position)
@@ -156,10 +177,36 @@ class ClientScript(Plugin):
api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path)))
if not self.original_paths[type].get(location):
self.original_paths[type][location] = []
self.original_paths[type][location].append((file_path, api_path))
if not self.urls[type].get(location):
self.urls[type][location] = []
self.urls[type][location].append(api_path)
if not self.paths[type].get(location):
self.paths[type][location] = []
self.paths[type][location].append((file_path, api_path))
self.paths[type][location].append(file_path)
prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow']
prefix_tags = ['ms', 'moz', 'webkit']
def prefix(self, data):
trimmed_data = re.sub('(\t|\n|\r)+', '', data)
new_data = ''
colon_split = trimmed_data.split(';')
for splt in colon_split:
curl_split = splt.strip().split('{')
for curly in curl_split:
curly = curly.strip()
for prop in self.prefix_properties:
if curly[:len(prop) + 1] == prop + ':':
for tag in self.prefix_tags:
new_data += ' -%s-%s; ' % (tag, curly)
new_data += curly + (' { ' if len(curl_split) > 1 else ' ')
new_data += '; '
new_data = new_data.replace('{ ;', '; ').replace('} ;', '} ')
return new_data
@@ -16,8 +16,8 @@ var DownloadersBase = new Class({
var setting_page = App.getPage('Settings');
setting_page.addEvent('create', function(){
Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self));
});
Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self))
})
},
@@ -44,19 +44,19 @@ var DownloadersBase = new Class({
if(json.success){
message = new Element('span.success', {
'text': 'Connection successful'
}).inject(button, 'after');
}).inject(button, 'after')
}
else {
var msg_text = 'Connection failed. Check logs for details.';
if(json.hasOwnProperty('msg')) msg_text = json.msg;
message = new Element('span.failed', {
'text': msg_text
}).inject(button, 'after');
}).inject(button, 'after')
}
(function(){
message.destroy();
}).delay(3000);
}).delay(3000)
}
});
}
@@ -27,7 +27,7 @@ var UpdaterBase = new Class({
App.trigger('message', ['No updates available']);
}
}
});
})
},
@@ -50,8 +50,8 @@ var UpdaterBase = new Class({
self.message.destroy();
}
}
});
}, (timeout || 0));
})
}, (timeout || 0))
},
@@ -84,7 +84,7 @@ var UpdaterBase = new Class({
'click': self.doUpdate.bind(self)
}
})
).inject(document.body);
).inject(document.body)
},
doUpdate: function(){
@@ -96,7 +96,7 @@ var UpdaterBase = new Class({
if(json.success)
self.updating();
else
App.unBlockPage();
App.unBlockPage()
}
});
},
-36
View File
@@ -20,31 +20,14 @@ class Blackhole(DownloaderBase):
status_support = False
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
directory = self.conf('directory')
# The folder needs to exist
if not directory or not os.path.isdir(directory):
log.error('No directory set for blackhole %s download.', data.get('protocol'))
else:
try:
# Filedata can be empty, which probably means it a magnet link
if not filedata or len(filedata) < 50:
try:
if data.get('protocol') == 'torrent_magnet':
@@ -53,16 +36,13 @@ class Blackhole(DownloaderBase):
except:
log.error('Failed download torrent via magnet url: %s', traceback.format_exc())
# If it's still empty, don't know what to do!
if not filedata or len(filedata) < 50:
log.error('No nzb/torrent available: %s', data.get('url'))
return False
# Create filename with imdb id and other nice stuff
file_name = self.createFileName(data, filedata, media)
full_path = os.path.join(directory, file_name)
# People want thinks nice and tidy, create a subdir
if self.conf('create_subdir'):
try:
new_path = os.path.splitext(full_path)[0]
@@ -73,8 +53,6 @@ class Blackhole(DownloaderBase):
log.error('Couldnt create sub dir, reverting to old one: %s', full_path)
try:
# Make sure the file doesn't exist yet, no need in overwriting it
if not os.path.isfile(full_path):
log.info('Downloading %s to %s.', (data.get('protocol'), full_path))
with open(full_path, 'wb') as f:
@@ -96,10 +74,6 @@ class Blackhole(DownloaderBase):
return False
def test(self):
""" Test and see if the directory is writable
:return: boolean
"""
directory = self.conf('directory')
if directory and os.path.isdir(directory):
@@ -114,10 +88,6 @@ class Blackhole(DownloaderBase):
return False
def getEnabledProtocol(self):
""" What protocols is this downloaded used for
:return: list with protocols
"""
if self.conf('use_for') == 'both':
return super(Blackhole, self).getEnabledProtocol()
elif self.conf('use_for') == 'torrent':
@@ -126,12 +96,6 @@ class Blackhole(DownloaderBase):
return ['nzb']
def isEnabled(self, manual = False, data = None):
""" Check if protocol is used (and enabled)
:param manual: The user has clicked to download a link through the webUI
:param data: dict returned from provider
Contains the release information
:return: boolean
"""
if not data: data = {}
for_protocol = ['both']
if data and 'torrent' in data.get('protocol'):
-29
View File
@@ -25,11 +25,6 @@ class Deluge(DownloaderBase):
drpc = None
def connect(self, reconnect = False):
""" Connect to the delugeRPC, re-use connection when already available
:param reconnect: force reconnect
:return: DelugeRPC instance
"""
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
@@ -47,20 +42,6 @@ class Deluge(DownloaderBase):
return self.drpc
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -115,21 +96,11 @@ class Deluge(DownloaderBase):
return self.downloadReturnId(remote_torrent)
def test(self):
""" Check if connection works
:return: bool
"""
if self.connect(True) and self.drpc.test():
return True
return False
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking Deluge download status.')
-427
View File
@@ -1,427 +0,0 @@
from base64 import b16encode, b32decode, b64encode
from distutils.version import LooseVersion
from hashlib import sha1
import httplib
import json
import os
import re
import urllib2
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
from couchpotato.core.helpers.encoding import isInt, sp
from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
from bencode import bencode as benc, bdecode
log = CPLog(__name__)
autoload = 'Hadouken'
class Hadouken(DownloaderBase):
protocol = ['torrent', 'torrent_magnet']
hadouken_api = None
def connect(self):
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
if not self.conf('api_key'):
log.error('Config properties are not filled in correctly, API key is missing.')
return False
self.hadouken_api = HadoukenAPI(host[0], port = host[1], api_key = self.conf('api_key'))
return True
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
log.debug("Sending '%s' (%s) to Hadouken.", (data.get('name'), data.get('protocol')))
if not self.connect():
return False
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
torrent_filename = self.createFileName(data, filedata, media)
if data.get('protocol') == 'torrent_magnet':
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
torrent_params['trackers'] = self.torrent_trackers
torrent_params['name'] = torrent_filename
else:
info = bdecode(filedata)['info']
torrent_hash = sha1(benc(info)).hexdigest().upper()
# Convert base 32 to hex
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to Hadouken
if data.get('protocol') == 'torrent_magnet':
self.hadouken_api.add_magnet_link(data.get('url'), torrent_params)
else:
self.hadouken_api.add_file(filedata, torrent_params)
return self.downloadReturnId(torrent_hash)
def test(self):
""" Tests the given host:port and API key """
if not self.connect():
return False
version = self.hadouken_api.get_version()
if not version:
log.error('Could not get Hadouken version.')
return False
# The minimum required version of Hadouken is 4.5.6.
if LooseVersion(version) >= LooseVersion('4.5.6'):
return True
log.error('Hadouken v4.5.6 (or newer) required. Found v%s', version)
return False
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking Hadouken download status.')
if not self.connect():
return []
release_downloads = ReleaseDownloadList(self)
queue = self.hadouken_api.get_by_hash_list(ids)
if not queue:
return []
for torrent in queue:
if torrent is None:
continue
torrent_filelist = self.hadouken_api.get_files_by_hash(torrent['InfoHash'])
torrent_files = []
save_path = torrent['SavePath']
# The 'Path' key for each file_item contains
# the full path to the single file relative to the
# torrents save path.
# For a single file torrent the result would be,
# - Save path: "C:\Downloads"
# - file_item['Path'] = "file1.iso"
# Resulting path: "C:\Downloads\file1.iso"
# For a multi file torrent the result would be,
# - Save path: "C:\Downloads"
# - file_item['Path'] = "dirname/file1.iso"
# Resulting path: "C:\Downloads\dirname/file1.iso"
for file_item in torrent_filelist:
torrent_files.append(sp(os.path.join(save_path, file_item['Path'])))
release_downloads.append({
'id': torrent['InfoHash'].upper(),
'name': torrent['Name'],
'status': self.get_torrent_status(torrent),
'seed_ratio': self.get_seed_ratio(torrent),
'original_status': torrent['State'],
'timeleft': -1,
'folder': sp(save_path if len(torrent_files == 1) else os.path.join(save_path, torrent['Name'])),
'files': torrent_files
})
return release_downloads
def get_seed_ratio(self, torrent):
""" Returns the seed ratio for a given torrent.
Keyword arguments:
torrent -- The torrent to calculate seed ratio for.
"""
up = torrent['TotalUploadedBytes']
down = torrent['TotalDownloadedBytes']
if up > 0 and down > 0:
return up / down
return 0
def get_torrent_status(self, torrent):
""" Returns the CouchPotato status for a given torrent.
Keyword arguments:
torrent -- The torrent to translate status for.
"""
if torrent['IsSeeding'] and torrent['IsFinished'] and torrent['Paused']:
return 'completed'
if torrent['IsSeeding']:
return 'seeding'
return 'busy'
def pause(self, release_download, pause = True):
""" Pauses or resumes the torrent specified by the ID field
in release_download.
Keyword arguments:
release_download -- The CouchPotato release_download to pause/resume.
pause -- Boolean indicating whether to pause or resume.
"""
if not self.connect():
return False
return self.hadouken_api.pause(release_download['id'], pause)
def removeFailed(self, release_download):
""" Removes a failed torrent and also remove the data associated with it.
Keyword arguments:
release_download -- The CouchPotato release_download to remove.
"""
log.info('%s failed downloading, deleting...', release_download['name'])
if not self.connect():
return False
return self.hadouken_api.remove(release_download['id'], remove_data = True)
def processComplete(self, release_download, delete_files = False):
""" Removes the completed torrent from Hadouken and optionally removes the data
associated with it.
Keyword arguments:
release_download -- The CouchPotato release_download to remove.
delete_files: Boolean indicating whether to remove the associated data.
"""
log.debug('Requesting Hadouken to remove the torrent %s%s.',
(release_download['name'], ' and cleanup the downloaded files' if delete_files else ''))
if not self.connect():
return False
return self.hadouken_api.remove(release_download['id'], remove_data = delete_files)
class HadoukenAPI(object):
def __init__(self, host = 'localhost', port = 7890, api_key = None):
self.url = 'http://' + str(host) + ':' + str(port)
self.api_key = api_key
self.requestId = 0;
self.opener = urllib2.build_opener()
self.opener.addheaders = [('User-agent', 'couchpotato-hadouken-client/1.0'), ('Accept', 'application/json')]
if not api_key:
log.error('API key missing.')
def add_file(self, filedata, torrent_params):
""" Add a file to Hadouken with the specified parameters.
Keyword arguments:
filedata -- The binary torrent data.
torrent_params -- Additional parameters for the file.
"""
data = {
'method': 'torrents.addFile',
'params': [b64encode(filedata), torrent_params]
}
return self._request(data)
def add_magnet_link(self, magnetLink, torrent_params):
""" Add a magnet link to Hadouken with the specified parameters.
Keyword arguments:
magnetLink -- The magnet link to send.
torrent_params -- Additional parameters for the magnet link.
"""
data = {
'method': 'torrents.addUrl',
'params': [magnetLink, torrent_params]
}
return self._request(data)
def get_by_hash_list(self, infoHashList):
""" Gets a list of torrents filtered by the given info hash list.
Keyword arguments:
infoHashList -- A list of info hashes.
"""
data = {
'method': 'torrents.getByInfoHashList',
'params': [infoHashList]
}
return self._request(data)
def get_files_by_hash(self, infoHash):
""" Gets a list of files for the torrent identified by the
given info hash.
Keyword arguments:
infoHash -- The info hash of the torrent to return files for.
"""
data = {
'method': 'torrents.getFiles',
'params': [infoHash]
}
return self._request(data)
def get_version(self):
""" Gets the version, commitish and build date of Hadouken. """
data = {
'method': 'core.getVersion',
'params': None
}
result = self._request(data)
if not result:
return False
return result['Version']
def pause(self, infoHash, pause):
""" Pauses/unpauses the torrent identified by the given info hash.
Keyword arguments:
infoHash -- The info hash of the torrent to operate on.
pause -- If true, pauses the torrent. Otherwise resumes.
"""
data = {
'method': 'torrents.pause',
'params': [infoHash]
}
if not pause:
data['method'] = 'torrents.resume'
return self._request(data)
def remove(self, infoHash, remove_data = False):
""" Removes the torrent identified by the given info hash and
optionally removes the data as well.
Keyword arguments:
infoHash -- The info hash of the torrent to remove.
remove_data -- If true, removes the data associated with the torrent.
"""
data = {
'method': 'torrents.remove',
'params': [infoHash, remove_data]
}
return self._request(data)
def _request(self, data):
self.requestId += 1
data['jsonrpc'] = '2.0'
data['id'] = self.requestId
request = urllib2.Request(self.url + '/jsonrpc', data = json.dumps(data))
request.add_header('Authorization', 'Token ' + self.api_key)
request.add_header('Content-Type', 'application/json')
try:
f = self.opener.open(request)
response = f.read()
f.close()
obj = json.loads(response)
if not 'error' in obj.keys():
return obj['result']
log.error('JSONRPC error, %s: %s', obj['error']['code'], obj['error']['message'])
except httplib.InvalidURL as err:
log.error('Invalid Hadouken host, check your config %s', err)
except urllib2.HTTPError as err:
if err.code == 401:
log.error('Invalid Hadouken API key, check your config')
else:
log.error('Hadouken HTTPError: %s', err)
except urllib2.URLError as err:
log.error('Unable to connect to Hadouken %s', err)
return False
config = [{
'name': 'hadouken',
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'hadouken',
'label': 'Hadouken',
'description': 'Use <a href="http://www.hdkn.net">Hadouken</a> (>= v4.5.6) to download torrents.',
'wizard': True,
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
'radio_group': 'torrent'
},
{
'name': 'host',
'default': 'localhost:7890'
},
{
'name': 'api_key',
'label': 'API key',
'type': 'password'
},
{
'name': 'label',
'description': 'Label to add torrent as.'
}
]
}
]
}]
+3 -28
View File
@@ -23,20 +23,6 @@ class NZBGet(DownloaderBase):
rpc = 'xmlrpc'
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -85,10 +71,6 @@ class NZBGet(DownloaderBase):
return False
def test(self):
""" Check if connection works
:return: bool
"""
rpc = self.getRPC()
try:
@@ -109,13 +91,6 @@ class NZBGet(DownloaderBase):
return True
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking NZBGet download status.')
@@ -188,12 +163,12 @@ class NZBGet(DownloaderBase):
nzb_id = nzb['NZBID']
if nzb_id in ids:
log.debug('Found %s in NZBGet history. TotalStatus: %s, ParStatus: %s, ScriptStatus: %s, Log: %s', (nzb['NZBFilename'] , nzb['Status'], nzb['ParStatus'], nzb['ScriptStatus'] , nzb['Log']))
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (nzb['NZBFilename'] , nzb['ParStatus'], nzb['ScriptStatus'] , nzb['Log']))
release_downloads.append({
'id': nzb_id,
'name': nzb['NZBFilename'],
'status': 'completed' if 'SUCCESS' in nzb['Status'] else 'failed',
'original_status': nzb['Status'],
'status': 'completed' if nzb['ParStatus'] in ['SUCCESS', 'NONE'] and nzb['ScriptStatus'] in ['SUCCESS', 'NONE'] else 'failed',
'original_status': nzb['ParStatus'] + ', ' + nzb['ScriptStatus'],
'timeleft': str(timedelta(seconds = 0)),
'folder': sp(nzb['DestDir'])
})
-25
View File
@@ -24,20 +24,6 @@ class NZBVortex(DownloaderBase):
session_id = None
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -59,10 +45,6 @@ class NZBVortex(DownloaderBase):
return False
def test(self):
""" Check if connection works
:return: bool
"""
try:
login_result = self.login()
except:
@@ -71,13 +53,6 @@ class NZBVortex(DownloaderBase):
return login_result
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
raw_statuses = self.call('nzb')
-18
View File
@@ -19,20 +19,6 @@ class Pneumatic(DownloaderBase):
status_support = False
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -77,10 +63,6 @@ class Pneumatic(DownloaderBase):
return False
def test(self):
""" Check if connection works
:return: bool
"""
directory = self.conf('directory')
if directory and os.path.isdir(directory):
@@ -1,68 +0,0 @@
from .main import PutIO
def autoload():
return PutIO()
config = [{
'name': 'putio',
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'putio',
'label': 'put.io',
'description': 'This will start a torrent download on <a href="http://put.io">Put.io</a>.',
'wizard': True,
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
'radio_group': 'torrent',
},
{
'name': 'oauth_token',
'label': 'oauth_token',
'description': 'This is the OAUTH_TOKEN from your putio API',
'advanced': True,
},
{
'name': 'folder',
'description': ('The folder on putio where you want the upload to go','Will find the first first folder that matches this name'),
'default': 0,
},
{
'name': 'callback_host',
'description': 'External reachable url to CP so put.io can do it\'s thing',
},
{
'name': 'download',
'description': 'Set this to have CouchPotato download the file from Put.io',
'type': 'bool',
'default': 0,
},
{
'name': 'delete_file',
'description': ('Set this to remove the file from putio after sucessful download','Does nothing if you don\'t select download'),
'type': 'bool',
'default': 0,
},
{
'name': 'download_dir',
'type': 'directory',
'label': 'Download Directory',
'description': 'The Directory to download files to, does nothing if you don\'t select download',
},
{
'name': 'manual',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
],
}
],
}]
-181
View File
@@ -1,181 +0,0 @@
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEventAsync
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from pio import api as pio
import datetime
log = CPLog(__name__)
autoload = 'Putiodownload'
class PutIO(DownloaderBase):
protocol = ['torrent', 'torrent_magnet']
downloading_list = []
oauth_authenticate = 'https://api.couchpota.to/authorize/putio/'
def __init__(self):
addApiView('downloader.putio.getfrom', self.getFromPutio, docs = {
'desc': 'Allows you to download file from prom Put.io',
})
addApiView('downloader.putio.auth_url', self.getAuthorizationUrl)
addApiView('downloader.putio.credentials', self.getCredentials)
addEvent('putio.download', self.putioDownloader)
return super(PutIO, self).__init__()
# This is a recusive function to check for the folders
def recursionFolder(self, client, folder = 0, tfolder = ''):
files = client.File.list(folder)
for f in files:
if f.content_type == 'application/x-directory':
if f.name == tfolder:
return f.id
else:
result = self.recursionFolder(client, f.id, tfolder)
if result != 0:
return result
return 0
# This will check the root for the folder, and kick of recusively checking sub folder
def convertFolder(self, client, folder):
if folder == 0:
return 0
else:
return self.recursionFolder(client, 0, folder)
def download(self, data = None, media = None, filedata = None):
if not media: media = {}
if not data: data = {}
log.info('Sending "%s" to put.io', data.get('name'))
url = data.get('url')
client = pio.Client(self.conf('oauth_token'))
putioFolder = self.convertFolder(client, self.conf('folder'))
log.debug('putioFolder ID is %s', putioFolder)
# It might be possible to call getFromPutio from the renamer if we can then we don't need to do this.
# Note callback_host is NOT our address, it's the internet host that putio can call too
callbackurl = None
if self.conf('download'):
callbackurl = 'http://' + self.conf('callback_host') + '%sdownloader.putio.getfrom/' %Env.get('api_base'.strip('/'))
resp = client.Transfer.add_url(url, callback_url = callbackurl, parent_id = putioFolder)
log.debug('resp is %s', resp.id);
return self.downloadReturnId(resp.id)
def test(self):
try:
client = pio.Client(self.conf('oauth_token'))
if client.File.list():
return True
except:
log.info('Failed to get file listing, check OAUTH_TOKEN')
return False
def getAuthorizationUrl(self, host = None, **kwargs):
callback_url = cleanHost(host) + '%sdownloader.putio.credentials/' % (Env.get('api_base').lstrip('/'))
log.debug('callback_url is %s', callback_url)
target_url = self.oauth_authenticate + "?target=" + callback_url
log.debug('target_url is %s', target_url)
return {
'success': True,
'url': target_url,
}
def getCredentials(self, **kwargs):
try:
oauth_token = kwargs.get('oauth')
except:
return 'redirect', Env.get('web_base') + 'settings/downloaders/'
log.debug('oauth_token is: %s', oauth_token)
self.conf('oauth_token', value = oauth_token);
return 'redirect', Env.get('web_base') + 'settings/downloaders/'
def getAllDownloadStatus(self, ids):
log.debug('Checking putio download status.')
client = pio.Client(self.conf('oauth_token'))
transfers = client.Transfer.list()
log.debug(transfers);
release_downloads = ReleaseDownloadList(self)
for t in transfers:
if t.id in ids:
log.debug('downloading list is %s', self.downloading_list)
if t.status == "COMPLETED" and self.conf('download') == False :
status = 'completed'
# So check if we are trying to download something
elif t.status == "COMPLETED" and self.conf('download') == True:
# Assume we are done
status = 'completed'
if not self.downloading_list:
now = datetime.datetime.utcnow()
date_time = datetime.datetime.strptime(t.finished_at,"%Y-%m-%dT%H:%M:%S")
# We need to make sure a race condition didn't happen
if (now - date_time) < datetime.timedelta(minutes=5):
# 5 minutes haven't passed so we wait
status = 'busy'
else:
# If we have the file_id in the downloading_list mark it as busy
if str(t.file_id) in self.downloading_list:
status = 'busy'
else:
status = 'busy'
release_downloads.append({
'id' : t.id,
'name': t.name,
'status': status,
'timeleft': t.estimated_time,
})
return release_downloads
def putioDownloader(self, fid):
log.info('Put.io Real downloader called with file_id: %s',fid)
client = pio.Client(self.conf('oauth_token'))
log.debug('About to get file List')
putioFolder = self.convertFolder(client, self.conf('folder'))
log.debug('PutioFolderID is %s', putioFolder)
files = client.File.list(parent_id=putioFolder)
downloaddir = self.conf('download_dir')
for f in files:
if str(f.id) == str(fid):
client.File.download(f, dest = downloaddir, delete_after_download = self.conf('delete_file'))
# Once the download is complete we need to remove it from the running list.
self.downloading_list.remove(fid)
return True
def getFromPutio(self, **kwargs):
try:
file_id = str(kwargs.get('file_id'))
except:
return {
'success' : False,
}
log.info('Put.io Download has been called file_id is %s', file_id)
if file_id not in self.downloading_list:
self.downloading_list.append(file_id)
fireEventAsync('putio.download',fid = file_id)
return {
'success': True,
}
return {
'success': False,
}
@@ -1,68 +0,0 @@
var PutIODownloader = new Class({
initialize: function(){
var self = this;
App.addEvent('loadSettings', self.addRegisterButton.bind(self));
},
addRegisterButton: function(){
var self = this;
var setting_page = App.getPage('Settings');
setting_page.addEvent('create', function(){
var fieldset = setting_page.tabs.downloaders.groups.putio,
l = window.location;
var putio_set = 0;
fieldset.getElements('input[type=text]').each(function(el){
putio_set += +(el.get('value') !== '');
});
new Element('.ctrlHolder').adopt(
// Unregister button
(putio_set > 0) ?
[
self.unregister = new Element('a.button.red', {
'text': 'Unregister "'+fieldset.getElement('input[name*=oauth_token]').get('value')+'"',
'events': {
'click': function(){
fieldset.getElements('input[name*=oauth_token]').set('value', '').fireEvent('change');
self.unregister.destroy();
self.unregister_or.destroy();
}
}
}),
self.unregister_or = new Element('span[text=or]')
]
: null,
// Register button
new Element('a.button', {
'text': putio_set > 0 ? 'Register a different account' : 'Register your put.io account',
'events': {
'click': function(){
Api.request('downloader.putio.auth_url', {
'data': {
'host': l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '')
},
'onComplete': function(json){
window.location = json.url;
}
});
}
}
})
).inject(fieldset.getElement('.test_button'), 'before');
});
}
});
window.addEvent('domready', function(){
new PutIODownloader();
});
@@ -41,30 +41,12 @@ class qBittorrent(DownloaderBase):
return self.qb
def test(self):
""" Check if connection works
:return: bool
"""
if self.connect():
return True
return False
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -113,14 +95,6 @@ class qBittorrent(DownloaderBase):
return 'busy'
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking qBittorrent download status.')
if not self.connect():
-26
View File
@@ -84,10 +84,6 @@ class rTorrent(DownloaderBase):
return self.rt
def test(self):
""" Check if connection works
:return: bool
"""
if self.connect(True):
return True
@@ -98,20 +94,6 @@ class rTorrent(DownloaderBase):
def download(self, data = None, media = None, filedata = None):
""" Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -179,14 +161,6 @@ class rTorrent(DownloaderBase):
return 'completed'
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking rTorrent download status.')
if not self.connect():
-27
View File
@@ -21,21 +21,6 @@ class Sabnzbd(DownloaderBase):
protocol = ['nzb']
def download(self, data = None, media = None, filedata = None):
"""
Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -84,11 +69,6 @@ class Sabnzbd(DownloaderBase):
return False
def test(self):
""" Check if connection works
Return message if an old version of SAB is used
:return: bool
"""
try:
sab_data = self.call({
'mode': 'version',
@@ -109,13 +89,6 @@ class Sabnzbd(DownloaderBase):
return True
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking SABnzbd download status.')
+1 -20
View File
@@ -19,21 +19,6 @@ class Synology(DownloaderBase):
status_support = False
def download(self, data = None, media = None, filedata = None):
"""
Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -65,10 +50,6 @@ class Synology(DownloaderBase):
return self.downloadReturnId('') if response else False
def test(self):
""" Check if connection works
:return: bool
"""
host = cleanHost(self.conf('host'), protocol = False).split(':')
try:
srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password'))
@@ -137,7 +118,7 @@ class SynologyRPC(object):
def _req(self, url, args, files = None):
response = {'success': False}
try:
req = requests.post(url, data = args, files = files, verify = False)
req = requests.post(url, data = args, files = files)
req.raise_for_status()
response = json.loads(req.text)
if response['success']:
@@ -34,21 +34,6 @@ class Transmission(DownloaderBase):
return self.trpc
def download(self, data = None, media = None, filedata = None):
"""
Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -103,22 +88,11 @@ class Transmission(DownloaderBase):
return self.downloadReturnId(data['hashString'])
def test(self):
""" Check if connection works
:return: bool
"""
if self.connect() and self.trpc.get_session():
return True
return False
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking Transmission download status.')
@@ -147,8 +121,6 @@ class Transmission(DownloaderBase):
status = 'failed'
elif torrent['status'] == 0 and torrent['percentDone'] == 1:
status = 'completed'
elif torrent['status'] == 16 and torrent['percentDone'] == 1:
status = 'completed'
elif torrent['status'] in [5, 6]:
status = 'seeding'
-26
View File
@@ -51,21 +51,6 @@ class uTorrent(DownloaderBase):
return self.utorrent_api
def download(self, data = None, media = None, filedata = None):
"""
Send a torrent/nzb file to the downloader
:param data: dict returned from provider
Contains the release information
:param media: media dict with information
Used for creating the filename when possible
:param filedata: downloaded torrent/nzb filedata
The file gets downloaded in the searcher and send to this function
This is done to have failed checking before using the downloader, so the downloader
doesn't need to worry about that
:return: boolean
One faile returns false, but the downloaded should log his own errors
"""
if not media: media = {}
if not data: data = {}
@@ -135,10 +120,6 @@ class uTorrent(DownloaderBase):
return self.downloadReturnId(torrent_hash)
def test(self):
""" Check if connection works
:return: bool
"""
if self.connect():
build_version = self.utorrent_api.get_build()
if not build_version:
@@ -150,13 +131,6 @@ class uTorrent(DownloaderBase):
return False
def getAllDownloadStatus(self, ids):
""" Get status of all active downloads
:param ids: list of (mixed) downloader ids
Used to match the releases for this downloader as there could be
other downloaders active that it should ignore
:return: list of releases
"""
log.debug('Checking uTorrent download status.')
+5 -8
View File
@@ -37,18 +37,15 @@ def toUnicode(original, *args):
except:
try:
detected = detect(original)
try:
if detected.get('confidence') > 0.8:
return original.decode(detected.get('encoding'))
except:
pass
if detected.get('encoding') == 'utf-8':
return original.decode('utf-8')
return ek(original, *args)
except:
raise
except:
log.error('Unable to decode value "%s..." : %s ', (repr(original)[:20], traceback.format_exc()))
return 'ERROR DECODING STRING'
ascii_text = str(original).encode('string_escape')
return toUnicode(ascii_text)
def ss(original, *args):
@@ -95,7 +92,7 @@ def ek(original, *args):
if isinstance(original, (str, unicode)):
try:
from couchpotato.environment import Env
return original.decode(Env.get('encoding'), 'ignore')
return original.decode(Env.get('encoding'))
except UnicodeDecodeError:
raise
+2 -11
View File
@@ -1,10 +1,9 @@
import os
import traceback
from couchpotato import CPLog, md5
from couchpotato import CPLog
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getExt
from couchpotato.core.plugins.base import Plugin
import six
@@ -93,15 +92,7 @@ class MediaBase(Plugin):
if not isinstance(image, (str, unicode)):
continue
# Check if it has top image
filename = '%s.%s' % (md5(image), getExt(image))
existing = existing_files.get(file_type, [])
has_latest = False
for x in existing:
if filename in x:
has_latest = True
if not has_latest or file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
if file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
file_path = fireEvent('file.download', url = image, single = True)
if file_path:
existing_files[file_type] = [toUnicode(file_path)]
@@ -94,8 +94,6 @@ class Provider(Plugin):
try:
data = XMLTree.fromstring(ss(data))
return self.getElements(data, item_path)
except XMLTree.ParseError:
log.error('Invalid XML returned, check "%s" manually for issues', url)
except:
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
@@ -68,12 +68,8 @@ class Base(NZBProvider, RSS):
if not date:
date = self.getTextElement(nzb, 'pubDate')
nzb_id = self.getTextElement(nzb, 'guid').split('/')[-1:].pop()
name = self.getTextElement(nzb, 'title')
detail_url = self.getTextElement(nzb, 'guid')
nzb_id = detail_url.split('/')[-1:].pop()
if '://' not in detail_url:
detail_url = (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id)
if not name:
continue
@@ -107,7 +103,7 @@ class Base(NZBProvider, RSS):
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
'detail_url': detail_url,
'detail_url': (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id),
'content': self.getTextElement(nzb, 'description'),
'description': description,
'score': host['extra_score'],
@@ -187,7 +183,7 @@ class Base(NZBProvider, RSS):
return 'try_next'
try:
data = self.urlopen(url, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
data = self.urlopen(url, show_error = False)
self.limits_reached[host] = False
return data
except HTTPError as e:
@@ -1,9 +1,13 @@
from urlparse import urlparse, parse_qs
import time
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
from dateutil.parser import parse
log = CPLog(__name__)
@@ -12,19 +16,27 @@ log = CPLog(__name__)
class Base(NZBProvider, RSS):
urls = {
'search': 'https://api.omgwtfnzbs.org/json/?%s',
'search': 'https://rss.omgwtfnzbs.org/rss-search.php?%s',
'detail_url': 'https://omgwtfnzbs.org/details.php?id=%s',
}
http_time_between_calls = 1 # Seconds
cat_ids = [
([15], ['dvdrip', 'scr', 'r5', 'tc', 'ts', 'cam']),
([15], ['dvdrip']),
([15, 16], ['brrip']),
([16], ['720p', '1080p', 'bd50']),
([17], ['dvdr']),
]
cat_backup_id = 'movie'
def search(self, movie, quality):
if quality['identifier'] in fireEvent('quality.pre_releases', single = True):
return []
return super(Base, self).search(movie, quality)
def _searchOnTitle(self, title, movie, quality, results):
q = '%s %s' % (title, movie['info']['year'])
@@ -35,20 +47,22 @@ class Base(NZBProvider, RSS):
'api': self.conf('api_key', default = ''),
})
nzbs = self.getJsonData(self.urls['search'] % params)
nzbs = self.getRSSData(self.urls['search'] % params)
if isinstance(nzbs, list):
for nzb in nzbs:
for nzb in nzbs:
results.append({
'id': nzb.get('nzbid'),
'name': toUnicode(nzb.get('release')),
'age': self.calculateAge(tryInt(nzb.get('usenetage'))),
'size': tryInt(nzb.get('sizebytes')) / 1024 / 1024,
'url': nzb.get('getnzb'),
'detail_url': nzb.get('details'),
'description': nzb.get('weblink')
})
enclosure = self.getElement(nzb, 'enclosure').attrib
nzb_id = parse_qs(urlparse(self.getTextElement(nzb, 'link')).query).get('id')[0]
results.append({
'id': nzb_id,
'name': toUnicode(self.getTextElement(nzb, 'title')),
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, 'pubDate')).timetuple()))),
'size': tryInt(enclosure['length']) / 1024 / 1024,
'url': enclosure['url'],
'detail_url': self.urls['detail_url'] % nzb_id,
'description': self.getTextElement(nzb, 'description')
})
config = [{
@@ -1,130 +0,0 @@
import re
import traceback
from couchpotato.core.helpers.variable import tryInt, getIdentifier
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://hdaccess.net/',
'detail': 'https://hdaccess.net/details.php?id=%s',
'search': 'https://hdaccess.net/searchapi.php?apikey=%s&username=%s&imdbid=%s&internal=%s',
'download': 'https://hdaccess.net/grab.php?torrent=%s&apikey=%s',
}
http_time_between_calls = 1 # Seconds
def _search(self, movie, quality, results):
data = self.getJsonData(self.urls['search'] % (self.conf('apikey'), self.conf('username'), getIdentifier(movie), self.conf('internal_only')))
if data:
try:
#for result in data[]:
for key, result in data.iteritems():
if tryInt(result['total_results']) == 0:
return
torrentscore = self.conf('extra_score')
releasegroup = result['releasegroup']
resolution = result['resolution']
encoding = result['encoding']
freeleech = tryInt(result['freeleech'])
seeders = tryInt(result['seeders'])
torrent_desc = '/ %s / %s / %s / %s seeders' % (releasegroup, resolution, encoding, seeders)
if freeleech > 0 and self.conf('prefer_internal'):
torrent_desc += '/ Internal'
torrentscore += 200
if seeders == 0:
torrentscore = 0
name = result['release_name']
year = tryInt(result['year'])
results.append({
'id': tryInt(result['torrentid']),
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
'url': self.urls['download'] % (result['torrentid'], self.conf('apikey')),
'detail_url': self.urls['detail'] % result['torrentid'],
'size': tryInt(result['size']),
'seeders': tryInt(result['seeders']),
'leechers': tryInt(result['leechers']),
'age': tryInt(result['age']),
'score': torrentscore
})
except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
config = [{
'name': 'hdaccess',
'groups': [
{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'HDAccess',
'wizard': True,
'description': '<a href="https://hdaccess.net">HDAccess</a>',
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAADuUlEQVQ4yz3T209bdQAH8O/vnNNzWno5FIpAKZdSLi23gWMDtumWuSXOyzJj9M1kyIOPS1xiYuKe9GUPezZZnGIiMTqTxS1bdIuYkG2MWKBAKYVszOgKFkrbA+259HfO+fli/PwPHzI+Pg5CCEAI2VcUlEsl1tHdU7P5bGOkWChEaaUCwvHpmkD93POn6bwgCMQGAMYYYwyCruuQnE7SPzjIstvb8l+bm5fXkokJSmlQEkUQAIpSRH5vd0tyum7I/sA1Z5VH2ctmiGWZjHw4McE1NAZtQ9fD25kXt1VN7es7dNjuGRjiJFeVpWo6slsZPhF/Ys/PPeIs2056ff7zIOS5rpU5/viJEwwEnu3Mi18dojjw0aWP6amz57h9RSE/35zinq2nuGjvIQwOj7K2SKeZWkk0auXSSZ+/ZopSy+CbW1pQKpWu6Jr2/qVPPqWRjm6HWi6Tm999g3RyGbndLCqGgVBrO3F7fHykK0YX47NNtGLYlBq/c+H2iD+3k704dHQUDcFmQVXLyP6zhfTqCl45fQYjx17FemoJunoAk1bQFGoVhkdPwNC0ix2dMT+3llodM02rKdo7gN3dHAEhuH/vNgDg3Pl3cPaNt2GZJpYX5lBbFwClBukfGobL5WrayW6NccVCISY4HIQxYts2Q3J5CXOPHuLlo6NoCoXQ2hbG0JFRpJYWcVDIQ5ZlyL5qW5b9hNlWjKsYBgzDgKppMCoGHty7A0orOHbyNNweL+obGnDm9TdhWSYS8Vn4a2shOZ0QJRGSKIHjeGGtWNhjqqpyG+k04k8eozPai9ZwByavf4kfpyZxZGwMfYOHsbwQx34hB5dL4syKweRq/xpXHwzNapqWSSYWMDszzYqFPEaOn4KiKJiZfoCZ6d8Am+GtC++iXCpjaf4P9vefT8HzfKarp3eWRKMxCILwuWXSz977YIK2RTodDoGH1+OG1+tDlbsKkuiAJEngeWBjNUUnv7rucIiOLyzTvMKJTgnVtbVXLctK3L31g+NAUajL5bEptaDpOnTdgGkzVHl9drms0ju3fnJIkphoaQtfbQiFwAcCAY5wnCE5Xff3i8XX4o9nGksH+8zl9hAGZlWMCivkc9z0L3fZ999+LTCGZKi55YJTFHfye3sc6e/vB88LpK6+iWlqSS4WcpcNXZtwOp3B6mo/REmCSSkEgd+qq3vpRkt75Fp9Y1BZWZwnhq4zEovF/u/MATAti4U7umvyu9kR27aikihC9vvTnV2xufVUMu/2uIksy/9tZvgX49fLmAMx3bsAAAAASUVORK5CYII=',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False,
},
{
'name': 'username',
'default': '',
'description': 'Enter your site username.',
},
{
'name': 'apikey',
'default': '',
'label': 'API Key',
'description': 'Enter your site api key. This can be find on <a href="https://hdaccess.net/usercp.php?action=security">Profile Security</a>',
},
{
'name': 'seed_ratio',
'label': 'Seed ratio',
'type': 'float',
'default': 0,
'description': 'Will not be (re)moved until this seed ratio is met. HDAccess minimum is 1:1.',
},
{
'name': 'seed_time',
'label': 'Seed time',
'type': 'int',
'default': 0,
'description': 'Will not be (re)moved until this seed time (in hours) is met. HDAccess minimum is 48 hours.',
},
{
'name': 'prefer_internal',
'advanced': True,
'type': 'bool',
'default': 1,
'description': 'Favors internal releases over non-internal releases.',
},
{
'name': 'internal_only',
'advanced': True,
'label': 'Internal Only',
'type': 'bool',
'default': False,
'description': 'Only download releases marked as HDAccess internal',
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 0,
'description': 'Starting score for each release found via this provider.',
}
],
},
],
}]
@@ -29,9 +29,6 @@ class Base(TorrentProvider):
}
post_data.update(params)
if self.conf('internal_only'):
post_data.update({'origin': [1]})
try:
result = self.getJsonData(self.urls['api'], data = json.dumps(post_data))
@@ -113,14 +110,6 @@ config = [{
'default': 0,
'description': 'Starting score for each release found via this provider.',
},
{
'name': 'internal_only',
'advanced': True,
'label': 'Internal Only',
'type': 'bool',
'default': False,
'description': 'Only download releases marked as HDBits internal'
}
],
},
],
@@ -14,11 +14,11 @@ log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://iptorrents.eu/',
'base_url': 'https://iptorrents.eu',
'login': 'https://iptorrents.eu/torrents/',
'login_check': 'https://iptorrents.eu/inbox.php',
'search': 'https://iptorrents.eu/torrents/?%s%%s&q=%s&qf=ti&p=%%d',
'test': 'https://www.iptorrents.com/',
'base_url': 'https://www.iptorrents.com',
'login': 'https://www.iptorrents.com/torrents/',
'login_check': 'https://www.iptorrents.com/inbox.php',
'search': 'https://www.iptorrents.com/torrents/?%s%%s&q=%s&qf=ti&p=%%d',
}
http_time_between_calls = 1 # Seconds
@@ -120,7 +120,7 @@ config = [{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'IPTorrents',
'description': '<a href="https://iptorrents.eu">IPTorrents</a>',
'description': '<a href="http://www.iptorrents.com">IPTorrents</a>',
'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRklEQVR42qWQO0vDUBiG8zeKY3EqQUtNO7g0J6ZJ1+ifKIIFQXAqDYKCyaaYxM3udrZLHdRFhXrZ6liCW6mubfk874EESgqaeOCF7/Y8hEh41aq6yZi2nyZgBGya9XKtZs4No05pAkZV2YbEmyMMsoSxLQeC46wCTdPPY4HruPQyGIhF97qLWsS78Miydn4XdK46NJ9OsQAYBzMIMf8MQ9wtCnTdWCaIDx/u7uljOIQEe0hiIWPamSTLay3+RxOCSPI9+RJAo7Er9r2bnqjBFAqyK+VyK4f5/Cr5ni8OFKVCz49PFI5GdNvvU7ttE1M1zMU+8AMqFksEhrMnQsBDzqmDAwzx2ehRLwT7yyCI+vSC99c3mozH1NxrJgWWtR1BOECfEJSVCm6WCzJGCA7+IWhBsM4zywDPwEp4vCjx2DzBH2ODAfsDb33Ps6dQwJgAAAAASUVORK5CYII=',
'options': [
@@ -42,7 +42,6 @@ class Base(TorrentProvider):
link = result.find('td', attrs = {'class': 'ttr_name'}).find('a')
url = result.find('td', attrs = {'class': 'td_dl'}).find('a')
seeders = result.find('td', attrs = {'class': 'ttr_seeders'}).find('a')
leechers = result.find('td', attrs = {'class': 'ttr_leechers'}).find('a')
torrent_id = link['href'].replace('details?id=', '')
@@ -52,7 +51,7 @@ class Base(TorrentProvider):
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['detail'] % torrent_id,
'size': self.parseSize(result.find('td', attrs = {'class': 'ttr_size'}).contents[0]),
'seeders': tryInt(seeders.string) if seeders else 0,
'seeders': tryInt(result.find('td', attrs = {'class': 'ttr_seeders'}).find('a').string),
'leechers': tryInt(leechers.string) if leechers else 0,
'get_more_info': self.getMoreInfo,
})
@@ -1,7 +1,7 @@
import traceback
from bs4 import BeautifulSoup
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
@@ -56,12 +56,11 @@ class Base(TorrentProvider):
full_id = link['href'].replace('details.php?id=', '')
torrent_id = full_id[:6]
name = toUnicode(link.get('title', link.contents[0]).encode('ISO-8859-1')).strip()
results.append({
'id': torrent_id,
'name': name,
'url': self.urls['download'] % (torrent_id, name),
'name': link.contents[0],
'url': self.urls['download'] % (torrent_id, link.contents[0]),
'detail_url': self.urls['detail'] % torrent_id,
'size': self.parseSize(cells[6].contents[0] + cells[6].contents[2]),
'seeders': tryInt(cells[8].find('span').contents[0]),
@@ -1,4 +1,3 @@
import re
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
@@ -9,12 +8,12 @@ log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://torrentday.eu/',
'login': 'https://torrentday.eu/torrents/',
'login_check': 'https://torrentday.eu/userdetails.php',
'detail': 'https://torrentday.eu/details.php?id=%s',
'search': 'https://torrentday.eu/V3/API/API.php',
'download': 'https://torrentday.eu/download.php/%s/%s',
'test': 'http://www.td.af/',
'login': 'http://www.td.af/torrents/',
'login_check': 'http://www.torrentday.com/userdetails.php',
'detail': 'http://www.td.af/details.php?id=%s',
'search': 'http://www.td.af/V3/API/API.php',
'download': 'http://www.td.af/download.php/%s/%s',
}
http_time_between_calls = 1 # Seconds
@@ -56,10 +55,6 @@ class Base(TorrentProvider):
}
def loginSuccess(self, output):
often = re.search('You tried too often, please wait .*</div>', output)
if often:
raise Exception(often.group(0)[:-6].strip())
return 'Password not correct' not in output
def loginCheckSuccess(self, output):
@@ -73,7 +68,7 @@ config = [{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'TorrentDay',
'description': '<a href="https://torrentday.eu/">TorrentDay</a>',
'description': '<a href="http://www.td.af/">TorrentDay</a>',
'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC5ElEQVQ4y12TXUgUURTH//fO7Di7foeQJH6gEEEIZZllVohfSG/6UA+RSFAQQj74VA8+Bj30lmAlRVSEvZRfhNhaka5ZUG1paKaW39tq5O6Ou+PM3M4o6m6X+XPPzD3zm/+dcy574r515WfIW8CZBM4YAA5Gc/aQC3yd7oXYEONcsISE5dTDh91HS0t7FEWhBUAeN9ynV/d9qJAgE4AECURAcVsGlCCnly26LMA0IQwTa52dje3d3e3hcPi8qqrrMjcVYI3EHCQZlkFOHBwR2QHh2ASAAIJxWGAQEDxjePhs3527XjJwnb37OHBq0T+Tyyjh+9KnEzNJ7nouc1Q/3A3HGsOvnJy+PSUlj81w2Lny9WuJ6+3AmTjD4HOcrdR2dWXLRQePvyaSLfQOPMPC8mC9iHCsOxSyzJCelzdSXlNzD5ujpb25Wbfc/XXJemTXF4+nnCNq+AMLe50uFfEJTiw4GXSFtiHL0SnIq66+p0kSArqO+eH3RdsAv9+f5vW7L7GICq6rmM8XBCAXlBw90rOyxibn5yzfkg/L09M52/jxqdESaIrBXHYZZbB1GX8cEpySxKIB8S5XcOnvqpli1zuwmrTtoLjw5LOK/eeuWsE4JH5IRPaPZKiKigmPp+5pa+u1aEjIMhEgrRkmi9mgxGUhM7LNJSzOzsE3+cOeExovXOjdytE0LV4zqNZUtV0uZzAGoGkhDH/2YHZiErmv4uyWQnZZWc+hoqL3WzlTExN5hhA8IEwkZWZOxwB++30YG/9GkYCPvqAaHAW5uWPROW86OmqCprUR7z1yZDAGQNuCvkoB/baIKUBWMTYymv+gra3eJNvjXu+B562tFyXqTJ6YuHK8rKwvBmC3vR7cOCPQLWFz8LnfXWUrJo9U19BwMyUlJRjTSMJ2ENxUiGxq9KXQfwqYlnWstvbR5aamG9g0uzM8Q4OFt++3NNixQ2NgYmeN03FOTUv7XVpV9aKisvLl1vN/WVhNc/Fi1NEAAAAASUVORK5CYII=',
'options': [
@@ -1,126 +0,0 @@
import traceback
from bs4 import BeautifulSoup
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
import six
log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://www.torrentleech.org/',
'login': 'https://www.torrentleech.org/user/account/login/',
'login_check': 'https://torrentleech.org/user/messages',
'detail': 'https://www.torrentleech.org/torrent/%s',
'search': 'https://www.torrentleech.org/torrents/browse/index/query/%s/categories/%s',
'download': 'https://www.torrentleech.org%s',
}
http_time_between_calls = 1 # Seconds
cat_backup_id = None
def _searchOnTitle(self, title, media, quality, results):
url = self.urls['search'] % self.buildUrl(title, media, quality)
data = self.getHTMLData(url)
if data:
html = BeautifulSoup(data)
try:
result_table = html.find('table', attrs = {'id': 'torrenttable'})
if not result_table:
return
entries = result_table.find_all('tr')
for result in entries[1:]:
link = result.find('td', attrs = {'class': 'name'}).find('a')
url = result.find('td', attrs = {'class': 'quickdownload'}).find('a')
details = result.find('td', attrs = {'class': 'name'}).find('a')
results.append({
'id': link['href'].replace('/torrent/', ''),
'name': six.text_type(link.string),
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % details['href'],
'size': self.parseSize(result.find_all('td')[4].string),
'seeders': tryInt(result.find('td', attrs = {'class': 'seeders'}).string),
'leechers': tryInt(result.find('td', attrs = {'class': 'leechers'}).string),
})
except:
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
def getLoginParams(self):
return {
'username': self.conf('username'),
'password': self.conf('password'),
'remember_me': 'on',
'login': 'submit',
}
def loginSuccess(self, output):
return '/user/account/logout' in output.lower() or 'welcome back' in output.lower()
loginCheckSuccess = loginSuccess
config = [{
'name': 'torrentleech',
'groups': [
{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'TorrentLeech',
'description': '<a href="http://torrentleech.org">TorrentLeech</a>',
'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACHUlEQVR4AZVSO48SYRSdGTCBEMKzILLAWiybkKAGMZRUUJEoDZX7B9zsbuQPYEEjNLTQkYgJDwsoSaxspEBsCITXjjNAIKi8AkzceXgmbHQ1NJ5iMufmO9/9zrmXlCSJ+B8o75J8Pp/NZj0eTzweBy0Wi4PBYD6f12o1r9ebTCZx+22HcrnMsuxms7m6urTZ7LPZDMVYLBZ8ZV3yo8aq9Pq0wzCMTqe77dDv9y8uLyAWBH6xWOyL0K/56fcb+rrPgPZ6PZfLRe1fsl6vCUmGKIqoqNXqdDr9Dbjps9znUV0uTqdTjuPkDoVCIfcuJ4gizjMMm8u9vW+1nr04czqdK56c37CbKY9j2+1WEARZ0Gq1RFHAz2q1qlQqXxoN69HRcDjUarW8ZD6QUigUOnY8uKYH8N1sNkul9yiGw+F6vS4Rxn8EsodEIqHRaOSnq9T7ajQazWQycEIR1AEBYDabSZJyHDucJyegwWBQr9ebTCaKvHd4cCQANUU9evwQ1Ofz4YvUKUI43GE8HouSiFiNRhOowWBIpVLyHITJkuW3PwgAEf3pgIwxF5r+OplMEsk3CPT5szCMnY7EwUdhwUh/CXiej0Qi3idPz89fdrpdbsfBzH7S3Q9K5pP4c0sAKpVKoVAQGO1ut+t0OoFAQHkH2Da/3/+but3uarWK0ZMQoNdyucRutdttmqZxMTzY7XaYxsrgtUjEZrNhkSwWyy/0NCatZumrNQAAAABJRU5ErkJggg==',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False,
},
{
'name': 'username',
'default': '',
},
{
'name': 'password',
'default': '',
'type': 'password',
},
{
'name': 'seed_ratio',
'label': 'Seed ratio',
'type': 'float',
'default': 1,
'description': 'Will not be (re)moved until this seed ratio is met.',
},
{
'name': 'seed_time',
'label': 'Seed time',
'type': 'int',
'default': 40,
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 20,
'description': 'Starting score for each release found via this provider.',
}
],
},
],
}]
@@ -13,12 +13,12 @@ log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://theshack.us.to/',
'login': 'https://theshack.us.to/login.php',
'login_check': 'https://theshack.us.to/inbox.php',
'detail': 'https://theshack.us.to/torrent/%s',
'search': 'https://theshack.us.to/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
'download': 'https://theshack.us.to/%s',
'test': 'http://torrentshack.eu/',
'login': 'http://torrentshack.eu/login.php',
'login_check': 'http://torrentshack.eu/inbox.php',
'detail': 'http://torrentshack.eu/torrent/%s',
'search': 'http://torrentshack.eu/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
'download': 'http://torrentshack.eu/%s',
}
http_time_between_calls = 1 # Seconds
@@ -42,7 +42,6 @@ class Base(TorrentProvider):
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
size = result.find('td', attrs = {'class': 'size'}).contents[0].strip('\n ')
tds = result.find_all('td')
results.append({
@@ -50,7 +49,7 @@ class Base(TorrentProvider):
'name': six.text_type(link.span.string).translate({ord(six.u('\xad')): None}),
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % link['href'],
'size': self.parseSize(size),
'size': self.parseSize(result.find_all('td')[5].string),
'seeders': tryInt(tds[len(tds)-2].string),
'leechers': tryInt(tds[len(tds)-1].string),
})
@@ -22,12 +22,12 @@ class Base(TorrentMagnetProvider, RSS):
http_time_between_calls = 0
def _searchOnTitle(self, title, media, quality, results):
def _search(self, media, quality, results):
search_url = self.urls['verified_search'] if self.conf('verified_only') else self.urls['search']
# Create search parameters
search_params = self.buildUrl(title, media, quality)
search_params = self.buildUrl(media)
smin = quality.get('size_min')
smax = quality.get('size_max')
@@ -2,25 +2,28 @@ import traceback
from couchpotato.core.helpers.variable import tryInt, getIdentifier
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
log = CPLog(__name__)
class Base(TorrentProvider):
class Base(TorrentMagnetProvider):
urls = {
'test': '%s/api/v2',
'search': '%s/api/v2/list_movies.json?limit=50&query_term=%s'
'test': '%s/api',
'search': '%s/api/list.json?keywords=%s&quality=%s',
'detail': '%s/api/movie.json?id=%s'
}
http_time_between_calls = 1 # seconds
proxy_list = [
'https://yts.re',
'https://yts.wf',
'https://yts.im',
'http://yify.unlocktorrent.com',
'http://yify-torrents.com.come.in',
'http://yts.re',
'http://yts.im'
'http://yify-torrents.im',
]
def search(self, movie, quality):
@@ -36,31 +39,28 @@ class Base(TorrentProvider):
if not domain:
return
search_url = self.urls['search'] % (domain, getIdentifier(movie))
search_url = self.urls['search'] % (domain, getIdentifier(movie), quality['identifier'])
data = self.getJsonData(search_url)
data = data.get('data')
if isinstance(data, dict) and data.get('movies'):
if data and data.get('MovieList'):
try:
for result in data.get('movies'):
for result in data.get('MovieList'):
for release in result.get('torrents', []):
if result['Quality'] and result['Quality'] not in result['MovieTitle']:
title = result['MovieTitle'] + ' BrRip ' + result['Quality']
else:
title = result['MovieTitle'] + ' BrRip'
if release['quality'] and release['quality'] not in result['title_long']:
title = result['title_long'] + ' BRRip ' + release['quality']
else:
title = result['title_long'] + ' BRRip'
results.append({
'id': release['hash'],
'name': title,
'url': release['url'],
'detail_url': result['url'],
'size': self.parseSize(release['size']),
'seeders': tryInt(release['seeds']),
'leechers': tryInt(release['peers']),
})
results.append({
'id': result['MovieID'],
'name': title,
'url': result['TorrentMagnetUrl'],
'detail_url': self.urls['detail'] % (domain, result['MovieID']),
'size': self.parseSize(result['Size']),
'seeders': tryInt(result['TorrentSeeds']),
'leechers': tryInt(result['TorrentPeers']),
})
except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
@@ -0,0 +1,277 @@
.search_form {
display: inline-block;
vertical-align: middle;
position: absolute;
right: 105px;
top: 0;
text-align: right;
height: 100%;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
z-index: 20;
border: 0 solid transparent;
border-bottom-width: 4px;
}
.search_form:hover {
border-color: #047792;
}
@media all and (max-width: 480px) {
.search_form {
right: 44px;
}
}
.search_form.focused,
.search_form.shown {
border-color: #04bce6;
}
.search_form .input {
height: 100%;
overflow: hidden;
width: 45px;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
}
.search_form.focused .input,
.search_form.shown .input {
width: 380px;
background: #4e5969;
}
.search_form .input input {
border-radius: 0;
display: block;
border: 0;
background: none;
color: #FFF;
font-size: 25px;
height: 100%;
width: 100%;
opacity: 0;
padding: 0 40px 0 10px;
transition: all .4s ease-in-out .2s;
}
.search_form.focused .input input,
.search_form.shown .input input {
opacity: 1;
}
.search_form input::-ms-clear {
width : 0;
height: 0;
}
@media all and (max-width: 480px) {
.search_form .input input {
font-size: 15px;
}
.search_form.focused .input,
.search_form.shown .input {
width: 277px;
}
}
.search_form .input a {
position: absolute;
top: 0;
right: 0;
width: 44px;
height: 100%;
cursor: pointer;
vertical-align: middle;
text-align: center;
line-height: 66px;
font-size: 15px;
color: #FFF;
}
.search_form .input a:after {
content: "\e03e";
}
.search_form.shown.filled .input a:after {
content: "\e04e";
}
@media all and (max-width: 480px) {
.search_form .input a {
line-height: 44px;
}
}
.search_form .results_container {
text-align: left;
position: absolute;
background: #5c697b;
margin: 4px 0 0;
width: 470px;
min-height: 50px;
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55);
display: none;
}
@media all and (max-width: 480px) {
.search_form .results_container {
width: 320px;
}
}
.search_form.focused.filled .results_container,
.search_form.shown.filled .results_container {
display: block;
}
.search_form .results {
max-height: 570px;
overflow-x: hidden;
}
.media_result {
overflow: hidden;
height: 50px;
position: relative;
}
.media_result .options {
position: absolute;
height: 100%;
top: 0;
left: 30px;
right: 0;
padding: 13px;
border: 1px solid transparent;
border-width: 1px 0;
border-radius: 0;
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
}
.media_result .options > .in_library_wanted {
margin-top: -7px;
}
.media_result .options > div {
border: 0;
}
.media_result .options .thumbnail {
vertical-align: middle;
}
.media_result .options select {
vertical-align: middle;
display: inline-block;
margin-right: 10px;
}
.media_result .options select[name=title] { width: 170px; }
.media_result .options select[name=profile] { width: 90px; }
.media_result .options select[name=category] { width: 80px; }
@media all and (max-width: 480px) {
.media_result .options select[name=title] { width: 90px; }
.media_result .options select[name=profile] { width: 50px; }
.media_result .options select[name=category] { width: 50px; }
}
.media_result .options .button {
vertical-align: middle;
display: inline-block;
}
.media_result .options .message {
height: 100%;
font-size: 20px;
color: #fff;
line-height: 20px;
}
.media_result .data {
position: absolute;
height: 100%;
top: 0;
left: 30px;
right: 0;
background: #5c697b;
cursor: pointer;
border-top: 1px solid rgba(255,255,255, 0.08);
transition: all .4s cubic-bezier(0.9,0,0.1,1);
}
.media_result .data.open {
left: 100% !important;
}
.media_result:last-child .data { border-bottom: 0; }
.media_result .in_wanted, .media_result .in_library {
position: absolute;
bottom: 2px;
left: 14px;
font-size: 11px;
}
.media_result .thumbnail {
width: 34px;
min-height: 100%;
display: block;
margin: 0;
vertical-align: top;
}
.media_result .info {
position: absolute;
top: 20%;
left: 15px;
right: 7px;
vertical-align: middle;
}
.media_result .info h2 {
margin: 0;
font-weight: normal;
font-size: 20px;
padding: 0;
}
.search_form .info h2 {
position: absolute;
width: 100%;
}
.media_result .info h2 .title {
display: block;
margin: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.search_form .info h2 .title {
position: absolute;
width: 88%;
}
.media_result .info h2 .year {
padding: 0 5px;
text-align: center;
position: absolute;
width: 12%;
right: 0;
}
@media all and (max-width: 480px) {
.search_form .info h2 .year {
font-size: 12px;
margin-top: 7px;
}
}
.search_form .mask,
.media_result .mask {
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
}
@@ -1,4 +1,4 @@
var BlockSearch = new Class({
Block.Search = new Class({
Extends: BlockBase,
@@ -9,46 +9,45 @@ var BlockSearch = new Class({
var focus_timer = 0;
self.el = new Element('div.search_form').adopt(
new Element('a.icon-search', {
'events': {
'click': self.clear.bind(self),
'touchend': self.clear.bind(self)
}
}),
new Element('div.wrapper').adopt(
self.result_container = new Element('div.results_container', {
'tween': {
'duration': 200
},
new Element('div.input').adopt(
self.input = new Element('input', {
'placeholder': 'Search & add a new media',
'events': {
'mousewheel': function(e){
(e).stopPropagation();
'input': self.keyup.bind(self),
'paste': self.keyup.bind(self),
'change': self.keyup.bind(self),
'keyup': self.keyup.bind(self),
'focus': function(){
if(focus_timer) clearTimeout(focus_timer);
self.el.addClass('focused');
if(this.get('value'))
self.hideResults(false)
},
'blur': function(){
focus_timer = (function(){
self.el.removeClass('focused')
}).delay(100);
}
}
}).grab(
self.results = new Element('div.results')
),
new Element('div.input').grab(
self.input = new Element('input', {
'placeholder': 'Search & add a new media',
'events': {
'input': self.keyup.bind(self),
'paste': self.keyup.bind(self),
'change': self.keyup.bind(self),
'keyup': self.keyup.bind(self),
'focus': function(){
if(focus_timer) clearTimeout(focus_timer);
if(this.get('value'))
self.hideResults(false);
},
'blur': function(){
focus_timer = (function(){
self.el.removeClass('focused');
}).delay(100);
}
}
})
)
}),
new Element('a.icon2', {
'events': {
'click': self.clear.bind(self),
'touchend': self.clear.bind(self)
}
})
),
self.result_container = new Element('div.results_container', {
'tween': {
'duration': 200
},
'events': {
'mousewheel': function(e){
(e).stopPropagation();
}
}
}).adopt(
self.results = new Element('div.results')
)
);
@@ -68,12 +67,11 @@ var BlockSearch = new Class({
self.last_q = '';
self.input.set('value', '');
self.el.addClass('focused');
self.input.focus();
self.media = {};
self.results.empty();
self.el.removeClass('filled');
self.el.removeClass('filled')
}
},
@@ -107,7 +105,7 @@ var BlockSearch = new Class({
self.api_request.cancel();
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer);
self.autocomplete_timer = self.autocomplete.delay(300, self);
self.autocomplete_timer = self.autocomplete.delay(300, self)
}
},
@@ -117,10 +115,10 @@ var BlockSearch = new Class({
if(!self.q()){
self.hideResults(true);
return;
return
}
self.list();
self.list()
},
list: function(){
@@ -141,7 +139,7 @@ var BlockSearch = new Class({
'q': q
},
'onComplete': self.fill.bind(self, q)
});
})
}
else
self.fill(q, cache);
@@ -160,25 +158,30 @@ var BlockSearch = new Class({
Object.each(json, function(media){
if(typeOf(media) == 'array'){
Object.each(media, function(me){
Object.each(media, function(m){
var m = new window['BlockSearch' + me.type.capitalize() + 'Item'](me);
var m = new Block.Search[m.type.capitalize() + 'Item'](m);
$(m).inject(self.results);
self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m;
if(q == m.imdb)
m.showOptions();
m.showOptions()
});
}
});
self.mask.fade('out');
// Calculate result heights
var w = window.getSize(),
rc = self.result_container.getCoordinates();
self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px');
self.mask.fade('out')
},
loading: function(bool){
this.el[bool ? 'addClass' : 'removeClass']('loading');
this.el[bool ? 'addClass' : 'removeClass']('loading')
},
q: function(){
@@ -1,242 +0,0 @@
@import "couchpotato/static/style/mixins";
.search_form {
display: inline-block;
z-index: 200;
width: 44px;
position: relative;
.icon-search {
position: absolute;
z-index: 2;
top: 50%;
left: 0;
height: 100%;
cursor: pointer;
text-align: center;
color: #FFF;
font-size: 20px;
@include translateY(-50%);
}
.wrapper {
position: absolute;
left: 44px;
bottom: 0;
background: $primary_color;
border-radius: $border_radius 0 0 $border_radius;
display: none;
box-shadow: 0 0 15px 2px rgba(0,0,0,.15);
&:before {
@include transform(rotate(45deg));
content: '';
display: block;
position: absolute;
height: 10px;
width: 10px;
background: $primary_color;
left: -6px;
bottom: 16px;
z-index: 1;
}
}
.input {
background: $background_color;
border-radius: $border_radius 0 0 $border_radius;
position: relative;
left: 4px;
height: 44px;
overflow: hidden;
width: 100%;
input {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 1;
&::-ms-clear {
width : 0;
height: 0;
}
}
}
&.focused,
&.shown {
border-color: #04bce6;
.wrapper {
display: block;
width: 380px;
}
.input {
input {
opacity: 1;
}
}
}
.results_container {
min-height: 50px;
text-align: left;
position: relative;
left: 4px;
display: none;
background: $background_color;
border-radius: $border_radius 0 0 0;
overflow: hidden;
.results {
max-height: 280px;
overflow-x: hidden;
.media_result {
overflow: hidden;
height: 50px;
position: relative;
.options {
position: absolute;
height: 100%;
top: 0;
left: 30px;
right: 0;
padding: 10px;
background: rgba(0,0,0,.3);
> .in_library_wanted {
margin-top: -7px;
}
> div {
border: 0;
@include flexbox();
}
.thumbnail {
vertical-align: middle;
}
select {
vertical-align: middle;
display: inline-block;
margin-right: 10px;
min-width: 70px;
@include flex(1 auto);
}
.button {
@include flex(1 auto);
vertical-align: middle;
display: inline-block;
}
.message {
height: 100%;
font-size: 20px;
color: #fff;
line-height: 20px;
}
}
.thumbnail {
width: 30px;
min-height: 100%;
display: block;
margin: 0;
vertical-align: top;
}
.data {
position: absolute;
height: 100%;
top: 0;
left: 30px;
right: 0;
cursor: pointer;
border-top: 1px solid rgba(255,255,255, 0.08);
transition: all .4s cubic-bezier(0.9,0,0.1,1);
@include translateX(0%);
background: $background_color;
&.open {
@include translateX(100%);
}
.in_wanted,
.in_library {
position: absolute;
bottom: 2px;
left: 14px;
font-size: 11px;
}
.info {
position: absolute;
top: 20%;
left: 15px;
right: 7px;
vertical-align: middle;
h2 {
margin: 0;
font-weight: 300;
font-size: 1.25em;
padding: 0;
position: absolute;
width: 100%;
@include flexbox();
.title {
display: inline-block;
margin: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
@include flex(1 auto);
}
.year {
opacity: .4;
padding: 0 5px;
width: auto;
display: none;
}
}
}
}
&:hover .info h2 .year {
display: inline-block;
}
&:last-child .data {
border-bottom: 0;
}
}
}
}
&.focused.filled,
&.shown.filled {
.results_container {
display: block;
}
.input {
border-radius: 0 0 0 $border_radius;
}
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ class MovieBase(MovieTypeBase):
return False
elif not params.get('info'):
try:
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), adding = True, single = True)
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
if not is_movie:
msg = 'Can\'t add movie, seems to be a TV show.'
log.error(msg)
@@ -1,52 +0,0 @@
var MovieDetails = new Class({
Extends: BlockBase,
sections: null,
initialize: function(parent, options){
var self = this;
self.sections = {};
self.el = new Element('div',{
'class': 'page active movie_details level_' + (options.level || 0)
}).adopt(
self.overlay = new Element('div.overlay', {
'events': {
'click': self.close.bind(self)
}
}).grab(
new Element('a.close.icon-left-arrow')
),
self.content = new Element('div.content').grab(
new Element('h1', {
'text': parent.getTitle() + (parent.get('year') ? ' (' + parent.get('year') + ')' : '')
})
)
);
self.addSection('description', new Element('div', {
'text': parent.get('plot')
}));
},
addSection: function(name, section_el){
var self = this;
name = name.toLowerCase();
self.content.grab(
self.sections[name] = new Element('div', {
'class': 'section section_' + name
}).grab(section_el)
);
},
close: function(){
var self = this;
self.el.dispose();
}
});
@@ -45,16 +45,15 @@ var MovieList = new Class({
}) : null
);
self.changeView(self.getSavedView() || self.options.view || 'thumb');
// Create the alphabet nav
if(self.options.navigation)
self.createNavigation();
if($(window).getSize().x <= 480 && !self.options.force_view)
self.changeView('list');
else
self.changeView(self.getSavedView() || self.options.view || 'details');
self.getMovies();
App.on('movie.added', self.movieAdded.bind(self));
App.on('movie.deleted', self.movieDeleted.bind(self));
App.on('movie.deleted', self.movieDeleted.bind(self))
},
movieDeleted: function(notification){
@@ -68,7 +67,7 @@ var MovieList = new Class({
self.setCounter(self.counter_count-1);
self.total_movies--;
}
});
})
}
self.checkIfEmpty();
@@ -90,11 +89,15 @@ var MovieList = new Class({
create: function(){
var self = this;
// Create the alphabet nav
if(self.options.navigation)
self.createNavigation();
if(self.options.load_more)
self.scrollspy = new ScrollSpy({
min: function(){
var c = self.load_more.getCoordinates();
return c.top - window.document.getSize().y - 300;
return c.top - window.document.getSize().y - 300
},
onEnter: self.loadMore.bind(self)
});
@@ -135,7 +138,7 @@ var MovieList = new Class({
self.empty_message = null;
}
if(self.total_movies && count === 0 && !self.empty_message){
if(self.total_movies && count == 0 && !self.empty_message){
var message = (self.filter.search ? 'for "'+self.filter.search+'"' : '') +
(self.filter.starts_with ? ' in <strong>'+self.filter.starts_with+'</strong>' : '');
@@ -227,33 +230,30 @@ var MovieList = new Class({
),
new Element('div.menus').adopt(
self.navigation_counter = new Element('span.counter[title=Total]'),
self.filter_menu = new BlockMenu(self, {
'class': 'filter',
'button_class': 'icon-filter'
self.filter_menu = new Block.Menu(self, {
'class': 'filter'
}),
self.navigation_actions = new Element('div.actions', {
self.navigation_actions = new Element('ul.actions', {
'events': {
'click': function(e, el){
(e).stop();
var new_view = self.current_view == 'list' ? 'thumb' : 'list';
'click:relay(li)': function(e, el){
var a = 'active';
self.navigation_actions.getElements('.'+a).removeClass(a);
self.changeView(new_view);
self.navigation_actions.getElement('[data-view='+new_view+']')
.addClass(a);
self.changeView(el.get('data-view'));
this.addClass(a);
el.inject(el.getParent(), 'top');
el.getSiblings().hide();
setTimeout(function(){
el.getSiblings().setStyle('display', null);
}, 100)
}
}
}),
self.navigation_menu = new BlockMenu(self, {
'class': 'extra',
'button_class': 'icon-dots'
self.navigation_menu = new Block.Menu(self, {
'class': 'extra'
})
)
);
).inject(self.el, 'top');
// Mass edit
self.mass_edit_select_class = new Form.Check(self.mass_edit_select);
@@ -261,7 +261,7 @@ var MovieList = new Class({
new Element('option', {
'value': profile.get('_id'),
'text': profile.get('label')
}).inject(self.mass_edit_quality);
}).inject(self.mass_edit_quality)
});
self.filter_menu.addLink(
@@ -273,7 +273,7 @@ var MovieList = new Class({
'change': self.search.bind(self)
}
})
).addClass('search icon-search');
).addClass('search');
var available_chars;
self.filter_menu.addEvent('open', function(){
@@ -289,8 +289,8 @@ var MovieList = new Class({
available_chars = json.chars;
available_chars.each(function(c){
self.letters[c.capitalize()].addClass('available');
});
self.letters[c.capitalize()].addClass('available')
})
}
});
@@ -301,23 +301,23 @@ var MovieList = new Class({
'events': {
'click:relay(li.available)': function(e, el){
self.activateLetter(el.get('data-letter'));
self.getMovies(true);
self.getMovies(true)
}
}
})
);
// Actions
['thumb', 'list'].each(function(view){
['mass_edit', 'details', 'list'].each(function(view){
var current = self.current_view == view;
new Element('a', {
'class': 'icon-' + view + (current ? ' active ' : ''),
new Element('li', {
'class': 'icon2 ' + view + (current ? ' active ' : ''),
'data-view': view
}).inject(self.navigation_actions, current ? 'top' : 'bottom');
});
// All
self.letters.all = new Element('li.letter_all.available.active', {
self.letters['all'] = new Element('li.letter_all.available.active', {
'text': 'ALL'
}).inject(self.navigation_alpha);
@@ -346,7 +346,7 @@ var MovieList = new Class({
var selected = 0,
movies = self.movies.length;
self.movies.each(function(movie){
selected += movie.isSelected() ? 1 : 0;
selected += movie.isSelected() ? 1 : 0
});
var indeterminate = selected > 0 && selected < movies,
@@ -441,10 +441,10 @@ var MovieList = new Class({
var ids = [];
self.movies.each(function(movie){
if (movie.isSelected())
ids.include(movie.get('_id'));
ids.include(movie.get('_id'))
});
return ids;
return ids
},
massEditToggleAll: function(){
@@ -453,10 +453,10 @@ var MovieList = new Class({
var select = self.mass_edit_select.get('checked');
self.movies.each(function(movie){
movie.select(select);
movie.select(select)
});
self.calculateSelected();
self.calculateSelected()
},
reset: function(){
@@ -493,12 +493,12 @@ var MovieList = new Class({
.addClass(new_view+'_list');
self.current_view = new_view;
Cookie.write(self.options.identifier+'_view3', new_view, {duration: 1000});
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000});
},
getSavedView: function(){
var self = this;
return Cookie.read(self.options.identifier+'_view3');
return Cookie.read(self.options.identifier+'_view2');
},
search: function(){
@@ -537,7 +537,7 @@ var MovieList = new Class({
self.load_more.set('text', 'loading...');
}
if(self.movies.length === 0 && self.options.loader){
if(self.movies.length == 0 && self.options.loader){
self.loader_first = new Element('div.loading').adopt(
new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'})
@@ -590,7 +590,7 @@ var MovieList = new Class({
loadMore: function(){
var self = this;
if(self.offset >= self.options.limit)
self.getMovies();
self.getMovies()
},
store: function(movies){
@@ -603,7 +603,7 @@ var MovieList = new Class({
checkIfEmpty: function(){
var self = this;
var is_empty = self.movies.length === 0 && (self.total_movies === 0 || self.total_movies === undefined);
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
if(self.title)
self.title[is_empty ? 'hide' : 'show']();
@@ -1,4 +1,4 @@
var MoviesManage = new Class({
Page.Manage = new Class({
Extends: PageBase,
@@ -126,12 +126,12 @@ var MoviesManage = new Class({
(folder_progress.eta > 0 ? ', ' + new Date ().increment('second', folder_progress.eta).timeDiffInWords().replace('from now', 'to go') : '')
}),
new Element('span.percentage', {'text': folder_progress.total ? Math.round(((folder_progress.total-folder_progress.to_go)/folder_progress.total)*100) + '%' : '0%'})
).inject(self.progress_container);
).inject(self.progress_container)
});
}
}
});
})
}, 1000);
},
@@ -141,10 +141,10 @@ var MoviesManage = new Class({
for (folder in progress_object) {
if (progress_object.hasOwnProperty(folder)) {
temp_array.push(folder);
temp_array.push(folder)
}
}
return temp_array.stableSort();
return temp_array.stableSort()
}
});
@@ -2,10 +2,7 @@ var MovieAction = new Class({
Implements: [Options],
class_name: 'action',
label: 'UNKNOWN',
button: null,
details: null,
class_name: 'action icon2',
initialize: function(movie, options){
var self = this;
@@ -14,33 +11,20 @@ var MovieAction = new Class({
self.movie = movie;
self.create();
if(self.button)
self.button.addClass(self.class_name);
if(self.el)
self.el.addClass(self.class_name)
},
create: function(){},
getButton: function(){
return this.button || null;
},
getDetails: function(){
return this.details || null;
},
getLabel: function(){
return this.label;
},
disable: function(){
if(this.el)
this.el.addClass('disable');
this.el.addClass('disable')
},
enable: function(){
if(this.el)
this.el.removeClass('disable');
this.el.removeClass('disable')
},
getTitle: function(){
@@ -53,7 +37,7 @@ var MovieAction = new Class({
try {
return self.movie.original_title ? self.movie.original_title : self.movie.titles[0];
}
catch(e2){
catch(e){
return 'Unknown';
}
}
@@ -62,10 +46,10 @@ var MovieAction = new Class({
get: function(key){
var self = this;
try {
return self.movie.get(key);
return self.movie.get(key)
}
catch(e){
return self.movie[key];
return self.movie[key]
}
},
@@ -79,7 +63,7 @@ var MovieAction = new Class({
},
toElement: function(){
return this.el || null;
return this.el || null
}
});
@@ -96,8 +80,7 @@ MA.IMDB = new Class({
self.id = self.movie.getIdentifier ? self.movie.getIdentifier() : self.get('imdb');
self.button = new Element('a.imdb', {
'text': 'IMDB',
self.el = new Element('a.imdb', {
'title': 'Go to the IMDB page of ' + self.getTitle(),
'href': 'http://www.imdb.com/title/'+self.id+'/',
'target': '_blank'
@@ -111,11 +94,22 @@ MA.IMDB = new Class({
MA.Release = new Class({
Extends: MovieAction,
label: 'Releases',
create: function(){
var self = this;
self.el = new Element('a.releases.download', {
'title': 'Show the releases that are available for ' + self.getTitle(),
'events': {
'click': self.show.bind(self)
}
});
if(!self.movie.data.releases || self.movie.data.releases.length == 0)
self.el.hide();
else
self.showHelper();
App.on('movie.searcher.ended', function(notification){
if(self.movie.data._id != notification.data._id) return;
@@ -124,7 +118,7 @@ MA.Release = new Class({
// Releases are currently displayed
if(self.options_container.isDisplayed()){
self.options_container.destroy();
self.getDetails();
self.createReleases();
}
else {
self.options_container.destroy();
@@ -135,7 +129,16 @@ MA.Release = new Class({
},
getDetails: function(refresh){
show: function(e){
var self = this;
if(e)
(e).preventDefault();
self.createReleases();
},
createReleases: function(refresh){
var self = this;
if(!self.options_container || refresh){
@@ -159,14 +162,14 @@ MA.Release = new Class({
var quality = Quality.getQuality(release.quality) || {},
info = release.info || {},
provider = self.get(release, 'provider') + (info.provider_extra ? self.get(release, 'provider_extra') : '');
provider = self.get(release, 'provider') + (info['provider_extra'] ? self.get(release, 'provider_extra') : '');
var release_name = self.get(release, 'name');
if(release.files && release.files.length > 0){
try {
var movie_file = release.files.filter(function(file){
var type = File.Type.get(file.type_id);
return type && type.identifier == 'movie';
return type && type.identifier == 'movie'
}).pick();
release_name = movie_file.path.split(Api.getOption('path_sep')).getLast();
}
@@ -174,19 +177,19 @@ MA.Release = new Class({
}
// Create release
release.el = new Element('div', {
release['el'] = new Element('div', {
'class': 'item '+release.status,
'id': 'release_'+release._id
}).adopt(
new Element('span.name', {'text': release_name, 'title': release_name}),
new Element('span.status', {'text': release.status, 'class': 'status '+release.status}),
new Element('span.status', {'text': release.status, 'class': 'release_status '+release.status}),
new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}),
new Element('span.size', {'text': info.size ? Math.floor(self.get(release, 'size')) : 'n/a'}),
new Element('span.size', {'text': info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
new Element('span.age', {'text': self.get(release, 'age')}),
new Element('span.score', {'text': self.get(release, 'score')}),
new Element('span.provider', { 'text': provider, 'title': provider }),
info.detail_url ? new Element('a.info.icon2', {
'href': info.detail_url,
info['detail_url'] ? new Element('a.info.icon2', {
'href': info['detail_url'],
'target': '_blank'
}) : new Element('a'),
new Element('a.download.icon2', {
@@ -280,7 +283,7 @@ MA.Release = new Class({
new Element('span.or', {
'text': 'or pick one below'
})] : null
);
)
}
self.last_release = null;
@@ -288,7 +291,9 @@ MA.Release = new Class({
}
return self.options_container;
// Show it
self.options_container.inject(self.movie, 'top');
self.movie.slide('in', self.options_container);
},
@@ -337,13 +342,13 @@ MA.Release = new Class({
'click': self.markMovieDone.bind(self)
}
})
);
)
}
},
get: function(release, type){
return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a';
return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a'
},
download: function(release){
@@ -381,7 +386,7 @@ MA.Release = new Class({
'data': {
'id': release._id
}
});
})
},
@@ -398,7 +403,7 @@ MA.Release = new Class({
movie.set('tween', {
'duration': 300,
'onComplete': function(){
self.movie.destroy();
self.movie.destroy()
}
});
movie.tween('height', 0);
@@ -424,35 +429,49 @@ MA.Trailer = new Class({
Extends: MovieAction,
id: null,
label: 'Trailer',
getDetails: function(){
create: function(){
var self = this;
if(!self.player_container){
var id = 'trailer-'+randomString();
self.player_container = new Element('div.icon-play[id='+id+']', {
'events': {
'click': function(e){
self.watch(id);
}
}
});
self.container = new Element('div.trailer_container')
.grab(self.player_container);
}
self.el = new Element('a.trailer', {
'title': 'Watch the trailer of ' + self.getTitle(),
'events': {
'click': self.watch.bind(self)
}
});
return self.player_container;
},
watch: function(){
watch: function(offset){
var self = this;
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18',
url = data_url.substitute({
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18';
var url = data_url.substitute({
'title': encodeURI(self.getTitle()),
'year': self.get('year')
});
'year': self.get('year'),
'offset': offset || 1
}),
size = $(self.movie).getSize(),
height = self.options.height || (size.x/16)*9,
id = 'trailer-'+randomString();
self.player_container = new Element('div[id='+id+']');
self.container = new Element('div.hide.trailer_container')
.adopt(self.player_container)
.inject($(self.movie), 'top');
self.container.setStyle('height', 0);
self.container.removeClass('hide');
self.close_button = new Element('a.hide.hide_trailer', {
'text': 'Hide trailer',
'events': {
'click': self.stop.bind(self)
}
}).inject(self.movie);
self.container.setStyle('height', height);
$(self.movie).setStyle('height', height);
new Request.JSONP({
'url': url,
@@ -472,6 +491,8 @@ MA.Trailer = new Class({
}
});
self.close_button.removeClass('hide');
var quality_set = false;
var change_quality = function(state){
if(!quality_set && (state.data == 1 || state.data || 2)){
@@ -487,9 +508,7 @@ MA.Trailer = new Class({
self.player.addEventListener('onStateChange', change_quality);
}
}).send();
return self.container;
}).send()
},
@@ -504,7 +523,7 @@ MA.Trailer = new Class({
setTimeout(function(){
self.container.destroy();
self.close_button.destroy();
}, 1800);
}, 1800)
}
@@ -517,8 +536,7 @@ MA.Edit = new Class({
create: function(){
var self = this;
self.button = new Element('a.edit', {
'text': 'Edit',
self.el = new Element('a.edit', {
'title': 'Change movie information, like title and quality.',
'events': {
'click': self.editMovie.bind(self)
@@ -567,7 +585,7 @@ MA.Edit = new Class({
// Fill categories
var categories = CategoryList.getAll();
if(categories.length === 0)
if(categories.length == 0)
self.category_select.hide();
else {
self.category_select.show();
@@ -641,8 +659,7 @@ MA.Refresh = new Class({
create: function(){
var self = this;
self.button = new Element('a.refresh', {
'text': 'Refresh',
self.el = new Element('a.refresh', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.doRefresh.bind(self)
@@ -653,7 +670,7 @@ MA.Refresh = new Class({
doRefresh: function(e){
var self = this;
(e).stop();
(e).preventDefault();
Api.request('media.refresh', {
'data': {
@@ -669,18 +686,17 @@ MA.Readd = new Class({
Extends: MovieAction,
create: function(){
var self = this,
movie_done = self.movie.data.status == 'done',
snatched;
var self = this;
var movie_done = self.movie.data.status == 'done';
if(self.movie.data.releases && !movie_done)
snatched = self.movie.data.releases.filter(function(release){
var snatched = self.movie.data.releases.filter(function(release){
return release.status && (release.status == 'snatched' || release.status == 'seeding' || release.status == 'downloaded' || release.status == 'done');
}).length;
if(movie_done || snatched && snatched > 0)
self.el = new Element('a.readd', {
'title': 'Re-add the movie and mark all previous snatched/downloaded as ignored',
'title': 'Readd the movie and mark all previous snatched/downloaded as ignored',
'events': {
'click': self.doReadd.bind(self)
}
@@ -776,7 +792,7 @@ MA.Delete = new Class({
movie.set('tween', {
'duration': 300,
'onComplete': function(){
self.movie.destroy();
self.movie.destroy()
}
});
movie.tween('height', 0);
@@ -831,7 +847,7 @@ MA.Files = new Class({
new Element('div.file.item').adopt(
new Element('span.name', {'text': file}),
new Element('span.type', {'text': type})
).inject(rel);
).inject(rel)
});
});
});
File diff suppressed because it is too large Load Diff
@@ -2,51 +2,22 @@ var Movie = new Class({
Extends: BlockBase,
actions: [],
details: null,
action: {},
initialize: function(list, options, data){
var self = this;
self.data = data;
self.view = options.view || 'details';
self.list = list;
self.el = new Element('a.movie', {
'events': {
'click': function(e){
(e).stop();
self.openDetails();
}
}
});
self.el = new Element('div.movie');
self.profile = Quality.getProfile(data.profile_id) || {};
self.category = CategoryList.getCategory(data.category_id) || {};
self.parent(self, options);
self.addEvents();
if(data.identifiers.imdb == 'tt1228705')
self.openDetails();
},
openDetails: function(){
var self = this;
if(!self.details){
self.details = new MovieDetails(self, {
'level': 3
});
// Add action items
self.actions.each(function(action, nr){
var details = action.getDetails();
if(details)
self.details.addSection(action.getLabel(), details);
});
}
App.getPageContainer().grab(self.details);
},
addEvents: function(){
@@ -59,6 +30,7 @@ var Movie = new Class({
if(self.data._id != notification.data._id) return;
self.busy(false);
self.removeView();
self.update.delay(2000, self, notification);
};
App.on('movie.update', self.global_events['movie.update']);
@@ -75,7 +47,7 @@ var Movie = new Class({
// Remove spinner
self.global_events['movie.searcher.ended'] = function(notification){
if(notification.data && self.data._id == notification.data._id)
self.busy(false);
self.busy(false)
};
App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']);
@@ -90,7 +62,7 @@ var Movie = new Class({
var updated = false;
self.data.releases.each(function(release){
if(release._id == data._id){
release.status = data.status;
release['status'] = data.status;
updated = true;
}
});
@@ -130,12 +102,12 @@ var Movie = new Class({
if(self.mask)
self.mask.destroy();
if(self.spinner)
self.spinner.destroy();
self.spinner.el.destroy();
self.spinner = null;
self.mask = null;
}, timeout || 400);
}
}, timeout || 1000);
}, timeout || 1000)
}
else if(!self.spinner) {
self.createMask();
@@ -158,6 +130,7 @@ var Movie = new Class({
self.data = notification.data;
self.el.empty();
self.removeView();
self.profile = Quality.getProfile(self.data.profile_id) || {};
self.category = CategoryList.getCategory(self.data.category_id) || {};
@@ -177,7 +150,7 @@ var Movie = new Class({
if(self.data.info.release_date)
[self.data.info.release_date.dvd, self.data.info.release_date.theater].each(function(timestamp){
if (timestamp > 0 && (eta === null || Math.abs(timestamp - now) < Math.abs(eta - now)))
if (timestamp > 0 && (eta == null || Math.abs(timestamp - now) < Math.abs(eta - now)))
eta = timestamp;
});
@@ -190,7 +163,7 @@ var Movie = new Class({
self.select_checkbox = new Element('input[type=checkbox].inlay', {
'events': {
'change': function(){
self.fireEvent('select');
self.fireEvent('select')
}
}
}),
@@ -208,6 +181,9 @@ var Movie = new Class({
'text': self.data.info.year || 'n/a'
})
),
self.description = new Element('div.description.tiny_scroll', {
'text': self.data.info.plot
}),
self.eta = eta_date && (now+8035200 > eta) ? new Element('div.eta', {
'text': eta_date,
'title': 'ETA'
@@ -217,24 +193,19 @@ var Movie = new Class({
'click': function(e){
var releases = self.el.getElement('.actions .releases');
if(releases.isVisible())
releases.fireEvent('click', [e]);
releases.fireEvent('click', [e])
}
}
})
),
self.actions_el = new Element('div.actions', {
'events': {
'click': function(e){
(e).stopPropagation();
}
}
})
self.actions = new Element('div.actions')
)
);
if(!self.thumbnail)
self.el.addClass('no_thumbnail');
//self.changeView(self.view);
self.select_checkbox_class = new Form.Check(self.select_checkbox);
// Add profile
@@ -242,9 +213,9 @@ var Movie = new Class({
self.profile.getTypes().each(function(type){
var q = self.addQuality(type.get('quality'), type.get('3d'));
if((type.finish === true || type.get('finish')) && !q.hasClass('finish')){
if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
q.addClass('finish');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
}
});
@@ -252,20 +223,17 @@ var Movie = new Class({
// Add releases
self.updateReleases();
self.options.actions.each(function(action){
var action = new action(self),
button = action.getButton();
if(button)
self.actions_el.grab(button);
self.actions.push(action);
Object.each(self.options.actions, function(action, key){
self.action[key.toLowerCase()] = action = new self.options.actions[key](self);
if(action.el)
self.actions.adopt(action)
});
},
updateReleases: function(){
var self = this;
if(!self.data.releases || self.data.releases.length === 0) return;
if(!self.data.releases || self.data.releases.length == 0) return;
self.data.releases.each(function(release){
@@ -277,7 +245,7 @@ var Movie = new Class({
if (q && !q.hasClass(status)){
q.addClass(status);
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status);
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status)
}
});
@@ -303,7 +271,7 @@ var Movie = new Class({
else if(self.data.info.titles.length > 0)
return self.getUnprefixedTitle(self.data.info.titles[0]);
return 'Unknown movie';
return 'Unknown movie'
},
getUnprefixedTitle: function(t){
@@ -316,6 +284,49 @@ var Movie = new Class({
return t;
},
slide: function(direction, el){
var self = this;
if(direction == 'in'){
self.temp_view = self.view;
self.changeView('details');
self.el.addEvent('outerClick', function(){
self.removeView();
self.slide('out')
});
el.show();
self.data_container.addClass('hide_right');
}
else {
self.el.removeEvents('outerClick');
setTimeout(function(){
if(self.el)
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
}, 600);
self.data_container.removeClass('hide_right');
}
},
changeView: function(new_view){
var self = this;
if(self.el)
self.el
.removeClass(self.view+'_view')
.addClass(new_view+'_view');
self.view = new_view;
},
removeView: function(){
var self = this;
self.el.removeClass(self.view+'_view')
},
getIdentifier: function(){
var self = this;
@@ -328,12 +339,12 @@ var Movie = new Class({
},
get: function(attr){
return this.data[attr] || this.data.info[attr];
return this.data[attr] || this.data.info[attr]
},
select: function(bool){
var self = this;
self.select_checkbox_class[bool ? 'check' : 'uncheck']();
self.select_checkbox_class[bool ? 'check' : 'uncheck']()
},
isSelected: function(){
@@ -1,367 +0,0 @@
@import "couchpotato/static/style/mixins";
.page.movies {
z-index: 21; // Sets navigation above
bottom: auto;
}
.page.movies_wanted, .page.movies_manage {
top: $header_height;
padding: 0;
}
.list_list {
font-weight: 300;
.poster {
display: none;
}
.movie {
display: block;
border-top: 1px solid $theme_off;
position: relative;
cursor: pointer;
&:last-child {
border-bottom: none;
}
&:hover {
background: rgba(0,0,0,.1);
}
.data {
padding: $padding/2 $padding;
.info {
@include flexbox();
flex-flow: row nowrap;
.title {
@include flex(1 auto);
.year {
display: inline-block;
margin-left: 10px;
opacity: .5;
}
}
.quality span {
float: left;
color: #FFF;
font-size: .7em;
padding: 2px 4px;
background: rgba(0,0,0,.2);
border-radius: 1px;
margin: 2px 0 0 2px;
}
}
}
}
}
.thumb_list {
font-size: 12px;
padding: 0 $padding;
.movie {
@include span(6);
float: left;
margin-bottom: $padding;
position: relative;
&:nth-child(4n+4){
@include span(last);
}
&:nth-child(4n+5){
clear: both;
}
.poster {
border-radius: $border_radius;
overflow: hidden;
width: 100%;
float: left;
}
.data {
clear: both;
.info {
height: 44px;
.title {
@include flexbox();
padding: 3px 0;
span {
@include flex(1 auto);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.year {
display: inline-block;
margin-left: 5px;
opacity: .5;
}
}
.quality {
white-space: nowrap;
overflow: hidden;
span {
color: #FFF;
font-size: .8em;
padding: 2px 4px;
background: rgba(0,0,0,.2);
border-radius: 1px;
margin-right: 2px;
}
}
}
}
.actions {
position: absolute;
top: $padding / 2;
right: $padding / 2;
display: none;
a {
display: block;
background: $background_color;
padding: $padding / 3;
width: auto;
margin-bottom: 1px;
clear: both;
float: right;
}
}
&:hover .actions {
display: block;
}
.mask {
bottom: 44px;
border-radius: $border_radius;
}
}
}
.check {
position: absolute;
top: 0;
left: $padding;
display: none;
}
.eta {
display: none;
}
.page.movie_details {
$gab-width: $header_width/3;
.overlay {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: $header_width;
background: rgba(0,0,0,.6);
border-radius: 3px 0 0 3px;
.close {
display: inline-block;
text-align: center;
font-size: 60px;
line-height: $header_height;
color: #FFF;
width: $gab-width;
cursor: pointer;
height: 100%;
}
}
.content {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: $header_width + $gab-width;
background: $background_color;
z-index: 200;
border-radius: 3px 0 0 3px;
h1 {
margin: 0;
padding: 0 $padding;
font-size: 24px;
line-height: $header_height;
color: rgba(0,0,0,.5);
font-weight: 300;
}
.section {
padding: $padding $padding;
border-top: 1px solid rgba(0,0,0,.1);
}
}
.releases {
.buttons {
margin-bottom: $padding/2;
}
.item span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
}
.item .name {
@include flex(1 auto);
text-align: left;
}
.status { min-width: 70px; max-width: 70px; }
.quality { min-width: 60px; max-width: 60px; }
.size { min-width: 40px; max-width: 40px; }
.age { min-width: 40px; max-width: 40px; }
.score { min-width: 45px; max-width: 45px; }
.provider { min-width: 110px; max-width: 110px; }
}
}
.alph_nav {
.mass_edit_form {
display: none;
}
.menus {
margin-right: $padding;
.button {
padding: 0 $padding/2;
line-height: $header_height;
color: rgba(0, 0, 0, 0.5);
}
.counter, .more_menu, .actions {
float: left;
}
.counter {
line-height: $header_height;
}
.actions {
a {
display: none;
}
.active {
display: inline-block;
}
}
.filter {
.wrapper {
width: 320px;
}
.button {
margin-top: -2px;
}
.search {
position: relative;
&:before {
position: absolute;
height: 100%;
line-height: 38px;
padding-left: $padding/2;
font-size: 16px;
opacity: .5;
}
input {
width: 100%;
padding: $padding/2 $padding/2 $padding/2 $padding*1.5;
background: $background_color;
border: none;
border-bottom: 1px solid $theme_off;
}
}
.numbers {
padding: $padding/2;
li {
float: left;
width: 10%;
height: 30px;
line-height: 30px;
text-align: center;
color: rgba(0,0,0,.2);
cursor: default;
&.active {
background: $theme_off;
}
&.available {
color: rgba(0,0,0,1);
cursor: pointer;
&:hover {
background: $theme_off;
}
}
}
}
}
.more_menu {
&.show .button {
color: rgba(0, 0, 0, 1);
}
.wrapper {
top: $header_height - 10px;
padding-top: 4px;
border-radius: $border_radius $border_radius 0 0;
&:before {
top: 0;
left: auto;
right: 22px;
}
ul {
border-radius: $border_radius $border_radius 0 0;
}
}
}
}
}
@@ -1,49 +0,0 @@
Page.Movies = new Class({
Extends: PageBase,
name: 'movies',
sub_pages: ['Wanted', 'Manage'],
default_page: 'Wanted',
current_page: null,
initialize: function(parent, options){
var self = this;
self.parent(parent, options);
self.navigation = new BlockNavigation();
$(self.navigation).inject(self.el, 'top');
},
defaultAction: function(action, params){
var self = this;
if(self.current_page){
self.current_page.hide();
if(self.current_page.list && self.current_page.list.navigation)
self.current_page.list.navigation.dispose();
}
var route = new Route();
route.parse(action);
var page_name = route.getPage() != 'index' ? route.getPage().capitalize() : self.default_page;
var page = self.sub_pages.filter(function(page){
return page.name == page_name;
}).pick()['class'];
page.open(route.getAction() || 'index', params);
page.show();
if(page.list && page.list.navigation)
page.list.navigation.inject(self.navigation);
self.current_page = page;
self.navigation.activate(page_name.toLowerCase());
}
});
@@ -1,4 +1,4 @@
var BlockSearchMovieItem = new Class({
Block.Search.MovieItem = new Class({
Implements: [Options, Events],
@@ -31,11 +31,9 @@ var BlockSearchMovieItem = new Class({
}
}).adopt(
self.info_container = new Element('div.info').adopt(
new Element('h2', {
'title': self.getTitle()
}).adopt(
new Element('h2').adopt(
self.title = new Element('span.title', {
'text': self.getTitle()
'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
}),
self.year = info.year ? new Element('span.year', {
'text': info.year
@@ -50,7 +48,7 @@ var BlockSearchMovieItem = new Class({
self.alternativeTitle({
'title': title
});
});
})
},
alternativeTitle: function(alternative){
@@ -70,7 +68,7 @@ var BlockSearchMovieItem = new Class({
},
get: function(key){
return this.info[key];
return this.info[key]
},
showOptions: function(){
@@ -79,7 +77,7 @@ var BlockSearchMovieItem = new Class({
self.createOptions();
self.data_container.addClass('open');
self.el.addEvent('outerClick', self.closeOptions.bind(self));
self.el.addEvent('outerClick', self.closeOptions.bind(self))
},
@@ -87,7 +85,7 @@ var BlockSearchMovieItem = new Class({
var self = this;
self.data_container.removeClass('open');
self.el.removeEvents('outerClick');
self.el.removeEvents('outerClick')
},
add: function(e){
@@ -134,11 +132,10 @@ var BlockSearchMovieItem = new Class({
if(!self.options_el.hasClass('set')){
var in_library;
if(info.in_library){
in_library = [];
var in_library = [];
(info.in_library.releases || []).each(function(release){
in_library.include(release.quality);
in_library.include(release.quality)
});
}
@@ -174,14 +171,14 @@ var BlockSearchMovieItem = new Class({
Array.each(self.alternative_titles, function(alt){
new Element('option', {
'text': alt.title
}).inject(self.title_select);
}).inject(self.title_select)
});
// Fill categories
var categories = CategoryList.getAll();
if(categories.length === 0)
if(categories.length == 0)
self.category_select.hide();
else {
self.category_select.show();
@@ -202,12 +199,12 @@ var BlockSearchMovieItem = new Class({
new Element('option', {
'value': profile.get('_id'),
'text': profile.get('label')
}).inject(self.profile_select);
}).inject(self.profile_select)
});
self.options_el.addClass('set');
if(categories.length === 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
if(categories.length == 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
!(self.info.in_wanted && self.info.in_wanted.profile_id || in_library))
self.add();
@@ -221,12 +218,12 @@ var BlockSearchMovieItem = new Class({
self.mask = new Element('div.mask').inject(self.el).fade('hide');
createSpinner(self.mask);
self.mask.fade('in');
self.mask.fade('in')
},
toElement: function(){
return this.el;
return this.el
}
});
@@ -1,4 +1,4 @@
var MoviesWanted = new Class({
Page.Wanted = new Class({
Extends: PageBase,
@@ -10,7 +10,7 @@ var MoviesWanted = new Class({
indexAction: function(){
var self = this;
if(!self.list){
if(!self.wanted){
self.manual_search = new Element('a', {
'title': 'Force a search for the full wanted list',
@@ -20,6 +20,7 @@ var MoviesWanted = new Class({
}
});
self.scan_folder = new Element('a', {
'title': 'Scan a folder and rename all movies in it',
'text': 'Manual folder scan',
@@ -29,7 +30,7 @@ var MoviesWanted = new Class({
});
// Wanted movies
self.list = new MovieList({
self.wanted = new MovieList({
'identifier': 'wanted',
'status': 'active',
'actions': [MA.IMDB, MA.Trailer, MA.Release, MA.Edit, MA.Refresh, MA.Readd, MA.Delete],
@@ -37,7 +38,7 @@ var MoviesWanted = new Class({
'menu': [self.manual_search, self.scan_folder],
'on_empty_element': App.createUserscriptButtons().addClass('empty_wanted')
});
$(self.list).inject(self.el);
$(self.wanted).inject(self.el);
// Check if search is in progress
self.startProgressInterval.delay(4000, self);
@@ -90,7 +91,7 @@ var MoviesWanted = new Class({
};
if(!self.folder_browser){
self.folder_browser = new Option.Directory("Scan", "folder", "", options);
self.folder_browser = new Option['Directory']("Scan", "folder", "", options);
self.folder_browser.save = function() {
var folder = self.folder_browser.getValue();
@@ -44,12 +44,11 @@ var Charts = new Class({
if( Cookie.read('suggestions_charts_menu_selected') === 'charts'){
self.show();
self.fireEvent.delay(0, self, 'created');
}
else
self.el.hide();
self.fireEvent.delay(0, self, 'created');
},
fill: function(json){
@@ -59,7 +58,7 @@ var Charts = new Class({
self.el_refreshing_text.hide();
self.el_refresh_link.show();
if(!json || json.count === 0){
if(!json || json.count == 0){
self.el_no_charts_enabled.show();
self.el_refresh_link.show();
self.el_refreshing_text.hide();
@@ -84,16 +83,17 @@ var Charts = new Class({
Object.each(chart.list, function(movie){
var m = new BlockSearchMovieItem(movie, {
var m = new Block.Search.MovieItem(movie, {
'onAdded': function(){
self.afterAdded(m, movie);
self.afterAdded(m, movie)
}
});
var in_database_class = (chart.hide_wanted && movie.in_wanted) ? 'hidden' : (movie.in_wanted ? 'chart_in_wanted' : ((chart.hide_library && movie.in_library) ? 'hidden': (movie.in_library ? 'chart_in_library' : ''))),
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
m.el.addClass(in_database_class)
m.el
.addClass(in_database_class)
.grab(
new Element('div.chart_number', {
'text': it++,
@@ -135,7 +135,7 @@ var Charts = new Class({
'text': plot,
'events': {
'click': function(){
this.toggleClass('full');
this.toggleClass('full')
}
}
}) : null
@@ -1,89 +0,0 @@
import re
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.media.movie.providers.automation.base import Automation
log = CPLog(__name__)
autoload = 'CrowdAI'
class CrowdAI(Automation, RSS):
interval = 1800
def getIMDBids(self):
movies = []
urls = dict(zip(splitString(self.conf('automation_urls')), [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]))
for url in urls:
if not urls[url]:
continue
rss_movies = self.getRSSData(url)
for movie in rss_movies:
description = self.getTextElement(movie, 'description')
grabs = 0
for item in movie:
if item.attrib.get('name') == 'grabs':
grabs = item.attrib.get('value')
break
if int(grabs) > tryInt(self.conf('number_grabs')):
title = re.match(r'.*Title: .a href.*/">(.*) \(\d{4}\).*', description).group(1)
log.info2('%s grabs for movie: %s, enqueue...', (grabs, title))
year = re.match(r'.*Year: (\d{4}).*', description).group(1)
imdb = self.search(title, year)
if imdb and self.isMinimalMovie(imdb):
movies.append(imdb['imdb'])
return movies
config = [{
'name': 'crowdai',
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'crowdai_automation',
'label': 'CrowdAI',
'description': 'Imports from any newznab powered NZB providers RSS feed depending on the number of grabs per movie. Go to your newznab site and find the RSS section. Then copy the copy paste the link under "Movies > x264 feed" here.',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'automation_urls_use',
'label': 'Use',
'default': '1',
},
{
'name': 'automation_urls',
'label': 'url',
'type': 'combined',
'combine': ['automation_urls_use', 'automation_urls'],
'default': 'http://YOUR_PROVIDER/rss?t=THE_MOVIE_CATEGORY&i=YOUR_USER_ID&r=YOUR_API_KEY&res=2&rls=2&num=100',
},
{
'name': 'number_grabs',
'default': '500',
'label': 'Grab threshold',
'description': 'Number of grabs required',
},
],
},
],
}]
@@ -48,12 +48,11 @@ class Letterboxd(Automation):
soup = BeautifulSoup(self.getHTMLData(self.url % username))
for movie in soup.find_all('li', attrs = {'class': 'poster-container'}):
img = movie.find('img', movie)
title = img.get('alt')
for movie in soup.find_all('a', attrs = {'class': 'frame'}):
match = removeEmpty(self.pattern.split(movie['title']))
movies.append({
'title': title
'title': match[0],
'year': match[1]
})
return movies
@@ -39,14 +39,15 @@ class Rottentomatoes(Automation, RSS):
if result:
log.info2('Something smells...')
rating = tryInt(self.getTextElement(movie, rating_tag))
name = result.group(0)
print rating, tryInt(self.conf('tomatometer_percent'))
if rating < tryInt(self.conf('tomatometer_percent')):
log.info2('%s seems to be rotten...', name)
else:
log.info2('Found %s with fresh rating %s', (name, rating))
log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
year = datetime.datetime.now().strftime("%Y")
imdb = self.search(name, year)
@@ -69,15 +69,12 @@ class CouchPotatoApi(MovieProvider):
name_enc = base64.b64encode(ss(name))
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
def isMovie(self, identifier = None, adding = False):
def isMovie(self, identifier = None):
if not identifier:
return
url = self.urls['is_movie'] % identifier
url += '?adding=1' if adding else ''
data = self.getJsonData(url, headers = self.getRequestHeaders())
data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders())
if data:
return data.get('is_movie', True)
@@ -2,7 +2,6 @@ import json
import re
import traceback
from couchpotato import Env
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import tryInt, tryFloat, splitString
@@ -18,8 +17,8 @@ autoload = 'OMDBAPI'
class OMDBAPI(MovieProvider):
urls = {
'search': 'http://www.omdbapi.com/?type=movie&%s',
'info': 'http://www.omdbapi.com/?type=movie&i=%s',
'search': 'http://www.omdbapi.com/?%s',
'info': 'http://www.omdbapi.com/?i=%s',
}
http_time_between_calls = 0
@@ -39,8 +38,7 @@ class OMDBAPI(MovieProvider):
}
cache_key = 'omdbapi.cache.%s' % q
url = self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')})
cached = self.getCache(cache_key, url, timeout = 3, headers = {'User-Agent': Env.getIdentifier()})
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')}), timeout = 3)
if cached:
result = self.parseMovie(cached)
@@ -58,7 +56,7 @@ class OMDBAPI(MovieProvider):
return {}
cache_key = 'omdbapi.cache.%s' % identifier
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3, headers = {'User-Agent': Env.getIdentifier()})
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3)
if cached:
result = self.parseMovie(cached)
@@ -11,7 +11,7 @@ autoload = 'Bitsoup'
class Bitsoup(MovieProvider, Base):
cat_ids = [
([17], ['3d']),
([80], ['720p', '1080p']),
([41], ['720p', '1080p']),
([20], ['dvdr']),
([19], ['brrip', 'dvdrip']),
]
@@ -1,11 +0,0 @@
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.hdaccess import Base
from couchpotato.core.media.movie.providers.base import MovieProvider
log = CPLog(__name__)
autoload = 'HDAccess'
class HDAccess(MovieProvider, Base):
pass
@@ -1,27 +0,0 @@
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.torrentleech import Base
from couchpotato.core.media.movie.providers.base import MovieProvider
log = CPLog(__name__)
autoload = 'TorrentLeech'
class TorrentLeech(MovieProvider, Base):
cat_ids = [
([13], ['720p', '1080p', 'bd50']),
([8], ['cam']),
([9], ['ts', 'tc']),
([10], ['r5', 'scr']),
([11], ['dvdrip']),
([13, 14], ['brrip']),
([12], ['dvdr']),
]
def buildUrl(self, title, media, quality):
return (
tryUrlencode(title.replace(':', '')),
','.join([str(x) for x in self.getCatId(quality)])
)
@@ -22,8 +22,8 @@ class TorrentShack(MovieProvider, Base):
# Movies-SD Pack - 983 (not included)
cat_ids = [
([970, 320], ['bd50']),
([300, 320], ['720p', '1080p']),
([970], ['bd50']),
([300], ['720p', '1080p']),
([350], ['dvdr']),
([400], ['brrip', 'dvdrip']),
]
@@ -1,5 +1,6 @@
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.logger import CPLog
from couchpotato.core.event import fireEvent
from couchpotato.core.media._base.providers.torrent.torrentz import Base
from couchpotato.core.media.movie.providers.base import MovieProvider
@@ -10,5 +11,5 @@ autoload = 'Torrentz'
class Torrentz(MovieProvider, Base):
def buildUrl(self, title, media, quality):
return tryUrlencode('"%s %s"' % (title, media['info']['year']))
def buildUrl(self, media):
return tryUrlencode('"%s"' % fireEvent('library.query', media, single = True))
@@ -12,7 +12,7 @@ autoload = 'RottenTomatoes'
class RottenTomatoes(UserscriptBase):
includes = ['*://www.rottentomatoes.com/m/*']
includes = ['*://www.rottentomatoes.com/m/*/']
excludes = ['*://www.rottentomatoes.com/m/*/*/']
version = 2
+2 -3
View File
@@ -394,9 +394,8 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
log.info('Trying next release for: %s', getTitle(media))
self.single(media, manual = manual, force_download = force_download)
return True
return False
return True
except:
log.error('Failed searching for next release: %s', traceback.format_exc())
return False
@@ -51,8 +51,8 @@ var SuggestList = new Class({
self.show();
else
self.hide();
self.fireEvent.delay(0, self, 'created');
self.fireEvent('created');
},
@@ -60,16 +60,16 @@ var SuggestList = new Class({
var self = this;
if(!json || json.count === 0){
if(!json || json.count == 0){
self.el.hide();
}
else {
Object.each(json.suggestions, function(movie){
var m = new BlockSearchMovieItem(movie, {
var m = new Block.Search.MovieItem(movie, {
'onAdded': function(){
self.afterAdded(m, movie);
self.afterAdded(m, movie)
}
});
m.data_container.grab(
@@ -114,7 +114,7 @@ var SuggestList = new Class({
'text': plot,
'events': {
'click': function(){
this.toggleClass('full');
this.toggleClass('full')
}
}
}) : null
+13 -13
View File
@@ -14,7 +14,6 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from .index import NotificationIndex, NotificationUnreadIndex
from couchpotato.environment import Env
from tornado.ioloop import IOLoop
log = CPLog(__name__)
@@ -111,11 +110,11 @@ class CoreNotifier(Notification):
if limit_offset:
splt = splitString(limit_offset)
limit = tryInt(splt[0])
offset = tryInt(0 if len(splt) is 1 else splt[1])
results = db.all('notification', limit = limit, offset = offset, with_doc = True)
limit = splt[0]
offset = 0 if len(splt) is 1 else splt[1]
results = db.get_many('notification', limit = limit, offset = offset, with_doc = True)
else:
results = db.all('notification', limit = 200, with_doc = True)
results = db.get_many('notification', limit = 200, with_doc = True)
notifications = []
for n in results:
@@ -149,15 +148,16 @@ class CoreNotifier(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
n = {
'_t': 'notification',
'time': int(time.time()),
}
try:
db = get_db()
n['message'] = toUnicode(message)
data['notification_type'] = listener if listener else 'unknown'
n = {
'_t': 'notification',
'time': int(time.time()),
'message': toUnicode(message)
}
if data.get('sticky'):
n['sticky'] = True
@@ -170,7 +170,7 @@ class CoreNotifier(Notification):
return True
except:
log.error('Failed notify "%s": %s', (n, traceback.format_exc()))
log.error('Failed notify: %s', traceback.format_exc())
def frontend(self, type = 'notification', data = None, message = None):
if not data: data = {}
@@ -190,7 +190,7 @@ class CoreNotifier(Notification):
while len(self.listeners) > 0 and not self.shuttingDown():
try:
listener, last_id = self.listeners.pop()
IOLoop.current().add_callback(listener, {
listener({
'success': True,
'result': [notification],
})
@@ -20,8 +20,8 @@ var NotificationBase = new Class({
self.notifications = [];
App.addEvent('load', function(){
App.block.notification = new BlockMenu(self, {
'button_class': 'icon-notifications',
App.block.notification = new Block.Menu(self, {
'button_class': 'icon2.eye-open',
'class': 'notification_menu',
'onOpen': self.markAsRead.bind(self)
});
@@ -32,7 +32,7 @@ var NotificationBase = new Class({
window.addEvent('load', function(){
self.startInterval.delay($(window).getSize().x <= 480 ? 2000 : 100, self);
});
})
},
@@ -46,15 +46,16 @@ var NotificationBase = new Class({
new Element('span.'+(result.read ? 'read' : '' )).adopt(
new Element('span.message', {'html': result.message}),
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
), 'top');
)
, 'top');
self.notifications.include(result);
if((result.important !== undefined || result.sticky !== undefined) && !result.read){
var sticky = true;
App.trigger('message', [result.message, sticky, result]);
App.trigger('message', [result.message, sticky, result])
}
else if(!result.read){
self.setBadge(self.notifications.filter(function(n){ return !n.read; }).length);
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
}
},
@@ -62,7 +63,7 @@ var NotificationBase = new Class({
setBadge: function(value){
var self = this;
self.badge.set('text', value);
self.badge[value ? 'show' : 'hide']();
self.badge[value ? 'show' : 'hide']()
},
markAsRead: function(force_ids){
@@ -71,13 +72,13 @@ var NotificationBase = new Class({
if(!force_ids) {
var rn = self.notifications.filter(function(n){
return !n.read && n.important === undefined;
return !n.read && n.important === undefined
});
ids = [];
var ids = [];
rn.each(function(n){
ids.include(n._id);
});
ids.include(n._id)
})
}
if(ids.length > 0)
@@ -86,9 +87,9 @@ var NotificationBase = new Class({
'ids': ids.join(',')
},
'onSuccess': function(){
self.setBadge('');
self.setBadge('')
}
});
})
},
@@ -103,7 +104,7 @@ var NotificationBase = new Class({
self.request = Api.request('notification.listener', {
'data': {'init':true},
'onSuccess': function(json){
self.processData(json, true);
self.processData(json, true)
}
}).send();
@@ -111,7 +112,7 @@ var NotificationBase = new Class({
if(self.request && self.request.isRunning()){
self.request.cancel();
self.startPoll();
self.startPoll()
}
}, 120000);
@@ -129,15 +130,15 @@ var NotificationBase = new Class({
self.request = Api.request('nonblock/notification.listener', {
'onSuccess': function(json){
self.processData(json, false);
self.processData(json, false)
},
'data': {
'last_id': self.last_id
},
'onFailure': function(){
self.startPoll.delay(2000, self);
self.startPoll.delay(2000, self)
}
}).send();
}).send()
},
@@ -159,7 +160,7 @@ var NotificationBase = new Class({
});
if(json.result.length > 0)
self.last_id = json.result.getLast().message_id;
self.last_id = json.result.getLast().message_id
}
// Restart poll
@@ -174,11 +175,11 @@ var NotificationBase = new Class({
var new_message = new Element('div', {
'class': 'message' + (sticky ? ' sticky' : ''),
'html': '<div class="inner">' + message + '</div>'
'html': message
}).inject(self.message_container, 'top');
setTimeout(function(){
new_message.addClass('show');
new_message.addClass('show')
}, 10);
var hide_message = function(){
@@ -210,8 +211,8 @@ var NotificationBase = new Class({
var setting_page = App.getPage('Settings');
setting_page.addEvent('create', function(){
Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self));
});
Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self))
})
},
@@ -234,21 +235,20 @@ var NotificationBase = new Class({
button.set('text', button_name);
var message;
if(json.success){
message = new Element('span.success', {
var message = new Element('span.success', {
'text': 'Notification successful'
}).inject(button, 'after');
}).inject(button, 'after')
}
else {
message = new Element('span.failed', {
var message = new Element('span.failed', {
'text': 'Notification failed. Check logs for details.'
}).inject(button, 'after');
}).inject(button, 'after')
}
(function(){
message.destroy();
}).delay(3000);
}).delay(3000)
}
});
}
@@ -23,26 +23,6 @@ config = [{
'default': 'localhost',
'description': 'Hostname/IP, default localhost'
},
{
'name': 'username',
'label': 'Username',
'default': '',
'description': 'Required for myPlex'
},
{
'name': 'password',
'label': 'Password',
'default': '',
'type': 'password',
'description': 'Required for myPlex'
},
{
'name': 'auth_token',
'label': 'Auth Token',
'default': '',
'advanced': True,
'description': 'Required for myPlex'
},
{
'name': 'clients',
'default': '',
+4 -39
View File
@@ -35,46 +35,11 @@ class PlexServer(object):
if path.startswith('/'):
path = path[1:]
#Maintain support for older Plex installations without myPlex
if not self.plex.conf('auth_token') and not self.plex.conf('username') and not self.plex.conf('password'):
data = self.plex.urlopen('%s/%s' % (
self.createHost(self.plex.conf('media_server'), port = 32400),
path
))
else:
#Fetch X-Plex-Token if it doesn't exist but a username/password do
if not self.plex.conf('auth_token') and (self.plex.conf('username') and self.plex.conf('password')):
import urllib2, base64
log.info("Fetching a new X-Plex-Token from plex.tv")
username = self.plex.conf('username')
password = self.plex.conf('password')
req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="")
authheader = "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1]
req.add_header("Authorization", authheader)
req.add_header("X-Plex-Product", "Couchpotato Notifier")
req.add_header("X-Plex-Client-Identifier", "b3a6b24dcab2224bdb101fc6aa08ea5e2f3147d6")
req.add_header("X-Plex-Version", "1.0")
try:
response = urllib2.urlopen(req)
except urllib2.URLError, e:
log.info("Error fetching token from plex.tv")
try:
auth_tree = etree.parse(response)
token = auth_tree.findall(".//authentication-token")[0].text
self.plex.conf('auth_token', token)
data = self.plex.urlopen('%s/%s' % (
self.createHost(self.plex.conf('media_server'), port = 32400),
path
))
except (ValueError, IndexError) as e:
log.info("Error parsing plex.tv response: " + ex(e))
#Add X-Plex-Token header for myPlex support workaround
data = self.plex.urlopen('%s/%s?X-Plex-Token=%s' % (
self.createHost(self.plex.conf('media_server'), port = 32400),
path,
self.plex.conf('auth_token')
))
if data_type == 'xml':
return etree.fromstring(data)
else:
@@ -16,7 +16,7 @@ var TwitterNotification = new Class({
var twitter_set = 0;
fieldset.getElements('input[type=text]').each(function(el){
twitter_set += +(el.get('value') !== '');
twitter_set += +(el.get('value') != '');
});
@@ -57,7 +57,7 @@ var TwitterNotification = new Class({
}
})
).inject(fieldset.getElement('.test_button'), 'before');
});
})
}
-68
View File
@@ -1,68 +0,0 @@
import traceback
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getIdentifier
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
log = CPLog(__name__)
autoload = 'Webhook'
class Webhook(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
post_data = {
'message': toUnicode(message)
}
if getIdentifier(data):
post_data.update({
'imdb_id': getIdentifier(data)
})
headers = {
'Content-type': 'application/x-www-form-urlencoded'
}
try:
self.urlopen(self.conf('url'), headers = headers, data = post_data, show_error = False)
return True
except:
log.error('Webhook notification failed: %s', traceback.format_exc())
return False
config = [{
'name': 'webhook',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'webhook',
'label': 'Webhook',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'url',
'description': 'The URL to send notification data to when '
},
{
'name': 'on_snatch',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Also send message when movie is snatched.',
}
]
}
]
}]
+3 -3
View File
@@ -39,7 +39,7 @@ class Plugin(object):
_locks = {}
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:34.0) Gecko/20100101 Firefox/34.0'
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20130519 Firefox/24.0'
http_last_use = {}
http_time_between_calls = 0
http_failed_request = {}
@@ -196,7 +196,7 @@ class Plugin(object):
headers['Host'] = headers.get('Host', None)
headers['User-Agent'] = headers.get('User-Agent', self.user_agent)
headers['Accept-encoding'] = headers.get('Accept-encoding', 'gzip')
headers['Connection'] = headers.get('Connection', 'keep-alive')
headers['Connection'] = headers.get('Connection', 'close')
headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0')
r = Env.get('http_opener')
@@ -206,7 +206,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 Exception('Disabled calls to %s for 15 minutes because so many failed requests' % host)
raise Exception('Disabled calls to %s for 15 minutes because so many failed requests')
else:
return ''
else:
-1
View File
@@ -87,7 +87,6 @@ class FileBrowser(Plugin):
try:
dirs = self.getDirectories(path = path, show_hidden = show_hidden)
except:
log.error('Failed getting directory "%s" : %s', (path, traceback.format_exc()))
dirs = []
parent = os.path.dirname(path.rstrip(os.path.sep))
@@ -52,7 +52,7 @@ var CategoryListBase = new Class({
});
});
})
},
@@ -71,7 +71,7 @@ var CategoryListBase = new Class({
'events': {
'click': function(){
var category = self.createCategory();
$(category).inject(self.category_container);
$(category).inject(self.category_container)
}
}
})
@@ -79,15 +79,15 @@ var CategoryListBase = new Class({
// Add categories, that aren't part of the core (for editing)
Array.each(self.categories, function(category){
$(category).inject(self.category_container);
$(category).inject(self.category_container)
});
},
getCategory: function(id){
return this.categories.filter(function(category){
return category.data._id == id;
}).pick();
return category.data._id == id
}).pick()
},
getAll: function(){
@@ -97,7 +97,7 @@ var CategoryListBase = new Class({
createCategory: function(data){
var self = this;
data = data || {'id': randomString()};
var data = data || {'id': randomString()};
var category = new Category(data);
self.categories.include(category);
@@ -225,7 +225,7 @@ var Category = new Class({
)
);
self.makeSortable();
self.makeSortable()
},
@@ -248,7 +248,7 @@ var Category = new Class({
}
});
}).delay(delay || 0, self);
}).delay(delay || 0, self)
},
@@ -262,13 +262,13 @@ var Category = new Class({
'preferred' : self.el.getElement('.category_preferred input').get('value'),
'ignored' : self.el.getElement('.category_ignored input').get('value'),
'destination': self.data.destination
};
}
},
del: function(){
var self = this;
if(self.data.label === undefined){
if(self.data.label == undefined){
self.el.destroy();
return;
}
@@ -318,11 +318,11 @@ var Category = new Class({
},
get: function(attr){
return this.data[attr];
return this.data[attr]
},
toElement: function(){
return this.el;
return this.el
}
});
+15 -14
View File
@@ -8,19 +8,20 @@ Page.Log = new Class({
has_tab: false,
log_items: [],
report_text: '### Steps to reproduce:\n'+
'1. ..\n'+
'2. ..\n'+
'\n'+
'### Information:\n'+
'Movie(s) I have this with: ...\n'+
'Quality of the movie being searched: ...\n'+
'Providers I use: ...\n'+
'Version of CouchPotato: {version}\n'+
'Running on: ...\n'+
'\n'+
'### Logs:\n'+
'```\n{issue}```',
report_text: '\
### Steps to reproduce:\n\
1. ..\n\
2. ..\n\
\n\
### Information:\n\
Movie(s) I have this with: ...\n\
Quality of the movie being searched: ...\n\
Providers I use: ...\n\
Version of CouchPotato: {version}\n\
Running on: ...\n\
\n\
### Logs:\n\
```\n{issue}```',
indexAction: function () {
var self = this;
@@ -132,7 +133,7 @@ Page.Log = new Class({
new Element('span.message', {
'text': log.message
})
));
))
});
return elements;
@@ -81,7 +81,7 @@ var Profile = new Class({
'quality': quality,
'finish': data.finish[nr] || false,
'3d': data['3d'] ? data['3d'][nr] || false : false
});
})
});
}
@@ -123,7 +123,7 @@ var Profile = new Class({
}
});
}).delay(delay, self);
}).delay(delay, self)
},
@@ -148,7 +148,7 @@ var Profile = new Class({
});
});
return data;
return data
},
addType: function(data){
@@ -177,7 +177,7 @@ var Profile = new Class({
var self = this;
return self.types.filter(function(type){
return type.get('quality');
return type.get('quality')
});
},
@@ -231,15 +231,15 @@ var Profile = new Class({
},
get: function(attr){
return this.data[attr];
return this.data[attr]
},
isCore: function(){
return this.data.core;
return this.data.core
},
toElement: function(){
return this.el;
return this.el
}
});
@@ -342,7 +342,7 @@ Profile.Type = new Class({
'text': q.label,
'value': q.identifier,
'data-allow_3d': q.allow_3d
}).inject(self.qualities);
}).inject(self.qualities)
});
self.qualities.set('value', self.data.quality);
@@ -358,7 +358,7 @@ Profile.Type = new Class({
'quality': self.qualities.get('value'),
'finish': +self.finish.checked,
'3d': +self['3d'].checked
};
}
},
get: function(key){
+1 -2
View File
@@ -240,7 +240,7 @@ class QualityPlugin(Plugin):
# Add additional size score if only 1 size validated
if len(size_scores) == 1:
self.calcScore(score, size_scores[0], 7)
self.calcScore(score, size_scores[0], 8)
del size_scores
# Return nothing if all scores are <= 0
@@ -491,7 +491,6 @@ class QualityPlugin(Plugin):
'Movie Name.2014.720p Web-Dl Aac2.0 h264-ReleaseGroup': {'size': 3800, 'quality': 'brrip'},
'Movie Name.2014.720p.WEBRip.x264.AC3-ReleaseGroup': {'size': 3000, 'quality': 'scr'},
'Movie.Name.2014.1080p.HDCAM.-.ReleaseGroup': {'size': 5300, 'quality': 'cam'},
'Movie.Name.2014.720p.HDSCR.4PARTS.MP4.AAC.ReleaseGroup': {'size': 2401, 'quality': 'scr'},
}
correct = 0
@@ -12,20 +12,20 @@ var QualityBase = new Class({
self.profiles = [];
Array.each(data.profiles, self.createProfilesClass.bind(self));
App.addEvent('loadSettings', self.addSettings.bind(self));
App.addEvent('loadSettings', self.addSettings.bind(self))
},
getProfile: function(id){
return this.profiles.filter(function(profile){
return profile.data._id == id;
}).pick();
return profile.data._id == id
}).pick()
},
// Hide items when getting profiles
getActiveProfiles: function(){
return Array.filter(this.profiles, function(profile){
return !profile.data.hide;
return !profile.data.hide
});
},
@@ -37,7 +37,7 @@ var QualityBase = new Class({
}
catch(e){}
return {};
return {}
},
addSettings: function(){
@@ -58,7 +58,7 @@ var QualityBase = new Class({
self.createProfileOrdering();
self.createSizes();
});
})
},
@@ -68,7 +68,7 @@ var QualityBase = new Class({
createProfiles: function(){
var self = this;
var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore(); });
var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore() });
var count = non_core_profiles.length;
self.settings.createGroup({
@@ -81,7 +81,7 @@ var QualityBase = new Class({
'events': {
'click': function(){
var profile = self.createProfilesClass();
$(profile).inject(self.profile_container);
$(profile).inject(self.profile_container)
}
}
})
@@ -89,7 +89,7 @@ var QualityBase = new Class({
// Add profiles, that aren't part of the core (for editing)
Array.each(non_core_profiles, function(profile){
$(profile).inject(self.profile_container);
$(profile).inject(self.profile_container)
});
},
@@ -97,7 +97,7 @@ var QualityBase = new Class({
createProfilesClass: function(data){
var self = this;
data = data || {'id': randomString()};
var data = data || {'id': randomString()};
var profile = new Profile(data);
self.profiles.include(profile);
@@ -190,6 +190,7 @@ var QualityBase = new Class({
'name': 'sizes'
}).inject(self.content);
new Element('div.item.head.ctrlHolder').adopt(
new Element('span.label', {'text': 'Quality'}),
new Element('span.min', {'text': 'Min'}),
@@ -203,7 +204,7 @@ var QualityBase = new Class({
'value': quality.size_min,
'events': {
'keyup': function(e){
self.changeSize(quality.identifier, 'size_min', e.target.get('value'));
self.changeSize(quality.identifier, 'size_min', e.target.get('value'))
}
}
}),
@@ -211,11 +212,11 @@ var QualityBase = new Class({
'value': quality.size_max,
'events': {
'keyup': function(e){
self.changeSize(quality.identifier, 'size_max', e.target.get('value'));
self.changeSize(quality.identifier, 'size_max', e.target.get('value'))
}
}
})
).inject(group);
).inject(group)
});
},
@@ -234,7 +235,7 @@ var QualityBase = new Class({
'value': value
}
});
}).delay(300);
}).delay(300)
}
+4 -30
View File
@@ -35,7 +35,6 @@ class Renamer(Plugin):
'desc': 'For the renamer to check for new files to rename in a folder',
'params': {
'async': {'desc': 'Optional: Set to 1 if you dont want to fire the renamer.scan asynchronous.'},
'to_folder': {'desc': 'Optional: The folder to move releases to. Leave empty for default folder.'},
'media_folder': {'desc': 'Optional: The folder of the media to scan. Keep empty for default renamer folder.'},
'files': {'desc': 'Optional: Provide the release files if more releases are in the same media_folder, delimited with a \'|\'. Note that no dedicated release folder is expected for releases with one file.'},
'base_folder': {'desc': 'Optional: The folder to find releases in. Leave empty for default folder.'},
@@ -45,13 +44,6 @@ class Renamer(Plugin):
},
})
addApiView('renamer.progress', self.getProgress, docs = {
'desc': 'Get the progress of current renamer scan',
'return': {'type': 'object', 'example': """{
'progress': False || True,
}"""},
})
addEvent('renamer.scan', self.scan)
addEvent('renamer.check_snatched', self.checkSnatched)
@@ -75,17 +67,11 @@ class Renamer(Plugin):
return True
def getProgress(self, **kwargs):
return {
'progress': self.renaming_started
}
def scanView(self, **kwargs):
async = tryInt(kwargs.get('async', 0))
base_folder = kwargs.get('base_folder')
media_folder = sp(kwargs.get('media_folder'))
to_folder = kwargs.get('to_folder')
# Backwards compatibility, to be removed after a few versions :)
if not media_folder:
@@ -109,13 +95,13 @@ class Renamer(Plugin):
})
fire_handle = fireEvent if not async else fireEventAsync
fire_handle('renamer.scan', base_folder = base_folder, release_download = release_download, to_folder = to_folder)
fire_handle('renamer.scan', base_folder = base_folder, release_download = release_download)
return {
'success': True
}
def scan(self, base_folder = None, release_download = None, to_folder = None):
def scan(self, base_folder = None, release_download = None):
if not release_download: release_download = {}
if self.isDisabled():
@@ -129,9 +115,7 @@ class Renamer(Plugin):
base_folder = sp(self.conf('from'))
from_folder = sp(self.conf('from'))
if not to_folder:
to_folder = sp(self.conf('to'))
to_folder = sp(self.conf('to'))
# Get media folder to process
media_folder = sp(release_download.get('folder'))
@@ -885,9 +869,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
#If information is not available, we don't want the tag in the filename
replaced = replaced.replace('<' + x + '>', '')
if self.conf('replace_doubles'):
replaced = self.replaceDoubles(replaced.lstrip('. '))
replaced = self.replaceDoubles(replaced.lstrip('. '))
for x, r in replacements.items():
if x in ['thename', 'namethe']:
replaced = replaced.replace(six.u('<%s>') % toUnicode(x), toUnicode(r))
@@ -1344,14 +1326,6 @@ config = [{
'type': 'choice',
'options': rename_options
},
{
'advanced': True,
'name': 'replace_doubles',
'type': 'bool',
'label': 'Clean Name',
'description': ('Attempt to clean up double separaters due to missing data for fields.','Sometimes this eliminates wanted white space (see <a href="https://github.com/RuudBurger/CouchPotatoServer/issues/2782">#2782</a>).'),
'default': True
},
{
'name': 'unrar',
'type': 'bool',
+1 -1
View File
@@ -16,7 +16,7 @@ autoload = 'Subtitle'
class Subtitle(Plugin):
services = ['opensubtitles', 'thesubdb', 'subswiki', 'subscenter']
services = ['opensubtitles', 'thesubdb', 'subswiki', 'podnapisi']
def __init__(self):
addEvent('renamer.before', self.searchSingle)
@@ -35,7 +35,7 @@ Page.Userscript = new Class({
if(json.error)
self.frame.set('html', json.error);
else {
var item = new BlockSearchMovieItem(json.movie);
var item = new Block.Search.MovieItem(json.movie);
self.frame.adopt(item);
item.showOptions();
}
@@ -54,7 +54,7 @@ var UserscriptSettingTab = new Class({
initialize: function(){
var self = this;
App.addEvent('loadSettings', self.addSettings.bind(self));
App.addEvent('loadSettings', self.addSettings.bind(self))
},
@@ -88,7 +88,7 @@ var UserscriptSettingTab = new Class({
'events': {
'click': function(e){
(e).stop();
alert('Drag it to your bookmark ;)');
alert('Drag it to your bookmark ;)')
}
}
}),
@@ -41,7 +41,7 @@ Page.Wizard = new Class({
'content': function(){
return App.createUserscriptButtons().setStyles({
'background-image': "url('https://couchpota.to/media/images/userscript.gif')"
});
})
}
},
'finish': {
@@ -103,7 +103,7 @@ Page.Wizard = new Class({
(function(){
var sc = self.el.getElement('.wgroup_'+action);
self.scroll.start(0, sc.getCoordinates().top-80);
}).delay(1);
}).delay(1)
},
orderGroups: function(){
@@ -114,9 +114,8 @@ Page.Wizard = new Class({
self.groups.each(function(group){
var group_container;
if(self.headers[group]){
group_container = new Element('.wgroup_'+group, {
var group_container = new Element('.wgroup_'+group, {
'styles': {
'opacity': 0.2
},
@@ -128,7 +127,7 @@ Page.Wizard = new Class({
if(self.headers[group].include){
self.headers[group].include.each(function(inc){
group_container.addClass('wgroup_'+inc);
});
})
}
var content = self.headers[group].content;
@@ -149,7 +148,7 @@ Page.Wizard = new Class({
tab_navigation = [];
self.headers[group].include.each(function(inc){
tab_navigation.include(tabs.getElement('.t_'+inc));
});
})
}
if(tab_navigation && group_container){
@@ -166,7 +165,7 @@ Page.Wizard = new Class({
'href': App.createUrl('wizard/'+group),
'text': (self.headers[group].label || group).capitalize()
})
).inject(tabs);
).inject(tabs)
}
else
@@ -191,7 +190,7 @@ Page.Wizard = new Class({
}
if(self.headers[group] && self.headers[group].event)
self.headers[group].event.call();
self.headers[group].event.call()
});
// Remove toggle
@@ -221,22 +220,22 @@ Page.Wizard = new Class({
g.tween('opacity', 1);
};
if(nr === 0)
if(nr == 0)
func();
new ScrollSpy( {
min: function(){
var c = g.getCoordinates();
var top = c.top-(window.getSize().y/2);
return top > minimum ? minimum : top;
return top > minimum ? minimum : top
},
max: function(){
var c = g.getCoordinates();
return c.top+(c.height/2);
return c.top+(c.height/2)
},
onEnter: func,
onLeave: function(){
g.tween('opacity', 0.2);
g.tween('opacity', 0.2)
}
});
});
-8
View File
@@ -157,15 +157,7 @@ class Settings(object):
values[section] = {}
for option in self.p.items(section):
(option_name, option_value) = option
is_password = False
try: is_password = self.types[section][option_name] == 'password'
except: pass
values[section][option_name] = self.get(option_name, section)
if is_password and values[section][option_name]:
values[section][option_name] = len(values[section][option_name]) * '*'
return values
def save(self):
+1 -7
View File
@@ -244,13 +244,11 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# Basic config
host = Env.setting('host', default = '0.0.0.0')
host6 = Env.setting('host6', default = '::')
# app.debug = development
config = {
'use_reloader': reloader,
'port': tryInt(Env.setting('port', default = 5050)),
'host': host if host and len(host) > 0 else '0.0.0.0',
'host6': host6 if host6 and len(host6) > 0 else '::',
'ssl_cert': Env.setting('ssl_cert', default = None),
'ssl_key': Env.setting('ssl_key', default = None),
}
@@ -333,10 +331,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
while try_restart:
try:
server.listen(config['port'], config['host'])
try: server.listen(config['port'], config['host6'])
except: log.info2('Tried to bind to IPV6 but failed')
loop.start()
server.close_all_connections()
server.stop()
Binary file not shown.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
+226 -20
View File
@@ -3,36 +3,242 @@
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="lobster_14regular" horiz-adv-x="2048" >
<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 />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode="&#xfb01;" 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="&#xfb02;" 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="&#xfb03;" 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="&#xfb04;" 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="&#xd;" horiz-adv-x="2048" />
<glyph unicode=" " horiz-adv-x="444" />
<glyph unicode="&#x09;" horiz-adv-x="444" />
<glyph unicode="&#xa0;" 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="&#x22;" 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="&#x26;" 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="&#x3c;" 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="&#x3e;" 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="h" horiz-adv-x="1034" 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="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" horiz-adv-x="1034" 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="&#x2000;" horiz-adv-x="768" />
<glyph unicode="&#x2001;" horiz-adv-x="1536" />
<glyph unicode="&#x2002;" horiz-adv-x="768" />
<glyph unicode="&#x2003;" horiz-adv-x="1536" />
<glyph unicode="&#x2004;" horiz-adv-x="512" />
<glyph unicode="&#x2005;" horiz-adv-x="384" />
<glyph unicode="&#x2006;" horiz-adv-x="256" />
<glyph unicode="&#x2007;" horiz-adv-x="256" />
<glyph unicode="&#x2008;" horiz-adv-x="192" />
<glyph unicode="&#x2009;" horiz-adv-x="307" />
<glyph unicode="&#x200a;" horiz-adv-x="85" />
<glyph unicode="&#x202f;" horiz-adv-x="307" />
<glyph unicode="&#x205f;" horiz-adv-x="384" />
<glyph unicode="&#x25fc;" horiz-adv-x="1140" d="M0 0v1140h1140v-1140h-1140z" />
<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="&#xa1;" 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="&#xa2;" 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="&#xa3;" 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="&#xa4;" 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="&#xa5;" 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="&#xa6;" horiz-adv-x="1089" d="M449 455h190v-967h-190v967zM449 616v922h190v-922h-190z" />
<glyph unicode="&#xa7;" 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="&#xa8;" 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="&#xa9;" 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="&#xaa;" 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="&#xab;" 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="&#xac;" horiz-adv-x="909" d="M39 455l37 161h741l-104 -512h-232l72 351h-514z" />
<glyph unicode="&#xad;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#xae;" 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="&#xb0;" 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="&#xb1;" 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="&#xb2;" 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="&#xb3;" 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="&#xb4;" horiz-adv-x="647" d="M246 1149l186 387h215l-248 -387h-153z" />
<glyph unicode="&#xb5;" 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="&#xb6;" 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="&#xb7;" 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="&#xb8;" 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="&#xb9;" horiz-adv-x="444" d="M45 707l158 690h-113l14 51q148 0 302 86l-189 -827h-172z" />
<glyph unicode="&#xba;" 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="&#xbb;" 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="&#xbc;" 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="&#xbd;" 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="&#xbe;" 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="&#xbf;" 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="&#xc0;" 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="&#xc1;" 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="&#xc2;" 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="&#xc3;" 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="&#xc4;" 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="&#xc5;" 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="&#xc6;" 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="&#xc7;" 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="&#xc8;" 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="&#xc9;" 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="&#xca;" 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="&#xcb;" 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="&#xcc;" 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="&#xcd;" 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="&#xce;" 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="&#xcf;" 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="&#xd0;" 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="&#xd1;" 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="&#xd2;" 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="&#xd3;" 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="&#xd4;" 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="&#xd5;" 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="&#xd6;" 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="&#xd7;" 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="&#xd8;" 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="&#xd9;" 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="&#xda;" 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="&#xdb;" 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="&#xdc;" 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="&#xdd;" 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="&#xde;" 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="&#xdf;" 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="&#xe0;" 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="&#xe1;" 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="&#xe2;" 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="&#xe3;" 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="&#xe4;" 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="&#xe5;" 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="&#xe6;" 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="&#xe7;" 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="&#xe8;" 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="&#xe9;" 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="&#xea;" 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="&#xeb;" 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="&#xec;" 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="&#xed;" 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="&#xee;" 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="&#xef;" 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="&#xf0;" 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="&#xf1;" 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="&#xf2;" 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="&#xf3;" 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="&#xf4;" 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="&#xf5;" 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="&#xf6;" 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="&#xf7;" 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="&#xf8;" 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="&#xf9;" 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="&#xfa;" 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="&#xfb;" 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="&#xfc;" 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="&#xfd;" 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="&#xfe;" 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="&#xff;" 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="&#x152;" 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="&#x153;" 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="&#x178;" 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="&#x2c6;" horiz-adv-x="600" d="M0 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
<glyph unicode="&#x2dc;" 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="&#x2000;" horiz-adv-x="1024" />
<glyph unicode="&#x2001;" horiz-adv-x="2048" />
<glyph unicode="&#x2002;" horiz-adv-x="1024" />
<glyph unicode="&#x2003;" horiz-adv-x="2048" />
<glyph unicode="&#x2004;" horiz-adv-x="682" />
<glyph unicode="&#x2005;" horiz-adv-x="512" />
<glyph unicode="&#x2006;" horiz-adv-x="341" />
<glyph unicode="&#x2007;" horiz-adv-x="341" />
<glyph unicode="&#x2008;" horiz-adv-x="256" />
<glyph unicode="&#x2009;" horiz-adv-x="409" />
<glyph unicode="&#x200a;" horiz-adv-x="113" />
<glyph unicode="&#x2010;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2011;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2012;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2013;" horiz-adv-x="720" d="M39 455l37 161h532l-35 -161h-534z" />
<glyph unicode="&#x2014;" horiz-adv-x="909" d="M39 455l37 161h717l-35 -161h-719z" />
<glyph unicode="&#x2018;" 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="&#x2019;" 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="&#x201a;" 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="&#x201c;" 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="&#x201d;" 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="&#x201e;" 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="&#x2022;" 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="&#x2026;" 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="&#x202f;" horiz-adv-x="409" />
<glyph unicode="&#x2039;" horiz-adv-x="735" d="M41 487l18 84l629 349l-20 -113l-494 -287l397 -237l-24 -117z" />
<glyph unicode="&#x203a;" horiz-adv-x="704" d="M31 166l28 117l492 237l-397 287l18 113l504 -349l-23 -110z" />
<glyph unicode="&#x205f;" horiz-adv-x="512" />
<glyph unicode="&#x20ac;" 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="&#x2122;" 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="&#xe000;" horiz-adv-x="1136" d="M0 0v1137h1137v-1137h-1137z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Some files were not shown because too many files have changed in this diff Show More