import React, { memo, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Button, Select, CrossIcon } from 'evergreen-ui'; import { FaRegCheckCircle } from 'react-icons/fa'; import i18n from 'i18next'; import { useTranslation, Trans } from 'react-i18next'; import { IoIosHelpCircle } from 'react-icons/io'; import KeyframeCutButton from './components/KeyframeCutButton'; import ExportButton from './components/ExportButton'; import ExportModeButton from './components/ExportModeButton'; import PreserveMovDataButton from './components/PreserveMovDataButton'; import MovFastStartButton from './components/MovFastStartButton'; import ToggleExportConfirm from './components/ToggleExportConfirm'; import OutSegTemplateEditor from './components/OutSegTemplateEditor'; import HighlightedText from './components/HighlightedText'; import { withBlur, toast } from './util'; import { isMov as ffmpegIsMov } from './util/streams'; import useUserSettings from './hooks/useUserSettings'; const sheetStyle = { position: 'fixed', left: 0, right: 0, top: 0, bottom: 0, zIndex: 10, background: 'rgba(105, 105, 105, 0.7)', backdropFilter: 'blur(10px)', overflowY: 'scroll', display: 'flex', }; const boxStyle = { margin: '15px 15px 50px 15px', background: 'rgba(25, 25, 25, 0.6)', borderRadius: 10, padding: '10px 20px', minHeight: 500, position: 'relative' }; const outDirStyle = { background: 'rgb(193, 98, 0)', borderRadius: '.4em', padding: '0 .3em', wordBreak: 'break-all', cursor: 'pointer' }; const warningStyle = { color: '#faa', fontSize: '80%' }; const HelpIcon = ({ onClick }) => ; const ExportConfirm = memo(({ areWeCutting, selectedSegments, segmentsToExport, willMerge, visible, onClosePress, onExportConfirm, outFormat, renderOutFmt, outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, filePath, currentSegIndexSafe, getOutSegError, nonFilteredSegments, }) => { const { t } = useTranslation(); const { changeOutDir, keyframeCut, preserveMovData, movFastStart, avoidNegativeTs, setAvoidNegativeTs, autoDeleteMergedSegments, exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, preserveMetadataOnMerge, togglePreserveMetadataOnMerge, enableSmartCut, setEnableSmartCut, effectiveExportMode } = useUserSettings(); const isMov = ffmpegIsMov(outFormat); const isIpod = outFormat === 'ipod'; const exportModeDescription = useMemo(() => ({ sesgments_to_chapters: t('Don\'t cut the file, but instead export an unmodified original which has chapters generated from segments'), merge: t('Auto merge segments to one file after export'), 'merge+separate': t('Auto merge segments to one file after export, but keep segments too'), separate: t('Export to separate files'), })[effectiveExportMode], [effectiveExportMode, t]); const onPreserveMovDataHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Preserve all MOV/MP4 metadata tags (e.g. EXIF, GPS position etc.) from source file? Note that some players have trouble playing back files where all metadata is preserved, like iTunes and other Apple software') }); }, []); const onMovFastStartHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Enable this to allow faster playback of the resulting file. This may cause processing to take a little longer') }); }, []); const onOutFmtHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Defaults to same format as input file. You can losslessly change the file format (container) of the file with this option. Not all formats support all codecs. Matroska/MP4/MOV support the most common codecs. Sometimes it\'s even impossible to export to the same output format as input.') }); }, []); const onKeyframeCutHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('With "keyframe cut", we will cut at the nearest keyframe before the desired start cutpoint. This is recommended for most files. With "Normal cut" you may have to manually set the cutpoint a few frames before the next keyframe to achieve a precise cut') }); }, []); const onSmartCutHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('This experimental feature will re-encode the part of the video from the cutpoint until the next keyframe in order to attempt to make a 100% accurate cut. Only works on some files. I\'ve had success with some h264 files, and only a few h265 files. See more here: {{url}}', { url: 'https://github.com/mifi/lossless-cut/issues/126' }) }); }, []); const onTracksHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Not all formats support all track types, and LosslessCut is unable to properly cut some track types, so you may have to sacrifice some tracks by disabling them in order to get correct result.') }); }, []); const onSegmentsToChaptersHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('When merging, do you want to create chapters in the merged file, according to the cut segments? NOTE: This may dramatically increase processing time') }); }, []); const onPreserveMetadataOnMergeHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('When merging, do you want to preserve metadata from your original file? NOTE: This may dramatically increase processing time') }); }, []); const onOutSegTemplateHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: i18n.t('You can customize the file name of the output segment(s) using special variables.') }); }, []); const onExportModeHelpPress = useCallback(() => { toast.fire({ icon: 'info', timer: 10000, text: exportModeDescription }); }, [exportModeDescription]); const onAvoidNegativeTsHelpPress = useCallback(() => { // https://ffmpeg.org/ffmpeg-all.html#Format-Options const texts = { make_non_negative: i18n.t('Shift timestamps to make them non-negative. Also note that this affects only leading negative timestamps, and not non-monotonic negative timestamps.'), make_zero: i18n.t('Shift timestamps so that the first timestamp is 0. (LosslessCut default)'), auto: i18n.t('Enables shifting when required by the target format.'), disabled: i18n.t('Disables shifting of timestamp.'), }; toast.fire({ icon: 'info', timer: 10000, text: `${avoidNegativeTs}: ${texts[avoidNegativeTs]}` }); }, [avoidNegativeTs]); const outSegTemplateHelpIcon = ; const canEditTemplate = !willMerge || !autoDeleteMergedSegments; // https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container return ( {visible && ( <>

{t('Export options')}

    {selectedSegments.length !== nonFilteredSegments.length &&
  • {t('{{selectedSegments}} of {{nonFilteredSegments}} segments selected', { selectedSegments: selectedSegments.length, nonFilteredSegments: nonFilteredSegments.length })}
  • } {selectedSegments.length >= 2 && (
  • {t('Merge {{segments}} cut segments to one file?', { segments: selectedSegments.length })}
  • )}
  • {t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
  • Input has {{ numStreamsTotal }} tracks - setStreamsSelectorShown(true)}>Keeping {{ numStreamsToCopy }} tracks
  • {t('Save output to path:')} {outputDir}
  • {canEditTemplate && (
  • )}

{t('Advanced options')}

{willMerge && (
  • {t('Create chapters from merged segments? (slow)')}
  • {t('Preserve original metadata when merging? (slow)')}
)}

{t('Depending on your specific file/player, you may have to try different options for best results.')}

    {areWeCutting && ( <>
  • {t('Smart cut (experimental):')}
  • {!enableSmartCut && (
  • {t('Cut mode:')} {!keyframeCut && {t('Note: Keyframe cut is recommended for most common files')}}
  • )} )} {isMov && ( <>
  • {t('Enable MOV Faststart?')} {isIpod && !movFastStart && {t('For the ipod format, it is recommended to activate this option')}}
  • {t('Preserve all MP4/MOV metadata?')} {isIpod && preserveMovData && {t('For the ipod format, it is recommended to deactivate this option')}}
  • )} {!enableSmartCut && (
  • {t('Shift timestamps (avoid_negative_ts)')}
  • )}
{t('Show this page before exporting?')}
onExportConfirm()} size={1.7} />
)}
); }); export default ExportConfirm;