Merge branch 'main' into similar_index_clear

This commit is contained in:
Laurens Priem
2025-08-28 18:50:12 +05:30
committed by GitHub
7 changed files with 108 additions and 49 deletions

View File

@@ -1831,7 +1831,8 @@
"videosProcessed": "Videos processed",
"totalVideos": "Total videos",
"skippedVideos": "Skipped videos",
"videoStreamingDescription": "Play videos instantly on any device.\nEnable to process video streams on this device.",
"videoStreamingDescriptionLine1": "Play videos instantly on any device.",
"videoStreamingDescriptionLine2": "Enable to process video streams on this device.",
"videoStreamingNote": "Only videos from last 60 days and under 1 minute are processed on this device. For older/longer videos, enable streaming in the desktop app.",
"createStream": "Create stream",
"recreateStream": "Recreate stream",

View File

@@ -10,7 +10,7 @@ import "package:photos/core/event_bus.dart";
import "package:photos/events/compute_control_event.dart";
import "package:thermal/thermal.dart";
enum _ComputeRunState {
enum ComputeRunState {
idle,
runningML,
generatingStream,
@@ -35,7 +35,7 @@ class ComputeController {
bool interactionOverride = false;
late Timer _userInteractionTimer;
_ComputeRunState _currentRunState = _ComputeRunState.idle;
ComputeRunState _currentRunState = ComputeRunState.idle;
bool _waitingToRunML = false;
bool get isDeviceHealthy => _isDeviceHealthy;
@@ -70,10 +70,20 @@ class ComputeController {
_logger.info('init done ');
}
bool requestCompute({bool ml = false, bool stream = false}) {
_logger.info("Requesting compute: ml: $ml, stream: $stream");
if (!_isDeviceHealthy || !_canRunGivenUserInteraction()) {
_logger.info("Device not healthy or user interacting, denying request.");
bool requestCompute({
bool ml = false,
bool stream = false,
bool bypassInteractionCheck = false,
}) {
_logger.info(
"Requesting compute: ml: $ml, stream: $stream, bypassInteraction: $bypassInteractionCheck",
);
if (!_isDeviceHealthy) {
_logger.info("Device not healthy, denying request.");
return false;
}
if (!bypassInteractionCheck && !_canRunGivenUserInteraction()) {
_logger.info("User interacting, denying request.");
return false;
}
bool result = false;
@@ -87,13 +97,17 @@ class ComputeController {
return result;
}
ComputeRunState get computeState {
return _currentRunState;
}
bool _requestML() {
if (_currentRunState == _ComputeRunState.idle) {
_currentRunState = _ComputeRunState.runningML;
if (_currentRunState == ComputeRunState.idle) {
_currentRunState = ComputeRunState.runningML;
_waitingToRunML = false;
_logger.info("ML request granted");
return true;
} else if (_currentRunState == _ComputeRunState.runningML) {
} else if (_currentRunState == ComputeRunState.runningML) {
return true;
}
_logger.info(
@@ -104,12 +118,9 @@ class ComputeController {
}
bool _requestStream() {
if (_currentRunState == _ComputeRunState.idle && !_waitingToRunML) {
if (_currentRunState == ComputeRunState.idle && !_waitingToRunML) {
_logger.info("Stream request granted");
_currentRunState = _ComputeRunState.generatingStream;
return true;
} else if (_currentRunState == _ComputeRunState.generatingStream &&
!_waitingToRunML) {
_currentRunState = ComputeRunState.generatingStream;
return true;
}
_logger.info(
@@ -124,13 +135,13 @@ class ComputeController {
);
if (ml) {
if (_currentRunState == _ComputeRunState.runningML) {
_currentRunState = _ComputeRunState.idle;
if (_currentRunState == ComputeRunState.runningML) {
_currentRunState = ComputeRunState.idle;
}
_waitingToRunML = false;
} else if (stream) {
if (_currentRunState == _ComputeRunState.generatingStream) {
_currentRunState = _ComputeRunState.idle;
if (_currentRunState == ComputeRunState.generatingStream) {
_currentRunState = ComputeRunState.idle;
}
}
}

View File

@@ -32,6 +32,7 @@ import "package:photos/service_locator.dart";
import "package:photos/services/file_magic_service.dart";
import "package:photos/services/filedata/model/file_data.dart";
import "package:photos/services/isolated_ffmpeg_service.dart";
import "package:photos/services/machine_learning/compute_controller.dart";
import "package:photos/ui/notification/toast.dart";
import "package:photos/utils/exif_util.dart";
import "package:photos/utils/file_key.dart";
@@ -120,8 +121,9 @@ class VideoPreviewService {
if (file.uploadedFileID == null) return false;
// Check if already in queue
final bool alreadyInQueue =
await uploadLocksDB.isInStreamQueue(file.uploadedFileID!);
final bool alreadyInQueue = await uploadLocksDB.isInStreamQueue(
file.uploadedFileID!,
);
if (alreadyInQueue) {
return false; // Indicates file was already in queue
}
@@ -131,7 +133,7 @@ class VideoPreviewService {
// Start processing if not already processing
if (uploadingFileId < 0) {
queueFiles(duration: Duration.zero);
queueFiles(duration: Duration.zero, isManual: true);
} else {
_items[file.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.inQueue,
@@ -252,10 +254,12 @@ class VideoPreviewService {
BuildContext? ctx,
EnteFile enteFile, [
bool forceUpload = false,
bool isManual = false,
]) async {
if (!_allowStream()) {
final canStream = _isPermissionGranted();
if (!canStream) {
_logger.info(
"Pause preview due to disabledSteaming($isVideoStreamingEnabled) or computeController permission)",
"Pause preview due to disabledSteaming($isVideoStreamingEnabled) or computeController permission) - isManual: $isManual",
);
if (isVideoStreamingEnabled) _logger.info("No permission to run compute");
clearQueue();
@@ -295,7 +299,8 @@ class VideoPreviewService {
"Starting video preview generation for ${enteFile.displayName}",
);
// elimination case for <=10 MB with H.264
var (props, result, file) = await _checkFileForPreviewCreation(enteFile);
var (props, result, file) =
await _checkFileForPreviewCreation(enteFile, isManual);
if (result) {
removeFile = true;
return;
@@ -575,8 +580,9 @@ class VideoPreviewService {
Future<void> _removeFromLocks(EnteFile enteFile) async {
final bool isFailurePresent =
_failureFiles?.contains(enteFile.uploadedFileID!) ?? false;
final bool isInManualQueue =
await uploadLocksDB.isInStreamQueue(enteFile.uploadedFileID!);
final bool isInManualQueue = await uploadLocksDB.isInStreamQueue(
enteFile.uploadedFileID!,
);
if (isFailurePresent) {
await uploadLocksDB.deleteStreamUploadErrorEntry(
@@ -917,27 +923,35 @@ class VideoPreviewService {
}
Future<(FFProbeProps?, bool, File?)> _checkFileForPreviewCreation(
EnteFile enteFile,
) async {
EnteFile enteFile, [
bool isManual = false,
]) async {
if ((enteFile.pubMagicMetadata?.sv ?? 0) == 1) {
_logger.info("Skip Preview due to sv=1 for ${enteFile.displayName}");
return (null, true, null);
}
if (enteFile.fileSize == null || enteFile.duration == null) {
_logger.warning(
"Skip Preview due to misisng size/duration for ${enteFile.displayName}",
);
return (null, true, null);
}
final int size = enteFile.fileSize!;
final int duration = enteFile.duration!;
if (size >= 500 * 1024 * 1024 || duration > 60) {
_logger.info("Skip Preview due to size: $size or duration: $duration");
return (null, true, null);
if (!isManual) {
if (enteFile.fileSize == null || enteFile.duration == null) {
_logger.warning(
"Skip Preview due to misisng size/duration for ${enteFile.displayName}",
);
return (null, true, null);
}
final int size = enteFile.fileSize!;
final int duration = enteFile.duration!;
if (size >= 500 * 1024 * 1024 || duration > 60) {
_logger.info("Skip Preview due to size: $size or duration: $duration");
return (null, true, null);
}
}
FFProbeProps? props;
File? file;
bool skipFile = false;
if (enteFile.fileSize == null && isManual) {
return (props, skipFile, file);
}
final size = enteFile.fileSize ?? 0;
try {
final isFileUnder10MB = size <= 10 * 1024 * 1024;
if (isFileUnder10MB) {
@@ -1025,8 +1039,9 @@ class VideoPreviewService {
}
// First try to find the file in the 60-day list
var queueFile =
files.firstWhereOrNull((f) => f.uploadedFileID == queueFileId);
var queueFile = files.firstWhereOrNull(
(f) => f.uploadedFileID == queueFileId,
);
// If not found in 60-day list, fetch it individually
queueFile ??=
@@ -1124,11 +1139,27 @@ class VideoPreviewService {
computeController.requestCompute(stream: true);
}
void queueFiles({Duration duration = const Duration(seconds: 5)}) {
bool _allowManualStream() {
return isVideoStreamingEnabled &&
computeController.requestCompute(
stream: true,
bypassInteractionCheck: true,
);
}
bool _isPermissionGranted() {
return isVideoStreamingEnabled &&
computeController.computeState == ComputeRunState.generatingStream;
}
void queueFiles({
Duration duration = const Duration(seconds: 5),
bool isManual = false,
}) {
Future.delayed(duration, () async {
if (_hasQueuedFile) return;
final isStreamAllowed = _allowStream();
final isStreamAllowed = isManual ? _allowManualStream() : _allowStream();
if (!isStreamAllowed) return;
await _ensurePreviewIdsInitialized();

View File

@@ -100,7 +100,12 @@ class _VideoStreamingSettingsPageState
children: [
TextSpan(
text: AppLocalizations.of(context)
.videoStreamingDescription,
.videoStreamingDescriptionLine1,
),
const TextSpan(text: " "),
TextSpan(
text: AppLocalizations.of(context)
.videoStreamingDescriptionLine2,
),
const TextSpan(text: " "),
TextSpan(
@@ -113,7 +118,6 @@ class _VideoStreamingSettingsPageState
),
],
),
textAlign: TextAlign.justify,
style: getEnteTextTheme(context).mini.copyWith(
color: getEnteColorScheme(context).textMuted,
),
@@ -145,10 +149,17 @@ class _VideoStreamingSettingsPageState
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text.rich(
TextSpan(
text: AppLocalizations.of(context)
.videoStreamingDescription +
"\n",
children: [
TextSpan(
text: AppLocalizations.of(context)
.videoStreamingDescriptionLine1,
),
const TextSpan(text: "\n"),
TextSpan(
text: AppLocalizations.of(context)
.videoStreamingDescriptionLine2,
),
const TextSpan(text: "\n"),
TextSpan(
text: AppLocalizations.of(context).moreDetails,
style: TextStyle(

View File

@@ -497,6 +497,7 @@ class FileAppBarState extends State<FileAppBar> {
final userId = Configuration.instance.getUserID();
return widget.file.fileType == FileType.video &&
widget.file.isUploaded &&
widget.file.fileSize != null &&
(widget.file.pubMagicMetadata?.sv ?? 0) != 1 &&
widget.file.ownerID == userId;
}

View File

@@ -1,4 +1,7 @@
- Similar images design changes. Also changed the vectorDB index file name, so internal users will have another migration (long loading time).
- Prateek: Enable immediate manual video stream processing by bypassing user interaction timer
- Prateek: Fix multiple concurrent streaming processes bug in ComputeController
- Prateek: Fix video streaming description text display spacing in advanced settings
- Ashil: Render cached thumbnails faster (noticeable in gallery scrolling)
- Similar images UI changes
- Neeraj: Fix for double enteries for local file

View File

@@ -1,3 +1,4 @@
- Video streaming improvements
- Added support for custom domain links
- Image editor fixes:
- Fixed bottom navigation bar color in light theme