import padStart from 'lodash/padStart'; import Swal from 'sweetalert2'; import i18n from 'i18next'; import randomColor from './random-color'; const path = window.require('path'); const fs = window.require('fs-extra'); export function formatDuration({ seconds: _seconds, fileNameFriendly, fps }) { const seconds = _seconds || 0; const minutes = seconds / 60; const hours = minutes / 60; const hoursPadded = padStart(Math.floor(hours), 2, '0'); const minutesPadded = padStart(Math.floor(minutes % 60), 2, '0'); const secondsPadded = padStart(Math.floor(seconds) % 60, 2, '0'); const ms = seconds - Math.floor(seconds); const msPadded = fps != null ? padStart(Math.floor(ms * fps), 2, '0') : padStart(Math.floor(ms * 1000), 3, '0'); // Be nice to filenames and use . const delim = fileNameFriendly ? '.' : ':'; return `${hoursPadded}${delim}${minutesPadded}${delim}${secondsPadded}.${msPadded}`; } export function parseDuration(str) { if (!str) return undefined; const match = str.trim().match(/^(\d{2}):(\d{2}):(\d{2})\.(\d{3})$/); if (!match) return undefined; const hours = parseInt(match[1], 10); const minutes = parseInt(match[2], 10); const seconds = parseInt(match[3], 10); const ms = parseInt(match[4], 10); if (hours > 59 || minutes > 59 || seconds > 59) return undefined; return ((((hours * 60) + minutes) * 60) + seconds) + (ms / 1000); } export function getOutDir(customOutDir, filePath) { if (customOutDir) return customOutDir; if (filePath) return path.dirname(filePath); return undefined; } export function getOutPath(customOutDir, filePath, nameSuffix) { if (!filePath) return undefined; const parsed = path.parse(filePath); return path.join(getOutDir(customOutDir, filePath), `${parsed.name}-${nameSuffix}`); } export async function checkDirWriteAccess(dirPath) { try { await fs.access(dirPath, fs.constants.W_OK); } catch (err) { if (err.code === 'EPERM') return false; // if (err.code === 'EACCES') return false; console.error(err); } return true; } export async function transferTimestamps(inPath, outPath) { try { const stat = await fs.stat(inPath); await fs.utimes(outPath, stat.atime.getTime() / 1000, stat.mtime.getTime() / 1000); } catch (err) { console.error('Failed to set output file modified time', err); } } export async function transferTimestampsWithOffset(inPath, outPath, offset) { try { const stat = await fs.stat(inPath); const time = (stat.mtime.getTime() / 1000) + offset; await fs.utimes(outPath, time, time); } catch (err) { console.error('Failed to set output file modified time', err); } } export const toast = Swal.mixin({ toast: true, position: 'top', showConfirmButton: false, timer: 5000, }); export const errorToast = (title) => toast.fire({ icon: 'error', title, }); export async function showFfmpegFail(err) { console.error(err); return errorToast(`${i18n.t('Failed to run ffmpeg:')} ${err.stack}`); } export function setFileNameTitle(filePath) { const appName = 'LosslessCut'; document.title = filePath ? `${appName} - ${path.basename(filePath)}` : appName; } export function filenamify(name) { return name.replace(/[^0-9a-zA-Z_.]/g, '_'); } export async function promptTimeOffset(inputValue) { const { value } = await Swal.fire({ title: i18n.t('Set custom start time offset'), text: i18n.t('Instead of video apparently starting at 0, you can offset by a specified value (useful for viewing/cutting videos according to timecodes)'), input: 'text', inputValue: inputValue || '', showCancelButton: true, inputPlaceholder: '00:00:00.000', }); if (value === undefined) { return undefined; } const duration = parseDuration(value); // Invalid, try again if (duration === undefined) return promptTimeOffset(value); return duration; } export function generateColor() { return randomColor(1, 0.95); } export function withBlur(cb) { return (e) => { cb(e); e.target.blur(); }; } export function getSegColors(seg) { if (!seg) return {}; const { color } = seg; return { segBgColor: color.alpha(0.5).string(), segActiveBgColor: color.lighten(0.5).alpha(0.5).string(), segBorderColor: color.lighten(0.5).string(), }; }