mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
make it more explicit when changing mp4 to mov
and show a notification #1075
This commit is contained in:
parent
590e6a87cd
commit
69f600a0c0
@ -87,9 +87,9 @@ Smart cut is experimental, so don't expect too much. But if you're having proble
|
|||||||
- If Smart cut gives you repeated (duplicate) segments, you can try to enable the Export Option "Shift all start times".
|
- If Smart cut gives you repeated (duplicate) segments, you can try to enable the Export Option "Shift all start times".
|
||||||
- Sometimes it helps to convert (remux) your videos [to mp4 first](https://github.com/mifi/lossless-cut/discussions/1292#discussioncomment-10425084) (e.g. from mkv) using LosslessCut, before smart cutting them.
|
- Sometimes it helps to convert (remux) your videos [to mp4 first](https://github.com/mifi/lossless-cut/discussions/1292#discussioncomment-10425084) (e.g. from mkv) using LosslessCut, before smart cutting them.
|
||||||
|
|
||||||
## My file changes from MP4 to MOV
|
## MP4/MOV issues
|
||||||
|
|
||||||
Some MP4 files ffmpeg is not able to export as MP4 and therefore needs to use MOV instead. Unfortunately I don't know any way to fix this.
|
Some MP4 files FFmpeg is not able to export as MP4 and MOV needs to be selected instead. Unfortunately I don't know any way to fix this. Sometimes certain players are not able to play back certain exported `.mov` files ([Adobe Premiere](https://github.com/mifi/lossless-cut/issues/1075#issuecomment-2327459890) 👀). You can try to rename the exported MOV file extension to `.mp4` and see if it helps. Or vice versa, rename an exported MP4 file to `.mov`.
|
||||||
|
|
||||||
## Output file name is missing characters
|
## Output file name is missing characters
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ import {
|
|||||||
isIphoneHevc, isProblematicAvc1, tryMapChaptersToEdl,
|
isIphoneHevc, isProblematicAvc1, tryMapChaptersToEdl,
|
||||||
getDuration, getTimecodeFromStreams, createChaptersFromSegments,
|
getDuration, getTimecodeFromStreams, createChaptersFromSegments,
|
||||||
RefuseOverwriteError, extractSubtitleTrackToSegments,
|
RefuseOverwriteError, extractSubtitleTrackToSegments,
|
||||||
|
mapRecommendedDefaultFormat,
|
||||||
} from './ffmpeg';
|
} from './ffmpeg';
|
||||||
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, getSubtitleStreams, getVideoTrackForStreamIndex, getAudioTrackForStreamIndex, enableVideoTrack, enableAudioTrack } from './util/streams';
|
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, getSubtitleStreams, getVideoTrackForStreamIndex, getAudioTrackForStreamIndex, enableVideoTrack, enableAudioTrack } from './util/streams';
|
||||||
import { exportEdlFile, readEdlFile, loadLlcProject, askForEdlImport } from './edlStore';
|
import { exportEdlFile, readEdlFile, loadLlcProject, askForEdlImport } from './edlStore';
|
||||||
@ -1305,7 +1306,6 @@ function App() {
|
|||||||
// console.log('file meta read', fileMeta);
|
// console.log('file meta read', fileMeta);
|
||||||
|
|
||||||
const fileFormatNew = await getDefaultOutFormat({ filePath: fp, fileMeta });
|
const fileFormatNew = await getDefaultOutFormat({ filePath: fp, fileMeta });
|
||||||
|
|
||||||
if (!fileFormatNew) throw new Error('Unable to determine file format');
|
if (!fileFormatNew) throw new Error('Unable to determine file format');
|
||||||
|
|
||||||
const timecode = autoLoadTimecode ? getTimecodeFromStreams(fileMeta.streams) : undefined;
|
const timecode = autoLoadTimecode ? getTimecodeFromStreams(fileMeta.streams) : undefined;
|
||||||
@ -1373,8 +1373,14 @@ function App() {
|
|||||||
if (!haveVideoStream) setWaveformMode('big-waveform');
|
if (!haveVideoStream) setWaveformMode('big-waveform');
|
||||||
setMainFileMeta({ streams: fileMeta.streams, formatData: fileMeta.format, chapters: fileMeta.chapters });
|
setMainFileMeta({ streams: fileMeta.streams, formatData: fileMeta.format, chapters: fileMeta.chapters });
|
||||||
setCopyStreamIdsForPath(fp, () => copyStreamIdsForPathNew);
|
setCopyStreamIdsForPath(fp, () => copyStreamIdsForPathNew);
|
||||||
setFileFormat(outFormatLocked || fileFormatNew);
|
|
||||||
setDetectedFileFormat(fileFormatNew);
|
setDetectedFileFormat(fileFormatNew);
|
||||||
|
if (outFormatLocked) {
|
||||||
|
setFileFormat(outFormatLocked);
|
||||||
|
} else {
|
||||||
|
const recommendedDefaultFormat = mapRecommendedDefaultFormat({ sourceFormat: fileFormatNew, streams: fileMeta.streams });
|
||||||
|
if (recommendedDefaultFormat.message) showNotification({ icon: 'info', text: recommendedDefaultFormat.message });
|
||||||
|
setFileFormat(recommendedDefaultFormat.format);
|
||||||
|
}
|
||||||
|
|
||||||
// only show one toast, or else we will only show the last one
|
// only show one toast, or else we will only show the last one
|
||||||
if (existingHtml5FriendlyFile) {
|
if (existingHtml5FriendlyFile) {
|
||||||
|
@ -8,7 +8,7 @@ import invariant from 'tiny-invariant';
|
|||||||
import Checkbox from './Checkbox';
|
import Checkbox from './Checkbox';
|
||||||
|
|
||||||
import { ReactSwal } from '../swal';
|
import { ReactSwal } from '../swal';
|
||||||
import { readFileMeta, getDefaultOutFormat } from '../ffmpeg';
|
import { readFileMeta, getDefaultOutFormat, mapRecommendedDefaultFormat } from '../ffmpeg';
|
||||||
import useFileFormatState from '../hooks/useFileFormatState';
|
import useFileFormatState from '../hooks/useFileFormatState';
|
||||||
import OutputFormatSelect from './OutputFormatSelect';
|
import OutputFormatSelect from './OutputFormatSelect';
|
||||||
import useUserSettings from '../hooks/useUserSettings';
|
import useUserSettings from '../hooks/useUserSettings';
|
||||||
@ -69,8 +69,8 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
|
|||||||
const fileFormatNew = await getDefaultOutFormat({ filePath: firstPath, fileMeta: fileMetaNew });
|
const fileFormatNew = await getDefaultOutFormat({ filePath: firstPath, fileMeta: fileMetaNew });
|
||||||
if (aborted) return;
|
if (aborted) return;
|
||||||
setFileMeta(fileMetaNew);
|
setFileMeta(fileMetaNew);
|
||||||
setFileFormat(fileFormatNew);
|
|
||||||
setDetectedFileFormat(fileFormatNew);
|
setDetectedFileFormat(fileFormatNew);
|
||||||
|
setFileFormat(mapRecommendedDefaultFormat({ sourceFormat: fileFormatNew, streams: fileMetaNew.streams }).format);
|
||||||
setUniqueSuffix(Date.now());
|
setUniqueSuffix(Date.now());
|
||||||
})().catch(console.error);
|
})().catch(console.error);
|
||||||
|
|
||||||
|
@ -236,27 +236,28 @@ export async function createChaptersFromSegments({ segmentPaths, chapterNames }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ffmpeg only supports encoding certain formats, and some of the detected input
|
* Some of the detected input formats are not the same as the muxer name used for encoding.
|
||||||
* formats are not the same as the muxer name used for encoding.
|
|
||||||
* Therefore we have to map between detected input format and encode format
|
* Therefore we have to map between detected input format and encode format
|
||||||
* See also ffmpeg -formats
|
* See also ffmpeg -formats
|
||||||
*/
|
*/
|
||||||
function mapDefaultFormat({ streams, requestedFormat }: { streams: FFprobeStream[], requestedFormat: string | undefined }) {
|
function mapInputToOutputFormat(requestedFormat: string | undefined) {
|
||||||
if (requestedFormat === 'mp4') {
|
// see file aac raw adts.aac
|
||||||
// Only MOV supports these codecs, so default to MOV instead https://github.com/mifi/lossless-cut/issues/948
|
|
||||||
// eslint-disable-next-line unicorn/no-lonely-if
|
|
||||||
if (streams.some((stream) => pcmAudioCodecs.includes(stream.codec_name))) {
|
|
||||||
return 'mov';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// see sample.aac
|
|
||||||
if (requestedFormat === 'aac') return 'adts';
|
if (requestedFormat === 'aac') return 'adts';
|
||||||
|
|
||||||
return requestedFormat;
|
return requestedFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function determineOutputFormat(ffprobeFormatsStr: string | undefined, filePath: string) {
|
export function mapRecommendedDefaultFormat({ streams, sourceFormat }: { streams: FFprobeStream[], sourceFormat: string | undefined }) {
|
||||||
|
// Certain codecs cannot be muxed by ffmpeg into mp4, but in MOV they can
|
||||||
|
// so we default to MOV instead in those cases https://github.com/mifi/lossless-cut/issues/948
|
||||||
|
if (sourceFormat === 'mp4' && streams.some((stream) => pcmAudioCodecs.includes(stream.codec_name))) {
|
||||||
|
return { format: 'mov', message: i18n.t('This file contains an audio track that FFmpeg is unable to mux into the MP4 format, so MOV has been auto-selected as the default output format.') };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { format: sourceFormat };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function determineSourceFileFormat(ffprobeFormatsStr: string | undefined, filePath: string) {
|
||||||
const ffprobeFormats = (ffprobeFormatsStr || '').split(',').map((str) => str.trim()).filter(Boolean);
|
const ffprobeFormats = (ffprobeFormatsStr || '').split(',').map((str) => str.trim()).filter(Boolean);
|
||||||
|
|
||||||
const [firstFfprobeFormat] = ffprobeFormats;
|
const [firstFfprobeFormat] = ffprobeFormats;
|
||||||
@ -342,10 +343,10 @@ async function determineOutputFormat(ffprobeFormatsStr: string | undefined, file
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDefaultOutFormat({ filePath, fileMeta: { format, streams } }: { filePath: string, fileMeta: { format: Pick<FFprobeFormat, 'format_name'>, streams: FFprobeStream[] } }) {
|
export async function getDefaultOutFormat({ filePath, fileMeta: { format } }: { filePath: string, fileMeta: { format: Pick<FFprobeFormat, 'format_name'> } }) {
|
||||||
const assumedFormat = await determineOutputFormat(format.format_name, filePath);
|
const assumedFormat = await determineSourceFileFormat(format.format_name, filePath);
|
||||||
|
|
||||||
return mapDefaultFormat({ streams, requestedFormat: assumedFormat });
|
return mapInputToOutputFormat(assumedFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readFileMeta(filePath: string) {
|
export async function readFileMeta(filePath: string) {
|
||||||
|
@ -221,7 +221,7 @@ export function getOutFileExtension({ isCustomFormatSelected, outFormat, filePat
|
|||||||
// https://github.com/mifi/lossless-cut/issues/1075#issuecomment-1072084286
|
// https://github.com/mifi/lossless-cut/issues/1075#issuecomment-1072084286
|
||||||
const hasMovIncorrectExtension = outFormat === 'mov' && inputExt.toLowerCase() !== '.mov';
|
const hasMovIncorrectExtension = outFormat === 'mov' && inputExt.toLowerCase() !== '.mov';
|
||||||
|
|
||||||
// OK, just keep the current extension. Because most players will not care about the extension
|
// OK, just keep the current extension. Because most other players will not care about the extension
|
||||||
if (!hasMovIncorrectExtension) return inputExt;
|
if (!hasMovIncorrectExtension) return inputExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user