mirror of
https://github.com/immich-app/immich.git
synced 2026-07-20 05:34:22 +03:00
Compare commits
9 Commits
renovate/m
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d531af230a | ||
|
|
fc40c223f4 | ||
|
|
c2779258b4 | ||
|
|
76e9aa012e | ||
|
|
cb328c63cf | ||
|
|
afc470e8f4 | ||
|
|
b156ae46b6 | ||
|
|
67b1a9d99e | ||
|
|
d4f7c2ae0a |
@@ -1,8 +1,8 @@
|
||||
ARG DEVICE=cpu
|
||||
|
||||
FROM python:3.11-bookworm@sha256:5c34b355088846dddc8afb7442c20b9433dccdc8d66192dc52c616adeaa106a3 AS builder-cpu
|
||||
FROM python:3.11-bookworm@sha256:20ec607c68642c64c73269ce245aa0f060913f4129d15d9687850aa158e6269c AS builder-cpu
|
||||
|
||||
FROM python:3.13-slim-trixie@sha256:6771159cd4fa5d9bba1258caf0b82e6b73458c694d178ad97c5e925c2d0e1a91 AS builder-openvino
|
||||
FROM python:3.13-slim-trixie@sha256:f82c96458eedc847b233e582eb31336f4954b39cae020b6dcf5b3ed0e5cbcd74 AS builder-openvino
|
||||
|
||||
FROM builder-cpu AS builder-cuda
|
||||
|
||||
@@ -39,12 +39,12 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:b18992999dbe963a45a8a4da40ac2b1975be1a776d939d098c647482bcad5cba AS prod-cpu
|
||||
FROM python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe AS prod-cpu
|
||||
|
||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||
MACHINE_LEARNING_MODEL_ARENA=false
|
||||
|
||||
FROM python:3.13-slim-trixie@sha256:6771159cd4fa5d9bba1258caf0b82e6b73458c694d178ad97c5e925c2d0e1a91 AS prod-openvino
|
||||
FROM python:3.13-slim-trixie@sha256:f82c96458eedc847b233e582eb31336f4954b39cae020b6dcf5b3ed0e5cbcd74 AS prod-openvino
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||
|
||||
885
machine-learning/uv.lock
generated
885
machine-learning/uv.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -129,6 +129,7 @@ class AppConfig {
|
||||
.themeDynamic => theme.dynamicTheme,
|
||||
.themeColorfulInterface => theme.colorfulInterface,
|
||||
.imagePreferRemote => image.preferRemote,
|
||||
.imageLoadPreview => image.loadPreview,
|
||||
.imageLoadOriginal => image.loadOriginal,
|
||||
.viewerLoopVideo => viewer.loopVideo,
|
||||
.viewerLoadOriginalVideo => viewer.loadOriginalVideo,
|
||||
@@ -181,6 +182,7 @@ class AppConfig {
|
||||
.themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)),
|
||||
.themeColorfulInterface => copyWith(theme: theme.copyWith(colorfulInterface: value as bool)),
|
||||
.imagePreferRemote => copyWith(image: image.copyWith(preferRemote: value as bool)),
|
||||
.imageLoadPreview => copyWith(image: image.copyWith(loadPreview: value as bool)),
|
||||
.imageLoadOriginal => copyWith(image: image.copyWith(loadOriginal: value as bool)),
|
||||
.viewerLoopVideo => copyWith(viewer: viewer.copyWith(loopVideo: value as bool)),
|
||||
.viewerLoadOriginalVideo => copyWith(viewer: viewer.copyWith(loadOriginalVideo: value as bool)),
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
class ImageConfig {
|
||||
final bool preferRemote;
|
||||
final bool loadPreview;
|
||||
final bool loadOriginal;
|
||||
|
||||
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
|
||||
const ImageConfig({this.preferRemote = false, this.loadPreview = true, this.loadOriginal = false});
|
||||
|
||||
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
|
||||
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
|
||||
ImageConfig copyWith({bool? preferRemote, bool? loadPreview, bool? loadOriginal}) => ImageConfig(
|
||||
preferRemote: preferRemote ?? this.preferRemote,
|
||||
loadPreview: loadPreview ?? this.loadPreview,
|
||||
loadOriginal: loadOriginal ?? this.loadOriginal,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal);
|
||||
(other is ImageConfig &&
|
||||
other.preferRemote == preferRemote &&
|
||||
other.loadPreview == loadPreview &&
|
||||
other.loadOriginal == loadOriginal);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(preferRemote, loadOriginal);
|
||||
int get hashCode => Object.hash(preferRemote, loadPreview, loadOriginal);
|
||||
|
||||
@override
|
||||
String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)';
|
||||
String toString() =>
|
||||
'ImageConfig(preferRemoteImage: $preferRemote, loadPreview: $loadPreview, loadOriginal: $loadOriginal)';
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ enum SettingsKey<T> {
|
||||
|
||||
// Image
|
||||
imagePreferRemote<bool>(),
|
||||
imageLoadPreview<bool>(),
|
||||
imageLoadOriginal<bool>(),
|
||||
|
||||
// Viewer
|
||||
|
||||
@@ -745,15 +745,10 @@ class AddToAlbumHeader extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Future<void> onCreateAlbum() async {
|
||||
final albumName = await showDialog<String?>(context: context, builder: (context) => const NewAlbumNameModal());
|
||||
if (albumName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedAssets = ref.read(multiSelectProvider).selectedAssets;
|
||||
final newAlbum = await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.createAlbumWithAssets(title: albumName, assets: selectedAssets);
|
||||
.createAlbumWithAssets(title: "Untitled Album", assets: selectedAssets);
|
||||
|
||||
if (newAlbum == null) {
|
||||
ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr());
|
||||
|
||||
@@ -45,7 +45,6 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||
|
||||
completer.operation.valueOrCancellation().whenComplete(() {
|
||||
cachedStream.removeListener(listener);
|
||||
cachedOperation = null;
|
||||
});
|
||||
cachedOperation = completer.operation;
|
||||
return null;
|
||||
@@ -95,6 +94,9 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||
isFinished = isFinal;
|
||||
return codec;
|
||||
} catch (e) {
|
||||
if (isCancelled) {
|
||||
return null;
|
||||
}
|
||||
if (isFinal) {
|
||||
isFinished = true;
|
||||
PaintingBinding.instance.imageCache.evict(this);
|
||||
@@ -106,18 +108,33 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ImageInfo> initialImageStream() async* {
|
||||
Stream<ImageInfo> initialImageStream({required bool isFinal}) async* {
|
||||
final cachedOperation = this.cachedOperation;
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (cachedOperation == null) {
|
||||
// image resolved synchronously
|
||||
isFinished = isFinal;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final cachedImage = await cachedOperation.valueOrCancellation();
|
||||
if (cachedImage != null && !isCancelled) {
|
||||
yield cachedImage;
|
||||
if (isCancelled || cachedImage == null) {
|
||||
return;
|
||||
}
|
||||
isFinished = isFinal;
|
||||
yield cachedImage;
|
||||
} catch (e, stack) {
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (isFinal) {
|
||||
isFinished = true;
|
||||
PaintingBinding.instance.imageCache.evict(this);
|
||||
rethrow;
|
||||
}
|
||||
_log.severe('Error loading initial image', e, stack);
|
||||
} finally {
|
||||
this.cachedOperation = null;
|
||||
|
||||
@@ -98,51 +98,55 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||
}
|
||||
|
||||
Stream<ImageInfo> _codec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||
yield* initialImageStream();
|
||||
final loadOriginal = SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||
final loadPreview = SettingsRepository.instance.appConfig.image.loadPreview;
|
||||
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final loadOriginal = SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
var request = this.request = LocalImageRequest(
|
||||
localId: key.id,
|
||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||
assetType: key.assetType,
|
||||
);
|
||||
yield* loadRequest(request, decode, isFinal: !loadOriginal);
|
||||
if (loadPreview) {
|
||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
final previewRequest = request = LocalImageRequest(
|
||||
localId: key.id,
|
||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||
assetType: key.assetType,
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadOriginal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
request = this.request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
||||
|
||||
yield* loadRequest(request, decode, isFinal: true);
|
||||
final originalRequest = request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
||||
yield* loadRequest(originalRequest, decode, isFinal: true);
|
||||
}
|
||||
|
||||
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||
yield* initialImageStream();
|
||||
yield* initialImageStream(isFinal: false);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
final previewRequest = request = LocalImageRequest(
|
||||
localId: key.id,
|
||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||
assetType: key.assetType,
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||
if (SettingsRepository.instance.appConfig.image.loadPreview) {
|
||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
final previewRequest = request = LocalImageRequest(
|
||||
localId: key.id,
|
||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||
assetType: key.assetType,
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// always try original for animated, since previews don't support animation
|
||||
|
||||
@@ -108,31 +108,35 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
}
|
||||
|
||||
Stream<ImageInfo> _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||
yield* initialImageStream();
|
||||
final isImage = assetType == AssetType.image;
|
||||
final loadOriginal = isImage && SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||
final loadPreview = isImage && SettingsRepository.instance.appConfig.image.loadPreview;
|
||||
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final previewRequest = request = RemoteImageRequest(
|
||||
uri: getThumbnailUrlForRemoteId(
|
||||
key.assetId,
|
||||
type: AssetMediaSize.preview,
|
||||
thumbhash: key.thumbhash,
|
||||
edited: key.edited,
|
||||
),
|
||||
);
|
||||
final loadOriginal = assetType == AssetType.image && SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||
if (loadPreview) {
|
||||
final previewRequest = request = RemoteImageRequest(
|
||||
uri: getThumbnailUrlForRemoteId(
|
||||
key.assetId,
|
||||
type: AssetMediaSize.preview,
|
||||
thumbhash: key.thumbhash,
|
||||
edited: key.edited,
|
||||
),
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadOriginal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final originalRequest = request = RemoteImageRequest(
|
||||
uri: getOriginalUrlForRemoteId(key.assetId, edited: key.edited),
|
||||
);
|
||||
@@ -140,24 +144,26 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
}
|
||||
|
||||
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||
yield* initialImageStream();
|
||||
yield* initialImageStream(isFinal: false);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final previewRequest = request = RemoteImageRequest(
|
||||
uri: getThumbnailUrlForRemoteId(
|
||||
key.assetId,
|
||||
type: AssetMediaSize.preview,
|
||||
thumbhash: key.thumbhash,
|
||||
edited: key.edited,
|
||||
),
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||
if (SettingsRepository.instance.appConfig.image.loadPreview) {
|
||||
final previewRequest = request = RemoteImageRequest(
|
||||
uri: getThumbnailUrlForRemoteId(
|
||||
key.assetId,
|
||||
type: AssetMediaSize.preview,
|
||||
thumbhash: key.thumbhash,
|
||||
edited: key.edited,
|
||||
),
|
||||
);
|
||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// always try original for animated, since previews don't support animation
|
||||
|
||||
@@ -12,7 +12,11 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isPreview = useState(ref.read(appConfigProvider).image.loadPreview);
|
||||
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
|
||||
useValueChanged<bool, void>(isPreview.value, (_, __) {
|
||||
ref.read(settingsProvider).write(.imageLoadPreview, isPreview.value);
|
||||
});
|
||||
useValueChanged<bool, void>(isOriginal.value, (_, __) {
|
||||
ref.read(settingsProvider).write(.imageLoadOriginal, isOriginal.value);
|
||||
});
|
||||
@@ -25,6 +29,12 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
icon: Icons.image_outlined,
|
||||
subtitle: "setting_image_viewer_help".t(context: context),
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: isPreview,
|
||||
title: "setting_image_viewer_preview_title".t(context: context),
|
||||
subtitle: "setting_image_viewer_preview_subtitle".t(context: context),
|
||||
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: isOriginal,
|
||||
title: "setting_image_viewer_original_title".t(context: context),
|
||||
|
||||
@@ -103,12 +103,6 @@
|
||||
"default": false,
|
||||
"title": "Case sensitive",
|
||||
"description": "Whether matching should be case-sensitive"
|
||||
},
|
||||
"usePath": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"title": "Use path",
|
||||
"description": "Match the full path on the server instead of the original filename"
|
||||
}
|
||||
},
|
||||
"required": ["pattern"]
|
||||
|
||||
@@ -54,11 +54,11 @@ const methods = wrapper<Manifest>({
|
||||
},
|
||||
|
||||
assetFileFilter: ({ data, config }) => {
|
||||
const { pattern, matchType = 'contains', caseSensitive = false, usePath = false } = config;
|
||||
const { pattern, matchType = 'contains', caseSensitive = false } = config;
|
||||
|
||||
const { asset } = data;
|
||||
|
||||
const fileName = usePath ? asset.originalPath : asset.originalFileName;
|
||||
const fileName = asset.originalFileName || '';
|
||||
const searchName = caseSensitive ? fileName : fileName.toLowerCase();
|
||||
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ select
|
||||
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
|
||||
where
|
||||
"workflow"."id" = "workflow_step"."workflowId"
|
||||
order by
|
||||
"workflow_step"."order" asc
|
||||
) as agg
|
||||
) as "steps"
|
||||
from
|
||||
@@ -59,8 +57,6 @@ select
|
||||
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
|
||||
where
|
||||
"workflow"."id" = "workflow_step"."workflowId"
|
||||
order by
|
||||
"workflow_step"."order" asc
|
||||
) as agg
|
||||
) as "steps"
|
||||
from
|
||||
|
||||
@@ -39,8 +39,7 @@ export class WorkflowRepository {
|
||||
'plugin_method.name as methodName',
|
||||
'workflow_step.config',
|
||||
'workflow_step.enabled',
|
||||
])
|
||||
.orderBy('workflow_step.order', 'asc'),
|
||||
]),
|
||||
).as('steps'),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user