Skip to content

Commit

Permalink
make it more explicit when changing mp4 to mov
Browse files Browse the repository at this point in the history
and show a notification #1075
  • Loading branch information
mifi committed Sep 3, 2024
1 parent 590e6a8 commit 69f600a
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 23 deletions.
4 changes: 2 additions & 2 deletions issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
- 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

Expand Down
10 changes: 8 additions & 2 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
isIphoneHevc, isProblematicAvc1, tryMapChaptersToEdl,
getDuration, getTimecodeFromStreams, createChaptersFromSegments,
RefuseOverwriteError, extractSubtitleTrackToSegments,
mapRecommendedDefaultFormat,
} from './ffmpeg';
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, getSubtitleStreams, getVideoTrackForStreamIndex, getAudioTrackForStreamIndex, enableVideoTrack, enableAudioTrack } from './util/streams';
import { exportEdlFile, readEdlFile, loadLlcProject, askForEdlImport } from './edlStore';
Expand Down Expand Up @@ -1305,7 +1306,6 @@ function App() {
// console.log('file meta read', fileMeta);

const fileFormatNew = await getDefaultOutFormat({ filePath: fp, fileMeta });

if (!fileFormatNew) throw new Error('Unable to determine file format');

const timecode = autoLoadTimecode ? getTimecodeFromStreams(fileMeta.streams) : undefined;
Expand Down Expand Up @@ -1373,8 +1373,14 @@ function App() {
if (!haveVideoStream) setWaveformMode('big-waveform');
setMainFileMeta({ streams: fileMeta.streams, formatData: fileMeta.format, chapters: fileMeta.chapters });
setCopyStreamIdsForPath(fp, () => copyStreamIdsForPathNew);
setFileFormat(outFormatLocked || 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
if (existingHtml5FriendlyFile) {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/src/components/ConcatDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import invariant from 'tiny-invariant';
import Checkbox from './Checkbox';

import { ReactSwal } from '../swal';
import { readFileMeta, getDefaultOutFormat } from '../ffmpeg';
import { readFileMeta, getDefaultOutFormat, mapRecommendedDefaultFormat } from '../ffmpeg';
import useFileFormatState from '../hooks/useFileFormatState';
import OutputFormatSelect from './OutputFormatSelect';
import useUserSettings from '../hooks/useUserSettings';
Expand Down Expand Up @@ -69,8 +69,8 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
const fileFormatNew = await getDefaultOutFormat({ filePath: firstPath, fileMeta: fileMetaNew });
if (aborted) return;
setFileMeta(fileMetaNew);
setFileFormat(fileFormatNew);
setDetectedFileFormat(fileFormatNew);
setFileFormat(mapRecommendedDefaultFormat({ sourceFormat: fileFormatNew, streams: fileMetaNew.streams }).format);
setUniqueSuffix(Date.now());
})().catch(console.error);

Expand Down
33 changes: 17 additions & 16 deletions src/renderer/src/ffmpeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,27 +236,28 @@ export async function createChaptersFromSegments({ segmentPaths, chapterNames }:
}

/**
* ffmpeg only supports encoding certain formats, and some of the detected input
* formats are not the same as the muxer name used for encoding.
* Some of the detected input formats are not the same as the muxer name used for encoding.
* Therefore we have to map between detected input format and encode format
* See also ffmpeg -formats
*/
function mapDefaultFormat({ streams, requestedFormat }: { streams: FFprobeStream[], requestedFormat: string | undefined }) {
if (requestedFormat === 'mp4') {
// 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
function mapInputToOutputFormat(requestedFormat: string | undefined) {
// see file aac raw adts.aac
if (requestedFormat === 'aac') return 'adts';

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 [firstFfprobeFormat] = ffprobeFormats;
Expand Down Expand Up @@ -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[] } }) {
const assumedFormat = await determineOutputFormat(format.format_name, filePath);
export async function getDefaultOutFormat({ filePath, fileMeta: { format } }: { filePath: string, fileMeta: { format: Pick<FFprobeFormat, 'format_name'> } }) {
const assumedFormat = await determineSourceFileFormat(format.format_name, filePath);

return mapDefaultFormat({ streams, requestedFormat: assumedFormat });
return mapInputToOutputFormat(assumedFormat);
}

export async function readFileMeta(filePath: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export function getOutFileExtension({ isCustomFormatSelected, outFormat, filePat
// https://github.com/mifi/lossless-cut/issues/1075#issuecomment-1072084286
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;
}

Expand Down

0 comments on commit 69f600a

Please sign in to comment.