From 62189f2294e5ad6b41a4197a26831632e22976f5 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 10 Apr 2020 15:51:35 +0800 Subject: [PATCH] More flexible convert to friendly format #297 #160 --- src/App.jsx | 79 ++++++++++++++++++++++++++++++--------------------- src/ffmpeg.js | 39 ++++++++++++++++++++----- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index fe29f960..0672fdaa 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -853,20 +853,6 @@ const App = memo(() => { }); }, [playing, filePath]); - const onVideoError = useCallback(() => { - const { error } = videoRef.current; - if (!error) return; - if (!fileUri) return; // Probably MEDIA_ELEMENT_ERROR: Empty src attribute - - console.error(error.message); - - const MEDIA_ERR_SRC_NOT_SUPPORTED = 4; - if (error.code === MEDIA_ERR_SRC_NOT_SUPPORTED && !dummyVideoPath) { - console.log('MEDIA_ERR_SRC_NOT_SUPPORTED - trying to create dummy'); - tryCreateDummyVideo(); - } - }, [tryCreateDummyVideo, fileUri, dummyVideoPath]); - const deleteSource = useCallback(async () => { if (!filePath) return; @@ -1094,7 +1080,7 @@ const App = memo(() => { setHtml5FriendlyPath(html5FriendlyPathRequested); showUnsupportedFileMessage(); } else if ( - !(await checkAndSetExistingHtml5FriendlyFile('slow-audio') || await checkAndSetExistingHtml5FriendlyFile('slow') || await checkAndSetExistingHtml5FriendlyFile('fast')) + !(await checkAndSetExistingHtml5FriendlyFile('slowest') || await checkAndSetExistingHtml5FriendlyFile('slow-audio') || await checkAndSetExistingHtml5FriendlyFile('slow') || await checkAndSetExistingHtml5FriendlyFile('fast-audio') || await checkAndSetExistingHtml5FriendlyFile('fast')) && !doesPlayerSupportFile(streams) ) { await createDummyVideo(cod, fp); @@ -1274,6 +1260,40 @@ const App = memo(() => { userOpenFiles(filePaths); }, [userOpenFiles, loadEdlFile]); + const html5ifyInternal = useCallback(async ({ customOutDir: cod, filePath: fp, speed, hasAudio: ha, hasVideo: hv }) => { + const path = getHtml5ifiedPath(cod, fp, speed); + const includeVideo = hv; + const includeAudio = ['fast-audio', 'slow-audio', 'slowest'].includes(speed) && ha; + const encode = ['slow-audio', 'slow', 'slowest'].includes(speed); + const highQuality = speed === 'slowest'; + await ffmpegHtml5ify({ filePath: fp, outPath: path, encode, includeVideo, includeAudio, highQuality }); + return path; + }, [getHtml5ifiedPath]); + + const html5ifyAndLoad = useCallback(async (speed) => { + const path = await html5ifyInternal({ customOutDir, filePath, speed, hasAudio, hasVideo }); + load({ filePath, html5FriendlyPathRequested: path, customOutDir }); + }, [hasAudio, hasVideo, customOutDir, filePath, html5ifyInternal, load]); + + const onVideoError = useCallback(async () => { + const { error } = videoRef.current; + if (!error) return; + if (!fileUri) return; // Probably MEDIA_ELEMENT_ERROR: Empty src attribute + + console.error(error.message); + + const MEDIA_ERR_SRC_NOT_SUPPORTED = 4; + if (error.code === MEDIA_ERR_SRC_NOT_SUPPORTED && !dummyVideoPath) { + console.log('MEDIA_ERR_SRC_NOT_SUPPORTED - trying to create dummy'); + + if (hasVideo) tryCreateDummyVideo(); + else { + toast.fire({ icon: 'info', text: 'This file does not have any supported. Encoding a preview file...' }); + await html5ifyAndLoad('slow-audio'); + } + } + }, [tryCreateDummyVideo, fileUri, dummyVideoPath, hasVideo, html5ifyAndLoad]); + useEffect(() => { function fileOpened(event, filePaths) { userOpenFiles(filePaths); @@ -1290,9 +1310,11 @@ const App = memo(() => { async function askForHtml5ifySpeed(allowedOptions) { const availOptions = { fastest: i18n.t('Fastest: Low playback speed (no audio)'), - fast: i18n.t('Fast: Full quality remux, unlikely to work'), - slow: i18n.t('Slow: Low video quality encode (no audio)'), - 'slow-audio': i18n.t('Slowest: Low video/audio quality encode'), + fast: i18n.t('Fast: Full quality remux (no audio), likely to fail'), + 'fast-audio': i18n.t('Fast: Full quality remux, likely to fail'), + slow: i18n.t('Slow: Low quality encode (no audio)'), + 'slow-audio': i18n.t('Slow: Low quality encode'), + slowest: i18n.t('Slowest: High quality encode'), }; const inputOptions = {}; allowedOptions.forEach((allowedOption) => { @@ -1313,28 +1335,19 @@ const App = memo(() => { return value; } - async function html5ifyInternal({ customOutDir: cod, filePath: fp, speed, hasAudio: ha, hasVideo: hv }) { - const path = getHtml5ifiedPath(cod, fp, speed); - const encodeVideo = ['slow', 'slow-audio'].includes(speed) && hv; - const encodeAudio = speed === 'slow-audio' && ha; - await ffmpegHtml5ify(fp, path, encodeVideo, encodeAudio); - return path; - } - async function html5ify() { if (!filePath) return; try { setWorking(true); - const speed = await askForHtml5ifySpeed(['fastest', 'fast', 'slow', 'slow-audio']); + const speed = await askForHtml5ifySpeed(['fastest', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']); if (!speed) return; if (speed === 'fastest') { await createDummyVideo(customOutDir, filePath); - } else if (['fast', 'slow', 'slow-audio'].includes(speed)) { - const path = await html5ifyInternal({ customOutDir, filePath, speed, hasAudio, hasVideo }); - load({ filePath, html5FriendlyPathRequested: path, customOutDir }); + } else if (['fast-audio', 'fast', 'slow', 'slow-audio', 'slowest'].includes(speed)) { + await html5ifyAndLoad(speed); } } catch (err) { errorToast(i18n.t('Failed to html5ify file')); @@ -1416,7 +1429,7 @@ const App = memo(() => { const failedFiles = []; let i = 0; - const speed = await askForHtml5ifySpeed(['fast', 'slow', 'slow-audio']); + const speed = await askForHtml5ifySpeed(['fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']); if (!speed) return; try { @@ -1486,9 +1499,9 @@ const App = memo(() => { electron.ipcRenderer.removeListener('batchConvertFriendlyFormat', batchConvertFriendlyFormat); }; }, [ - load, mergeFiles, outputDir, filePath, isFileOpened, customOutDir, startTimeOffset, getHtml5ifiedPath, + mergeFiles, outputDir, filePath, isFileOpened, customOutDir, startTimeOffset, createDummyVideo, resetState, extractAllStreams, userOpenFiles, cutSegmentsHistory, - loadEdlFile, cutSegments, edlFilePath, askBeforeClose, toggleHelp, toggleSettings, assureOutDirAccess, hasAudio, hasVideo, + loadEdlFile, cutSegments, edlFilePath, askBeforeClose, toggleHelp, toggleSettings, assureOutDirAccess, html5ifyAndLoad, html5ifyInternal, ]); async function showAddStreamSourceDialog() { diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 1d266f5b..894c5d01 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -306,18 +306,43 @@ export async function cutMultiple({ return outFiles; } -export async function html5ify(filePath, outPath, encodeVideo, encodeAudio) { - console.log('Making HTML5 friendly version', { filePath, outPath, encodeVideo }); +export async function html5ify({ filePath, outPath, encode, includeVideo, includeAudio, highQuality }) { + console.log('Making HTML5 friendly version', { filePath, outPath, encode, includeVideo, includeAudio, highQuality }); let videoArgs; - if (!encodeVideo) videoArgs = ['-vcodec', 'copy']; - else if (os.platform() === 'darwin') { - videoArgs = ['-vf', 'scale=-2:400,format=yuv420p', '-allow_sw', '1', '-sws_flags', 'lanczos', '-vcodec', 'h264', '-b:v', '1500k']; + let audioArgs; + + if (includeVideo) { + if (!encode) { + videoArgs = ['-vcodec', 'copy']; + } else if (os.platform() === 'darwin') { + if (highQuality) { + videoArgs = ['-vf', 'format=yuv420p', '-allow_sw', '1', '-vcodec', 'h264', '-b:v', '15M']; + } else { + videoArgs = ['-vf', 'scale=-2:400,format=yuv420p', '-allow_sw', '1', '-sws_flags', 'lanczos', '-vcodec', 'h264', '-b:v', '1500k']; + } + } else if (highQuality) { + videoArgs = ['-vf', 'format=yuv420p', '-vcodec', 'libx264', '-profile:v', 'high', '-preset:v', 'slow', '-crf', '17']; + } else { + videoArgs = ['-vf', 'scale=-2:400,format=yuv420p', '-sws_flags', 'neighbor', '-vcodec', 'libx264', '-profile:v', 'baseline', '-x264opts', 'level=3.0', '-preset:v', 'ultrafast', '-crf', '28']; + } } else { - videoArgs = ['-vf', 'scale=-2:400,format=yuv420p', '-sws_flags', 'neighbor', '-vcodec', 'libx264', '-profile:v', 'baseline', '-x264opts', 'level=3.0', '-preset:v', 'ultrafast', '-crf', '28']; + videoArgs = ['-vn']; } - const audioArgs = encodeAudio ? ['-acodec', 'aac', '-ar', '44100', '-ac', '2', '-b:a', '96k'] : ['-an']; + if (includeAudio) { + if (encode) { + if (highQuality) { + audioArgs = ['-acodec', 'aac', '-b:a', '192k']; + } else { + audioArgs = ['-acodec', 'aac', '-ar', '44100', '-ac', '2', '-b:a', '96k']; + } + } else { + audioArgs = ['-acodec', 'copy']; + } + } else { + audioArgs = ['-an']; + } const ffmpegArgs = [ '-hide_banner',