diff --git a/FAQ.md b/FAQ.md index 2a79dbf..6331183 100644 --- a/FAQ.md +++ b/FAQ.md @@ -42,7 +42,15 @@ sudo apt-get install libav-tools curl Create a dyno with the following buildpacks: * `heroku/php` -* `https://github.com/heroku/heroku-buildpack-nodejs` +* `heroku/nodejs` +* `heroku/python` + +You might also need to add the following config variables: + +```env +CONVERT=1 +PYTHON=/app/.heroku/python/bin/python +``` Then push the code to Heroku and it should work out of the box. diff --git a/classes/Config.php b/classes/Config.php index 87fdf10..c57c848 100644 --- a/classes/Config.php +++ b/classes/Config.php @@ -109,6 +109,9 @@ class Config if (getenv('CONVERT')) { $this->convert = (bool) getenv('CONVERT'); } + if (getenv('PYTHON')) { + $this->python = getenv('PYTHON'); + } } /** diff --git a/classes/VideoDownload.php b/classes/VideoDownload.php index 2284840..39230c9 100644 --- a/classes/VideoDownload.php +++ b/classes/VideoDownload.php @@ -265,6 +265,9 @@ class VideoDownload } $video = $this->getJSON($url, $format, $password); + if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) { + throw(new \Exception('Conversion of M3U8 files is not supported.')); + } //Vimeo needs a correct user-agent ini_set( diff --git a/composer.json b/composer.json index 622e1d7..eb602b7 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "type": "project", "require": { "smarty/smarty": "~3.1.29", - "slim/slim": "~3.6.0", + "slim/slim": "~3.7.0", "mathmarques/smarty-view": "~1.1.0", "symfony/yaml": "~3.2.0", "symfony/process": "~3.2.0", @@ -19,8 +19,9 @@ "squizlabs/php_codesniffer": "~2.7.0", "phpunit/phpunit": "~5.7.2", "ffmpeg/ffmpeg": "dev-release", - "rg3/youtube-dl": "~2016.09.08", - "rudloff/rtmpdump-bin": "~2.3" + "rg3/youtube-dl": "~2016.12.22", + "rudloff/rtmpdump-bin": "~2.3", + "heroku/heroku-buildpack-php": "*" }, "extra": { "paas": { @@ -34,10 +35,10 @@ "type": "package", "package": { "name": "rg3/youtube-dl", - "version": "2016.09.08", + "version": "2016.12.22", "dist": { "type": "zip", - "url": "https://github.com/rg3/youtube-dl/archive/2016.12.18.zip" + "url": "https://github.com/rg3/youtube-dl/archive/2016.12.22.zip" } } }, diff --git a/composer.lock b/composer.lock index c2fe3b9..660f855 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "a16b07104a463e2beb02558d3c6d97b1", - "content-hash": "ed2f2e09c9147797cc42a8ffb8d878bb", + "content-hash": "91e14c843bc92c4d5af1ec7abcddf15a", "packages": [ { "name": "aura/session", @@ -67,7 +66,7 @@ "session", "sessions" ], - "time": "2016-10-03 20:28:32" + "time": "2016-10-03T20:28:32+00:00" }, { "name": "container-interop/container-interop", @@ -94,7 +93,7 @@ "MIT" ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "time": "2014-12-30 15:22:37" + "time": "2014-12-30T15:22:37+00:00" }, { "name": "jeremykendall/php-domain-parser", @@ -156,7 +155,7 @@ "domain parsing", "url parsing" ], - "time": "2015-03-30 12:49:45" + "time": "2015-03-30T12:49:45+00:00" }, { "name": "league/uri", @@ -220,7 +219,7 @@ "url", "ws" ], - "time": "2016-03-24 08:38:29" + "time": "2016-03-24T08:38:29+00:00" }, { "name": "mathmarques/smarty-view", @@ -270,20 +269,20 @@ "template", "view" ], - "time": "2016-08-25 19:04:49" + "time": "2016-08-25T19:04:49+00:00" }, { "name": "nikic/fast-route", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/nikic/FastRoute.git", - "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af" + "reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/8ea928195fa9b907f0d6e48312d323c1a13cc2af", - "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/f3dcf5130e634b6123d40727d612ec6aa4f61fb3", + "reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3", "shasum": "" }, "require": { @@ -313,7 +312,7 @@ "router", "routing" ], - "time": "2016-06-12 19:08:51" + "time": "2016-10-20T17:36:47+00:00" }, { "name": "pimple/pimple", @@ -359,7 +358,7 @@ "container", "dependency injection" ], - "time": "2015-09-11 15:10:35" + "time": "2015-09-11T15:10:35+00:00" }, { "name": "psr/http-message", @@ -409,7 +408,7 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" }, { "name": "ptachoire/process-builder-chain", @@ -445,7 +444,7 @@ } ], "description": "Add ability to chain symfony processes", - "time": "2016-04-10 08:33:20" + "time": "2016-04-10T08:33:20+00:00" }, { "name": "rudloff/smarty-plugin-noscheme", @@ -486,20 +485,20 @@ } ], "description": "Smarty modifier that removes the scheme in URLs", - "time": "2016-04-09 00:40:13" + "time": "2016-04-09T00:40:13+00:00" }, { "name": "slim/slim", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64" + "reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", - "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/4254e40d81559e35cdf856bcbaca5f3af468b7ef", + "reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef", "shasum": "" }, "require": { @@ -549,14 +548,14 @@ } ], "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", - "homepage": "http://slimframework.com", + "homepage": "https://slimframework.com", "keywords": [ "api", "framework", "micro", "router" ], - "time": "2016-11-20 20:48:49" + "time": "2016-12-20T20:30:47+00:00" }, { "name": "smarty/smarty", @@ -609,7 +608,7 @@ "keywords": [ "templating" ], - "time": "2016-12-14 21:57:25" + "time": "2016-12-14T21:57:25+00:00" }, { "name": "symfony/process", @@ -658,7 +657,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-11-24 10:40:28" + "time": "2016-11-24T10:40:28+00:00" }, { "name": "symfony/yaml", @@ -713,7 +712,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-12-10 10:07:06" + "time": "2016-12-10T10:07:06+00:00" } ], "packages-dev": [ @@ -769,7 +768,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "ffmpeg/ffmpeg", @@ -785,6 +784,50 @@ ], "type": "library" }, + { + "name": "heroku/heroku-buildpack-php", + "version": "v117", + "source": { + "type": "git", + "url": "https://github.com/heroku/heroku-buildpack-php.git", + "reference": "960199a978308c75926fd9bb4775f7113bf1d777" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/960199a978308c75926fd9bb4775f7113bf1d777", + "reference": "960199a978308c75926fd9bb4775f7113bf1d777", + "shasum": "" + }, + "bin": [ + "bin/heroku-hhvm-apache2", + "bin/heroku-hhvm-nginx", + "bin/heroku-php-apache2", + "bin/heroku-php-nginx" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Zuelke", + "email": "dz@heroku.com" + } + ], + "description": "Toolkit for starting a PHP application locally, with or without foreman, using the same config for PHP/HHVM and Apache2/Nginx as on Heroku", + "homepage": "https://github.com/heroku/heroku-buildpack-php", + "keywords": [ + "apache", + "apache2", + "foreman", + "heroku", + "hhvm", + "nginx", + "php" + ], + "time": "2016-12-09T19:37:38+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.5.5", @@ -825,7 +868,7 @@ "object", "object graph" ], - "time": "2016-10-31 17:19:45" + "time": "2016-10-31T17:19:45+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -879,7 +922,7 @@ "reflection", "static analysis" ], - "time": "2015-12-27 11:43:31" + "time": "2015-12-27T11:43:31+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -924,7 +967,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30 07:12:33" + "time": "2016-09-30T07:12:33+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -971,7 +1014,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25 06:54:22" + "time": "2016-11-25T06:54:22+00:00" }, { "name": "phpspec/prophecy", @@ -1034,20 +1077,20 @@ "spy", "stub" ], - "time": "2016-11-21 14:58:47" + "time": "2016-11-21T14:58:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929" + "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/903fd6318d0a90b4770a009ff73e4a4e9c437929", - "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c14196e64a78570034afd0b7a9f3757ba71c2a0a", + "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a", "shasum": "" }, "require": { @@ -1097,7 +1140,7 @@ "testing", "xunit" ], - "time": "2016-11-28 16:00:31" + "time": "2016-12-20T15:22:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1144,7 +1187,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03 07:40:28" + "time": "2016-10-03T07:40:28+00:00" }, { "name": "phpunit/php-text-template", @@ -1185,7 +1228,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -1229,7 +1272,7 @@ "keywords": [ "timer" ], - "time": "2016-05-12 18:03:57" + "time": "2016-05-12T18:03:57+00:00" }, { "name": "phpunit/php-token-stream", @@ -1278,20 +1321,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15 14:06:22" + "time": "2016-11-15T14:06:22+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.4", + "version": "5.7.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09" + "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/af91da3f2671006ff5d0628023de3b7ac4d1ef09", - "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50fd2be8f3e23e91da825f36f08e5f9633076ffe", + "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe", "shasum": "" }, "require": { @@ -1360,7 +1403,7 @@ "testing", "xunit" ], - "time": "2016-12-13 16:19:44" + "time": "2016-12-28T07:18:51+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1419,14 +1462,14 @@ "mock", "xunit" ], - "time": "2016-12-08 20:27:08" + "time": "2016-12-08T20:27:08+00:00" }, { "name": "rg3/youtube-dl", - "version": "2016.09.08", + "version": "2016.12.22", "dist": { "type": "zip", - "url": "https://github.com/rg3/youtube-dl/archive/2016.12.18.zip", + "url": "https://github.com/rg3/youtube-dl/archive/2016.12.22.zip", "reference": null, "shasum": null }, @@ -1458,7 +1501,7 @@ "GPL-2.0" ], "description": "rtmpdump binary for Linux 64 bit", - "time": "2016-04-12 19:17:32" + "time": "2016-04-12T19:17:32+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1503,7 +1546,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" + "time": "2016-02-13T06:45:14+00:00" }, { "name": "sebastian/comparator", @@ -1567,7 +1610,7 @@ "compare", "equality" ], - "time": "2016-11-19 09:18:40" + "time": "2016-11-19T09:18:40+00:00" }, { "name": "sebastian/diff", @@ -1619,7 +1662,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08 07:14:41" + "time": "2015-12-08T07:14:41+00:00" }, { "name": "sebastian/environment", @@ -1669,7 +1712,7 @@ "environment", "hhvm" ], - "time": "2016-11-26 07:53:53" + "time": "2016-11-26T07:53:53+00:00" }, { "name": "sebastian/exporter", @@ -1736,7 +1779,7 @@ "export", "exporter" ], - "time": "2016-11-19 08:54:04" + "time": "2016-11-19T08:54:04+00:00" }, { "name": "sebastian/global-state", @@ -1787,7 +1830,7 @@ "keywords": [ "global state" ], - "time": "2015-10-12 03:26:01" + "time": "2015-10-12T03:26:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -1833,7 +1876,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-11-19 07:35:10" + "time": "2016-11-19T07:35:10+00:00" }, { "name": "sebastian/recursion-context", @@ -1886,7 +1929,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19 07:33:16" + "time": "2016-11-19T07:33:16+00:00" }, { "name": "sebastian/resource-operations", @@ -1928,7 +1971,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", @@ -1971,7 +2014,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03 07:35:21" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -2049,7 +2092,7 @@ "phpcs", "standards" ], - "time": "2016-11-30 04:02:31" + "time": "2016-11-30T04:02:31+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2108,7 +2151,7 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2016-11-14T01:06:16+00:00" }, { "name": "symfony/var-dumper", @@ -2171,7 +2214,7 @@ "debug", "dump" ], - "time": "2016-12-11 14:34:22" + "time": "2016-12-11T14:34:22+00:00" }, { "name": "webmozart/assert", @@ -2221,7 +2264,7 @@ "check", "validate" ], - "time": "2016-11-23 20:04:58" + "time": "2016-11-23T20:04:58+00:00" } ], "aliases": [], diff --git a/css/style.css b/css/style.css index 1c5c8e7..31ae741 100644 --- a/css/style.css +++ b/css/style.css @@ -1,6 +1,7 @@ body { text-align:center; + background-color: #EBEBEB; background-image:url('../img/fond.jpg'); font-family: 'Open Sans', sans-serif; font-weight:400; diff --git a/manifest.json b/manifest.json index 0c4b662..25aae7f 100644 --- a/manifest.json +++ b/manifest.json @@ -33,5 +33,6 @@ "lang": "en", "start_url": "./", "theme_color": "#4F4F4F", + "background_color": "#EBEBEB", "orientation": "portrait" } diff --git a/package.json b/package.json index c2a8d92..ea7dba8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "alltube", "description": "HTML GUI for youtube-dl", - "version": "0.7.0", + "version": "0.7.1", "author": "Pierre Rudloff", "bugs": "https://github.com/Rudloff/alltube/issues", "dependencies": { diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index d952cad..0574eaf 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -27,6 +27,16 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $this->config = Config::getInstance('config_test.yml'); } + /** + * Destroy variables created by setUp(). + * + * @return void + */ + protected function tearDown() + { + Config::destroyInstance(); + } + /** * Test the getInstance function. * @@ -55,15 +65,19 @@ class ConfigTest extends \PHPUnit_Framework_TestCase } /** - * Test the getInstance function with the CONVERT environment variable. + * Test the getInstance function with the CONVERT and PYTHON environment variables. * * @return void */ public function testGetInstanceWithEnv() { - putenv('CONVERT=1'); Config::destroyInstance(); + putenv('CONVERT=1'); + putenv('PYTHON=foo'); $config = Config::getInstance('config_test.yml'); $this->assertEquals($config->convert, true); + $this->assertEquals($config->python, 'foo'); + putenv('CONVERT'); + putenv('PYTHON'); } } diff --git a/tests/VideoDownloadTest.php b/tests/VideoDownloadTest.php index c1053bd..8af3275 100644 --- a/tests/VideoDownloadTest.php +++ b/tests/VideoDownloadTest.php @@ -76,15 +76,16 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase /** * Test getURL function. * - * @param string $url URL - * @param string $format Format - * @param string $filename Filename - * @param string $domain Domain + * @param string $url URL + * @param string $format Format + * @param string $filename Filename + * @param string $extension File extension + * @param string $domain Domain * * @return void * @dataProvider urlProvider */ - public function testGetURL($url, $format, $filename, $domain) + public function testGetURL($url, $format, $filename, $extension, $domain) { $videoURL = $this->download->getURL($url, $format); $this->assertContains($domain, $videoURL); @@ -146,35 +147,34 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase return [ [ 'https://www.youtube.com/watch?v=M7IpKCZ47pU', null, - "It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU.mp4", + "It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU", + 'mp4', 'googlevideo.com', - "It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU.mp3", ], [ 'https://www.youtube.com/watch?v=RJJ6FCAXvKg', 22, "'Heart Attack' - Demi Lovato ". - '(Sam Tsui & Against The Current)-RJJ6FCAXvKg.mp4', + '(Sam Tsui & Against The Current)-RJJ6FCAXvKg', + 'mp4', 'googlevideo.com', - "'Heart Attack' - Demi Lovato ". - '(Sam Tsui & Against The Current)-RJJ6FCAXvKg.mp3', ], [ 'https://vimeo.com/24195442', null, - 'Carving the Mountains-24195442.mp4', + 'Carving the Mountains-24195442', + 'mp4', 'vimeocdn.com', - 'Carving the Mountains-24195442.mp3', ], [ 'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best', - 'Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m.flv', + 'Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m', + 'flv', 'bbcodspdns.fcod.llnwd.net', - 'Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m.mp3', ], [ 'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0', 'bestaudio/best', - 'GRIP sucht den Sommerkönig-folge-203-0.f4v', + 'GRIP sucht den Sommerkönig-folge-203-0', + 'f4v', 'edgefcs.net', - 'GRIP sucht den Sommerkönig-folge-203-0.mp3', ], ]; } @@ -228,17 +228,18 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase /** * Test getFilename function. * - * @param string $url URL - * @param string $format Format - * @param string $filename Filename + * @param string $url URL + * @param string $format Format + * @param string $filename Filename + * @param string $extension File extension * * @return void * @dataProvider urlProvider */ - public function testGetFilename($url, $format, $filename) + public function testGetFilename($url, $format, $filename, $extension) { $videoFilename = $this->download->getFilename($url, $format); - $this->assertEquals($videoFilename, $filename); + $this->assertEquals($videoFilename, $filename.'.'.$extension); } /** @@ -267,10 +268,10 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase * @return void * @dataProvider urlProvider */ - public function testGetAudioFilename($url, $format, $filename, $domain, $audioFilename) + public function testGetAudioFilename($url, $format, $filename) { $videoFilename = $this->download->getAudioFilename($url, $format); - $this->assertEquals($videoFilename, $audioFilename); + $this->assertEquals($videoFilename, $filename.'.mp3'); } /** @@ -323,4 +324,15 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase $config->rtmpdump = 'foobar'; $this->download->getAudioStream($url, $format); } + + /** + * Test getAudioStream function with a M3U8 file. + * + * @return void + * @expectedException Exception + */ + public function testGetAudioStreamM3uError() + { + $this->download->getAudioStream('https://twitter.com/verge/status/813055465324056576/video/1', 'best'); + } }