modifyAudioFile method
Future<File>
modifyAudioFile({
- required String filepath,
- String? modifiedName,
- MediaFormat? format,
- Map<
String, String> ? metadata, - FFProbeCliTool? ffProbe,
- AudioFileModificationProgressCb? onProgress,
- bool overrideOutputFile = true,
Implementation
Future<File> modifyAudioFile({
required String filepath,
String? modifiedName,
MediaFormat? format,
Map<String, String>? metadata,
FFProbeCliTool? ffProbe,
AudioFileModificationProgressCb? onProgress,
bool overrideOutputFile = true,
}) async {
final inFile = await checkFileExists(filepath).resolveUri();
// No modifications made, return original file
if (null == format && null == metadata) return inFile;
if (null != format && !format.isAudioOnly) {
throw ArgumentError.value(
format,
"format",
"Must provide an audio-only media format",
);
}
final inFileModel =
await (ffProbe ?? FFProbeCliTool()).getModel(inFile.path);
// Check that filepath leads to media file
if (inFileModel.streams.isEmpty) {
throw CliArgumentException(
filepath,
name: "filepath",
message: "The provided filepath does not lead to a media file.",
);
}
final streamModel = inFileModel.streams.singleOrNull;
// Check that there is only a single stream model
if (null == streamModel) {
throw CliArgumentException(
filepath,
name: "filepath",
message: "The provided filepath must lead to a media file "
"which contains only one audio stream, but the file contains "
"the following ${inFileModel.streams.length} streams:\n\n"
"${inFileModel.streams.join("\n")}",
);
}
// Check that file is a media file which contains only audio data
if (streamModel is! FFProbeAudioStreamModel) {
throw CliArgumentException(
filepath,
name: "filepath",
message: "The provided filepath must lead to a media file "
"which contains an audio stream, but the file contains "
"the following stream:\n$streamModel",
);
}
final originalDuration = inFileModel.format.duration;
final outFileExtension = format?.fileExtension ?? inFile.extension;
final outFileBasename = modifiedName ?? inFile.nameWithoutExtension;
final outFile = inFile.parent
.file("$outFileBasename.$outFileExtension")
.getNewFileWithIdenticalBasename();
final proc = await startProcess([
"-progress", "pipe:1", // write progress to stdout
"-stats",
"-stats_period", "0.10", // write stats every 100ms
"-loglevel", "info", //
if (overrideOutputFile) "-y", // override output file, if its exists
"-i", inFile.path, //
for (final MapEntry(key: k, value: v) in (metadata ?? {}).entries) ...[
"-metadata",
"$k=$v",
],
outFile.path,
]);
final progressSub =
proc.stdout.map(String.fromCharCodes).listen((rawEvent) {
//+i first event
// bitrate=N/A
// total_size=4067
// out_time_us=N/A
// out_time_ms=N/A
// out_time=00:00:00.000000
// dup_frames=0
// drop_frames=0
// speed= 0x
// progress=continue
//+i middle event
// bitrate= 107.0kbits/s
// total_size=2097152
// out_time_us=156766621
// out_time_ms=156766621
// out_time=00:02:36.766621
// dup_frames=0
// drop_frames=0
// speed=92.1x
// progress=continue
//+i last event
// bitrate= 113.3kbits/s
// total_size=2977722
// out_time_us=210323447
// out_time_ms=210323447
// out_time=00:03:30.323447
// dup_frames=0
// drop_frames=0
// speed= 92x
// progress=end
final event = rawEvent.split("\n").fold(
<String, String>{},
(map, line) {
if (line.isBlank) return map;
final parts = line.split("=");
map[parts[0].trim()] = parts[1].trim();
return map;
},
);
final outTime = Duration(
microseconds: int.tryParse(event["out_time_us"]!) ?? 0,
);
final isFinished = switch (event["progress"]) {
"continue" => false,
"end" => true,
final val => throw FormatException(
"Value for 'progress' must be 'continue' or 'end', "
"but it is '$val'",
rawEvent,
)
};
// When converting the file format, the duration may not be exactly equal
// afterwards.
var percent = outTime.inMilliseconds / originalDuration.inMilliseconds;
if (percent > 0.999 || isFinished) {
percent = 1.0;
}
onProgress?.call(
AudioFileModificationProgressEvent._(
outputFilepath: outFile.path,
outTime: outTime,
percent: percent,
totalSize: event["total_size"]!.toInt(),
isFinished: isFinished,
),
);
});
final error = proc.stderr
.map(String.fromCharCodes)
.fold("", (prev, e) => "$prev\n\n$e");
final code = await proc.exitCode;
await progressSub.cancel();
if (0 != code) {
throw CliResultException(
exitCode: code,
stderr: await error,
message: "Failed to modify the file at '$filepath'",
);
}
return outFile;
}