From 433d08e9dfdb641c111107408f8fba71fb5475ad Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 12 Feb 2021 21:20:48 -0700 Subject: [PATCH] Added ability to crop files Fixed bug in downloading playlists --- backend/app.js | 44 ++++++++++++++++++++++++++++---- backend/db.js | 7 ++++- src/app/main/main.component.css | 4 +++ src/app/main/main.component.html | 21 +++++++++++++-- src/app/main/main.component.ts | 14 +++++++++- src/app/posts.services.ts | 5 ++-- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/backend/app.js b/backend/app.js index 9dbad30..dba7f14 100644 --- a/backend/app.js +++ b/backend/app.js @@ -853,7 +853,7 @@ async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvid let zipFolderPath = null; if (!fullPathProvided) { - zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); + zipFolderPath = (type === 'audio') ? audioFolderPath : videoFolderPath if (user_uid) zipFolderPath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, zipFolderPath); } else { zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path')); @@ -1155,7 +1155,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // download file - youtubedl.exec(url, downloadConfig, {}, function(err, output) { + youtubedl.exec(url, downloadConfig, {}, async function(err, output) { if (download_checker) clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) download['downloading'] = false; @@ -1227,8 +1227,12 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { const file_path = options.noRelativePath ? path.basename(full_file_path) : full_file_path.substring(fileFolderPath.length, full_file_path.length); const customPath = options.noRelativePath ? path.dirname(full_file_path).split(path.sep).pop() : null; + if (options.cropFileSettings) { + await cropFile(full_file_path, options.cropFileSettings.cropFileStart, options.cropFileSettings.cropFileEnd, ext); + } + // registers file in DB - file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category); + file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category, options.cropFileSettings); if (file_name) file_names.push(file_name); } @@ -1587,6 +1591,8 @@ async function getUrlInfos(urls) { }); } +// ffmpeg helper functions + async function convertFileToMp3(input_file, output_file) { logger.verbose(`Converting ${input_file} to ${output_file}...`); return new Promise(resolve => { @@ -1604,6 +1610,33 @@ async function convertFileToMp3(input_file, output_file) { }); } +async function cropFile(file_path, start, end, ext) { + return new Promise(resolve => { + const temp_file_path = `${file_path}.cropped${ext}`; + let base_ffmpeg_call = ffmpeg(file_path); + if (start) { + base_ffmpeg_call = base_ffmpeg_call.seekOutput(start); + } + if (end) { + base_ffmpeg_call = base_ffmpeg_call.duration(end - start); + } + base_ffmpeg_call + .on('end', () => { + logger.verbose(`Cropping for '${file_path}' complete.`); + fs.unlinkSync(file_path); + fs.moveSync(temp_file_path, file_path); + resolve(true); + }) + .on('error', (err, test, test2) => { + logger.error(`Failed to crop ${file_path}.`); + logger.error(err); + resolve(false); + }).save(temp_file_path); + }); +} + +// archive helper functions + async function writeToBlacklist(type, line) { let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt'); // adds newline to the beginning of the line @@ -1908,7 +1941,8 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) { youtubeUsername: req.body.youtubeUsername, youtubePassword: req.body.youtubePassword, ui_uid: req.body.ui_uid, - user: req.isAuthenticated() ? req.user.uid : null + user: req.isAuthenticated() ? req.user.uid : null, + cropFileSettings: req.body.cropFileSettings } const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override') || config_api.globalArgsRequiresSafeDownload(); @@ -2666,7 +2700,7 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => { for (let i = 0; i < fileNames.length; i++) { fileNames[i] = decodeURIComponent(fileNames[i]); } - file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid); + file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid || req.user.uid); if (!path.isAbsolute(file)) file = path.join(__dirname, file); } res.sendFile(file, function (err) { diff --git a/backend/db.js b/backend/db.js index 6d5cdb6..16a8d0c 100644 --- a/backend/db.js +++ b/backend/db.js @@ -15,7 +15,7 @@ function initialize(input_db, input_users_db, input_logger) { setLogger(input_logger); } -function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null) { +function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null, cropFileSettings = null) { let db_path = null; const file_id = utils.removeFileExtension(file_path); const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub); @@ -32,6 +32,11 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null, custo // if category exists, only include essential info if (category) file_object['category'] = {name: category['name'], uid: category['uid']}; + // modify duration + if (cropFileSettings) { + file_object['duration'] = (cropFileSettings.cropFileEnd || file_object.duration) - cropFileSettings.cropFileStart; + } + if (!sub) { if (multiUserMode) { const user_uid = multiUserMode.user; diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css index 4ee7412..8411370 100644 --- a/src/app/main/main.component.css +++ b/src/app/main/main.component.css @@ -124,6 +124,10 @@ mat-form-field.mat-form-field { width: 100%; } +.advanced-input-time { + margin-left: 10px; +} + .edit-button { margin-left: 10px; top: -5px; diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 9cb905e..26b9da3 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -129,7 +129,7 @@ -
+
Use authentication @@ -139,11 +139,28 @@
-
+
+
+ + + Crop file + + + + + Seconds + +
+
+ + + Seconds + +
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 30694bf..51a90ce 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -54,6 +54,9 @@ export class MainComponent implements OnInit { youtubeAuthEnabled = false; youtubeUsername = null; youtubePassword = null; + cropFile = false; + cropFileStart = null; + cropFileEnd = null; urlError = false; path = ''; url = ''; @@ -521,8 +524,17 @@ export class MainComponent implements OnInit { const customQualityConfiguration = this.getSelectedVideoFormat(); + let cropFileSettings = null; + + if (this.cropFile) { + cropFileSettings = { + cropFileStart: this.cropFileStart, + cropFileEnd: this.cropFileEnd + } + } + this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality), - customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => { + customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid, cropFileSettings).subscribe(posts => { // update download object new_download.downloading = false; new_download.percent_complete = 100; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 6657f00..a6c2954 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -183,7 +183,7 @@ export class PostsService implements CanActivate { } // tslint:disable-next-line: max-line-length - makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) { + makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null, cropFileSettings = null) { return this.http.post(this.path + 'tomp4', {url: url, selectedHeight: selectedQuality, customQualityConfiguration: customQualityConfiguration, @@ -191,7 +191,8 @@ export class PostsService implements CanActivate { customOutput: customOutput, youtubeUsername: youtubeUsername, youtubePassword: youtubePassword, - ui_uid: ui_uid}, this.httpOptions); + ui_uid: ui_uid, + cropFileSettings: cropFileSettings}, this.httpOptions); } killAllDownloads() {