improvements

- allow to convert to friendly format with audio #322
- add create dummy video progress
- trash dummy video when remove source
pull/333/head
Mikael Finstad 6 years ago
parent b8339616c1
commit fcbeb56968

@ -316,9 +316,9 @@ const App = memo(() => {
seekRel((1 / (detectedFps || 60)) * dir);
}, [seekRel, detectedFps]);
useEffect(() => () => {
/* useEffect(() => () => {
if (dummyVideoPath) unlink(dummyVideoPath).catch(console.error);
}, [dummyVideoPath]);
}, [dummyVideoPath]); */
// 360 means we don't modify rotation
const isRotationSet = rotation !== 360;
@ -820,12 +820,17 @@ const App = memo(() => {
useEffect(() => () => waveform && URL.revokeObjectURL(waveform.url), [waveform]);
function showUnsupportedFileMessage() {
toast.fire({ timer: 10000, icon: 'info', title: i18n.t('File not natively supported'), text: i18n.t('Preview will have no audio and low quality. The final export will however be lossless with audio. You may convert it from the menu for a better preview.') });
toast.fire({ timer: 10000, icon: 'info', title: i18n.t('File not natively supported'), text: i18n.t('Preview may have no audio or low quality. The final export will however be lossless with audio. You may convert it from the menu for a better preview.') });
}
const createDummyVideo = useCallback(async (cod, fp) => {
const html5ifiedDummyPathDummy = getOutPath(cod, fp, 'html5ified-dummy.mkv');
await html5ifyDummy(fp, html5ifiedDummyPathDummy);
try {
setCutProgress(0);
await html5ifyDummy(fp, html5ifiedDummyPathDummy, setCutProgress);
} finally {
setCutProgress();
}
setDummyVideoPath(html5ifiedDummyPathDummy);
setHtml5FriendlyPath();
showUnsupportedFileMessage();
@ -834,7 +839,7 @@ const App = memo(() => {
const tryCreateDummyVideo = useCallback(async () => {
try {
if (working) return;
setWorking(i18n.t('Loading file'));
setWorking(i18n.t('Converting to supported format'));
await createDummyVideo(customOutDir, filePath);
} catch (err) {
console.error(err);
@ -879,6 +884,8 @@ const App = memo(() => {
setWorking(i18n.t('Deleting source'));
if (html5FriendlyPath) await trash(html5FriendlyPath).catch(console.error);
if (dummyVideoPath) await trash(dummyVideoPath).catch(console.error);
// throw new Error('test');
await trash(filePath);
toast.fire({ icon: 'info', title: i18n.t('File has been moved to trash') });
@ -895,6 +902,7 @@ const App = memo(() => {
if (value) {
if (html5FriendlyPath) await unlink(html5FriendlyPath).catch(console.error);
if (dummyVideoPath) await unlink(dummyVideoPath).catch(console.error);
await unlink(filePath);
toast.fire({ icon: 'info', title: i18n.t('File has been permanently deleted') });
}
@ -1063,7 +1071,10 @@ const App = memo(() => {
}
}, [playing, canvasPlayerEnabled]);
const getHtml5ifiedPath = useCallback((cod, fp, type) => getOutPath(cod, fp, `html5ified-${type}.mp4`), []);
const getHtml5ifiedPath = useCallback((cod, fp, type) => {
const ext = type === 'fastest-audio' ? 'mkv' : 'mp4';
return getOutPath(cod, fp, `html5ified-${type}.${ext}`);
}, []);
const loadEdlFile = useCallback(async (edlPath) => {
try {
@ -1085,8 +1096,8 @@ const App = memo(() => {
}
}, [cutSegmentsHistory, setCutSegments]);
const load = useCallback(async ({ filePath: fp, customOutDir: cod, html5FriendlyPathRequested }) => {
console.log('Load', { fp, cod, html5FriendlyPathRequested });
const load = useCallback(async ({ filePath: fp, customOutDir: cod, html5FriendlyPathRequested, dummyVideoPathRequested }) => {
console.log('Load', { fp, cod, html5FriendlyPathRequested, dummyVideoPathRequested });
if (working) return;
@ -1099,7 +1110,13 @@ const App = memo(() => {
const ret = existing && await exists(existing);
if (ret) {
console.log('Found existing friendly file', existing);
setHtml5FriendlyPath(existing);
if (speed === 'fastest-audio') {
setDummyVideoPath(existing);
setHtml5FriendlyPath();
} else {
setHtml5FriendlyPath(existing);
}
showUnsupportedFileMessage();
}
return ret;
@ -1149,8 +1166,12 @@ const App = memo(() => {
if (html5FriendlyPathRequested) {
setHtml5FriendlyPath(html5FriendlyPathRequested);
showUnsupportedFileMessage();
} else if (dummyVideoPathRequested) {
setDummyVideoPath(dummyVideoPathRequested);
setHtml5FriendlyPath();
showUnsupportedFileMessage();
} else if (
!(await checkAndSetExistingHtml5FriendlyFile('slowest') || await checkAndSetExistingHtml5FriendlyFile('slow-audio') || await checkAndSetExistingHtml5FriendlyFile('slow') || await checkAndSetExistingHtml5FriendlyFile('fast-audio') || await checkAndSetExistingHtml5FriendlyFile('fast'))
!(await checkAndSetExistingHtml5FriendlyFile('slowest') || await checkAndSetExistingHtml5FriendlyFile('slow-audio') || await checkAndSetExistingHtml5FriendlyFile('slow') || await checkAndSetExistingHtml5FriendlyFile('fast-audio') || await checkAndSetExistingHtml5FriendlyFile('fast') || await checkAndSetExistingHtml5FriendlyFile('fastest-audio'))
&& !doesPlayerSupportFile(streams)
) {
await createDummyVideo(cod, fp);
@ -1332,12 +1353,24 @@ const App = memo(() => {
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';
let audio;
if (ha) {
if (speed === 'slowest') audio = 'hq';
else if (speed === 'slow-audio') audio = 'lq-aac';
else if (speed === 'fast-audio') audio = 'copy';
else if (speed === 'fastest-audio') audio = 'lq-flac';
}
let video;
if (hv) {
if (speed === 'slowest') video = 'hq';
else if (['slow-audio', 'slow'].includes(speed)) video = 'lq';
else video = 'copy';
}
try {
await ffmpegHtml5ify({ filePath: fp, outPath: path, encode, includeVideo, includeAudio, highQuality, onProgress: setCutProgress });
await ffmpegHtml5ify({ filePath: fp, outPath: path, video, audio, onProgress: setCutProgress });
} finally {
setCutProgress();
}
@ -1345,8 +1378,13 @@ const App = memo(() => {
}, [getHtml5ifiedPath]);
const html5ifyAndLoad = useCallback(async (speed) => {
const path = await html5ifyInternal({ customOutDir, filePath, speed, hasAudio, hasVideo });
load({ filePath, html5FriendlyPathRequested: path, customOutDir });
if (speed === 'fastest-audio') {
const path = await html5ifyInternal({ customOutDir, filePath, speed, hasAudio, hasVideo: false });
load({ filePath, dummyVideoPathRequested: path, customOutDir });
} else {
const path = await html5ifyInternal({ customOutDir, filePath, speed, hasAudio, hasVideo });
load({ filePath, html5FriendlyPathRequested: path, customOutDir });
}
}, [hasAudio, hasVideo, customOutDir, filePath, html5ifyInternal, load]);
const html5ifyCurrentFile = useCallback(async () => {
@ -1355,12 +1393,12 @@ const App = memo(() => {
try {
setWorking(i18n.t('Converting to supported format'));
const speed = await askForHtml5ifySpeed(['fastest', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']);
const speed = await askForHtml5ifySpeed(['fastest', 'fastest-audio', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']);
if (!speed) return;
if (speed === 'fastest') {
await createDummyVideo(customOutDir, filePath);
} else if (['fast-audio', 'fast', 'slow', 'slow-audio', 'slowest'].includes(speed)) {
} else if (['fastest-audio', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest'].includes(speed)) {
await html5ifyAndLoad(speed);
}
} catch (err) {
@ -1382,13 +1420,14 @@ const App = memo(() => {
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 is not natively supported. Encoding a preview file...' });
await html5ifyAndLoad('slow-audio');
if (hasVideo) {
tryCreateDummyVideo();
} else if (hasAudio) {
toast.fire({ icon: 'info', text: 'This file is not natively supported. Creating a preview file...' });
await html5ifyAndLoad('fastest-audio');
}
}
}, [tryCreateDummyVideo, fileUri, dummyVideoPath, hasVideo, html5ifyAndLoad]);
}, [tryCreateDummyVideo, fileUri, dummyVideoPath, hasVideo, hasAudio, html5ifyAndLoad]);
useEffect(() => {
function fileOpened(event, filePaths) {
@ -1475,7 +1514,7 @@ const App = memo(() => {
const failedFiles = [];
let i = 0;
const speed = await askForHtml5ifySpeed(['fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']);
const speed = await askForHtml5ifySpeed(['fastest-audio', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest']);
if (!speed) return;
try {

@ -321,42 +321,60 @@ export async function getDuration(filePath) {
return parseFloat(JSON.parse(stdout).format.duration);
}
export async function html5ify({ filePath, outPath, encode, includeVideo, includeAudio, highQuality, onProgress }) {
console.log('Making HTML5 friendly version', { filePath, outPath, encode, includeVideo, includeAudio, highQuality });
export async function html5ify({ filePath, outPath, video, audio, onProgress }) {
console.log('Making HTML5 friendly version', { filePath, outPath, video, audio });
let videoArgs;
let audioArgs;
if (includeVideo) {
if (!encode) {
videoArgs = ['-vcodec', 'copy'];
} else if (os.platform() === 'darwin') {
if (highQuality) {
const isMac = os.platform() === 'darwin';
switch (video) {
case 'hq': {
if (isMac) {
videoArgs = ['-vf', 'format=yuv420p', '-allow_sw', '1', '-vcodec', 'h264', '-b:v', '15M'];
} else {
videoArgs = ['-vf', 'format=yuv420p', '-vcodec', 'libx264', '-profile:v', 'high', '-preset:v', 'slow', '-crf', '17'];
}
break;
}
case 'lq': {
if (isMac) {
videoArgs = ['-vf', 'scale=-2:400,format=yuv420p', '-allow_sw', '1', '-sws_flags', 'lanczos', '-vcodec', 'h264', '-b:v', '1500k'];
} 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 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'];
break;
}
case 'copy': {
videoArgs = ['-vcodec', 'copy'];
break;
}
default: {
videoArgs = ['-vn'];
}
} else {
videoArgs = ['-vn'];
}
if (includeAudio) {
if (encode) {
if (highQuality) {
audioArgs = ['-acodec', 'aac', '-b:a', '192k'];
} else {
audioArgs = ['-acodec', 'aac', '-ar', '44100', '-ac', '2', '-b:a', '96k'];
}
} else {
switch (audio) {
case 'hq': {
audioArgs = ['-acodec', 'aac', '-b:a', '192k'];
break;
}
case 'lq-flac': {
audioArgs = ['-acodec', 'flac', '-ar', '11025', '-ac', '2'];
break;
}
case 'lq-aac': {
audioArgs = ['-acodec', 'aac', '-ar', '44100', '-ac', '2', '-b:a', '96k'];
break;
}
case 'copy': {
audioArgs = ['-acodec', 'copy'];
break;
}
default: {
audioArgs = ['-an'];
}
} else {
audioArgs = ['-an'];
}
const ffmpegArgs = [
@ -380,7 +398,7 @@ export async function html5ify({ filePath, outPath, encode, includeVideo, includ
// This is just used to load something into the player with correct length,
// so user can seek and then we render frames using ffmpeg
export async function html5ifyDummy(filePath, outPath) {
export async function html5ifyDummy(filePath, outPath, onProgress) {
console.log('Making HTML5 friendly dummy', { filePath, outPath });
const duration = await getDuration(filePath);
@ -389,14 +407,16 @@ export async function html5ifyDummy(filePath, outPath) {
'-hide_banner',
// This is just a fast way of generating an empty dummy file
// TODO use existing audio track file if it has one
'-f', 'lavfi', '-i', 'anullsrc=channel_layout=stereo:sample_rate=44100',
'-t', duration,
'-acodec', 'flac',
'-y', outPath,
];
const { stdout } = await runFfmpeg(ffmpegArgs);
const process = runFfmpeg(ffmpegArgs);
handleProgress(process, duration, onProgress);
const { stdout } = await process;
console.log(stdout);
await transferTimestamps(filePath, outPath);

@ -166,6 +166,7 @@ export function getSegColors(seg) {
export async function askForHtml5ifySpeed(allowedOptions) {
const availOptions = {
fastest: i18n.t('Fastest: Low playback speed (no audio)'),
'fastest-audio': i18n.t('Fastest: Low playback speed'),
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)'),

Loading…
Cancel
Save