diff --git a/lib/blocs/transcription/transcription_bloc.dart b/lib/blocs/transcription/transcription_bloc.dart index 7ebe141..f6d93f7 100644 --- a/lib/blocs/transcription/transcription_bloc.dart +++ b/lib/blocs/transcription/transcription_bloc.dart @@ -73,6 +73,16 @@ class TranscriptionBloc extends Bloc { final result = await _whisperService.transcribe( videoPath, // Pass video file directly language: event.language ?? 'auto', + onProgress: (progress) { + if (!emit.isDone) { + emit(Transcribing( + videoPath: videoPath, + fileName: fileName, + statusMessage: 'Transcribing...', + progress: progress, + )); + } + }, ); emit(TranscriptionComplete( diff --git a/lib/blocs/transcription/transcription_state.dart b/lib/blocs/transcription/transcription_state.dart index 56f5c57..d709202 100644 --- a/lib/blocs/transcription/transcription_state.dart +++ b/lib/blocs/transcription/transcription_state.dart @@ -65,15 +65,19 @@ class Transcribing extends TranscriptionState { final String videoPath; final String fileName; final String statusMessage; + final int progress; + final bool? isGpu; const Transcribing({ required this.videoPath, required this.fileName, this.statusMessage = 'Transcribing...', + this.progress = 0, + this.isGpu, }); @override - List get props => [videoPath, fileName, statusMessage]; + List get props => [videoPath, fileName, statusMessage, progress, isGpu]; } /// Transcription completed successfully. diff --git a/lib/models/transcription_result.dart b/lib/models/transcription_result.dart index 48d6c59..c432d7f 100644 --- a/lib/models/transcription_result.dart +++ b/lib/models/transcription_result.dart @@ -12,21 +12,33 @@ class TranscriptionResult extends Equatable { /// All subtitle segments with timestamps. final List segments; + /// Whether GPU acceleration was used. + final bool? isGpu; + + /// Time taken for transcription in milliseconds. + final int? timingMs; + const TranscriptionResult({ required this.language, required this.duration, required this.segments, + this.isGpu, + this.timingMs, }); TranscriptionResult copyWith({ String? language, Duration? duration, List? segments, + bool? isGpu, + int? timingMs, }) { return TranscriptionResult( language: language ?? this.language, duration: duration ?? this.duration, segments: segments ?? this.segments, + isGpu: isGpu ?? this.isGpu, + timingMs: timingMs ?? this.timingMs, ); } @@ -37,6 +49,8 @@ class TranscriptionResult extends Equatable { segments: (json['segments'] as List) .map((e) => SubtitleSegment.fromJson(e as Map)) .toList(), + isGpu: json['isGpu'] as bool?, + timingMs: json['timingMs'] as int?, ); } @@ -45,9 +59,11 @@ class TranscriptionResult extends Equatable { 'language': language, 'durationMs': duration.inMilliseconds, 'segments': segments.map((e) => e.toJson()).toList(), + 'isGpu': isGpu, + 'timingMs': timingMs, }; } @override - List get props => [language, duration, segments]; + List get props => [language, duration, segments, isGpu, timingMs]; } diff --git a/lib/services/whisper/whisper_service.dart b/lib/services/whisper/whisper_service.dart index 2fe6a93..94f356a 100644 --- a/lib/services/whisper/whisper_service.dart +++ b/lib/services/whisper/whisper_service.dart @@ -49,6 +49,7 @@ class WhisperService { Future transcribe( String mediaPath, { String language = 'auto', + void Function(int progress)? onProgress, }) async { if (_currentModel == null) { throw StateError('No model loaded. Call loadModel() first.'); @@ -60,6 +61,7 @@ class WhisperService { lang: language, withTimestamps: true, convert: true, // Auto-convert via registered FFmpeg converter + onProgress: onProgress, ); if (result == null) { @@ -83,6 +85,8 @@ class WhisperService { language: language, duration: segments.isNotEmpty ? segments.last.endTime : Duration.zero, segments: segments, + isGpu: result.transcription.gpu, + timingMs: result.transcription.timing, ); } diff --git a/lib/ui/widgets/transcription_panel.dart b/lib/ui/widgets/transcription_panel.dart index bf2dade..251af60 100644 --- a/lib/ui/widgets/transcription_panel.dart +++ b/lib/ui/widgets/transcription_panel.dart @@ -139,17 +139,31 @@ class TranscriptionPanel extends StatelessWidget { } if (state is Transcribing) { + final s = state as Transcribing; + String label = l10n.processingTranscription; + if (s.isGpu == true) { + label += ' (GPU Accel)'; + } return _buildProgressRow( context, icon: Icons.mic_rounded, - label: l10n.processingTranscription, - progress: null, + label: label, + progress: s.progress > 0 ? s.progress / 100.0 : null, color: Colors.purple, ); } if (state is TranscriptionComplete) { final s = state as TranscriptionComplete; + String infoText = + l10n.segmentsExtracted(s.result.segments.length, s.result.language); + if (s.result.isGpu == true) { + infoText += ' • GPU'; + } + if (s.result.timingMs != null) { + infoText += ' • ${s.result.timingMs}ms'; + } + return Row( children: [ const Icon( @@ -159,7 +173,7 @@ class TranscriptionPanel extends StatelessWidget { ), const SizedBox(width: 8), Text( - l10n.segmentsExtracted(s.result.segments.length, s.result.language), + infoText, style: theme.textTheme.bodySmall?.copyWith( color: Colors.greenAccent, ),