info: added stream info (video, multimedia heic)
This commit is contained in:
parent
52e4f96f7b
commit
4526df5a77
6 changed files with 223 additions and 4 deletions
|
@ -210,6 +210,9 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
|||
if (mediaDir.isNotEmpty()) {
|
||||
metadataMap[Metadata.DIR_MEDIA] = mediaDir
|
||||
}
|
||||
// Android's `MediaExtractor` and `MediaPlayer` cannot be used for details
|
||||
// about embedded images as they do not list them as separate tracks
|
||||
// and only identify at most one
|
||||
}
|
||||
|
||||
if (metadataMap.isNotEmpty()) {
|
||||
|
|
94
lib/model/video/channel_layouts.dart
Normal file
94
lib/model/video/channel_layouts.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
// channel layout constants from FFmpeg libavutil/channel_layout.h
|
||||
class ChannelLayouts {
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
static const FRONT_LEFT = 0x00000001;
|
||||
static const FRONT_RIGHT = 0x00000002;
|
||||
static const FRONT_CENTER = 0x00000004;
|
||||
static const LOW_FREQUENCY = 0x00000008;
|
||||
static const BACK_LEFT = 0x00000010;
|
||||
static const BACK_RIGHT = 0x00000020;
|
||||
static const FRONT_LEFT_OF_CENTER = 0x00000040;
|
||||
static const FRONT_RIGHT_OF_CENTER = 0x00000080;
|
||||
static const BACK_CENTER = 0x00000100;
|
||||
static const SIDE_LEFT = 0x00000200;
|
||||
static const SIDE_RIGHT = 0x00000400;
|
||||
static const TOP_CENTER = 0x00000800;
|
||||
static const TOP_FRONT_LEFT = 0x00001000;
|
||||
static const TOP_FRONT_CENTER = 0x00002000;
|
||||
static const TOP_FRONT_RIGHT = 0x00004000;
|
||||
static const TOP_BACK_LEFT = 0x00008000;
|
||||
static const TOP_BACK_CENTER = 0x00010000;
|
||||
static const TOP_BACK_RIGHT = 0x00020000;
|
||||
static const STEREO_LEFT = 0x20000000;
|
||||
static const STEREO_RIGHT = 0x40000000;
|
||||
|
||||
static const WIDE_LEFT = 0x0000000080000000;
|
||||
static const WIDE_RIGHT = 0x0000000100000000;
|
||||
static const SURROUND_DIRECT_LEFT = 0x0000000200000000;
|
||||
static const SURROUND_DIRECT_RIGHT = 0x0000000400000000;
|
||||
static const LOW_FREQUENCY_2 = 0x0000000800000000;
|
||||
|
||||
static const LAYOUT_NATIVE = 0x8000000000000000;
|
||||
|
||||
static const LAYOUT_MONO = (FRONT_CENTER);
|
||||
static const LAYOUT_STEREO = (FRONT_LEFT | FRONT_RIGHT);
|
||||
static const LAYOUT_2POINT1 = (LAYOUT_STEREO | LOW_FREQUENCY);
|
||||
static const LAYOUT_2_1 = (LAYOUT_STEREO | BACK_CENTER);
|
||||
static const LAYOUT_SURROUND = (LAYOUT_STEREO | FRONT_CENTER);
|
||||
static const LAYOUT_3POINT1 = (LAYOUT_SURROUND | LOW_FREQUENCY);
|
||||
static const LAYOUT_4POINT0 = (LAYOUT_SURROUND | BACK_CENTER);
|
||||
static const LAYOUT_4POINT1 = (LAYOUT_4POINT0 | LOW_FREQUENCY);
|
||||
static const LAYOUT_2_2 = (LAYOUT_STEREO | SIDE_LEFT | SIDE_RIGHT);
|
||||
static const LAYOUT_QUAD = (LAYOUT_STEREO | BACK_LEFT | BACK_RIGHT);
|
||||
static const LAYOUT_5POINT0 = (LAYOUT_SURROUND | SIDE_LEFT | SIDE_RIGHT);
|
||||
static const LAYOUT_5POINT1 = (LAYOUT_5POINT0 | LOW_FREQUENCY);
|
||||
static const LAYOUT_5POINT0_BACK = (LAYOUT_SURROUND | BACK_LEFT | BACK_RIGHT);
|
||||
static const LAYOUT_5POINT1_BACK = (LAYOUT_5POINT0_BACK | LOW_FREQUENCY);
|
||||
static const LAYOUT_6POINT0 = (LAYOUT_5POINT0 | BACK_CENTER);
|
||||
static const LAYOUT_6POINT0_FRONT = (LAYOUT_2_2 | FRONT_LEFT_OF_CENTER | FRONT_RIGHT_OF_CENTER);
|
||||
static const LAYOUT_HEXAGONAL = (LAYOUT_5POINT0_BACK | BACK_CENTER);
|
||||
static const LAYOUT_6POINT1 = (LAYOUT_5POINT1 | BACK_CENTER);
|
||||
static const LAYOUT_6POINT1_BACK = (LAYOUT_5POINT1_BACK | BACK_CENTER);
|
||||
static const LAYOUT_6POINT1_FRONT = (LAYOUT_6POINT0_FRONT | LOW_FREQUENCY);
|
||||
static const LAYOUT_7POINT0 = (LAYOUT_5POINT0 | BACK_LEFT | BACK_RIGHT);
|
||||
static const LAYOUT_7POINT0_FRONT = (LAYOUT_5POINT0 | FRONT_LEFT_OF_CENTER | FRONT_RIGHT_OF_CENTER);
|
||||
static const LAYOUT_7POINT1 = (LAYOUT_5POINT1 | BACK_LEFT | BACK_RIGHT);
|
||||
static const LAYOUT_7POINT1_WIDE = (LAYOUT_5POINT1 | FRONT_LEFT_OF_CENTER | FRONT_RIGHT_OF_CENTER);
|
||||
static const LAYOUT_7POINT1_WIDE_BACK = (LAYOUT_5POINT1_BACK | FRONT_LEFT_OF_CENTER | FRONT_RIGHT_OF_CENTER);
|
||||
static const LAYOUT_OCTAGONAL = (LAYOUT_5POINT0 | BACK_LEFT | BACK_CENTER | BACK_RIGHT);
|
||||
static const LAYOUT_HEXADECAGONAL = (LAYOUT_OCTAGONAL | WIDE_LEFT | WIDE_RIGHT | TOP_BACK_LEFT | TOP_BACK_RIGHT | TOP_BACK_CENTER | TOP_FRONT_CENTER | TOP_FRONT_LEFT | TOP_FRONT_RIGHT);
|
||||
static const LAYOUT_STEREO_DOWNMIX = (STEREO_LEFT | STEREO_RIGHT);
|
||||
|
||||
static const names = {
|
||||
LAYOUT_NATIVE: 'native',
|
||||
LAYOUT_MONO: 'mono',
|
||||
LAYOUT_STEREO: 'stereo',
|
||||
LAYOUT_2POINT1: '2.1',
|
||||
LAYOUT_2_1: '2_1',
|
||||
LAYOUT_SURROUND: 'surround',
|
||||
LAYOUT_3POINT1: '3.1',
|
||||
LAYOUT_4POINT0: '4.0',
|
||||
LAYOUT_4POINT1: '4.1',
|
||||
LAYOUT_2_2: '2_2',
|
||||
LAYOUT_QUAD: 'quad',
|
||||
LAYOUT_5POINT0: '5.0',
|
||||
LAYOUT_5POINT1: '5.1',
|
||||
LAYOUT_5POINT0_BACK: '5.0 back',
|
||||
LAYOUT_5POINT1_BACK: '5.1 back',
|
||||
LAYOUT_6POINT0: '6.0',
|
||||
LAYOUT_6POINT0_FRONT: '6.0 front',
|
||||
LAYOUT_HEXAGONAL: 'hexagonal',
|
||||
LAYOUT_6POINT1: '6.1',
|
||||
LAYOUT_6POINT1_BACK: '6.1 back',
|
||||
LAYOUT_6POINT1_FRONT: '6.1 front',
|
||||
LAYOUT_7POINT0: '7.0',
|
||||
LAYOUT_7POINT0_FRONT: '7.0 front',
|
||||
LAYOUT_7POINT1: '7.1',
|
||||
LAYOUT_7POINT1_WIDE: '7.1 wide',
|
||||
LAYOUT_7POINT1_WIDE_BACK: '7.1 wide back',
|
||||
LAYOUT_OCTAGONAL: 'octagonal',
|
||||
LAYOUT_HEXADECAGONAL: 'hexadecagonal',
|
||||
LAYOUT_STEREO_DOWNMIX: 'stereo downmix',
|
||||
};
|
||||
}
|
110
lib/model/video/streams.dart
Normal file
110
lib/model/video/streams.dart
Normal file
|
@ -0,0 +1,110 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/video/channel_layouts.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:fijkplayer/fijkplayer.dart';
|
||||
|
||||
class StreamInfo {
|
||||
static Future<Map> getVideoInfo(AvesEntry entry) async {
|
||||
final player = FijkPlayer();
|
||||
await player.setDataSource(entry.uri, autoPlay: false);
|
||||
|
||||
final completer = Completer();
|
||||
void onChange() {
|
||||
if ([FijkState.prepared, FijkState.error].contains(player.state)) {
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
|
||||
player.addListener(onChange);
|
||||
await player.prepareAsync();
|
||||
await completer.future;
|
||||
player.removeListener(onChange);
|
||||
|
||||
final info = await player.getInfo();
|
||||
await player.release();
|
||||
return info;
|
||||
}
|
||||
|
||||
static String formatBitrate(int size, {int round = 2}) {
|
||||
const divider = 1000;
|
||||
const symbol = 'bit/s';
|
||||
|
||||
if (size < divider) return '$size $symbol';
|
||||
|
||||
if (size < divider * divider && size % divider == 0) {
|
||||
return '${(size / divider).toStringAsFixed(0)} K$symbol';
|
||||
}
|
||||
if (size < divider * divider) {
|
||||
return '${(size / divider).toStringAsFixed(round)} K$symbol';
|
||||
}
|
||||
|
||||
if (size < divider * divider * divider && size % divider == 0) {
|
||||
return '${(size / (divider * divider)).toStringAsFixed(0)} M$symbol';
|
||||
}
|
||||
return '${(size / divider / divider).toStringAsFixed(round)} M$symbol';
|
||||
}
|
||||
|
||||
static Map<String, String> formatStreamInfo(Map stream) {
|
||||
final dir = <String, String>{};
|
||||
for (final kv in stream.entries) {
|
||||
final value = kv.value;
|
||||
if (value != null) {
|
||||
final key = kv.key;
|
||||
switch (key) {
|
||||
case 'index':
|
||||
case 'fps_num':
|
||||
case 'sar_num':
|
||||
case 'tbr_num':
|
||||
case 'tbr_den':
|
||||
break;
|
||||
case 'bitrate':
|
||||
dir['Bitrate'] = formatBitrate(value, round: 1);
|
||||
break;
|
||||
case 'channel_layout':
|
||||
dir['Channel Layout'] = ChannelLayouts.names[value] ?? value.toString();
|
||||
break;
|
||||
case 'codec_name':
|
||||
dir['Codec'] = value.toString().toUpperCase().replaceAll('_', ' ');
|
||||
break;
|
||||
case 'fps_den':
|
||||
dir['Frame Rate'] = roundToPrecision(stream['fps_num'] / stream['fps_den'], decimals: 3).toString();
|
||||
break;
|
||||
case 'height':
|
||||
dir['Height'] = '$value pixels';
|
||||
break;
|
||||
case 'language':
|
||||
dir['Language'] = value;
|
||||
break;
|
||||
case 'sample_rate':
|
||||
dir['Sample Rate'] = '$value Hz';
|
||||
break;
|
||||
case 'sar_den':
|
||||
dir['SAR'] = '${stream['sar_num']}:${stream['sar_den']}';
|
||||
break;
|
||||
case 'type':
|
||||
switch (value) {
|
||||
case 'timedtext':
|
||||
dir['Type'] = 'timed text';
|
||||
break;
|
||||
case 'audio':
|
||||
case 'video':
|
||||
case 'metadata':
|
||||
case 'subtitle':
|
||||
case 'unknown':
|
||||
default:
|
||||
dir['Type'] = value;
|
||||
}
|
||||
break;
|
||||
case 'width':
|
||||
dir['Width'] = '$value pixels';
|
||||
break;
|
||||
default:
|
||||
dir[key] = value.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/video/streams.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/services/svg_metadata_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
@ -139,6 +142,18 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
|||
if (_loadedMetadataUri.value == entry.uri) return;
|
||||
if (isVisible) {
|
||||
final rawMetadata = await (entry.isSvg ? SvgMetadataService.getAllMetadata(entry) : metadataService.getAllMetadata(entry)) ?? {};
|
||||
if (entry.isVideo || (entry.mimeType == MimeTypes.heif && entry.isMultipage)) {
|
||||
final info = await StreamInfo.getVideoInfo(entry);
|
||||
if (info.containsKey('streams')) {
|
||||
final streams = (info['streams'] as List).cast<Map>();
|
||||
for (final stream in streams) {
|
||||
if (stream.containsKey('index')) {
|
||||
final index = stream['index'] + 1;
|
||||
rawMetadata['Stream $index'] = StreamInfo.formatStreamInfo(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final directories = rawMetadata.entries.map((dirKV) {
|
||||
var directoryName = dirKV.key as String ?? '';
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: aves
|
||||
resolved-ref: a4640923c7c6141ff543f676a8cb7d2fe8b0ffba
|
||||
resolved-ref: "118fa08b30a7948df04d2692649b124a09ccb480"
|
||||
url: "git://github.com/deckerst/fijkplayer.git"
|
||||
source: git
|
||||
version: "0.8.7"
|
||||
|
|
|
@ -42,9 +42,6 @@ dependencies:
|
|||
firebase_analytics:
|
||||
firebase_crashlytics:
|
||||
flutter_highlight:
|
||||
# flutter_ijkplayer:
|
||||
# git:
|
||||
# url: git://github.com/deckerst/flutter_ijkplayer.git
|
||||
flutter_localized_locales:
|
||||
flutter_map:
|
||||
flutter_markdown:
|
||||
|
|
Loading…
Reference in a new issue