feat: Add support for progress callback

This commit is contained in:
lml committed 2026-03-16 13:31:57 +08:00
1 parent 4a8cae998f
commit abce9d359b
5 files changed
+53 -5

No files matched your search

@@ -73,6 +73,16 @@ class TranscriptionBloc extends Bloc<TranscriptionEvent, TranscriptionState> {
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(
@@ -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<Object?> get props => [videoPath, fileName, statusMessage];
List<Object?> get props => [videoPath, fileName, statusMessage, progress, isGpu];
}
/// Transcription completed successfully.
+17 -1
View File
@@ -12,21 +12,33 @@ class TranscriptionResult extends Equatable {
/// All subtitle segments with timestamps.
final List<SubtitleSegment> 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<SubtitleSegment>? 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<dynamic>)
.map((e) => SubtitleSegment.fromJson(e as Map<String, dynamic>))
.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<Object?> get props => [language, duration, segments];
List<Object?> get props => [language, duration, segments, isGpu, timingMs];
}
@@ -49,6 +49,7 @@ class WhisperService {
Future<TranscriptionResult> 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,
);
}
+17 -3
View File
@@ -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,
),