diff --git a/appinfo/info.xml b/appinfo/info.xml
index a7972a4..48c43e0 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -5,7 +5,7 @@
Aria2 and youtube-dl web gui for nextcloud
built-in torrent search;Start and stop Aria2 process, manage Aria2 from the web;
Download videos from youtube, twitter and other sites;
- 0.1.3
+ 0.1.4
agpl
jiaxinhuang
NCDownloader
diff --git a/css/style.css b/css/style.css
index a5502fa..0481d96 100644
--- a/css/style.css
+++ b/css/style.css
@@ -11,6 +11,7 @@ a {
display : flex;
flex-flow: row nowrap;
}
+
#ncdownloader-form-wrapper .form-input-wrapper select,
#ncdownloader-form-wrapper .form-input-wrapper input {
justify-content: left;
@@ -26,6 +27,7 @@ a:focus {
outline : 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
+
div .number {
background : none repeat scroll 0 0 #5f5853;
border-radius: 999px;
@@ -39,9 +41,13 @@ div .number {
padding : 0 8px;
}
+#ncdownloader-settings-form input[type=text] {
+ width: 160px;
+}
+
#ncdownloader-form-wrapper input[type=text] {
padding: 0px 5px;
- flex:auto;
+ flex : auto;
}
#ncdownloader-table-data .table-cell {
@@ -161,6 +167,18 @@ div .number {
color: red;
}
+.checkboxes label {
+ display: inline-block;
+ padding-right: 10px;
+ white-space: nowrap;
+ }
+ .checkboxes input {
+ vertical-align: middle;
+ }
+ .checkboxes label span {
+ vertical-align: middle;
+ }
+
@media only screen and (min-width: 800px) {}
@media only screen and (max-width: 1024px) {
@@ -168,6 +186,9 @@ div .number {
position : relative;
margin-top: 2em;
}
+ #ncdownloader-settings-form input[type=text] {
+ width: 135px;
+ }
}
diff --git a/lib/Controller/MainController.php b/lib/Controller/MainController.php
index 0c8bb5a..f917bd9 100644
--- a/lib/Controller/MainController.php
+++ b/lib/Controller/MainController.php
@@ -86,7 +86,7 @@ class MainController extends Controller
'data' => serialize(['link' => $url]),
];
$this->dbconn->save($data);
- $resp = ['gid' => $result, 'file' => $filename, 'result' => $result];
+ $resp = ['message' => $filename, 'result' => $result,'file' => $filename];
return $resp;
}
diff --git a/lib/Controller/YoutubeController.php b/lib/Controller/YoutubeController.php
index 2d74b95..c3ff4ba 100644
--- a/lib/Controller/YoutubeController.php
+++ b/lib/Controller/YoutubeController.php
@@ -34,7 +34,10 @@ class YoutubeController extends Controller
$this->aria2->init();
$this->tablename = $this->dbconn->queryBuilder->getTableName("ncdownloader_info");
}
-
+ /**
+ * @NoAdminRequired
+ *
+ */
public function Index()
{
$data = $this->dbconn->getYoutubeByUid($this->uid);
@@ -66,12 +69,12 @@ class YoutubeController extends Controller
$resp['counter'] = ['youtube-dl' => count($data)];
return new JSONResponse($resp);
}
-
public function Download()
{
$params = array();
$url = trim($this->request->getParam('form_input_text'));
$yt = $this->youtube;
+ $yt->audioOnly = (bool) $this->request->getParam('audioOnly');
if (!$yt->isInstalled()) {
return new JSONResponse($this->installYTD());
}
@@ -102,7 +105,7 @@ class YoutubeController extends Controller
}
if ($this->dbconn->deleteByGid($gid)) {
- return new JSONResponse(['message' => $gid . " deleted!"]);
+ return new JSONResponse(['message' => $gid . " Deleted!"]);
}
}
@@ -131,7 +134,7 @@ class YoutubeController extends Controller
'data' => serialize(['link' => $url]),
];
$this->dbconn->save($data);
- $resp = ['gid' => $result, 'file' => $filename, 'result' => $result];
+ $resp = ['message' => $filename, 'result' => $result];
return $resp;
}
diff --git a/lib/Tools/Helper.php b/lib/Tools/Helper.php
index 22ce44f..20f3ef1 100644
--- a/lib/Tools/Helper.php
+++ b/lib/Tools/Helper.php
@@ -284,5 +284,9 @@ class Helper
}
return sprintf('%04x%04x%04x%04x%04x%04x%04x%04x', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479));
}
+ public static function ffmpegInstalled()
+ {
+ return (bool) self::findBinaryPath('ffmpeg');
+ }
}
diff --git a/lib/Tools/Youtube.php b/lib/Tools/Youtube.php
index 56e6b9f..ef358e3 100644
--- a/lib/Tools/Youtube.php
+++ b/lib/Tools/Youtube.php
@@ -9,8 +9,9 @@ use Symfony\Component\Process\Process;
class Youtube
{
private $ipv4Only;
- private $audioOnly = 0;
- private $audioFormat, $videoFormat = 'mp4';
+ public $audioOnly = 0;
+ private $audioFormat = 'm4a', $videoFormat;
+ private $format = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best';
private $options = [];
private $downloadDir;
private $timeout = 60 * 60 * 15;
@@ -18,15 +19,25 @@ class Youtube
private $defaultDir = "/tmp/downloads";
private $env = [];
- public function __construct($config)
+ public function __construct(array $options)
{
- $config += ['downloadDir' => '/tmp/downloads'];
- $this->bin = $config['binary'] ?? Helper::findBinaryPath('youtube-dl');
- $this->init();
- $this->setDownloadDir($config['downloadDir']);
+ $options += ['downloadDir' => '/tmp/downloads', 'settings' => []];
+ $this->init($options);
}
- public function init()
+ public function init(array $options)
{
+ $this->bin = Helper::findBinaryPath('youtube-dl');
+ extract($options);
+ $this->setDownloadDir($downloadDir);
+ if (!empty($settings)) {
+ foreach ($settings as $key => $value) {
+ if (empty($value)) {
+ $this->addOption($key);
+ } else {
+ $this->setOption($key, $value);
+ }
+ }
+ }
if (empty($lang = getenv('LANG')) || strpos(strtolower($lang), 'utf-8') === false) {
$lang = 'C.UTF-8';
}
@@ -39,6 +50,25 @@ class Youtube
$this->env[$key] = $val;
}
+ public function audioMode()
+ {
+ if (Helper::ffmpegInstalled()) {
+ $this->addOption('--prefer-ffmpeg');
+ $this->addOption('--add-metadata');
+ $this->addOption('--metadata-from-title');
+ $this->addOption("%(artist)s - %(title)s");
+ $this->addOption('--audio-format');
+ $this->addOption($this->audioFormat);
+ }
+ $this->addOption('--extract-audio');
+ return $this;
+ }
+
+ public function setAudioQuality($value = 0)
+ {
+ $this->addOption('--audio-quality', $value);
+ }
+
public function GetUrlOnly()
{
$this->addOption('--get-filename');
@@ -61,7 +91,7 @@ class Youtube
return $this->getDownloadDir;
}
- public function prependOption($option)
+ public function prependOption(string $option)
{
array_unshift($this->options, $option);
}
@@ -89,6 +119,13 @@ class Youtube
public function download($url)
{
+ if ($this->audioOnly) {
+ $this->audioMode();
+ $this->outTpl = "/%(id)s-%(title)s." . $this->audioFormat;
+ } else {
+ $this->addOption('--format');
+ $this->addOption($this->format);
+ }
$this->helper = YoutubeHelper::create();
$this->downloadDir = $this->downloadDir ?? $this->defaultDir;
$this->prependOption($this->downloadDir . $this->outTpl);
@@ -96,6 +133,7 @@ class Youtube
$this->setUrl($url);
$this->prependOption($this->bin);
$process = new Process($this->options, null, $this->env);
+ //\OC::$server->getLogger()->error($process->getCommandLine(), ['app' => 'PHP']);
$process->setTimeout($this->timeout);
$process->run(function ($type, $buffer) use ($url) {
if (Process::ERR === $type) {
@@ -108,16 +146,7 @@ class Youtube
$this->helper->updateStatus(Helper::STATUS['COMPLETE']);
return ['message' => $this->helper->file ?? $process->getErrorOutput()];
}
- return $process->getErrorOutput();
-
- }
- public function getFilePath($output)
- {
- $rules = '#\[download\]\s+Destination:\s+(?.*\.(?(mp4|mp3|aac)))$#i';
-
- preg_match($rules, $output, $matches);
-
- return $matches['filename'] ?? null;
+ return ['error' => $process->getErrorOutput()];
}
private function onError($buffer)
@@ -150,7 +179,12 @@ class Youtube
//$index = array_search('-i', $this->options);
//array_splice($this->options, $index + 1, 0, $url);
}
-
+ public function setOption($key, $value)
+ {
+ $this->addOption($key);
+ $this->addOption($value);
+ return $this;
+ }
public function addOption($option)
{
array_push($this->options, $option);
diff --git a/lib/Tools/YoutubeHelper.php b/lib/Tools/YoutubeHelper.php
index 8fa2975..768b0e6 100644
--- a/lib/Tools/YoutubeHelper.php
+++ b/lib/Tools/YoutubeHelper.php
@@ -29,7 +29,7 @@ class YoutubeHelper
}
public function getFilePath($output)
{
- $rules = '#\[download\]\s+Destination:\s+(?.*\.(?(mp4|mp3|aac|webm|m4a|ogg)))$#i';
+ $rules = '#\[download\]\s+Destination:\s+(?.*\.(?(mp4|mp3|aac|webm|m4a|ogg|3gp|mkv|wav|flv)))$#i';
preg_match($rules, $output, $matches);
diff --git a/package-lock.json b/package-lock.json
index 50e5f94..9fba468 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"bootstrap": "^5.1.0",
"html-webpack-plugin": "^5.3.2",
"jquery": "^3.6.0",
+ "popper.js": "^1.16.1",
"sass": "^1.38.0",
"sass-loader": "^10.2.0",
"svg-url-loader": "^7.1.1",
@@ -9933,6 +9934,16 @@
"node": ">=4"
}
},
+ "node_modules/popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
+ "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -22516,6 +22527,11 @@
"find-up": "^2.1.0"
}
},
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
diff --git a/package.json b/package.json
index 68883c0..749fa4f 100755
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"bootstrap": "^5.1.0",
"html-webpack-plugin": "^5.3.2",
"jquery": "^3.6.0",
+ "popper.js": "^1.16.1",
"sass": "^1.38.0",
"sass-loader": "^10.2.0",
"svg-url-loader": "^7.1.1",
diff --git a/src/inputAction.js b/src/inputAction.js
index 9815b8e..560ea4f 100644
--- a/src/inputAction.js
+++ b/src/inputAction.js
@@ -40,6 +40,9 @@ const createInputBox = (event, type) => {
selectOptions.push({ name: 'TPB', label: 'THEPIRATEBAY', selected: 1 });
container = inputBox.getInstance(name, type, path).createOptions(selectOptions).create().addSpinner();
//container.appendChild(inputBox.createLoading());
+ } else if (type === 'ytdl') {
+ let checkbox = [{id:'audio-only',label:'Audio Only'}];
+ container = inputBox.getInstance(name, type, path).createCheckbox(checkbox).create().getContainer();
} else {
container = inputBox.getInstance(name, type, path).create().getContainer();
}
@@ -69,11 +72,13 @@ const inputHandler = (event) => {
let inputData = helper.getData('form-input-wrapper');
let inputValue = inputData.form_input_text;
+
if (inputData.type !== 'search' && !helper.isURL(inputValue) && !helper.isMagnetURI(inputValue)) {
helper.message(t("ncdownloader", "Invalid url"));
return;
}
if (inputData.type === 'ytdl') {
+ inputData.audioOnly = document.getElementById('audio-only').checked;
helper.message(t("ncdownloader", "Your download has started!"), 5000);
}
if (inputData.type === 'search') {
diff --git a/src/inputBox.js b/src/inputBox.js
index bbb02bd..8edf201 100644
--- a/src/inputBox.js
+++ b/src/inputBox.js
@@ -5,6 +5,7 @@ import helper from './helper'
class inputBox {
path;
selectOptions = [];
+ checkbox = [];
constructor(btnName, id, path = null) {
this.btnName = btnName;
this.id = id;
@@ -18,6 +19,9 @@ class inputBox {
this.textInput = this._createTextInput(this.id);
this.buttonContainer = this._createButtonContainer();
this.formContainer.appendChild(this.textInput);
+ if (this.checkbox.length !== 0) {
+ this.formContainer.appendChild(this._createCheckbox());
+ }
if (this.selectOptions.length !== 0) {
this.formContainer.appendChild(this._createSelect());
}
@@ -60,6 +64,42 @@ class inputBox {
return select;
}
+ _createCheckbox() {
+ let div = document.createElement("div");
+ div.classList.add("checkboxes");
+ this.checkbox.forEach(element => {
+ div.appendChild(element);
+ })
+ return div;
+ }
+
+ createCheckbox(data) {
+ if (!data) {
+ return;
+ }
+ data.forEach(element => {
+ let div = document.createElement('div');
+ let label = document.createElement('label');
+ let text = document.createTextNode(element.label);
+ let span = document.createElement('span');
+ span.appendChild(text);
+
+ let input = document.createElement('input');
+ input.setAttribute('type', 'checkbox');
+ input.setAttribute('id', element.id);
+ input.setAttribute('value', 'off');
+ input.setAttribute('name', element.name || element.id);
+
+ label.setAttribute('for',element.id);
+ label.classList.add("checkbox-label");
+ label.appendChild(input);
+ label.appendChild(span);
+ div.appendChild(label);
+ this.checkbox.push(div);
+ });
+ return this;
+ }
+
createOptions(data) {
if (!data) {
return;