diff --git a/appinfo/application.php b/appinfo/application.php index 4a526a2..943bed9 100644 --- a/appinfo/application.php +++ b/appinfo/application.php @@ -40,6 +40,7 @@ class Application extends App $config = [ 'binary' => $this->settings->setType(Settings::TYPE['SYSTEM'])->get("ncd_yt_binary"), 'downloadDir' => $this->getRealDownloadDir(), + 'settings' => $this->settings->setType(Settings::TYPE['USER'])->getYoutube(), ]; return new Youtube($config); }); diff --git a/appinfo/routes.php b/appinfo/routes.php index 89b02e5..700fc17 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -20,6 +20,9 @@ return [ ['name' => 'Settings#aria2Get', 'url' => '/personal/aria2/get', 'verb' => 'POST'], ['name' => 'Settings#aria2Save', 'url' => '/personal/aria2/save', 'verb' => 'POST'], ['name' => 'Settings#aria2Delete', 'url' => '/personal/aria2/delete', 'verb' => 'POST'], + ['name' => 'Settings#youtubeGet', 'url' => '/personal/youtube-dl/get', 'verb' => 'POST'], + ['name' => 'Settings#youtubeSave', 'url' => '/personal/youtube-dl/save', 'verb' => 'POST'], + ['name' => 'Settings#youtubeDelete', 'url' => '/personal/youtube-dl/delete', 'verb' => 'POST'], ], ]; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index e80907c..a24d18f 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -7,7 +7,6 @@ use OCA\NCDownloader\Tools\Settings; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; -use OC_Util; class SettingsController extends Controller { @@ -34,13 +33,11 @@ class SettingsController extends Controller { $params = $this->request->getParams(); foreach ($params as $key => $value) { - if (substr($key, 0, 1) == '_') { - continue; - } - $this->save($key, $value); + $resp = $this->save($key, $value); } + return new JSONResponse($resp); } - /** + /** * @NoAdminRequired * @NoCSRFRequired */ @@ -54,15 +51,13 @@ class SettingsController extends Controller { $this->settings->setType($this->settings::TYPE['SYSTEM']); $params = $this->request->getParams(); - foreach ($params as $key => $value) { - if (substr($key, 0, 1) == '_') { - continue; - } - $this->save($key, $value); - } + foreach ($params as $key => $value) { + $resp = $this->save($key, $value); + } + return new JSONResponse($resp); } - /** + /** * @NoAdminRequired * @NoCSRFRequired */ @@ -70,26 +65,72 @@ class SettingsController extends Controller { $params = $this->request->getParams(); $data = Helper::filterData($params, Helper::aria2Options()); - $this->settings->save("custom_aria2_settings", json_encode($data)); + $resp = $this->settings->save("custom_aria2_settings", json_encode($data)); + return new JSONResponse($resp); } - /** + /** * @NoAdminRequired * @NoCSRFRequired */ public function aria2Delete() { - $saved = json_decode($this->settings->get("custom_aria2_settings"),1); + $saved = json_decode($this->settings->get("custom_aria2_settings"), 1); $params = $this->request->getParams(); $data = Helper::filterData($params, Helper::aria2Options()); foreach ($data as $key => $value) { unset($saved[$key]); } - $this->settings->save("custom_aria2_settings", json_encode($saved)); - return new JSONResponse($saved); + $resp = $this->settings->save("custom_aria2_settings", json_encode($saved)); + return new JSONResponse($resp); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + */ + public function youtubeGet() + { + $data = json_decode($this->settings->get("custom_youtube_dl_settings")); + return new JSONResponse($data); + } + + public function youtubeSave() + { + $params = $this->request->getParams(); + $data = array_filter($params, function ($key) { + return (bool) (!in_array(substr($key, 0, 1), ['_'])); + }, ARRAY_FILTER_USE_KEY); + $resp = $this->settings->save("custom_youtube_dl_settings", json_encode($data)); + return new JSONResponse($resp); + } + /** + * @NoAdminRequired + * @NoCSRFRequired + */ + public function youtubeDelete() + { + $saved = json_decode($this->settings->get("custom_youtube_dl_settings"), 1); + $params = $this->request->getParams(); + foreach ($data as $key => $value) { + unset($saved[$key]); + } + $resp = $this->settings->save("custom_youtube_dl_settings", json_encode($saved)); + return new JSONResponse($resp); } public function save($key, $value) { - $this->settings->save($key, $value); + //key starting with _ is invalid + if (substr($key, 0, 1) == '_') { + return; + } + $key = Helper::sanitize($key); + $value = Helper::sanitize($value); + try { + $this->settings->save($key, $value); + } catch (\Exception $e) { + return ['error' => $e->getMessage()]; + } + return ['message' => "Saved!"]; } } diff --git a/lib/Tools/Settings.php b/lib/Tools/Settings.php index e4357b0..631f499 100644 --- a/lib/Tools/Settings.php +++ b/lib/Tools/Settings.php @@ -49,6 +49,12 @@ class Settings extends AllConfig $settings = $this->allConfig->getUserValue($this->user, $this->appName, "custom_aria2_settings", ''); return json_decode($settings, 1); } + + public function getYoutube() + { + $settings = $this->get("custom_youtube_dl_settings"); + return json_decode($settings, 1); + } public function getAll() { if ($this->type === self::TYPE['APP']) { @@ -61,13 +67,18 @@ class Settings extends AllConfig } public function save($key, $value) { - if ($this->type == self::TYPE['USER'] && isset($this->user)) { - return $this->allConfig->setUserValue($this->user, $this->appName, $key, $value); - } else if ($this->type == self::TYPE['SYSTEM']) { - return $this->allConfig->setSystemValue($key, $value); - } else { - return $this->allConfig->setAppValue($this->appName, $key, $value); + try { + if ($this->type == self::TYPE['USER'] && isset($this->user)) { + $this->allConfig->setUserValue($this->user, $this->appName, $key, $value); + } else if ($this->type == self::TYPE['SYSTEM']) { + $this->allConfig->setSystemValue($key, $value); + } else { + $this->allConfig->setAppValue($this->appName, $key, $value); + } + } catch (\Exception $e) { + return ['error' => $e->getMessage]; } + return ['message' => "Saved!"]; } public function getAllAppValues() diff --git a/lib/Tools/Youtube.php b/lib/Tools/Youtube.php index 3d2ad0d..ebd70ac 100644 --- a/lib/Tools/Youtube.php +++ b/lib/Tools/Youtube.php @@ -30,15 +30,15 @@ class Youtube if (isset($binary) && @is_executable($binary)) { $this->bin = $binary; } else { - $this->bin = Helper::findBinaryPath('youtube-dl',__DIR__."/../../bin/youtube-dl"); + $this->bin = Helper::findBinaryPath('youtube-dl', __DIR__ . "/../../bin/youtube-dl"); } $this->setDownloadDir($downloadDir); if (!empty($settings)) { foreach ($settings as $key => $value) { if (empty($value)) { - $this->addOption($key); + $this->addOption($key, true); } else { - $this->setOption($key, $value); + $this->setOption($key, $value, true); } } } @@ -47,6 +47,7 @@ class Youtube } $this->setEnv('LANG', $lang); $this->addOption("--no-mtime"); + $this->addOption('--ignore-errors'); } public function setEnv($key, $val) @@ -59,8 +60,7 @@ class Youtube if (Helper::ffmpegInstalled()) { $this->addOption('--prefer-ffmpeg'); $this->addOption('--add-metadata'); - $this->addOption('--metadata-from-title'); - $this->addOption("%(artist)s-%(title)s"); + $this->setOption('--metadata-from-title',"%(artist)s-%(title)s"); $this->addOption('--extract-audio'); } $this->outTpl = "/%(id)s-%(title)s.m4a"; @@ -114,7 +114,7 @@ class Youtube { $this->downloadDir = $this->downloadDir ?? $this->defaultDir; $this->prependOption($this->downloadDir . $this->outTpl); - $this->prependOption("-o"); + $this->prependOption("--output"); $this->setUrl($url); $this->prependOption($this->bin); // $this->buildCMD(); @@ -136,16 +136,15 @@ class Youtube if ($this->audioOnly) { $this->audioMode(); } else { - $this->setOption('--format',$this->format); + $this->setOption('--format', $this->format); } $this->helper = YoutubeHelper::create(); $this->downloadDir = $this->downloadDir ?? $this->defaultDir; - $this->prependOption($this->downloadDir . $this->outTpl); - $this->prependOption("-o"); + $this->setOption("--output", $this->downloadDir . $this->outTpl); $this->setUrl($url); $this->prependOption($this->bin); $process = new Process($this->options, null, $this->env); - //\OC::$server->getLogger()->error($process->getWorkingDirectory(), ['app' => 'PHP']); + \OC::$server->getLogger()->error($process->getCommandLine(), ['app' => 'PHP']); $process->setTimeout($this->timeout); $process->run(function ($type, $buffer) use ($url) { if (Process::ERR === $type) { @@ -186,25 +185,27 @@ class Youtube public function setUrl($url) { - $this->addOption('-i'); - $this->addOption($url); + $this->prependOption($url); //$index = array_search('-i', $this->options); //array_splice($this->options, $index + 1, 0, $url); } - public function setOption($key, $value) + public function setOption($key, $value, $hyphens = false) { - $this->addOption($key); - $this->addOption($value); + $this->addOption($key, $hyphens); + $this->addOption($value, $hyphens); return $this; } - public function addOption($option) + public function addOption(String $option, $hyphens = false) { + if ($hyphens && substr($option, 0, 2) !== '--') { + $option = "--" . $option; + } array_push($this->options, $option); } public function forceIPV4() { - $this->addOption('-4'); + $this->addOption('force-ipv4', true); return $this; } diff --git a/lib/Tools/youtubedlOptions.php b/lib/Tools/youtubedlOptions.php new file mode 100644 index 0000000..f2cfa9f --- /dev/null +++ b/lib/Tools/youtubedlOptions.php @@ -0,0 +1,166 @@ + 'on download errors, for example to skip unavailable videos in a playlist', + 'abort-on-error' => 'downloading of further videos (in the playlist or the command line) if an error occurs', + 'dump-user-agent' => 'the current browser identification', + 'list-extractors' => 'all supported extractors', + 'extractor-descriptions' => 'descriptions of all supported extractors', + 'force-generic-extractor' => 'extraction to use the generic extractor', + 'default-search' => 'Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-', + 'ignore-config' => 'not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the', + 'config-location' => 'Location of the configuration file; either the path to the config or its containing directory.', + 'flat-playlist' => 'not extract the videos of a playlist, only list them.', + 'mark-watched' => 'videos watched (YouTube only)', + 'no-mark-watched' => 'not mark videos watched (YouTube only)', + 'no-color' => 'not emit color codes in output', + 'proxy' => 'Use the specified HTTP/HTTPS/SOCKS proxy. To enable SOCKS proxy, specify a proper scheme. For example', + 'socket-timeout' => 'Time to wait before giving up, in seconds', + 'source-address' => 'Client-side IP address to bind to', + 'force-ipv4' => 'all connections via IPv4', + 'force-ipv6' => 'all connections via IPv6', + 'geo-verification-proxy' => 'Use this proxy to verify the IP address for some geo-restricted sites. The default proxy specified by --proxy (or', + 'geo-bypass' => 'geographic restriction via faking X-Forwarded-For HTTP header', + 'no-geo-bypass' => 'not bypass geographic restriction via faking X-Forwarded-For HTTP header', + 'geo-bypass-country' => 'Force bypass geographic restriction with explicitly provided two-letter ISO 3166-2 country code', + 'playlist-start' => 'Playlist video to start at (default is 1)', + 'playlist-end' => 'Playlist video to end at (default is last)', + 'match-title' => 'Download only matching titles (regex or caseless sub-string)', + 'reject-title' => 'Skip download for matching titles (regex or caseless sub-string)', + 'max-downloads' => 'Abort after downloading NUMBER files', + 'min-filesize' => 'Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)', + 'max-filesize' => 'Do not download any videos larger than SIZE (e.g. 50k or 44.6m)', + 'date' => 'Download only videos uploaded in this date', + 'datebefore' => 'Download only videos uploaded on or before this date (i.e. inclusive)', + 'dateafter' => 'Download only videos uploaded on or after this date (i.e. inclusive)', + 'min-views' => 'Do not download any videos with less than COUNT views', + 'max-views' => 'Do not download any videos with more than COUNT views', + 'match-filter' => 'Generic video filter. Specify any key (see the "OUTPUT TEMPLATE" for a list of available keys) to match if the key', + 'no-playlist' => 'only the video, if the URL refers to a video and a playlist.', + 'yes-playlist' => 'the playlist, if the URL refers to a video and a playlist.', + 'age-limit' => 'Download only videos suitable for the given age', + 'download-archive' => 'Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.', + 'include-ads' => 'advertisements as well (experimental)', + 'limit-rate' => 'Maximum download rate in bytes per second (e.g. 50K or 4.2M)', + 'retries' => 'Number of retries (default is 10), or "infinite".', + 'fragment-retries' => 'Number of retries for a fragment (default is 10), or "infinite" (DASH, hlsnative and ISM)', + 'skip-unavailable-fragments' => 'unavailable fragments (DASH, hlsnative and ISM)', + 'abort-on-unavailable-fragment' => 'downloading when some fragment is not available', + 'keep-fragments' => 'downloaded fragments on disk after downloading is finished; fragments are erased by default', + 'buffer-size' => 'Size of download buffer (e.g. 1024 or 16K) (default is 1024)', + 'no-resize-buffer' => 'not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial', + 'http-chunk-size' => 'Size of a chunk for chunk-based HTTP downloading (e.g. 10485760 or 10M) (default is disabled). May be useful for', + 'playlist-reverse' => 'playlist videos in reverse order', + 'playlist-random' => 'playlist videos in random order', + 'xattr-set-filesize' => 'file xattribute ytdl.filesize with expected file size', + 'hls-prefer-native' => 'the native HLS downloader instead of ffmpeg', + 'hls-prefer-ffmpeg' => 'ffmpeg instead of the native HLS downloader', + 'hls-use-mpegts' => 'the mpegts container for HLS videos, allowing to play the video while downloading (some players may not be able', + 'external-downloader' => 'Use the specified external downloader. Currently supports aria2c,avconv,axel,curl,ffmpeg,httpie,wget', + 'external-downloader-args' => 'Give these arguments to the external downloader', + 'batch-file' => 'File containing URLs to download (\'-\' for stdin), one URL per line. Lines starting with \'#\', \';\' or \']\' are', + 'id' => 'only video ID in file name', + 'output' => 'Output filename template, see the "OUTPUT TEMPLATE" for all the info', + 'output-na-placeholder' => 'Placeholder value for unavailable meta fields in output filename template (default is "NA")', + 'autonumber-start' => 'Specify the start value for %(autonumber)s (default is 1)', + 'restrict-filenames' => 'filenames to only ASCII characters, and avoid "&" and spaces in filenames', + 'no-overwrites' => 'not overwrite files', + 'continue' => 'resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.', + 'no-continue' => 'not resume partially downloaded files (restart from beginning)', + 'no-part' => 'not use .part files - write directly into output file', + 'no-mtime' => 'not use the Last-modified header to set the file modification time', + 'write-description' => 'video description to a .description file', + 'write-info-json' => 'video metadata to a .info.json file', + 'write-annotations' => 'video annotations to a .annotations.xml file', + 'load-info-json' => 'JSON file containing the video information (created with the "--write-info-json" option)', + 'cookies' => 'File to read cookies from and dump cookie jar in', + 'cache-dir' => 'Location in the filesystem where youtube-dl can store some downloaded information permanently. By default', + 'no-cache-dir' => 'filesystem caching', + 'rm-cache-dir' => 'all filesystem cache files', + 'write-thumbnail' => 'thumbnail image to disk', + 'write-all-thumbnails' => 'all thumbnail image formats to disk', + 'list-thumbnails' => 'and list all available thumbnail formats', + 'quiet' => 'quiet mode', + 'no-warnings' => 'warnings', + 'simulate' => 'not download the video and do not write anything to disk', + 'skip-download' => 'not download the video', + 'get-url' => 'Simulate, quiet but print URL', + 'get-title' => 'Simulate, quiet but print title', + 'get-id' => 'Simulate, quiet but print id', + 'get-thumbnail' => 'Simulate, quiet but print thumbnail URL', + 'get-description' => 'Simulate, quiet but print video description', + 'get-duration' => 'Simulate, quiet but print video length', + 'get-filename' => 'Simulate, quiet but print output filename', + 'get-format' => 'Simulate, quiet but print output format', + 'dump-json' => 'Simulate, quiet but print JSON information. See the "OUTPUT TEMPLATE" for a description of available keys.', + 'dump-single-json' => 'Simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump', + 'print-json' => 'quiet and print the video information as JSON (video is still being downloaded).', + 'newline' => 'progress bar as new lines', + 'no-progress' => 'not print progress bar', + 'console-title' => 'progress in console titlebar', + 'verbose' => 'various debugging information', + 'dump-pages' => 'downloaded pages encoded using base64 to debug problems (very verbose)', + 'write-pages' => 'downloaded intermediary pages to files in the current directory to debug problems', + 'print-traffic' => 'sent and read HTTP traffic', + 'call-home' => 'the youtube-dl server for debugging', + 'no-call-home' => 'NOT contact the youtube-dl server for debugging', + 'encoding' => 'Force the specified encoding (experimental)', + 'no-check-certificate' => 'HTTPS certificate validation', + 'prefer-insecure' => 'an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)', + 'user-agent' => 'Specify a custom user agent', + 'referer' => 'Specify a custom referer, use if the video access is restricted to one domain', + 'bidi-workaround' => 'around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH', + 'sleep-interval' => 'Number of seconds to sleep before each download when used alone or a lower bound of a range for randomized sleep', + 'max-sleep-interval' => 'Upper bound of a range for randomized sleep before each download (maximum possible number of seconds to sleep).', + 'format' => 'Video format code, see the "FORMAT SELECTION" for all the info', + 'all-formats' => 'all available video formats', + 'prefer-free-formats' => 'free video formats unless a specific one is requested', + 'list-formats' => 'all available formats of requested videos', + 'youtube-skip-dash-manifest' => 'not download the DASH manifests and related data on YouTube videos', + 'merge-output-format' => 'If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm,', + 'write-sub' => 'subtitle file', + 'write-auto-sub' => 'automatically generated subtitle file (YouTube only)', + 'all-subs' => 'all the available subtitles of the video', + 'list-subs' => 'all available subtitles for the video', + 'sub-format' => 'Subtitle format, accepts formats preference, for example: "srt" or "ass/srt/best"', + 'sub-lang' => 'Languages of the subtitles to download (optional) separated by commas, use --list-subs for available language tags', + 'username' => 'Login with this account ID', + 'password' => 'Account password. If this option is left out, youtube-dl will ask interactively.', + 'twofactor' => 'Two-factor authentication code', + 'netrc' => '.netrc authentication data', + 'video-password' => 'Video password (vimeo, youku)', + 'ap-mso' => 'Adobe Pass multiple-system operator (TV provider) identifier, use --ap-list-mso for a list of available MSOs', + 'ap-username' => 'Multiple-system operator account login', + 'ap-password' => 'Multiple-system operator account password. If this option is left out, youtube-dl will ask interactively.', + 'ap-list-mso' => 'all supported multiple-system operators', + 'extract-audio' => 'video files to audio-only files (requires ffmpeg/avconv and ffprobe/avprobe)', + 'audio-format' => 'Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "best" by default; No effect', + 'audio-quality' => 'Specify ffmpeg/avconv audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate', + 'recode-video' => 'Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)', + 'postprocessor-args' => 'Give these arguments to the postprocessor', + 'keep-video' => 'the video file on disk after the post-processing; the video is erased by default', + 'no-post-overwrites' => 'not overwrite post-processed files; the post-processed files are overwritten by default', + 'embed-subs' => 'subtitles in the video (only for mp4, webm and mkv videos)', + 'embed-thumbnail' => 'thumbnail in the audio as cover art', + 'add-metadata' => 'metadata to the video file', + 'metadata-from-title' => 'Parse additional metadata like song title / artist from the video title. The format syntax is the same as --output.', + 'xattrs' => 'metadata to the video file\'s xattrs (using dublin core and xdg standards)', + 'fixup' => 'Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning),', + 'prefer-avconv' => 'avconv over ffmpeg for running the postprocessors', + 'prefer-ffmpeg' => 'ffmpeg over avconv for running the postprocessors (default)', + 'ffmpeg-location' => 'Location of the ffmpeg/avconv binary; either the path to the binary or its containing directory.', + 'exec' => 'Execute a command on the file after downloading and post-processing, similar to find\'s -exec syntax. Example:', + 'convert-subs' => 'Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)', + ); + } +} diff --git a/src/css/settings.scss b/src/css/settings.scss new file mode 100644 index 0000000..9b9a5f4 --- /dev/null +++ b/src/css/settings.scss @@ -0,0 +1,33 @@ +.ncdownloader-personal-settings,.ncdownloader-admin-settings { + position: relative; + + #ncdownloader-message-banner { + position : fixed; + top : 50px; + text-align : center; + padding : 15px; + margin-bottom : 20px; + border : 1px solid transparent; + border-radius : 4px; + text-shadow : 0 1px 0 rgba(255, 255, 255, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + box-shadow : inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + } + + #ncdownloader-message-banner.success, + .message-banner.success { + color : #3c763d; + background-color: #dff0d8; + border-color : #d6e9c6; + width : 100%; + } + + #ncdownloader-message-banner.error, + .message-banner.error { + color : #a94442; + background-color: #f2dede; + border-color : #ebccd1; + width : 100%; + } + +} \ No newline at end of file diff --git a/src/lib/settingsForm.js b/src/lib/settingsForm.js index 96c477a..95c392c 100644 --- a/src/lib/settingsForm.js +++ b/src/lib/settingsForm.js @@ -6,6 +6,10 @@ class settingsForm { static getInstance() { return new this(); } + setParent(selector) { + this.parent = selector; + return this; + } create(parent, element) { let label = this._createLabel(element.name, element.id) let input = this._createInput(element); @@ -15,11 +19,8 @@ class settingsForm { [label, input, cancelBtn].forEach(ele => { container.appendChild(ele); }) - let button; - if (button = parent.querySelector('button.add-custom-aria2-settings')) { - return parent.insertBefore(container, button); - } - return parent.appendChild(container); + + return parent.prepend(container); } createCustomInput(keyId, valueId) { @@ -85,14 +86,14 @@ class settingsForm { _createInput(data) { let input = document.createElement('input'); let type = data.type || "text"; - let placeholder = data.placeholder || ''; - let value = data.value || placeholder; + let placeholder = data.placeholder || 'Leave empty if no value needed'; + let value = data.value || ''; input.setAttribute('type', type); input.setAttribute('id', data.id); input.setAttribute("name", data.name || data.id); if (type === 'text') { input.setAttribute('value', value); - input.setAttribute('placeholder', value); + input.setAttribute('placeholder', placeholder); } input.classList.add('form-input-' + type); return input; diff --git a/src/settings.js b/src/settings.js index e1b8b08..cf74369 100644 --- a/src/settings.js +++ b/src/settings.js @@ -7,67 +7,76 @@ import settingsForm from './lib/settingsForm' import autoComplete from './lib/autoComplete'; import eventHandler from './lib/eventHandler'; import aria2Options from './utils/aria2Options'; +import { names as ytdOptions } from './utils/youtubedlOptions'; import helper from './utils/helper'; import './css/autoComplete.css' -import './css/style.scss' - - 'use strict'; window.addEventListener('DOMContentLoaded', function () { - - eventHandler.add('click', '.ncdownloader-admin-settings', 'input[type="button"]', function (event) { - event.stopPropagation(); - OC_msg.startSaving('#ncdownloader-message-banner',"Saving"); - const target = this.getAttribute("data-rel"); - let inputData = helper.getData(target); - const path = inputData.url || "/apps/ncdownloader/admin/save"; - let url = generateUrl(path); - Http.getInstance(url).setData(helper.getData(target)).setHandler(function () { - OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK"); - }).send(); - }); - eventHandler.add('click', '.ncdownloader-personal-settings', 'input[type="button"]', function (event) { - event.preventDefault(); - event.stopPropagation(); - if (event.target.matches('.custom-aria2-settings-container')) { + let customOptions = ['ncd_downloader_dir', 'ncd_torrents_dir', 'ncd_seed_ratio', 'ncd_seed_time', 'ncd_rpctoken', 'ncd_yt_binary', 'ncd_aria2_binary']; + const saveHandler = (e, name) => { + e.stopImmediatePropagation(); + let element = e.target; + let data = helper.getData(element.getAttribute("data-rel")); + let url = generateUrl(data.path); + delete data.path; + OC_msg.startSaving('#ncdownloader-message-banner'); + helper.makePair(data, name); + let badOptions = []; + if (name === 'youtube-dl-settings') { + for (let key in data) { + if (!ytdOptions.includes(key) && !customOptions.includes(key)) { + delete data[key]; + badOptions.push(key) + } + } + } else { + for (let key in data) { + if (!aria2Options.includes(key) && !customOptions.includes(key)) { + delete data[key]; + badOptions.push(key) + } + } + } + if (badOptions.length > 0) { + OC_msg.finishedError('#ncdownloader-message-banner', 'invalid options: ' + badOptions.join(',')); return; } - OC_msg.startSaving('#ncdownloader-message-banner'); - const target = this.getAttribute("data-rel"); - let inputData = helper.getData(target); - const path = inputData.url || "/apps/ncdownloader/personal/save"; - let url = generateUrl(path); - Http.getInstance(url).setData(inputData).setHandler(function (data) { - OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK"); + Http.getInstance(url).setData(data).setHandler(function (data) { + if (data.hasOwnProperty("error")) + OC_msg.finishedError('#ncdownloader-message-banner', data.error); + else if (data.hasOwnProperty("message")) + OC_msg.finishedSuccess('#ncdownloader-message-banner', data.message); + else { + OC_msg.finishedSuccess('#ncdownloader-message-banner', "DONE"); + } }).send(); - }); - eventHandler.add('click', '#custom-aria2-settings-container', "button.add-custom-aria2-settings", function (e) { + } + const addOption = (e, name, options) => { e.preventDefault(); e.stopPropagation(); + let baseName = `${name}-settings`; let element = e.target; - let selector = "#aria2-settings-key-1"; + let selector = `#${baseName}-key-1`; let form = settingsForm.getInstance(); let nodeList, key, value; - nodeList = document.querySelectorAll("[id^='aria2-settings-key']") + nodeList = document.querySelectorAll(`[id^='${baseName}-key']`) if (nodeList.length === 0) { - key = "aria2-settings-key-1"; - value = "aria2-settings-value-1"; + key = `${baseName}-key-1`; + value = `${baseName}-value-1`; } else { let index = nodeList.length + 1; - key = "aria2-settings-key-" + index; - value = "aria2-settings-value-" + index; - selector = "[id^='aria2-settings-key']"; + key = `${baseName}-key-${index}`; + value = `${baseName}-value-${index}`; + selector = `[id^='${baseName}-key']`; } element.before(form.createCustomInput(key, value)); - //appended the latest one - nodeList = document.querySelectorAll("[id^='aria2-settings-key']") try { autoComplete.getInstance({ - selector: (nodeList.length !== 0) ? nodeList : selector, + selector: `[id^='${baseName}-key']`, minChars: 1, source: function (term, suggest) { term = term.toLowerCase(); - let suggestions = [], data = aria2Options; + let suggestions = [], data = options; for (const item of data) { if (item.toLowerCase().indexOf(term, 0) !== -1) { suggestions.push(item); @@ -77,22 +86,19 @@ window.addEventListener('DOMContentLoaded', function () { } }).run(); } catch (error) { - console.error(error); + OC_msg.finishedError('#ncdownloader-message-banner', error);; } } - ) - eventHandler.add("click", "#custom-aria2-settings-container", "button.save-custom-aria2-settings", function (e) { - e.stopImmediatePropagation(); - let data = helper.getData(this.getAttribute("data-rel")); - let url = generateUrl(data.path); - delete data.path; - OC_msg.startSaving('.message-banner'); - helper.makePair(data); - Http.getInstance(url).setData(data).setHandler(function (data) { - OC_msg.finishedSuccess('.message-banner', "OK"); - }).send(); - }) + eventHandler.add('click', '.ncdownloader-admin-settings', 'input[type="button"]', (e) => saveHandler(e)); + eventHandler.add('click', '.ncdownloader-personal-settings', 'input[type="button"]', (e) => saveHandler(e)); + eventHandler.add("click", "#custom-aria2-settings-container", "button.save-custom-aria2-settings", (e) => saveHandler(e)) + eventHandler.add("click", "#custom-youtube-dl-settings-container", "button.save-custom-youtube-dl-settings", (e) => saveHandler(e, 'youtube-dl-settings')) + + eventHandler.add('click', '#custom-aria2-settings-container', "button.add-custom-aria2-settings", (e) => addOption(e, 'aria2', aria2Options)) + eventHandler.add('click', '#custom-youtube-dl-settings-container', "button.add-custom-youtube-dl-settings", (e) => addOption(e, 'youtube-dl', ytdOptions)) + + eventHandler.add('click', '.ncdownloader-personal-settings', 'button.icon-close', function (e) { e.stopImmediatePropagation(); e.preventDefault(); @@ -109,4 +115,15 @@ window.addEventListener('DOMContentLoaded', function () { } settingsForm.getInstance().render(input); }).send(); + + Http.getInstance(generateUrl("/apps/ncdownloader/personal/youtube-dl/get")).setHandler(function (data) { + if (!data) { + return; + } + let input = []; + for (let key in data) { + input.push({ name: key, value: data[key], id: key }); + } + settingsForm.getInstance().setParent("custom-youtube-dl-settings-container").render(input); + }).send(); }); \ No newline at end of file diff --git a/src/utils/helper.js b/src/utils/helper.js index b9810aa..0fbd785 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -115,7 +115,7 @@ const helper = { let index; if ((index = key.indexOf(prefix + "-key-")) !== -1) { let valueKey = prefix + "-value-" + key.substring(key.lastIndexOf('-') + 1); - if (!data[valueKey]) continue; + if (data[valueKey] === undefined) continue; let newkey = data[key]; data[newkey] = data[valueKey]; delete data[key]; diff --git a/src/utils/youtubedlOptions.js b/src/utils/youtubedlOptions.js new file mode 100644 index 0000000..a716436 --- /dev/null +++ b/src/utils/youtubedlOptions.js @@ -0,0 +1,3 @@ +const options = { "ignore-errors": "on download errors, for example to skip unavailable videos in a playlist", "abort-on-error": "downloading of further videos (in the playlist or the command line) if an error occurs", "dump-user-agent": "the current browser identification", "list-extractors": "all supported extractors", "extractor-descriptions": "descriptions of all supported extractors", "force-generic-extractor": "extraction to use the generic extractor", "default-search": "Use this prefix for unqualified URLs. For example \"gvsearch2:\" downloads two videos from google videos for youtube-", "ignore-config": "not read configuration files. When given in the global configuration file \/etc\/youtube-dl.conf: Do not read the", "config-location": "Location of the configuration file; either the path to the config or its containing directory.", "flat-playlist": "not extract the videos of a playlist, only list them.", "mark-watched": "videos watched (YouTube only)", "no-mark-watched": "not mark videos watched (YouTube only)", "no-color": "not emit color codes in output", "proxy": "Use the specified HTTP\/HTTPS\/SOCKS proxy. To enable SOCKS proxy, specify a proper scheme. For example", "socket-timeout": "Time to wait before giving up, in seconds", "source-address": "Client-side IP address to bind to", "force-ipv4": "all connections via IPv4", "force-ipv6": "all connections via IPv6", "geo-verification-proxy": "Use this proxy to verify the IP address for some geo-restricted sites. The default proxy specified by --proxy (or", "geo-bypass": "geographic restriction via faking X-Forwarded-For HTTP header", "no-geo-bypass": "not bypass geographic restriction via faking X-Forwarded-For HTTP header", "geo-bypass-country": "Force bypass geographic restriction with explicitly provided two-letter ISO 3166-2 country code", "playlist-start": "Playlist video to start at (default is 1)", "playlist-end": "Playlist video to end at (default is last)", "match-title": "Download only matching titles (regex or caseless sub-string)", "reject-title": "Skip download for matching titles (regex or caseless sub-string)", "max-downloads": "Abort after downloading NUMBER files", "min-filesize": "Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)", "max-filesize": "Do not download any videos larger than SIZE (e.g. 50k or 44.6m)", "date": "Download only videos uploaded in this date", "datebefore": "Download only videos uploaded on or before this date (i.e. inclusive)", "dateafter": "Download only videos uploaded on or after this date (i.e. inclusive)", "min-views": "Do not download any videos with less than COUNT views", "max-views": "Do not download any videos with more than COUNT views", "match-filter": "Generic video filter. Specify any key (see the \"OUTPUT TEMPLATE\" for a list of available keys) to match if the key", "no-playlist": "only the video, if the URL refers to a video and a playlist.", "yes-playlist": "the playlist, if the URL refers to a video and a playlist.", "age-limit": "Download only videos suitable for the given age", "download-archive": "Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.", "include-ads": "advertisements as well (experimental)", "limit-rate": "Maximum download rate in bytes per second (e.g. 50K or 4.2M)", "retries": "Number of retries (default is 10), or \"infinite\".", "fragment-retries": "Number of retries for a fragment (default is 10), or \"infinite\" (DASH, hlsnative and ISM)", "skip-unavailable-fragments": "unavailable fragments (DASH, hlsnative and ISM)", "abort-on-unavailable-fragment": "downloading when some fragment is not available", "keep-fragments": "downloaded fragments on disk after downloading is finished; fragments are erased by default", "buffer-size": "Size of download buffer (e.g. 1024 or 16K) (default is 1024)", "no-resize-buffer": "not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial", "http-chunk-size": "Size of a chunk for chunk-based HTTP downloading (e.g. 10485760 or 10M) (default is disabled). May be useful for", "playlist-reverse": "playlist videos in reverse order", "playlist-random": "playlist videos in random order", "xattr-set-filesize": "file xattribute ytdl.filesize with expected file size", "hls-prefer-native": "the native HLS downloader instead of ffmpeg", "hls-prefer-ffmpeg": "ffmpeg instead of the native HLS downloader", "hls-use-mpegts": "the mpegts container for HLS videos, allowing to play the video while downloading (some players may not be able", "external-downloader": "Use the specified external downloader. Currently supports aria2c,avconv,axel,curl,ffmpeg,httpie,wget", "external-downloader-args": "Give these arguments to the external downloader", "batch-file": "File containing URLs to download ('-' for stdin), one URL per line. Lines starting with '#', ';' or ']' are", "id": "only video ID in file name", "output": "Output filename template, see the \"OUTPUT TEMPLATE\" for all the info", "output-na-placeholder": "Placeholder value for unavailable meta fields in output filename template (default is \"NA\")", "autonumber-start": "Specify the start value for %(autonumber)s (default is 1)", "restrict-filenames": "filenames to only ASCII characters, and avoid \"&\" and spaces in filenames", "no-overwrites": "not overwrite files", "continue": "resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.", "no-continue": "not resume partially downloaded files (restart from beginning)", "no-part": "not use .part files - write directly into output file", "no-mtime": "not use the Last-modified header to set the file modification time", "write-description": "video description to a .description file", "write-info-json": "video metadata to a .info.json file", "write-annotations": "video annotations to a .annotations.xml file", "load-info-json": "JSON file containing the video information (created with the \"--write-info-json\" option)", "cookies": "File to read cookies from and dump cookie jar in", "cache-dir": "Location in the filesystem where youtube-dl can store some downloaded information permanently. By default", "no-cache-dir": "filesystem caching", "rm-cache-dir": "all filesystem cache files", "write-thumbnail": "thumbnail image to disk", "write-all-thumbnails": "all thumbnail image formats to disk", "list-thumbnails": "and list all available thumbnail formats", "quiet": "quiet mode", "no-warnings": "warnings", "simulate": "not download the video and do not write anything to disk", "skip-download": "not download the video", "get-url": "Simulate, quiet but print URL", "get-title": "Simulate, quiet but print title", "get-id": "Simulate, quiet but print id", "get-thumbnail": "Simulate, quiet but print thumbnail URL", "get-description": "Simulate, quiet but print video description", "get-duration": "Simulate, quiet but print video length", "get-filename": "Simulate, quiet but print output filename", "get-format": "Simulate, quiet but print output format", "dump-json": "Simulate, quiet but print JSON information. See the \"OUTPUT TEMPLATE\" for a description of available keys.", "dump-single-json": "Simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump", "print-json": "quiet and print the video information as JSON (video is still being downloaded).", "newline": "progress bar as new lines", "no-progress": "not print progress bar", "console-title": "progress in console titlebar", "verbose": "various debugging information", "dump-pages": "downloaded pages encoded using base64 to debug problems (very verbose)", "write-pages": "downloaded intermediary pages to files in the current directory to debug problems", "print-traffic": "sent and read HTTP traffic", "call-home": "the youtube-dl server for debugging", "no-call-home": "NOT contact the youtube-dl server for debugging", "encoding": "Force the specified encoding (experimental)", "no-check-certificate": "HTTPS certificate validation", "prefer-insecure": "an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)", "user-agent": "Specify a custom user agent", "referer": "Specify a custom referer, use if the video access is restricted to one domain", "bidi-workaround": "around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH", "sleep-interval": "Number of seconds to sleep before each download when used alone or a lower bound of a range for randomized sleep", "max-sleep-interval": "Upper bound of a range for randomized sleep before each download (maximum possible number of seconds to sleep).", "format": "Video format code, see the \"FORMAT SELECTION\" for all the info", "all-formats": "all available video formats", "prefer-free-formats": "free video formats unless a specific one is requested", "list-formats": "all available formats of requested videos", "youtube-skip-dash-manifest": "not download the DASH manifests and related data on YouTube videos", "merge-output-format": "If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm,", "write-sub": "subtitle file", "write-auto-sub": "automatically generated subtitle file (YouTube only)", "all-subs": "all the available subtitles of the video", "list-subs": "all available subtitles for the video", "sub-format": "Subtitle format, accepts formats preference, for example: \"srt\" or \"ass\/srt\/best\"", "sub-lang": "Languages of the subtitles to download (optional) separated by commas, use --list-subs for available language tags", "username": "Login with this account ID", "password": "Account password. If this option is left out, youtube-dl will ask interactively.", "twofactor": "Two-factor authentication code", "netrc": ".netrc authentication data", "video-password": "Video password (vimeo, youku)", "ap-mso": "Adobe Pass multiple-system operator (TV provider) identifier, use --ap-list-mso for a list of available MSOs", "ap-username": "Multiple-system operator account login", "ap-password": "Multiple-system operator account password. If this option is left out, youtube-dl will ask interactively.", "ap-list-mso": "all supported multiple-system operators", "extract-audio": "video files to audio-only files (requires ffmpeg\/avconv and ffprobe\/avprobe)", "audio-format": "Specify audio format: \"best\", \"aac\", \"flac\", \"mp3\", \"m4a\", \"opus\", \"vorbis\", or \"wav\"; \"best\" by default; No effect", "audio-quality": "Specify ffmpeg\/avconv audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate", "recode-video": "Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)", "postprocessor-args": "Give these arguments to the postprocessor", "keep-video": "the video file on disk after the post-processing; the video is erased by default", "no-post-overwrites": "not overwrite post-processed files; the post-processed files are overwritten by default", "embed-subs": "subtitles in the video (only for mp4, webm and mkv videos)", "embed-thumbnail": "thumbnail in the audio as cover art", "add-metadata": "metadata to the video file", "metadata-from-title": "Parse additional metadata like song title \/ artist from the video title. The format syntax is the same as --output.", "xattrs": "metadata to the video file's xattrs (using dublin core and xdg standards)", "fixup": "Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning),", "prefer-avconv": "avconv over ffmpeg for running the postprocessors", "prefer-ffmpeg": "ffmpeg over avconv for running the postprocessors (default)", "ffmpeg-location": "Location of the ffmpeg\/avconv binary; either the path to the binary or its containing directory.", "exec": "Execute a command on the file after downloading and post-processing, similar to find's -exec syntax. Example:", "convert-subs": "Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)" } +const names = Object.keys(options); +export { names, options } diff --git a/templates/settings/Admin.php b/templates/settings/Admin.php index 3503517..90bf3f4 100644 --- a/templates/settings/Admin.php +++ b/templates/settings/Admin.php @@ -1,13 +1,12 @@