Files

134 lines
3.5 KiB
Dart

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
/// Service for extracting audio from video files.
///
/// Uses system/bundled ffmpeg to produce 16kHz mono WAV.
class AudioExtractor {
/// Extract audio from a video file and convert to 16kHz mono WAV.
///
/// Returns the path to the extracted WAV file.
Future<String> extractAudio(String videoPath) async {
final tempDir = await getTemporaryDirectory();
final outputFileName = '${p.basenameWithoutExtension(videoPath)}_audio.wav';
final outputPath = p.join(tempDir.path, 'caption_trans', outputFileName);
// Ensure output directory exists
await Directory(p.dirname(outputPath)).create(recursive: true);
// Delete existing file if present
final outputFile = File(outputPath);
if (await outputFile.exists()) {
await outputFile.delete();
}
if (Platform.isMacOS) {
await _extractWithProcess(videoPath, outputPath);
} else if (Platform.isWindows) {
await _extractWithBundledFfmpeg(videoPath, outputPath);
} else {
throw UnsupportedError(
'Unsupported platform: ${Platform.operatingSystem}',
);
}
// Verify output file was created
if (!await File(outputPath).exists()) {
throw Exception('Audio extraction failed: output file not created');
}
return outputPath;
}
/// Extract using system ffmpeg (macOS).
Future<void> _extractWithProcess(String videoPath, String outputPath) async {
final result = await Process.run('ffmpeg', [
'-i',
videoPath,
'-vn',
'-acodec',
'pcm_s16le',
'-ar',
'16000',
'-ac',
'1',
'-y',
outputPath,
]);
if (result.exitCode != 0) {
throw Exception(
'FFmpeg failed (exit code ${result.exitCode}): ${result.stderr}',
);
}
}
/// Extract using bundled ffmpeg.exe on Windows.
Future<void> _extractWithBundledFfmpeg(
String videoPath,
String outputPath,
) async {
// Look for bundled ffmpeg in app directory
final ffmpegPath = await _findBundledFfmpeg();
final result = await Process.run(ffmpegPath, [
'-i',
videoPath,
'-vn',
'-acodec',
'pcm_s16le',
'-ar',
'16000',
'-ac',
'1',
'-y',
outputPath,
]);
if (result.exitCode != 0) {
throw Exception(
'FFmpeg failed (exit code ${result.exitCode}): ${result.stderr}',
);
}
}
/// Find the bundled ffmpeg executable.
Future<String> _findBundledFfmpeg() async {
if (Platform.isWindows) {
// Check alongside the executable
final exeDir = p.dirname(Platform.resolvedExecutable);
final candidates = [
p.join(exeDir, 'ffmpeg.exe'),
p.join(exeDir, 'bundled', 'ffmpeg.exe'),
p.join(exeDir, 'data', 'flutter_assets', 'assets', 'ffmpeg.exe'),
];
for (final candidate in candidates) {
if (await File(candidate).exists()) {
return candidate;
}
}
// Fallback to system PATH
return 'ffmpeg';
}
// macOS/Linux fallback
return 'ffmpeg';
}
/// Clean up temporary audio files.
Future<void> cleanup() async {
try {
final tempDir = await getTemporaryDirectory();
final audioDir = Directory(p.join(tempDir.path, 'caption_trans'));
if (await audioDir.exists()) {
await audioDir.delete(recursive: true);
}
} catch (_) {
// Ignore cleanup errors
}
}
}