mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-21 18:02:35 +01:00
improve types
This commit is contained in:
parent
dac0ac9f44
commit
1a674fa735
@ -589,7 +589,7 @@ function App() {
|
||||
showNotification({ timer: 13000, text: i18n.t('File is not natively supported. Preview playback may be slow and of low quality, but the final export will be lossless. You may convert the file from the menu for a better preview.') });
|
||||
}, [showNotification]);
|
||||
|
||||
const showPreviewFileLoadedMessage = useCallback((fileName) => {
|
||||
const showPreviewFileLoadedMessage = useCallback((fileName: string) => {
|
||||
showNotification({ icon: 'info', text: i18n.t('Loaded existing preview file: {{ fileName }}', { fileName }) });
|
||||
}, [showNotification]);
|
||||
|
||||
|
@ -5,11 +5,12 @@ import Timecode from 'smpte-timecode';
|
||||
import minBy from 'lodash/minBy';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
import { pcmAudioCodecs, getMapStreamsArgs, isMov, LiteFFprobeStream } from './util/streams';
|
||||
import { pcmAudioCodecs, getMapStreamsArgs, isMov } from './util/streams';
|
||||
import { getSuffixedOutPath, isExecaError } from './util';
|
||||
import { isDurationValid } from './segments';
|
||||
import { FFprobeChapter, FFprobeFormat, FFprobeProbeResult, FFprobeStream } from '../../../ffprobe';
|
||||
import { parseSrt, parseSrtToSegments } from './edlFormats';
|
||||
import { CopyfileStreams, LiteFFprobeStream } from './types';
|
||||
|
||||
const FileType = window.require('file-type');
|
||||
const { pathExists } = window.require('fs-extra');
|
||||
@ -58,7 +59,7 @@ export function isCuttingEnd(cutTo: number, duration: number | undefined) {
|
||||
return cutTo < duration;
|
||||
}
|
||||
|
||||
function getIntervalAroundTime(time, window) {
|
||||
function getIntervalAroundTime(time: number, window: number) {
|
||||
return {
|
||||
from: Math.max(time - window / 2, 0),
|
||||
to: time + window / 2,
|
||||
@ -681,7 +682,7 @@ export const getVideoTimescaleArgs = (videoTimebase: number | undefined) => (vid
|
||||
|
||||
// inspired by https://gist.github.com/fernandoherreradelasheras/5eca67f4200f1a7cc8281747da08496e
|
||||
export async function cutEncodeSmartPart({ filePath, cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoTimebase, allFilesMeta, copyFileStreams, videoStreamIndex, ffmpegExperimental }: {
|
||||
filePath: string, cutFrom: number, cutTo: number, outPath: string, outFormat: string, videoCodec: string, videoBitrate: number, videoTimebase: number, allFilesMeta, copyFileStreams, videoStreamIndex: number, ffmpegExperimental: boolean,
|
||||
filePath: string, cutFrom: number, cutTo: number, outPath: string, outFormat: string, videoCodec: string, videoBitrate: number, videoTimebase: number, allFilesMeta, copyFileStreams: CopyfileStreams, videoStreamIndex: number, ffmpegExperimental: boolean,
|
||||
}) {
|
||||
function getVideoArgs({ streamIndex, outputIndex }: { streamIndex: number, outputIndex: number }) {
|
||||
if (streamIndex !== videoStreamIndex) return undefined;
|
||||
|
@ -432,8 +432,8 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
||||
|
||||
console.log('Smart cut on video stream', videoStreamIndex);
|
||||
|
||||
const onCutProgress = (progress) => onSingleProgress(i, progress / 2);
|
||||
const onConcatProgress = (progress) => onSingleProgress(i, (1 + progress) / 2);
|
||||
const onCutProgress = (progress: number) => onSingleProgress(i, progress / 2);
|
||||
const onConcatProgress = (progress: number) => onSingleProgress(i, (1 + progress) / 2);
|
||||
|
||||
const copyFileStreamsFiltered = [{
|
||||
path: filePath,
|
||||
|
@ -10,7 +10,7 @@ const mapVideoCodec = (codec: string) => ({ av1: 'libsvtav1' }[codec] ?? codec);
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function getSmartCutParams({ path, videoDuration, desiredCutFrom, streams }: {
|
||||
path: string, videoDuration: number | undefined, desiredCutFrom: number, streams: FFprobeStream[],
|
||||
path: string, videoDuration: number | undefined, desiredCutFrom: number, streams: Pick<FFprobeStream, 'time_base' | 'codec_type' | 'disposition' | 'index' | 'bit_rate' | 'codec_name'>[],
|
||||
}) {
|
||||
const videoStreams = getRealVideoStreams(streams);
|
||||
if (videoStreams.length > 1) throw new Error('Can only smart cut video with exactly one video stream');
|
||||
|
@ -119,8 +119,10 @@ export type CopyfileStreams = {
|
||||
|
||||
export interface Chapter { start: number, end: number, name?: string | undefined }
|
||||
|
||||
export type LiteFFprobeStream = Pick<FFprobeStream, 'index' | 'codec_type' | 'codec_tag' | 'codec_name' | 'disposition' | 'tags' | 'sample_rate' | 'time_base'>;
|
||||
|
||||
export type AllFilesMeta = Record<string, {
|
||||
streams: FFprobeStream[];
|
||||
streams: LiteFFprobeStream[];
|
||||
formatData: FFprobeFormat;
|
||||
chapters: FFprobeChapter[];
|
||||
}>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import { LiteFFprobeStream, getMapStreamsArgs, getStreamIdsToCopy } from './streams';
|
||||
import { getMapStreamsArgs, getStreamIdsToCopy } from './streams';
|
||||
import { FFprobeStreamDisposition } from '../../../../ffprobe';
|
||||
import { LiteFFprobeStream } from '../types';
|
||||
|
||||
|
||||
const makeDisposition = (override?: Partial<FFprobeStreamDisposition>): FFprobeStreamDisposition => ({
|
||||
@ -26,14 +27,14 @@ const makeDisposition = (override?: Partial<FFprobeStreamDisposition>): FFprobeS
|
||||
});
|
||||
|
||||
const streams1: LiteFFprobeStream[] = [
|
||||
{ index: 0, codec_type: 'video', codec_tag: '0x0000', codec_name: 'mjpeg', disposition: makeDisposition({ attached_pic: 1 }) },
|
||||
{ index: 1, codec_type: 'audio', codec_tag: '0x6134706d', codec_name: 'aac', disposition: makeDisposition() },
|
||||
{ index: 2, codec_type: 'video', codec_tag: '0x31637661', codec_name: 'h264', disposition: makeDisposition() },
|
||||
{ index: 3, codec_type: 'video', codec_tag: '0x0000', codec_name: 'hevc', disposition: makeDisposition() },
|
||||
{ index: 4, codec_type: 'audio', codec_tag: '0x6134706d', codec_name: 'aac', disposition: makeDisposition() },
|
||||
{ index: 5, codec_type: 'attachment', codec_tag: '0x0000', codec_name: 'ttf', disposition: makeDisposition() },
|
||||
{ index: 6, codec_type: 'data', codec_tag: '0x64636d74', codec_name: '', disposition: makeDisposition() },
|
||||
{ index: 7, codec_type: 'subtitle', codec_tag: '0x0000', codec_name: 'subrip', disposition: makeDisposition() },
|
||||
{ index: 0, codec_type: 'video', codec_tag: '0x0000', codec_name: 'mjpeg', time_base: '', disposition: makeDisposition({ attached_pic: 1 }) },
|
||||
{ index: 1, codec_type: 'audio', codec_tag: '0x6134706d', codec_name: 'aac', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 2, codec_type: 'video', codec_tag: '0x31637661', codec_name: 'h264', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 3, codec_type: 'video', codec_tag: '0x0000', codec_name: 'hevc', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 4, codec_type: 'audio', codec_tag: '0x6134706d', codec_name: 'aac', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 5, codec_type: 'attachment', codec_tag: '0x0000', codec_name: 'ttf', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 6, codec_type: 'data', codec_tag: '0x64636d74', codec_name: '', time_base: '', disposition: makeDisposition() },
|
||||
{ index: 7, codec_type: 'subtitle', codec_tag: '0x0000', codec_name: 'subrip', time_base: '', disposition: makeDisposition() },
|
||||
];
|
||||
|
||||
const path = '/path/to/file';
|
||||
@ -66,8 +67,8 @@ test('getMapStreamsArgs', () => {
|
||||
test('getMapStreamsArgs, subtitles to matroska', () => {
|
||||
const outFormat = 'matroska';
|
||||
|
||||
const streams = [
|
||||
{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text' },
|
||||
const streams: LiteFFprobeStream[] = [
|
||||
{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text', time_base: '', disposition: makeDisposition() },
|
||||
];
|
||||
|
||||
expect(getMapStreamsArgs({
|
||||
@ -136,7 +137,7 @@ test('getStreamIdsToCopy, includeAllStreams false', () => {
|
||||
|
||||
test('srt output', () => {
|
||||
expect(getMapStreamsArgs({
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text' }] } },
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text', time_base: '', disposition: makeDisposition() }] } },
|
||||
copyFileStreams: [{ path, streamIds: [0] }],
|
||||
outFormat: 'srt',
|
||||
})).toEqual([
|
||||
@ -146,7 +147,7 @@ test('srt output', () => {
|
||||
|
||||
test('webvtt output', () => {
|
||||
expect(getMapStreamsArgs({
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text' }] } },
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text', time_base: '', disposition: makeDisposition() }] } },
|
||||
copyFileStreams: [{ path, streamIds: [0] }],
|
||||
outFormat: 'webvtt',
|
||||
})).toEqual([
|
||||
@ -156,7 +157,7 @@ test('webvtt output', () => {
|
||||
|
||||
test('ass output', () => {
|
||||
expect(getMapStreamsArgs({
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text' }] } },
|
||||
allFilesMeta: { [path]: { streams: [{ index: 0, codec_type: 'subtitle', codec_tag: '0x67337874', codec_name: 'mov_text', time_base: '', disposition: makeDisposition() }] } },
|
||||
copyFileStreams: [{ path, streamIds: [0] }],
|
||||
outFormat: 'ass',
|
||||
})).toEqual([
|
||||
|
@ -1,5 +1,6 @@
|
||||
import invariant from 'tiny-invariant';
|
||||
import { FFprobeStream, FFprobeStreamDisposition } from '../../../../ffprobe';
|
||||
import { ChromiumHTMLAudioElement, ChromiumHTMLVideoElement } from '../types';
|
||||
import { AllFilesMeta, ChromiumHTMLAudioElement, ChromiumHTMLVideoElement, CopyfileStreams, LiteFFprobeStream } from '../types';
|
||||
|
||||
// https://www.ffmpeg.org/doxygen/3.2/libavutil_2utils_8c_source.html#l00079
|
||||
const defaultProcessedCodecTypes = new Set([
|
||||
@ -113,15 +114,15 @@ export const isMov = (format: string | undefined) => format != null && ['ismv',
|
||||
|
||||
type GetVideoArgsFn = (a: { streamIndex: number, outputIndex: number }) => string[] | undefined;
|
||||
|
||||
function getPerStreamFlags({ stream, outputIndex, outFormat, manuallyCopyDisposition = false, getVideoArgs = () => undefined }: {
|
||||
stream: FFprobeStream, outputIndex: number, outFormat: string | undefined, manuallyCopyDisposition?: boolean | undefined, getVideoArgs?: GetVideoArgsFn | undefined
|
||||
function getPerStreamFlags({ stream, outputIndex, outFormat, manuallyCopyDisposition = false, getVideoArgs = () => undefined, areWeCutting }: {
|
||||
stream: LiteFFprobeStream, outputIndex: number, outFormat: string | undefined, manuallyCopyDisposition?: boolean | undefined, getVideoArgs?: GetVideoArgsFn | undefined, areWeCutting: boolean | undefined
|
||||
}) {
|
||||
let args: string[] = [];
|
||||
|
||||
function addArgs(...newArgs) {
|
||||
function addArgs(...newArgs: string[]) {
|
||||
args.push(...newArgs);
|
||||
}
|
||||
function addCodecArgs(codec) {
|
||||
function addCodecArgs(codec: string) {
|
||||
addArgs(`-c:${outputIndex}`, codec);
|
||||
}
|
||||
|
||||
@ -203,20 +204,27 @@ function getPerStreamFlags({ stream, outputIndex, outFormat, manuallyCopyDisposi
|
||||
return args;
|
||||
}
|
||||
|
||||
export function getMapStreamsArgs({ startIndex = 0, outFormat, allFilesMeta, copyFileStreams, manuallyCopyDisposition, getVideoArgs }: {
|
||||
startIndex?: number, outFormat: string | undefined, allFilesMeta, copyFileStreams: { streamIds: number[], path: string }[], manuallyCopyDisposition?: boolean, getVideoArgs?: GetVideoArgsFn,
|
||||
export function getMapStreamsArgs({ startIndex = 0, outFormat, allFilesMeta, copyFileStreams, manuallyCopyDisposition, getVideoArgs, areWeCutting }: {
|
||||
startIndex?: number,
|
||||
outFormat: string | undefined,
|
||||
allFilesMeta: Record<string, Pick<AllFilesMeta[string], 'streams'>>,
|
||||
copyFileStreams: CopyfileStreams,
|
||||
manuallyCopyDisposition?: boolean,
|
||||
getVideoArgs?: GetVideoArgsFn,
|
||||
areWeCutting?: boolean,
|
||||
}) {
|
||||
let args: string[] = [];
|
||||
let outputIndex = startIndex;
|
||||
|
||||
copyFileStreams.forEach(({ streamIds, path }, fileIndex) => {
|
||||
streamIds.forEach((streamId) => {
|
||||
const { streams } = allFilesMeta[path];
|
||||
const { streams } = allFilesMeta[path]!;
|
||||
const stream = streams.find((s) => s.index === streamId);
|
||||
invariant(stream != null);
|
||||
args = [
|
||||
...args,
|
||||
'-map', `${fileIndex}:${streamId}`,
|
||||
...getPerStreamFlags({ stream, outputIndex, outFormat, manuallyCopyDisposition, getVideoArgs }),
|
||||
...getPerStreamFlags({ stream, outputIndex, outFormat, manuallyCopyDisposition, getVideoArgs, areWeCutting }),
|
||||
];
|
||||
outputIndex += 1;
|
||||
});
|
||||
@ -232,16 +240,14 @@ export function shouldCopyStreamByDefault(stream: FFprobeStream) {
|
||||
|
||||
export const attachedPicDisposition = 'attached_pic';
|
||||
|
||||
export type LiteFFprobeStream = Pick<FFprobeStream, 'index' | 'codec_type' | 'codec_tag' | 'codec_name' | 'disposition' | 'tags'>;
|
||||
|
||||
export function isStreamThumbnail(stream: LiteFFprobeStream) {
|
||||
export function isStreamThumbnail(stream: Pick<FFprobeStream, 'codec_type' | 'disposition'>) {
|
||||
return stream && stream.codec_type === 'video' && stream.disposition?.[attachedPicDisposition] === 1;
|
||||
}
|
||||
|
||||
export const getAudioStreams = <T extends LiteFFprobeStream>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'audio');
|
||||
export const getRealVideoStreams = <T extends LiteFFprobeStream>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'video' && !isStreamThumbnail(stream));
|
||||
export const getSubtitleStreams = <T extends LiteFFprobeStream>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'subtitle');
|
||||
export const isGpsStream = <T extends LiteFFprobeStream>(stream: T) => stream.codec_type === 'subtitle' && stream.tags?.['handler_name'] === '\u0010DJI.Subtitle';
|
||||
export const getAudioStreams = <T extends Pick<FFprobeStream, 'codec_type'>>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'audio');
|
||||
export const getRealVideoStreams = <T extends Pick<FFprobeStream, 'codec_type' | 'disposition'>>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'video' && !isStreamThumbnail(stream));
|
||||
export const getSubtitleStreams = <T extends Pick<FFprobeStream, 'codec_type'>>(streams: T[]) => streams.filter((stream) => stream.codec_type === 'subtitle');
|
||||
export const isGpsStream = <T extends Pick<FFprobeStream, 'codec_type' | 'tags'>>(stream: T) => stream.codec_type === 'subtitle' && stream.tags?.['handler_name'] === '\u0010DJI.Subtitle';
|
||||
|
||||
// videoTracks/audioTracks seems to be 1-indexed, while ffmpeg is 0-indexes
|
||||
const getHtml5TrackId = (ffmpegTrackIndex: number) => String(ffmpegTrackIndex + 1);
|
||||
@ -365,7 +371,7 @@ export function isAudioDefinitelyNotSupported(streams: FFprobeStream[]) {
|
||||
return audioStreams.every((stream) => ['ac3', 'eac3'].includes(stream.codec_name));
|
||||
}
|
||||
|
||||
export function getVideoTimebase(videoStream: FFprobeStream) {
|
||||
export function getVideoTimebase(videoStream: Pick<FFprobeStream, 'time_base'>) {
|
||||
const timebaseMatch = videoStream.time_base && videoStream.time_base.split('/');
|
||||
if (timebaseMatch) {
|
||||
const timebaseParsed = parseInt(timebaseMatch[1]!, 10);
|
||||
|
Loading…
Reference in New Issue
Block a user