mirror of
https://github.com/ente-io/ente.git
synced 2025-05-29 22:09:43 +00:00
146 lines
4.0 KiB
Dart
146 lines
4.0 KiB
Dart
// Adapted from: https://github.com/deckerst/aves
|
|
|
|
import "package:ffmpeg_kit_flutter_min/media_information.dart";
|
|
import "package:logging/logging.dart";
|
|
import "package:photos/models/ffmpeg/ffprobe_keys.dart";
|
|
import "package:photos/models/ffmpeg/ffprobe_props.dart";
|
|
|
|
class FFProbeUtil {
|
|
static final _logger = Logger('FFProbeUtil');
|
|
static const chaptersKey = 'chapters';
|
|
static const formatKey = 'format';
|
|
static const streamsKey = 'streams';
|
|
|
|
static Future<FFProbeProps> getProperties(
|
|
MediaInformation mediaInformation,
|
|
) async {
|
|
final properties = await getMetadata(mediaInformation);
|
|
|
|
try {
|
|
return FFProbeProps.fromJson(properties);
|
|
} catch (e, stackTrace) {
|
|
_logger.severe(
|
|
"Error parsing FFProbe properties: $properties",
|
|
e,
|
|
stackTrace,
|
|
);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
static Future<Map> getMetadata(MediaInformation information) async {
|
|
final props = information.getAllProperties();
|
|
if (props == null) return {};
|
|
|
|
final chapters = props[chaptersKey];
|
|
if (chapters is List) {
|
|
if (chapters.isEmpty) {
|
|
props.remove(chaptersKey);
|
|
}
|
|
}
|
|
|
|
final format = props.remove(formatKey);
|
|
if (format is Map) {
|
|
format.remove(FFProbeKeys.filename);
|
|
format.remove('size');
|
|
_normalizeGroup(format);
|
|
props.addAll(format);
|
|
}
|
|
|
|
final streams = props[streamsKey];
|
|
if (streams is List) {
|
|
streams.forEach((stream) {
|
|
if (stream is Map) {
|
|
_normalizeGroup(stream);
|
|
|
|
final fps = stream[FFProbeKeys.avgFrameRate];
|
|
if (fps is String) {
|
|
final parts = fps.split('/');
|
|
if (parts.length == 2) {
|
|
final num = int.tryParse(parts[0]);
|
|
final den = int.tryParse(parts[1]);
|
|
if (num != null && den != null) {
|
|
if (den > 0) {
|
|
stream[FFProbeKeys.fpsNum] = num;
|
|
stream[FFProbeKeys.fpsDen] = den;
|
|
}
|
|
stream.remove(FFProbeKeys.avgFrameRate);
|
|
}
|
|
}
|
|
}
|
|
|
|
final disposition = stream[FFProbeKeys.disposition];
|
|
if (disposition is Map) {
|
|
disposition.removeWhere((key, value) => value == 0);
|
|
stream[FFProbeKeys.disposition] = disposition.keys.join(', ');
|
|
}
|
|
|
|
final idValue = stream['id'];
|
|
if (idValue is String) {
|
|
final id = int.tryParse(idValue);
|
|
if (id != null) {
|
|
stream[FFProbeKeys.index] = id - 1;
|
|
stream.remove('id');
|
|
}
|
|
}
|
|
|
|
if (stream[FFProbeKeys.streamType] == 'data') {
|
|
stream[FFProbeKeys.streamType] = MediaStreamTypes.metadata;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return props;
|
|
}
|
|
|
|
static void _normalizeGroup(Map<dynamic, dynamic> stream) {
|
|
void replaceKey(k1, k2) {
|
|
final v = stream.remove(k1);
|
|
if (v != null) {
|
|
stream[k2] = v;
|
|
}
|
|
}
|
|
|
|
replaceKey('bit_rate', FFProbeKeys.bitrate);
|
|
replaceKey('codec_type', FFProbeKeys.streamType);
|
|
replaceKey('format_name', FFProbeKeys.mediaFormat);
|
|
replaceKey('level', FFProbeKeys.codecLevel);
|
|
replaceKey('nb_frames', FFProbeKeys.frameCount);
|
|
replaceKey('pix_fmt', FFProbeKeys.codecPixelFormat);
|
|
replaceKey('profile', FFProbeKeys.codecProfileId);
|
|
|
|
final tags = stream.remove('tags');
|
|
if (tags is Map) {
|
|
stream.addAll(tags);
|
|
}
|
|
|
|
<String>{
|
|
FFProbeKeys.codecProfileId,
|
|
FFProbeKeys.rFrameRate,
|
|
'bits_per_sample',
|
|
'closed_captions',
|
|
'codec_long_name',
|
|
'film_grain',
|
|
'has_b_frames',
|
|
'start_pts',
|
|
'start_time',
|
|
'vendor_id',
|
|
}.forEach((key) {
|
|
final value = stream[key];
|
|
switch (value) {
|
|
case final num v:
|
|
if (v == 0) {
|
|
stream.remove(key);
|
|
}
|
|
case final String v:
|
|
if (double.tryParse(v) == 0 ||
|
|
v == '0/0' ||
|
|
v == 'unknown' ||
|
|
v == '[0][0][0][0]') {
|
|
stream.remove(key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|