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/
+
+

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;