@@ -373,14 +373,16 @@ const TranscriptControlsPanel = () => {
373373} ;
374374
375375export const TranscriptPanel = ( ) => {
376+ const LARGE_TRANSCRIPTION_FILE_SIZE = 25 * 1024 * 1024 ;
376377 const { t } = useTranslation ( ) ;
377378 const navigate = useNavigate ( ) ;
378379 const {
379380 getCurrentMediaTranscripts,
380381 isTranscribing,
381382 currentFile,
382383 currentYouTube,
383- toggleTranscribing,
384+ startTranscribing,
385+ stopTranscribing,
384386 addTranscriptSegment,
385387 clearTranscript,
386388 exportTranscript,
@@ -391,6 +393,9 @@ export const TranscriptPanel = () => {
391393 setSelectedBookmarkId,
392394 setCurrentTime,
393395 setIsPlaying,
396+ loopStart,
397+ loopEnd,
398+ duration,
394399 } = usePlayerStore ( ) ;
395400
396401 const transcriptSegments = getCurrentMediaTranscripts ( ) ;
@@ -426,7 +431,6 @@ export const TranscriptPanel = () => {
426431 } )
427432 : transcriptSegments ;
428433
429-
430434 const handleTabSelect = ( id : string | null ) => {
431435 if ( id ) {
432436 // If switching to a specific bookmark tab
@@ -489,8 +493,22 @@ export const TranscriptPanel = () => {
489493 }
490494 } ;
491495
496+ const getPreferredTranscriptRange = ( ) => {
497+ if ( loopStart !== null && loopEnd !== null && loopEnd > loopStart ) {
498+ return { start : loopStart , end : loopEnd } ;
499+ }
500+
501+ return undefined ;
502+ } ;
503+
504+ const handleTranscribeDefault = ( ) => {
505+ transcribeMedia ( getPreferredTranscriptRange ( ) ) ;
506+ } ;
507+
492508 const handleTranscribeFull = ( ) => {
493- transcribeMedia ( ) ;
509+ transcribeMedia ( duration > 0 ? { start : 0 , end : duration } : undefined , {
510+ forceFullRange : true ,
511+ } ) ;
494512 } ;
495513
496514 const [ isProcessing , setIsProcessing ] = useState ( false ) ;
@@ -501,6 +519,14 @@ export const TranscriptPanel = () => {
501519 const [ currentProvider , setCurrentProvider ] = useState < TranscriptionProvider > ( "openai" ) ;
502520 const transcriptRef = useRef < HTMLDivElement > ( null ) ;
503521
522+ const isBookmarkTranscriptEmpty = Boolean ( activeTabId ) && filteredSegments . length === 0 ;
523+ const isFullTranscriptEmpty = ! activeTabId && transcriptSegments . length === 0 ;
524+ const shouldShowTranscribeActionsInHeader =
525+ ! isProcessing &&
526+ ! showApiKeyInput &&
527+ ! isBookmarkTranscriptEmpty &&
528+ ! isFullTranscriptEmpty ;
529+
504530 // Load API key and transcription provider from localStorage on component mount
505531 useEffect ( ( ) => {
506532 const loadSettings = ( ) => {
@@ -530,8 +556,23 @@ export const TranscriptPanel = () => {
530556 navigate ( "/ai-settings" ) ;
531557 } ;
532558
559+ type TimeRange = { start : number ; end : number } ;
560+
561+ const normalizeRange = ( range ?: Partial < TimeRange > ) : TimeRange | undefined => {
562+ if (
563+ range &&
564+ typeof range . start === "number" &&
565+ typeof range . end === "number" &&
566+ range . end > range . start
567+ ) {
568+ return { start : range . start , end : range . end } ;
569+ }
570+
571+ return undefined ;
572+ } ;
573+
533574 // Function to extract audio from the media file
534- const extractAudioFromMedia = async ( range ?: { start : number ; end : number } ) : Promise < Blob > => {
575+ const extractAudioFromMedia = async ( range ?: TimeRange ) : Promise < Blob > => {
535576 return new Promise ( ( resolve , reject ) => {
536577 if ( ! currentFile ) {
537578 reject ( new Error ( t ( "transcript.noFileLoaded" ) ) ) ;
@@ -541,10 +582,18 @@ export const TranscriptPanel = () => {
541582 // For audio files, we can use them directly or slice them if range provided
542583 if ( currentFile . type . includes ( "audio" ) ) {
543584 fetch ( currentFile . url )
544- . then ( ( response ) => response . arrayBuffer ( ) )
585+ . then ( async ( response ) => {
586+ if ( ! range ) {
587+ resolve ( await response . blob ( ) ) ;
588+ return ;
589+ }
590+
591+ return response . arrayBuffer ( ) ;
592+ } )
545593 . then ( async ( arrayBuffer ) => {
546- // If no range, return original blob if possible, or decode/encode to ensure WAV
547- // But to support detailed range slicing, we should always decode
594+ if ( ! range || ! arrayBuffer ) {
595+ return ;
596+ }
548597
549598 try {
550599 const audioContext = new AudioContext ( ) ;
@@ -588,6 +637,7 @@ export const TranscriptPanel = () => {
588637
589638 // Encode to WAV
590639 const wavBlob = encodeWAV ( slicedData , audioBuffer . sampleRate ) ;
640+ audioContext . close ( ) ;
591641 resolve ( wavBlob ) ;
592642
593643 } catch ( err ) {
@@ -661,7 +711,10 @@ export const TranscriptPanel = () => {
661711 } ;
662712
663713 // Function to transcribe the current media using the selected transcription service
664- const transcribeMedia = async ( range ?: { start : number ; end : number } ) => {
714+ const transcribeMedia = async (
715+ requestedRange ?: Partial < TimeRange > ,
716+ options ?: { forceFullRange ?: boolean }
717+ ) => {
665718 // Check if we have media to transcribe
666719 if ( ! currentFile && ! currentYouTube ) {
667720 toast . error ( t ( "transcript.noMediaToTranscribe" ) ) ;
@@ -674,16 +727,28 @@ export const TranscriptPanel = () => {
674727 return ;
675728 }
676729
730+ const range = options ?. forceFullRange
731+ ? normalizeRange ( requestedRange )
732+ : normalizeRange ( requestedRange ) || getPreferredTranscriptRange ( ) ;
733+
734+ if (
735+ currentFile &&
736+ ! range &&
737+ currentFile . size > LARGE_TRANSCRIPTION_FILE_SIZE
738+ ) {
739+ toast ( t ( "transcript.largeFileRangeRecommended" ) ) ;
740+ }
741+
677742 try {
678743 setIsProcessing ( true ) ;
679744 setErrorMessage ( "" ) ;
680745
681746 // Only clear if doing full transcript
682- if ( ! range ) {
747+ if ( ! range || options ?. forceFullRange ) {
683748 clearTranscript ( ) ;
684749 }
685750
686- toggleTranscribing ( ) ; // Set isTranscribing to true
751+ startTranscribing ( ) ;
687752 setProcessingProgress ( 10 ) ;
688753
689754 // For YouTube videos, we can't directly access the audio
@@ -790,6 +855,7 @@ export const TranscriptPanel = () => {
790855 await simulateTranscription ( ) ;
791856 } finally {
792857 setIsProcessing ( false ) ;
858+ stopTranscribing ( ) ;
793859 }
794860 } ;
795861
@@ -1220,31 +1286,49 @@ export const TranscriptPanel = () => {
12201286 < Settings size = { 16 } />
12211287 </ button >
12221288
1223- { /* Show transcribe button behavior based on active tab */ }
1224- { activeTabId ? (
1225- < button
1226- onClick = { handleTranscribeBookmark }
1227- className = { `p-1.5 rounded-full ${ isTranscribing
1228- ? "bg-green-100 text-green-600 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
1229- : "bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
1230- } `}
1231- title = { t ( "transcript.transcribeBookmark" ) }
1232- disabled = { isProcessing || ( ! currentFile && ! currentYouTube ) }
1233- >
1234- < FileAudio size = { 16 } />
1235- </ button >
1236- ) : (
1237- < button
1238- onClick = { handleTranscribeFull }
1239- className = { `p-1.5 rounded-full ${ isTranscribing
1240- ? "bg-green-100 text-green-600 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
1241- : "bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
1242- } `}
1243- title = { t ( "transcript.transcribeWithWhisper" ) }
1244- disabled = { isProcessing || ( ! currentFile && ! currentYouTube ) }
1245- >
1246- < FileAudio size = { 16 } />
1247- </ button >
1289+ { shouldShowTranscribeActionsInHeader && (
1290+ < >
1291+ { activeTabId ? (
1292+ < button
1293+ onClick = { handleTranscribeBookmark }
1294+ className = { `p-1.5 rounded-full ${ isTranscribing
1295+ ? "bg-green-100 text-green-600 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
1296+ : "bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
1297+ } `}
1298+ title = { t ( "transcript.transcribeBookmark" ) }
1299+ disabled = { isProcessing || ( ! currentFile && ! currentYouTube ) }
1300+ >
1301+ < FileAudio size = { 16 } />
1302+ </ button >
1303+ ) : (
1304+ < button
1305+ onClick = { handleTranscribeDefault }
1306+ className = { `p-1.5 rounded-full ${ isTranscribing
1307+ ? "bg-green-100 text-green-600 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
1308+ : "bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
1309+ } `}
1310+ title = {
1311+ loopStart !== null && loopEnd !== null
1312+ ? t ( "transcript.transcribeLoopRange" )
1313+ : t ( "transcript.transcribeWithWhisper" )
1314+ }
1315+ disabled = { isProcessing || ( ! currentFile && ! currentYouTube ) }
1316+ >
1317+ < FileAudio size = { 16 } />
1318+ </ button >
1319+ ) }
1320+
1321+ { ! activeTabId && currentFile && (
1322+ < button
1323+ onClick = { handleTranscribeFull }
1324+ className = "p-1.5 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
1325+ title = { t ( "transcript.transcribeFullRange" ) }
1326+ disabled = { isProcessing || ( ! currentFile && ! currentYouTube ) }
1327+ >
1328+ < Brain size = { 16 } />
1329+ </ button >
1330+ ) }
1331+ </ >
12481332 ) }
12491333
12501334 < button
@@ -1341,6 +1425,14 @@ export const TranscriptPanel = () => {
13411425 </ div >
13421426 { ( currentFile || currentYouTube ) && (
13431427 < div className = "space-y-2" >
1428+ < button
1429+ onClick = { handleTranscribeDefault }
1430+ className = "px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md text-sm font-medium transition-colors"
1431+ >
1432+ { loopStart !== null && loopEnd !== null
1433+ ? t ( "transcript.transcribeLoopRangeButton" )
1434+ : t ( "transcript.transcribeWithWhisper" ) }
1435+ </ button >
13441436 < div className = "text-sm" > { t ( "common.or" ) } </ div >
13451437 < div className = "flex justify-center" >
13461438 < TranscriptUploader variant = "prominent" />
0 commit comments