diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 00000000..88808f84 --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,5 @@ +version: "1" +rules: + - base: master + upstream: mifi:master # change `wei` to the owner of upstream repo + mergeMethod: merge diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25b45acc..d4734a8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,8 +4,8 @@ on: push: tags: - 'v*' - schedule: - - cron: '0 10 * * *' + # schedule: + # - cron: '0 10 * * *' workflow_dispatch: inputs: tmate_enabled: @@ -25,8 +25,7 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest, windows-11-arm] -# os: [windows-latest] + os: [ ubuntu-latest, windows-latest] steps: - name: Setup tmate session diff --git a/README.md b/README.md index d9736ccb..fbd1a3ed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +Releases: https://github.com/ac615223s5/lossless-cut/releases +Forked from https://github.com/mifi/lossless-cut/ + +

LosslessCut

diff --git a/src/renderer/src/dialogs/index.tsx b/src/renderer/src/dialogs/index.tsx index 44c10cef..636bd212 100644 --- a/src/renderer/src/dialogs/index.tsx +++ b/src/renderer/src/dialogs/index.tsx @@ -334,16 +334,16 @@ export async function askForAlignSegments() { const startOrEnd = await askForSegmentsStartOrEnd(i18n.t('Do you want to align the segment start or end timestamps to keyframes?')); if (startOrEnd == null) return undefined; - const { value: mode } = await getSwal().Swal.fire({ + const { value: mode } = await getSwal().Swal.fire({ input: 'radio', showCancelButton: true, inputOptions: { nearest: i18n.t('Nearest keyframe'), before: i18n.t('Previous keyframe'), after: i18n.t('Next keyframe'), - opposing: i18n.t('Segment start to previous keyframe and end to next keyframe'), - } satisfies Record, - inputValue: 'before', + keyframeCutFix: i18n.t('True keyframe cut'), + } satisfies Record, + inputValue: 'keyframeCutFix', text: i18n.t('Do you want to align segment times to the nearest, previous or next keyframe?'), }); diff --git a/src/renderer/src/ffmpeg.ts b/src/renderer/src/ffmpeg.ts index e44e6521..7331108c 100644 --- a/src/renderer/src/ffmpeg.ts +++ b/src/renderer/src/ffmpeg.ts @@ -108,7 +108,7 @@ export const findNextKeyframe = (keyframes: Frame[], time: number) => keyframes. const findPreviousKeyframe = (keyframes: Frame[], time: number) => keyframes.findLast((keyframe) => keyframe.time <= time); const findNearestKeyframe = (keyframes: Frame[], time: number) => minBy(keyframes, (keyframe) => Math.abs(keyframe.time - time)); -export type FindKeyframeMode = 'nearest' | 'before' | 'after'; +export type FindKeyframeMode = 'nearest' | 'before' | 'after' | 'keyframeCutFix'; function findKeyframe(keyframes: Frame[], time: number, mode: FindKeyframeMode) { switch (mode) { @@ -121,6 +121,9 @@ function findKeyframe(keyframes: Frame[], time: number, mode: FindKeyframeMode) case 'after': { return findNextKeyframe(keyframes, time); } + case 'keyframeCutFix': { + return findNextKeyframe(keyframes,time); + } default: { return undefined; } diff --git a/src/renderer/src/hooks/useSegments.tsx b/src/renderer/src/hooks/useSegments.tsx index 4371ef80..99a13af2 100644 --- a/src/renderer/src/hooks/useSegments.tsx +++ b/src/renderer/src/hooks/useSegments.tsx @@ -8,8 +8,7 @@ import sortBy from 'lodash/sortBy'; import { Trans, useTranslation } from 'react-i18next'; import { FaLink } from 'react-icons/fa'; -import TextInput from '../components/TextInput'; -import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg'; +import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime, getStreamFps } from '../ffmpeg'; import { getFileSize, shuffleArray } from '../util'; import { errorToast } from '../swal'; import { createNumSegments as createNumSegmentsDialog, createFixedByteSixedSegments as createFixedByteSixedSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, askForSegmentDuration, toastError } from '../dialogs'; @@ -516,25 +515,50 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea if (response == null) return; setWorking({ text: i18n.t('Aligning segments to keyframes') }); const { mode, startOrEnd } = response; + + if (filePath == null) throw new Error(); + const frameTime = 1 / (getStreamFps(videoStream) || 1000); + await modifySelectedSegmentTimes(async (segment) => { const newSegment = { ...segment }; - const align = async (key: 'start' | 'end') => { + async function align(key: string) { const time = newSegment[key]; invariant(filePath != null); if (time != null) { - const keyframe = await findKeyframeNearTime({ - filePath, - streamIndex: videoStream.index, - time, - mode: mode === 'opposing' ? (key === 'start' ? 'before' : 'after') : mode, - }); - if (keyframe == null) throw new UserFacingError(i18n.t('Cannot find any keyframe within 60 seconds of frame {{time}}', { time })); + let keyframe = await findKeyframeNearTime({ filePath, streamIndex: videoStream.index, time, mode }); + if (keyframe === undefined) { + if (mode !== 'keyframeCutFix') { + throw new UserFacingError(i18n.t('Cannot find any keyframe within 60 seconds of frame {{time}}', { time })); + } + keyframe = fileDuration; + console.log(2,time,keyframe); + } newSegment[key] = keyframe; } - }; - if (startOrEnd.includes('start')) await align('start'); - if (startOrEnd.includes('end')) await align('end'); + } + if (startOrEnd.includes('start')) { + if (mode === 'keyframeCutFix') { + newSegment.start += frameTime * 0.3; + } + await align('start'); + if (mode === 'keyframeCutFix') { + newSegment.start -= frameTime * 0.7; + } + } + if (startOrEnd.includes('end')) { + await align('end'); + if (mode === 'keyframeCutFix' && newSegment.end !== fileDuration) { + newSegment.end -= frameTime * 0.3; + } + } + if (startOrEnd.includes('start')) { + newSegment.start = Math.min(newSegment.start, newSegment.end - frameTime * 0.99); // don't know how ffmpeg interprets cuts between frames + } else { + newSegment.end = Math.max(newSegment.start + frameTime * 0.99, newSegment.end); + } + + return newSegment; }); } catch (err) { @@ -542,7 +566,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea } finally { setWorking(undefined); } - }, [videoStream, workingRef, setWorking, modifySelectedSegmentTimes, filePath, handleError]); + }, [filePath, videoStream, modifySelectedSegmentTimes, setWorking, workingRef, fileDuration, handleError]); const updateSegOrder = useCallback((index: number, newOrder: number) => { if (newOrder > cutSegments.length - 1 || newOrder < 0) return;