implement random segments

closes #1153
pull/1158/head
Mikael Finstad 2022-05-23 22:31:48 -07:00
rodzic 3f794c68b0
commit bb304c8fd7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
4 zmienionych plików z 67 dodań i 2 usunięć

Wyświetl plik

@ -53,6 +53,7 @@ The main feature is lossless trimming and cutting of video and audio files, whic
- View subtitles
- Customizable keyboard hotkeys
- Black scene detection
- Divide timeline into segments with length L or into N segments or even randomized segments!
## Example lossless use cases

Wyświetl plik

@ -214,6 +214,12 @@ module.exports = (app, mainWindow, newVersion) => {
mainWindow.webContents.send('createFixedDurationSegments');
},
},
{
label: i18n.t('Create random segments'),
click() {
mainWindow.webContents.send('createRandomSegments');
},
},
{
label: i18n.t('Invert all segments on timeline'),
click() {

Wyświetl plik

@ -69,7 +69,7 @@ import {
} from './util';
import { formatDuration } from './util/duration';
import { adjustRate } from './util/rate-calculator';
import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showCutFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages } from './dialogs';
import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showCutFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages } from './dialogs';
import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n';
import { createSegment, getCleanCutSegments, getSegApparentStart, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap } from './segments';
@ -1815,6 +1815,12 @@ const App = memo(() => {
if (segments) loadCutSegments(segments);
}, [checkFileOpened, duration, loadCutSegments]);
const createRandomSegments = useCallback(async () => {
if (!checkFileOpened() || !isDurationValid(duration)) return;
const segments = await createRandomSegmentsDialog(duration);
if (segments) loadCutSegments(segments);
}, [checkFileOpened, duration, loadCutSegments]);
const askSetStartTimeOffset = useCallback(async () => {
const newStartTimeOffset = await promptTimeOffset({
initialValue: startTimeOffset !== undefined ? formatDuration({ seconds: startTimeOffset }) : undefined,
@ -2199,6 +2205,7 @@ const App = memo(() => {
shuffleSegments,
createNumSegments,
createFixedDurationSegments,
createRandomSegments,
invertAllSegments,
fillSegmentsGaps,
fixInvalidDuration: tryFixInvalidDuration,
@ -2211,7 +2218,7 @@ const App = memo(() => {
const entries = Object.entries(action);
entries.forEach(([key, value]) => electron.ipcRenderer.on(key, value));
return () => entries.forEach(([key, value]) => electron.ipcRenderer.removeListener(key, value));
}, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, customOutDir, cutSegments, detectBlackScenes, detectedFps, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, invertAllSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]);
}, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, createRandomSegments, customOutDir, cutSegments, detectBlackScenes, detectedFps, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, invertAllSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]);
const showAddStreamSourceDialog = useCallback(async () => {
try {

Wyświetl plik

@ -239,6 +239,40 @@ async function askForSegmentDuration(fileDuration) {
return parseDuration(value);
}
// https://github.com/mifi/lossless-cut/issues/1153
async function askForSegmentsRandomDurationRange() {
function parse(str) {
const match = str.replace(/\s/g, '').match(/^duration([\d.]+)to([\d.]+),gap([-\d.]+)to([-\d.]+)$/i);
if (!match) return undefined;
const values = match.slice(1);
const parsed = values.map((val) => parseFloat(val));
const durationMin = parsed[0];
const durationMax = parsed[1];
const gapMin = parsed[2];
const gapMax = parsed[3];
if (!(parsed.every((val) => !Number.isNaN(val)) && durationMin <= durationMax && gapMin <= gapMax && durationMin > 0)) return undefined;
return { durationMin, durationMax, gapMin, gapMax };
}
const { value } = await Swal.fire({
input: 'text',
showCancelButton: true,
inputValue: 'Duration 3 to 5, Gap 0 to 2',
text: i18n.t('Divide timeline into segments with randomized durations and gaps between sergments, in a range specified in seconds with the correct format.'),
inputValidator: (v) => {
const parsed = parse(v);
if (!parsed) return i18n.t('Invalid input');
return undefined;
},
});
if (value == null) return undefined;
return parse(value);
}
async function askForShiftSegmentsVariant(time) {
const { value } = await Swal.fire({
input: 'radio',
@ -376,6 +410,23 @@ export async function createFixedDurationSegments(fileDuration) {
return edl;
}
export async function createRandomSegments(fileDuration) {
const response = await askForSegmentsRandomDurationRange();
if (response == null) return undefined;
const { durationMin, durationMax, gapMin, gapMax } = response;
const randomInRange = (min, max) => min + Math.random() * (max - min);
const edl = [];
for (let start = 0; start < fileDuration && edl.length < maxSegments; start += randomInRange(gapMin, gapMax)) {
const end = start + randomInRange(durationMin, durationMax);
edl.push({ start, end });
start = end;
}
return edl;
}
export async function showCutFailedDialog({ detectedFileFormat }) {
const html = (
<div style={{ textAlign: 'left' }}>