Show fps, bitrate and dim for video
This commit is contained in:
@@ -17,6 +17,20 @@ class FFProbeProps {
|
||||
DateTime? creationTime;
|
||||
String? bitrate;
|
||||
String? majorBrand;
|
||||
String? fps;
|
||||
String? codecWidth;
|
||||
String? codecHeight;
|
||||
|
||||
// dot separated bitrate, fps, codecWidth, codecHeight. Ignore null value
|
||||
String get videoInfo {
|
||||
final List<String> info = [];
|
||||
if (bitrate != null) info.add('$bitrate');
|
||||
if (fps != null) info.add('ƒ/$fps');
|
||||
if (codecWidth != null && codecHeight != null) {
|
||||
info.add('$codecWidth x $codecHeight');
|
||||
}
|
||||
return info.join(' * ');
|
||||
}
|
||||
|
||||
// toString() method
|
||||
@override
|
||||
@@ -37,6 +51,7 @@ class FFProbeProps {
|
||||
|
||||
for (final key in json!.keys) {
|
||||
final stringKey = key.toString();
|
||||
|
||||
switch (stringKey) {
|
||||
case FFProbeKeys.bitrate:
|
||||
case FFProbeKeys.bps:
|
||||
@@ -95,8 +110,21 @@ class FFProbeProps {
|
||||
parsedData[stringKey] = json[key];
|
||||
}
|
||||
}
|
||||
// iterate through the streams
|
||||
final List<dynamic> streams = json["streams"];
|
||||
for (final stream in streams) {
|
||||
final Map<String, dynamic> streamData = {};
|
||||
for (final key in stream.keys) {
|
||||
if (key == "r_frame_rate") {
|
||||
result.fps = stream[key];
|
||||
} else if (key == "coded_width") {
|
||||
result.codecWidth = stream[key].toString();
|
||||
} else if (key == "coded_height") {
|
||||
result.codecHeight = stream[key].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
result.prodData = parsedData;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ extension FilePropsExtn on EnteFile {
|
||||
bool get isOwner =>
|
||||
(ownerID == null) || (ownerID == Configuration.instance.getUserID());
|
||||
|
||||
bool get isVideo => fileType == FileType.video;
|
||||
|
||||
bool get canEditMetaInfo => isUploaded && isOwner;
|
||||
|
||||
bool get isTrash => this is TrashFile;
|
||||
|
||||
@@ -4,6 +4,7 @@ import "dart:io";
|
||||
|
||||
import "package:exif/exif.dart";
|
||||
import "package:ffmpeg_kit_flutter_min/ffprobe_kit.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
@@ -11,9 +12,11 @@ import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/people_changed_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/ffmpeg/ffprobe_props.dart";
|
||||
import "package:photos/models/file/extensions/file_props.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/models/file/file_type.dart';
|
||||
import "package:photos/models/metadata/file_magic.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/services/file_magic_service.dart";
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
|
||||
@@ -101,7 +104,7 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
||||
_exifData["exposureTime"] != null ||
|
||||
_exifData["ISO"] != null;
|
||||
});
|
||||
} else {
|
||||
} else if (flagService.internalUser && widget.file.isVideo) {
|
||||
getMediaInfo();
|
||||
}
|
||||
getExif(widget.file).then((exif) {
|
||||
@@ -115,9 +118,7 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
||||
final File? originFile = await getFile(widget.file, isOrigin: true);
|
||||
if (originFile == null) return;
|
||||
final session = await FFprobeKit.getMediaInformation(originFile.path);
|
||||
|
||||
final mediaInfo = session.getMediaInformation();
|
||||
|
||||
if (mediaInfo == null) {
|
||||
final failStackTrace = await session.getFailStackTrace();
|
||||
final output = await session.getOutput();
|
||||
@@ -128,11 +129,10 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
||||
}
|
||||
final properties = await FFProbeUtil.getProperties(mediaInfo);
|
||||
_videoMetadataNotifier.value = properties;
|
||||
|
||||
// print all the properties
|
||||
log("videoCustomProps ${properties.toString()}");
|
||||
log("PropData ${properties.prodData.toString()}");
|
||||
|
||||
if (kDebugMode) {
|
||||
log("videoCustomProps ${properties.toString()}");
|
||||
log("PropData ${properties.prodData.toString()}");
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -268,19 +268,17 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
||||
},
|
||||
),
|
||||
]);
|
||||
} else if (_videoMetadataNotifier.value != null) {
|
||||
} else if (flagService.internalUser && widget.file.isVideo) {
|
||||
fileDetailsTiles.addAll([
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _videoMetadataNotifier,
|
||||
builder: (context, value, _) {
|
||||
return (value != null && value.prodData != null)
|
||||
? Column(
|
||||
children: [
|
||||
VideoExifRowItem(file, value.prodData),
|
||||
const FileDetailsDivider(),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
return Column(
|
||||
children: [
|
||||
VideoExifRowItem(file, value),
|
||||
const FileDetailsDivider(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -79,7 +79,7 @@ class VideoExifDialog extends StatelessWidget {
|
||||
Widget _buildStreamInfo(BuildContext context, Map<String, dynamic> stream) {
|
||||
String titleString = stream['type']?.toString().toUpperCase() ?? '';
|
||||
final codeName = stream['codec_name']?.toString().toUpperCase() ?? '';
|
||||
if (codeName != 'NULL') {
|
||||
if (codeName != 'NULL' && codeName.isNotEmpty) {
|
||||
titleString += ' - $codeName';
|
||||
}
|
||||
return ExpansionTile(
|
||||
@@ -87,7 +87,7 @@ class VideoExifDialog extends StatelessWidget {
|
||||
titleString,
|
||||
style: getEnteTextTheme(context).smallBold,
|
||||
),
|
||||
childrenPadding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4),
|
||||
childrenPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 4),
|
||||
tilePadding: const EdgeInsets.symmetric(vertical: 4),
|
||||
children: [
|
||||
Column(
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/ffmpeg/ffprobe_props.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/info_item_widget.dart";
|
||||
import "package:photos/ui/viewer/file/vid_exif_dialog.dart";
|
||||
import "package:photos/ui/viewer/file/video_exif_dialog.dart";
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
class VideoExifRowItem extends StatefulWidget {
|
||||
final EnteFile file;
|
||||
final Map<String, dynamic>? exif;
|
||||
final FFProbeProps? props;
|
||||
const VideoExifRowItem(
|
||||
this.file,
|
||||
this.exif, {
|
||||
this.props, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -34,7 +35,8 @@ class _VideoProbeInfoState extends State<VideoExifRowItem> {
|
||||
return InfoItemWidget(
|
||||
leadingIcon: Icons.text_snippet_outlined,
|
||||
title: "Video Info",
|
||||
subtitleSection: _exifButton(context, widget.file, widget.exif),
|
||||
subtitleSection:
|
||||
_exifButton(context, widget.file, widget.props?.prodData),
|
||||
onTap: _onTap,
|
||||
);
|
||||
}
|
||||
@@ -46,24 +48,16 @@ class _VideoProbeInfoState extends State<VideoExifRowItem> {
|
||||
) async {
|
||||
late final String label;
|
||||
late final VoidCallback? onTap;
|
||||
final Map<String, dynamic> data = {};
|
||||
if (exif != null) {
|
||||
for (final key in exif.keys) {
|
||||
if (exif[key] != null) {
|
||||
data[key] = exif[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exif == null) {
|
||||
label = S.of(context).loadingExifData;
|
||||
onTap = null;
|
||||
} else if (exif.isNotEmpty) {
|
||||
label = "Tap to view more details";
|
||||
label = "${widget.props?.videoInfo ?? ''} ..";
|
||||
onTap = () => showBarModalBottomSheet(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return VideoExifDialog(
|
||||
probeData: data,
|
||||
probeData: exif,
|
||||
);
|
||||
},
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
||||
Reference in New Issue
Block a user