@ -1,7 +1,6 @@
import { CSSProperties , Dispatch , SetStateAction, memo , useCallback , useMemo } from 'react' ;
import { CSSProperties , Dispatch , ReactNode, SetStateAction, memo , useCallback , useMemo } from 'react' ;
import { motion , AnimatePresence } from 'framer-motion' ;
import { WarningSignIcon , InfoSignIcon } from 'evergreen-ui' ;
import { FaRegCheckCircle } from 'react-icons/fa' ;
import { FaExclamationTriangle , FaInfoCircle , FaRegCheckCircle } from 'react-icons/fa' ;
import i18n from 'i18next' ;
import { useTranslation , Trans } from 'react-i18next' ;
import { IoIosHelpCircle , IoIosSettings } from 'react-icons/io' ;
@ -27,17 +26,16 @@ import { FFprobeStream } from '../../../../ffprobe';
import { AvoidNegativeTs , PreserveMetadata } from '../../../../types' ;
import TextInput from './TextInput' ;
import { UseSegments } from '../hooks/useSegments' ;
import Warning from './Warning' ;
import CloseButton from './CloseButton' ;
const boxStyle : CSSProperties = { margin : '15px 15px 50px 15px' , borderRadius : 10 , padding : '10px 20px' , minHeight : 500 , position : 'relative' } ;
const outDirStyle : CSSProperties = { . . . highlightedTextStyle , wordBreak : 'break-all' , cursor : 'pointer' } ;
const warningStyle : CSSProperties = { fontSize : '80%' , marginBottom : '.5em' } ;
const noticeStyle : CSSProperties = { fontSize : '85%' , marginBottom : '.5em' } ;
const infoStyle : CSSProperties = { . . . noticeStyle , color : 'var(--blue-12)' } ;
const warningStyle : CSSProperties = { . . . noticeStyle , color : 'var(--orange-8)' } ;
const infoStyle : CSSProperties = { color : 'var(--gray-12)' , fontSize : '80%' , marginBottom : '.5em' } ;
const rightIconStyle: CSSProperties = { fontSize : '1.2em' , verticalAlign : 'middle ' } ;
const adjustCutFromValues = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
const adjustCutToValues = [ - 10 , - 9 , - 8 , - 7 , - 6 , - 5 , - 4 , - 3 , - 2 , - 1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
@ -53,6 +51,25 @@ function ShiftTimes({ values, num, setNum }: { values: number[], num: number, se
) ;
}
function renderNoticeIcon ( notice : { warning? : boolean | undefined } | undefined , style? : CSSProperties ) {
if ( ! notice ) return undefined ;
return notice . warning ? (
< FaExclamationTriangle style = { { flexShrink : '0' , fontSize : '.8em' , verticalAlign : 'baseline' , color : 'var(--orange-9)' , . . . style } } / >
) : (
< FaInfoCircle style = { { flexShrink : '0' , fontSize : '.8em' , verticalAlign : 'baseline' , color : 'var(--blue-10)' , . . . style } } / >
) ;
}
function renderNotice ( notice : { warning? : boolean | undefined , text : ReactNode } | undefined ) {
if ( notice == null ) return null ;
const { warning , text } = notice ;
return (
< div style = { { . . . ( warning ? warningStyle : infoStyle ) , gap : '0 .5em' } } >
{ renderNoticeIcon ( { warning } ) } { text }
< / div >
) ;
}
function ExportConfirm ( {
areWeCutting ,
segmentsToExport ,
@ -125,19 +142,49 @@ function ExportConfirm({
const areWeCuttingProblematicStreams = areWeCutting && mainCopiedThumbnailStreams . length > 0 ;
const notices = useMemo ( ( ) = > {
const ret : { warning? : true , text : string } [ ] = [ ] ;
if ( ! areWeCutting ) {
ret . push ( { text : t ( 'Exporting whole file without cutting, because there are no segments to export.' ) } ) ;
const generic : { warning? : true , text : string } [ ] = [ ] ;
const specific : Record < 'exportMode' | 'problematicStreams' | 'movFastStart' | 'preserveMovData' | 'smartCut' | 'cutMode' | 'avoidNegativeTs' | 'overwriteOutput' , { warning? : true , text : ReactNode } | undefined > = {
exportMode : effectiveExportMode === 'segments_to_chapters' ? { text : i18n.t ( 'Segments to chapters mode is active, this means that the file will not be cut. Instead chapters will be created from the segments.' ) } : undefined ,
problematicStreams : areWeCuttingProblematicStreams ? { warning : true , text : < Trans > Warning : Cutting thumbnail tracks is known to cause problems . Consider disabling track { { trackNumber : mainCopiedThumbnailStreams [ 0 ] ? mainCopiedThumbnailStreams [ 0 ] . index + 1 : 0 } } . < / Trans > } : undefined ,
movFastStart : isMov && isIpod && ! movFastStart ? { warning : true , text : t ( 'For the ipod format, it is recommended to activate this option' ) } : undefined ,
preserveMovData : isMov && isIpod && preserveMovData ? { warning : true , text : t ( 'For the ipod format, it is recommended to deactivate this option' ) } : undefined ,
smartCut : areWeCutting && needSmartCut ? { warning : true , text : t ( 'Smart cut is experimental and will not work on all files.' ) } : undefined ,
cutMode : areWeCutting && ! needSmartCut && ! keyframeCut ? { text : t ( 'Note: Keyframe cut is recommended for most common files' ) } : undefined ,
avoidNegativeTs : ! needSmartCut ? {
text : ( ( ) = > {
if ( willMerge ) {
if ( avoidNegativeTs !== 'make_non_negative' ) {
return t ( 'When merging, it\'s generally recommended to set this to "make_non_negative"' ) ;
}
return undefined ;
}
if ( ! [ 'make_zero' , 'auto' ] . includes ( avoidNegativeTs ) ) {
return t ( 'It\'s generally recommended to set this to one of: {{values}}' , { values : '"auto", "make_zero"' } ) ;
}
return undefined ;
} ) ( ) ,
} : undefined ,
overwriteOutput : enableOverwriteOutput ? { text : t ( 'Existing files will be overwritten without warning!' ) } : undefined ,
} ;
if ( effectiveExportMode === 'separate' && ! areWeCutting ) {
generic . push ( { text : t ( 'Exporting whole file without cutting, because there are no segments to export.' ) } ) ;
}
// https://github.com/mifi/lossless-cut/issues/1809
if ( areWeCutting && outFormat === 'flac' ) {
ret . push ( { warning : true , text : t ( 'There is a known issue in FFmpeg with cutting FLAC files. The file will be re-encoded, which is still lossless, but the export may be slower.' ) } ) ;
generic . push ( { warning : true , text : t ( 'There is a known issue in FFmpeg with cutting FLAC files. The file will be re-encoded, which is still lossless, but the export may be slower.' ) } ) ;
}
if ( areWeCutting && outputPlaybackRate !== 1 ) {
ret . push ( { warning : true , text : t ( 'Adjusting the output FPS and cutting at the same time will cause incorrect cuts. Consider instead doing it in two separate steps.' ) } ) ;
generic . push ( { warning : true , text : t ( 'Adjusting the output FPS and cutting at the same time will cause incorrect cuts. Consider instead doing it in two separate steps.' ) } ) ;
}
return ret ;
} , [ areWeCutting , outFormat , outputPlaybackRate , t ] ) ;
return {
generic ,
specific ,
totalNum : generic.filter ( ( n ) = > n . warning ) . length + Object . values ( specific ) . filter ( ( n ) = > n != null && n . warning ) . length ,
} ;
} , [ areWeCutting , areWeCuttingProblematicStreams , avoidNegativeTs , effectiveExportMode , enableOverwriteOutput , isIpod , isMov , keyframeCut , mainCopiedThumbnailStreams , movFastStart , needSmartCut , outFormat , outputPlaybackRate , preserveMovData , t , willMerge ] ) ;
const exportModeDescription = useMemo ( ( ) = > ( {
segments_to_chapters : t ( 'Don\'t cut the file, but instead export an unmodified original which has chapters generated from segments' ) ,
@ -244,386 +291,336 @@ function ExportConfirm({
className = { styles [ 'sheet' ] }
transition = { { duration : 0.3 , easings : [ 'easeOut' ] } }
>
< div style = { { margin : 'auto' } } >
< div style = { boxStyle } className = { styles [ 'box' ] } >
< h1 style = { { textTransform : 'uppercase' , fontSize : '1.4em' , marginTop : 0 , marginBottom : '.5em' } } > { t ( 'Export options' ) } < / h1 >
< CloseButton type = "submit" style = { { top : 0 , right : 0 } } onClick = { onClosePress } / >
< table className = { styles [ 'options' ] } >
< tbody >
{ notices . map ( ( { warning , text } ) = > (
< tr key = { text } >
< td colSpan = { 2 } >
< div style = { { . . . ( warning ? { . . . warningStyle , color : 'var(--orange-8)' } : infoStyle ) , display : 'flex' , alignItems : 'center' , gap : '0 .5em' } } >
{ warning ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" flexShrink = "0" / >
) : (
< InfoSignIcon verticalAlign = "middle" color = "info" flexShrink = "0" / >
) } { text }
< / div >
< / td >
< td / >
< / tr >
) ) }
{ segmentsOrInverse . selected . length !== segmentsOrInverse . all . length && (
< tr >
< td colSpan = { 2 } >
< FaRegCheckCircle size = { 12 } style = { { marginRight : 3 } } / > { t ( '{{selectedSegments}} of {{nonFilteredSegments}} segments selected' , { selectedSegments : segmentsOrInverse.selected.length , nonFilteredSegments : segmentsOrInverse.all.length } ) }
< / td >
< td / >
< / tr >
) }
< tr >
< td >
{ segmentsOrInverse . selected . length > 1 ? t ( 'Export mode for {{segments}} segments' , { segments : segmentsOrInverse.selected.length } ) : t ( 'Export mode' ) }
< / td >
< td >
< ExportModeButton selectedSegments = { segmentsOrInverse . selected } / >
< / td >
< td >
{ effectiveExportMode === 'segments_to_chapters' ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" title = { i18n . t ( 'Segments to chapters mode is active, this means that the file will not be cut. Instead chapters will be created from the segments.' ) } / >
) : (
< HelpIcon onClick = { onExportModeHelpPress } / >
) }
< div className = { styles [ 'box' ] } >
< h1 style = { { textTransform : 'uppercase' , fontSize : '1.4em' , marginTop : 0 , marginBottom : '.5em' } } > { t ( 'Export options' ) } < / h1 >
< CloseButton type = "submit" style = { { top : 0 , right : 0 } } onClick = { onClosePress } / >
< table className = { styles [ 'options' ] } >
< tbody >
{ notices . generic . map ( ( { warning , text } ) = > (
< tr key = { text } >
< td colSpan = { 2 } >
< div style = { { . . . ( warning ? { . . . noticeStyle , color : 'var(--orange-8)' } : infoStyle ) , display : 'flex' , alignItems : 'center' , gap : '0 .5em' } } >
{ renderNotice ( { warning , text } ) }
< / div >
< / td >
< td / >
< / tr >
) ) }
{ segmentsOrInverse . selected . length !== segmentsOrInverse . all . length && (
< tr >
< td >
{ t ( 'Output container format:' ) }
< / td >
< td >
{ renderOutFmt ( { height : 20 , maxWidth : 150 } ) }
< / td >
< td >
< HelpIcon onClick = { onOutFmtHelpPress } / >
< td colSpan = { 2 } >
< FaRegCheckCircle size = { 12 } style = { { marginRight : 3 } } / > { t ( '{{selectedSegments}} of {{nonFilteredSegments}} segments selected' , { selectedSegments : segmentsOrInverse.selected.length , nonFilteredSegments : segmentsOrInverse.all.length } ) }
< / td >
< td / >
< / tr >
) }
< tr >
< td >
{ segmentsOrInverse . selected . length > 1 ? t ( 'Export mode for {{segments}} segments' , { segments : segmentsOrInverse.selected.length } ) : t ( 'Export mode' ) }
{ renderNotice ( notices . specific [ 'exportMode' ] ) }
< / td >
< td >
< ExportModeButton selectedSegments = { segmentsOrInverse . selected } / >
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'exportMode' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onExportModeHelpPress } / > }
< / td >
< / tr >
< tr >
< td >
{ t ( 'Output container format:' ) }
< / td >
< td >
{ renderOutFmt ( { height : 20 , maxWidth : 150 } ) }
< / td >
< td >
< HelpIcon onClick = { onOutFmtHelpPress } / >
< / td >
< / tr >
< tr >
< td >
< Trans > Input has { { numStreamsTotal } } tracks < / Trans >
{ renderNotice ( notices . specific [ 'problematicStreams' ] ) }
< / td >
< td >
< HighlightedText style = { { cursor : 'pointer' } } onClick = { onShowStreamsSelectorClick } > < Trans > Keeping { { numStreamsToCopy } } tracks < / Trans > < / HighlightedText >
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'problematicStreams' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onTracksHelpPress } / > }
< / td >
< / tr >
< tr >
< td >
{ t ( 'Save output to path:' ) }
< / td >
< td >
< span role = "button" onClick = { changeOutDir } style = { outDirStyle } > { outputDir } < / span >
< / td >
< td / >
< / tr >
{ canEditSegTemplate && (
< tr >
< td >
< Trans > Input has { { numStreamsTotal } } tracks < / Trans >
{ areWeCuttingProblematicStreams && (
< Warning style = { warningStyle } > < Trans > Warning : Cutting thumbnail tracks is known to cause problems . Consider disabling track { { trackNumber : mainCopiedThumbnailStreams [ 0 ] ? mainCopiedThumbnailStreams [ 0 ] . index + 1 : 0 } } . < / Trans > < / Warning >
) }
< td colSpan = { 2 } >
< FileNameTemplateEditor template = { outSegTemplate } setTemplate = { setOutSegTemplate } defaultTemplate = { defaultOutSegTemplate } generateFileNames = { generateOutSegFileNames } currentSegIndexSafe = { currentSegIndexSafe } / >
< / td >
< td >
< HighlightedText style = { { cursor : 'pointer' } } onClick = { onShowStreamsSelectorClick } > < Trans > Keeping { { numStreamsToCopy } } tracks < / Trans > < / HighlightedText >
< / td >
< td >
{ areWeCuttingProblematicStreams ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" / >
) : (
< HelpIcon onClick = { onTracksHelpPress } / >
) }
< HelpIcon onClick = { onOutSegTemplateHelpPress } / >
< / td >
< / tr >
) }
{ willMerge && (
< tr >
< td >
{ t ( 'Save output to path:' ) }
< td colSpan = { 2 } >
<FileNameTemplateEditor template = { mergedFileTemplate } setTemplate = { setMergedFileTemplate } defaultTemplate = { defaultMergedFileTemplate } generateFileNames = { generateMergedFileNames } mergeMode / >
< / td >
< td >
< span role = "button" onClick = { changeOutDir } style = { outDirStyle } > { outputDir } < / span >
< HelpIcon onClick = { onMergedFileTemplateHelpPress } / >
< / td >
< td / >
< / tr >
{ canEditSegTemplate && (
) }
< tr >
< td >
{ t ( 'Overwrite existing files' ) }
{ renderNotice ( notices . specific [ 'overwriteOutput' ] ) }
< / td >
< td >
< Switch checked = { enableOverwriteOutput } onCheckedChange = { setEnableOverwriteOutput } / >
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'overwriteOutput' ] , rightIconStyle ) ? ? < HelpIcon onClick = { ( ) = > showHelpText ( { text : t ( 'Overwrite files when exporting, if a file with the same name as the output file name exists?' ) } ) } / > }
< / td >
< / tr >
< / tbody >
< / table >
< h3 style = { { marginBottom : '.5em' } } > { t ( 'Advanced options' ) } < / h3 >
< table className = { styles [ 'options' ] } >
< tbody >
{ areWeCutting && (
< >
< tr >
< td colSpan = { 2 } >
< FileNameTemplateEditor template = { outSegTemplate } setTemplate = { setOutSegTemplate } defaultTemplate = { defaultOutSegTemplate } generateFileNames = { generateOutSegFileNames } currentSegIndexSafe = { currentSegIndexSafe } / >
< td >
{t ( 'Shift all start times' ) }
< / td >
< td >
< HelpIcon onClick = { onOutSegTemplateHelpPress } / >
< ShiftTimes values = { adjustCutFromValues } num = { cutFromAdjustmentFrames } setNum = { setCutFromAdjustmentFrames } / >
< / td >
< td >
< HelpIcon onClick = { onCutFromAdjustmentFramesHelpPress } / >
< / td >
< / tr >
) }
{ willMerge && (
< tr >
< td colSpan = { 2 } >
< FileNameTemplateEditor template = { mergedFileTemplate } setTemplate = { setMergedFileTemplate } defaultTemplate = { defaultMergedFileTemplate } generateFileNames = { generateMergedFileNames } mergeMode / >
< td >
{ t ( 'Shift all end times' ) }
< / td >
< td >
< HelpIcon onClick = { onMergedFileTemplateHelpPress } / >
< ShiftTimes values = { adjustCutToValues } num = { cutToAdjustmentFrames } setNum = { setCutToAdjustmentFrame s} / >
< / td >
< td / >
< / tr >
) }
< / >
) }
< tr >
< td >
{ t ( 'Overwrite existing files' ) }
< / td >
< td >
< Switch checked = { enableOverwriteOutput } onCheckedChange = { setEnableOverwriteOutput } / >
< / td >
< td >
< HelpIcon onClick = { ( ) = > showHelpText ( { text : t ( 'Overwrite files when exporting, if a file with the same name as the output file name exists?' ) } ) } / >
< / td >
< / tr >
< / tbody >
< / table >
{ isMov && (
< >
< tr >
< td >
{ t ( 'Enable MOV Faststart?' ) }
< / td >
< td >
< Switch checked = { movFastStart } onCheckedChange = { toggleMovFastStart } / >
{ renderNotice ( notices . specific [ 'movFastStart' ] ) }
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'movFastStart' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onMovFastStartHelpPress } / > }
< / td >
< / tr >
< h3 style = { { marginBottom : '.5em' } } > { t ( 'Advanced options' ) } < / h3 >
< tr >
< td >
{ t ( 'Preserve all MP4/MOV metadata?' ) }
{ renderNotice ( notices . specific [ 'preserveMovData' ] ) }
< / td >
< td >
< Switch checked = { preserveMovData } onCheckedChange = { togglePreserveMovData } / >
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'preserveMovData' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onPreserveMovDataHelpPress } / > }
< / td >
< / tr >
< / >
) }
< tr >
< td >
{ t ( 'Preserve chapters' ) }
< / td >
< td >
< Switch checked = { preserveChapters } onCheckedChange = { togglePreserveChapters } / >
< / td >
< td >
< HelpIcon onClick = { onPreserveChaptersPress } / >
< / td >
< / tr >
< tr >
< td >
{ t ( 'Preserve metadata' ) }
< / td >
< td >
< Select value = { preserveMetadata } onChange = { ( e ) = > setPreserveMetadata ( e . target . value as PreserveMetadata ) } style = { { height : 20 , marginLeft : 5 } } >
< option value = { 'default' as PreserveMetadata } > { t ( 'Default' ) } < / option >
< option value = { 'none' satisfies PreserveMetadata } > { t ( 'None' ) } < / option >
< option value = { 'nonglobal' satisfies PreserveMetadata } > { t ( 'Non-global' ) } < / option >
< / Select >
< / td >
< td >
< HelpIcon onClick = { onPreserveMetadataHelpPress } / >
< / td >
< / tr >
{ willMerge && (
< >
< tr >
< td >
{ t ( 'Create chapters from merged segments? (slow)' ) }
< / td >
< td >
< Switch checked = { segmentsToChapters } onCheckedChange = { toggleSegmentsToChapters } / >
< / td >
< td >
< HelpIcon onClick = { onSegmentsToChaptersHelpPress } / >
< / td >
< / tr >
< table className = { styles [ 'options' ] } >
< tbody >
< tr >
< td >
{ t ( 'Preserve original metadata when merging? (slow)' ) }
< / td >
< td >
< Switch checked = { preserveMetadataOnMerge } onCheckedChange = { togglePreserveMetadataOnMerge } / >
< / td >
< td >
< HelpIcon onClick = { onPreserveMetadataOnMergeHelpPress } / >
< / td >
< / tr >
< / >
) }
< tr >
< td style = { { paddingTop : '.5em' , color : 'var(--gray-11)' , fontSize : '.9em' } } colSpan = { 2 } >
{ t ( 'Depending on your specific file/player, you may have to try different options for best results.' ) }
< / td >
< td / >
< / tr >
{ areWeCutting && (
< >
< tr >
< td >
{ t ( 'Smart cut (experimental):' ) }
{ renderNotice ( notices . specific [ 'smartCut' ] ) }
< / td >
< td >
< Switch checked = { enableSmartCut } onCheckedChange = { ( ) = > setEnableSmartCut ( ( v ) = > ! v ) } / >
< / td >
< td >
{ renderNoticeIcon ( notices . specific [ 'smartCut' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onSmartCutHelpPress } / > }
< / td >
< / tr >
{ areWeCutting && (
< >
< tr >
< td >
{ t ( 'Shift all start times' ) }
< / td >
< td >
< ShiftTimes values = { adjustCutFromValues } num = { cutFromAdjustmentFrames } setNum = { setCutFromAdjustmentFrames } / >
< / td >
< td >
< HelpIcon onClick = { onCutFromAdjustmentFramesHelpPress } / >
< / td >
< / tr >
{ needSmartCut && (
< tr >
< td >
{ t ( 'Shift all end times' ) }
{ t ( 'Smart cut auto detect bitrate' ) }
< / td >
< td >
< ShiftTimes values = { adjustCutToValues } num = { cutToAdjustmentFrames } setNum = { setCutToAdjustmentFrames } / >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'flex-end' } } >
{ smartCutBitrate != null && (
< >
< TextInput value = { smartCutBitrate } onChange = { handleSmartCutBitrateChange } style = { { width : '4em' , flexGrow : 0 , marginRight : '.3em' } } / >
< span style = { { marginRight : '.3em' } } > { t ( 'kbit/s' ) } < / span >
< / >
) }
< span > < Switch checked = { smartCutBitrate == null } onCheckedChange = { handleSmartCutBitrateToggle } / > < / span >
< / div >
< / td >
< td / >
< / tr >
< / >
) }
) }
{ isMov && (
< >
{ ! needSmartCut && (
< tr >
< td >
{ t ( 'Enable MOV Faststart?' ) }
{ t ( 'Keyframe cut mode' ) }
{ renderNotice ( notices . specific [ 'cutMode' ] ) }
< / td >
< td >
< Switch checked = { movFastStart } onCheckedChange = { toggleMovFastStart } / >
{ isIpod && ! movFastStart && < Warning style = { warningStyle } > { t ( 'For the ipod format, it is recommended to activate this option' ) } < / Warning > }
< Switch checked = { keyframeCut } onCheckedChange = { ( ) = > toggleKeyframeCut ( ) } / >
< / td >
< td >
{ isIpod && ! movFastStart ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" / >
) : (
< HelpIcon onClick = { onMovFastStartHelpPress } / >
) }
{ renderNoticeIcon ( notices . specific [ 'cutMode' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onKeyframeCutHelpPress } / > }
< / td >
< / tr >
) }
< / >
) }
< tr >
< td >
{ t ( 'Preserve all MP4/MOV metadata?' ) }
{ isIpod && preserveMovData && < Warning style = { warningStyle } > { t ( 'For the ipod format, it is recommended to deactivate this option' ) } < / Warning > }
< / td >
< td >
< Switch checked = { preserveMovData } onCheckedChange = { togglePreserveMovData } / >
< / td >
< td >
{ isIpod && preserveMovData ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" / >
) : (
< HelpIcon onClick = { onPreserveMovDataHelpPress } / >
) }
< / td >
< / tr >
< / >
) }
< tr >
< td >
{ t ( 'Preserve chapters' ) }
< / td >
< td >
< Switch checked = { preserveChapters } onCheckedChange = { togglePreserveChapters } / >
< / td >
< td >
< HelpIcon onClick = { onPreserveChaptersPress } / >
< / td >
< / tr >
{ ! needSmartCut && (
< tr >
< td >
{ t ( 'Preserve metadata' ) }
& quot ; ffmpeg & quot ; < code className = "highlighted" > avoid_negative_ts < / code >
{ renderNotice ( notices . specific [ 'avoidNegativeTs' ] ) }
< / td >
< td >
< Select value = { preserveMetadata } onChange = { ( e ) = > setPreserveMetadata ( e . target . value as PreserveMetadata ) } style = { { height : 20 , marginLeft : 5 } } >
< option value = { 'default' as PreserveMetadata } > { t ( 'Default' ) } < / option >
< option value = { 'none' satisfies PreserveMetadata } > { t ( 'None' ) } < / option >
< option value = { 'nonglobal' satisfies PreserveMetadata } > { t ( 'Non-global' ) } < / option >
< Select value = { avoidNegativeTs } onChange = { ( e ) = > setAvoidNegativeTs ( e . target . value as AvoidNegativeTs ) } style = { { height : 20 , marginLeft : 5 } } >
< option value = { 'auto' as AvoidNegativeTs } > auto < / option >
< option value = { 'make_zero' satisfies AvoidNegativeTs } > make_zero < / option >
< option value = { 'make_non_negative' satisfies AvoidNegativeTs } > make_non_negative < / option >
< option value = { 'disabled' satisfies AvoidNegativeTs } > disabled < / option >
< / Select >
< / td >
< td >
< HelpIcon onClick = { onPreserveMetadataHelpPress } / >
< / td >
< / tr >
{ willMerge && (
< >
< tr >
< td >
{ t ( 'Create chapters from merged segments? (slow)' ) }
< / td >
< td >
< Switch checked = { segmentsToChapters } onCheckedChange = { toggleSegmentsToChapters } / >
< / td >
< td >
< HelpIcon onClick = { onSegmentsToChaptersHelpPress } / >
< / td >
< / tr >
< tr >
< td >
{ t ( 'Preserve original metadata when merging? (slow)' ) }
< / td >
< td >
< Switch checked = { preserveMetadataOnMerge } onCheckedChange = { togglePreserveMetadataOnMerge } / >
< / td >
< td >
< HelpIcon onClick = { onPreserveMetadataOnMergeHelpPress } / >
< / td >
< / tr >
< / >
) }
< tr >
< td style = { { paddingTop : '.5em' , color : 'var(--gray-11)' , fontSize : '.9em' } } colSpan = { 2 } >
{ t ( 'Depending on your specific file/player, you may have to try different options for best results.' ) }
< / td >
< td / >
< / tr >
{ areWeCutting && (
< >
< tr >
< td >
{ t ( 'Smart cut (experimental):' ) }
{ needSmartCut && < Warning style = { warningStyle } > { t ( 'Smart cut is experimental and will not work on all files.' ) } < / Warning > }
< / td >
< td >
< Switch checked = { enableSmartCut } onCheckedChange = { ( ) = > setEnableSmartCut ( ( v ) = > ! v ) } / >
< / td >
< td >
{ needSmartCut ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" title = { i18n . t ( 'Experimental functionality has been activated!' ) } / >
) : (
< HelpIcon onClick = { onSmartCutHelpPress } / >
) }
< / td >
< / tr >
{ needSmartCut && (
< tr >
< td >
{ t ( 'Smart cut auto detect bitrate' ) }
< / td >
< td >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'flex-end' } } >
{ smartCutBitrate != null && (
< >
< TextInput value = { smartCutBitrate } onChange = { handleSmartCutBitrateChange } style = { { width : '4em' , flexGrow : 0 , marginRight : '.3em' } } / >
< span style = { { marginRight : '.3em' } } > { t ( 'kbit/s' ) } < / span >
< / >
) }
< span > < Switch checked = { smartCutBitrate == null } onCheckedChange = { handleSmartCutBitrateToggle } / > < / span >
< / div >
< / td >
< td / >
< / tr >
) }
{ ! needSmartCut && (
< tr >
< td >
{ t ( 'Keyframe cut mode' ) }
{ ! keyframeCut && < Warning style = { warningStyle } > { t ( 'Note: Keyframe cut is recommended for most common files' ) } < / Warning > }
< / td >
< td >
< Switch checked = { keyframeCut } onCheckedChange = { ( ) = > toggleKeyframeCut ( ) } / >
< / td >
< td >
{ ! keyframeCut ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" / >
) : (
< HelpIcon onClick = { onKeyframeCutHelpPress } / >
) }
< / td >
< / tr >
) }
< / >
) }
{ ! needSmartCut && ( ( ) = > {
const avoidNegativeTsWarn = ( ( ) = > {
if ( willMerge ) {
if ( avoidNegativeTs !== 'make_non_negative' ) {
return t ( 'When merging, it\'s generally recommended to set this to "make_non_negative"' ) ;
}
return undefined ;
}
if ( ! [ 'make_zero' , 'auto' ] . includes ( avoidNegativeTs ) ) {
return t ( 'It\'s generally recommended to set this to one of: {{values}}' , { values : '"auto", "make_zero"' } ) ;
}
return undefined ;
} ) ( ) ;
return (
< tr >
< td >
& quot ; ffmpeg & quot ; < code className = "highlighted" > avoid_negative_ts < / code >
{ avoidNegativeTsWarn != null && < Warning style = { warningStyle } > { avoidNegativeTsWarn } < / Warning > }
< / td >
< td >
< Select value = { avoidNegativeTs } onChange = { ( e ) = > setAvoidNegativeTs ( e . target . value as AvoidNegativeTs ) } style = { { height : 20 , marginLeft : 5 } } >
< option value = { 'auto' as AvoidNegativeTs } > auto < / option >
< option value = { 'make_zero' satisfies AvoidNegativeTs } > make_zero < / option >
< option value = { 'make_non_negative' satisfies AvoidNegativeTs } > make_non_negative < / option >
< option value = { 'disabled' satisfies AvoidNegativeTs } > disabled < / option >
< / Select >
< / td >
< td >
{ avoidNegativeTsWarn != null ? (
< WarningSignIcon verticalAlign = "middle" color = "warning" / >
) : (
< HelpIcon onClick = { onAvoidNegativeTsHelpPress } / >
) }
< / td >
< / tr >
) ;
} ) ( ) }
< tr >
< td >
{ t ( '"ffmpeg" experimental flag' ) }
< / td >
< td >
< Switch checked = { ffmpegExperimental } onCheckedChange = { setFfmpegExperimental } / >
< / td >
< td >
< HelpIcon onClick = { onFfmpegExperimentalHelpPress } / >
{ renderNoticeIcon ( notices . specific [ 'avoidNegativeTs' ] , rightIconStyle ) ? ? < HelpIcon onClick = { onAvoidNegativeTsHelpPress } / > }
< / td >
< / tr >
< tr >
< td >
{ t ( 'More settings' ) }
< / td >
< td >
< IoIosSettings size = { 24 } role = "button" onClick = { toggleSettings } style = { { marginLeft : 5 } } / >
< / td >
< td / >
< / tr >
< / tbody >
< / table >
< / div >
) }
< tr >
< td >
{ t ( '"ffmpeg" experimental flag' ) }
< / td >
< td >
< Switch checked = { ffmpegExperimental } onCheckedChange = { setFfmpegExperimental } / >
< / td >
< td >
< HelpIcon onClick = { onFfmpegExperimentalHelpPress } / >
< / td >
< / tr >
< tr >
< td >
{ t ( 'More settings' ) }
< / td >
< td >
< IoIosSettings size = { 24 } role = "button" onClick = { toggleSettings } style = { { marginLeft : 5 } } / >
< / td >
< td / >
< / tr >
< / tbody >
< / table >
< / div >
< / motion.div >
@ -633,10 +630,16 @@ function ExportConfirm({
animate = { { opacity : 1 , translateX : 0 } }
exit = { { opacity : 0 , translateX : 50 } }
transition = { { duration : 0.4 , easings : [ 'easeOut' ] } }
style = { { display : 'flex' , alignItems : 'flex-end' , background : 'var(--gray-2)' } }
style = { { display : 'flex' , alignItems : 'flex-end' , background : 'var(--gray-2)' , borderRadius : '.5em' , padding : '.3em' } }
>
< ToggleExportConfirm size = { 25 } / >
< div style = { { fontSize : 13 , marginLeft : 3 , marginRight : 7 , maxWidth : 120 , lineHeight : '100%' , color : exportConfirmEnabled ? 'var(--gray-12)' : 'var(--gray-11)' } } role = "button" onClick = { toggleExportConfirmEnabled } > { t ( 'Show this page before exporting?' ) } < / div >
< ToggleExportConfirm size = "1.5em" / >
< div style = { { fontSize : '.8em' , marginLeft : '.4em' , marginRight : '.5em' , maxWidth : '8.5em' , lineHeight : '100%' , color : exportConfirmEnabled ? 'var(--gray-12)' : 'var(--gray-11)' } } role = "button" onClick = { toggleExportConfirmEnabled } >
{ t ( 'Show this page before exporting?' ) }
< / div >
{ notices . totalNum > 0 && (
renderNoticeIcon ( { warning : true } , { fontSize : '1.5em' , marginRight : '.5em' } )
) }
< / motion.div >
< motion.div