diff --git a/Models/ImageItem.vb b/Models/ImageItem.vb index cf23c55..c746115 100644 --- a/Models/ImageItem.vb +++ b/Models/ImageItem.vb @@ -138,8 +138,6 @@ Namespace Models ''' ein Immich-Pseudo-Pfad ist. Public Property ImmichLocalPath As String - Private Shared ReadOnly _videoFormats As String() = {".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v"} - Public ReadOnly Property IsRawFile As Boolean Get Return Not IsFolder AndAlso RawPreviewService.IsSupportedRaw(FilePath) @@ -150,13 +148,13 @@ Namespace Models ''' (Belichtung/Kurven/Filter usw.) ergibt dafür konzeptionell keinen Sinn. Public ReadOnly Property IsVectorFile As Boolean Get - Return Not IsFolder AndAlso SvgPreviewService.IsSupportedSvg(FilePath) + Return Not IsFolder AndAlso MediaFormatService.IsSvg(FilePath) End Get End Property Public ReadOnly Property IsVideoFile As Boolean Get - Return Not IsFolder AndAlso _videoFormats.Contains(IO.Path.GetExtension(FilePath).ToLowerInvariant()) + Return Not IsFolder AndAlso MediaFormatService.IsVideo(FilePath) End Get End Property diff --git a/Services/ImmichService.vb b/Services/ImmichService.vb index e73674c..8b378c3 100644 --- a/Services/ImmichService.vb +++ b/Services/ImmichService.vb @@ -638,23 +638,7 @@ Namespace Services End Sub Private Shared Function GuessMimeType(filePath As String) As String - Select Case IO.Path.GetExtension(filePath).ToLowerInvariant() - Case ".jpg", ".jpeg" : Return "image/jpeg" - Case ".png" : Return "image/png" - Case ".gif" : Return "image/gif" - Case ".webp" : Return "image/webp" - Case ".bmp" : Return "image/bmp" - Case ".tif", ".tiff" : Return "image/tiff" - Case ".heic" : Return "image/heic" - Case ".heif" : Return "image/heif" - Case ".avif" : Return "image/avif" - Case ".mp4" : Return "video/mp4" - Case ".mov" : Return "video/quicktime" - Case ".mkv" : Return "video/x-matroska" - Case ".avi" : Return "video/x-msvideo" - Case ".webm" : Return "video/webm" - Case Else : Return "application/octet-stream" - End Select + Return MediaFormatService.GuessMimeType(filePath) End Function ''' Alle Alben des Servers, alphabetisch. Leere Liste bei Fehler/keiner Konfiguration. diff --git a/Services/MediaFormatService.vb b/Services/MediaFormatService.vb new file mode 100644 index 0000000..d727bf5 --- /dev/null +++ b/Services/MediaFormatService.vb @@ -0,0 +1,74 @@ +Imports System +Imports System.Collections.Generic +Imports System.IO +Imports System.Linq + +Namespace Services + + ''' + ''' Canonical media classification shared by folder discovery, gallery items, + ''' the viewer, video previews, and upload MIME detection. + ''' + Public NotInheritable Class MediaFormatService + + Private Sub New() + End Sub + + Public Shared ReadOnly DisplayImageExtensions As String() = { + ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".tif", ".webp", + ".heic", ".avif", + ".ico", ".svg", ".fpx", ".psd", ".psb" + } + + Public Shared ReadOnly VideoExtensions As String() = { + ".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v" + } + + Public Shared ReadOnly DisplayMediaExtensions As String() = + DisplayImageExtensions.Concat(VideoExtensions). + Concat(RawPreviewService.SupportedExtensions). + Distinct(StringComparer.OrdinalIgnoreCase). + ToArray() + + Public Shared Function IsVideo(filePath As String) As Boolean + If String.IsNullOrWhiteSpace(filePath) Then Return False + Return VideoExtensions.Contains(Path.GetExtension(filePath), StringComparer.OrdinalIgnoreCase) + End Function + + Public Shared Function IsSvg(filePath As String) As Boolean + If String.IsNullOrWhiteSpace(filePath) Then Return False + Return String.Equals(Path.GetExtension(filePath), ".svg", StringComparison.OrdinalIgnoreCase) + End Function + + Public Shared Function IsDisplayMedia(filePath As String) As Boolean + If String.IsNullOrWhiteSpace(filePath) Then Return False + Return DisplayMediaExtensions.Contains(Path.GetExtension(filePath), StringComparer.OrdinalIgnoreCase) + End Function + + Public Shared Function VideoPickerPatterns() As IEnumerable(Of String) + Return VideoExtensions.Select(Function(extension) "*" & extension) + End Function + + Public Shared Function GuessMimeType(filePath As String) As String + Select Case Path.GetExtension(filePath).ToLowerInvariant() + Case ".jpg", ".jpeg" : Return "image/jpeg" + Case ".png" : Return "image/png" + Case ".gif" : Return "image/gif" + Case ".webp" : Return "image/webp" + Case ".bmp" : Return "image/bmp" + Case ".tif", ".tiff" : Return "image/tiff" + Case ".heic" : Return "image/heic" + Case ".heif" : Return "image/heif" + Case ".avif" : Return "image/avif" + Case ".mp4" : Return "video/mp4" + Case ".mov" : Return "video/quicktime" + Case ".mkv" : Return "video/x-matroska" + Case ".avi" : Return "video/x-msvideo" + Case ".webm" : Return "video/webm" + Case Else : Return "application/octet-stream" + End Select + End Function + + End Class + +End Namespace diff --git a/Services/TransparencyBrushService.vb b/Services/TransparencyBrushService.vb index 5cd7892..2b7e6e3 100644 --- a/Services/TransparencyBrushService.vb +++ b/Services/TransparencyBrushService.vb @@ -41,9 +41,9 @@ Namespace Services ''' RAW-Endungen kommen aus RawPreviewService.SupportedExtensions - eine eigene Kopie ''' wuerde beim naechsten neuen Format vergessen (siehe Kommentar dort). Private Shared ReadOnly OpaqueOnlyExtensions As String() = { - ".jpg", ".jpeg", ".jpe", ".jfif", ".bmp", - ".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v" - }.Concat(RawPreviewService.SupportedExtensions).ToArray() + ".jpg", ".jpeg", ".jpe", ".jfif", ".bmp" + }.Concat(MediaFormatService.VideoExtensions). + Concat(RawPreviewService.SupportedExtensions).ToArray() Public Shared Function CanHaveTransparency(filePath As String) As Boolean If String.IsNullOrEmpty(filePath) Then Return True diff --git a/Services/VideoPreviewService.vb b/Services/VideoPreviewService.vb index 18ee359..113bca3 100644 --- a/Services/VideoPreviewService.vb +++ b/Services/VideoPreviewService.vb @@ -12,12 +12,10 @@ Namespace Services ''' gehalten, weil mehrere parallele Decoder schnell mehr Last erzeugen als der Thumbnail-Cache spart. Public Class VideoPreviewService - Private Shared ReadOnly _videoExtensions As String() = {".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v"} Private Shared ReadOnly _thumbnailGate As New SemaphoreSlim(1, 1) Public Shared Function IsSupportedVideo(filePath As String) As Boolean - If String.IsNullOrEmpty(filePath) Then Return False - Return _videoExtensions.Contains(Path.GetExtension(filePath).ToLowerInvariant()) + Return MediaFormatService.IsVideo(filePath) End Function Public Shared Function ExtractPreview(filePath As String, Optional maxDimension As Integer = 480, Optional timeoutMs As Integer = 4000) As MemoryStream diff --git a/ViewModels/GalleryViewModel.vb b/ViewModels/GalleryViewModel.vb index e0820ec..d880b23 100644 --- a/ViewModels/GalleryViewModel.vb +++ b/ViewModels/GalleryViewModel.vb @@ -3413,14 +3413,7 @@ Namespace ViewModels Me.RaisePropertyChanged(NameOf(HasBreadcrumbParent)) End Sub - ' ".fpx" gehört dazu: FerrumPix-Projekte erscheinen wie Bilder in Galerie und Filmstreifen - ' (Thumbnail aus dem eingebetteten Composite, siehe ThumbnailCacheService). - ' Anzeigbare Medien: feste Formate plus die kanonischen RAW-Endungen. - Private ReadOnly _imageExtensions As String() = { - ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".tif", ".webp", ".heic", ".avif", - ".ico", ".svg", ".fpx", ".psd", ".psb", - ".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v" - }.Concat(RawPreviewService.SupportedExtensions).ToArray() + Private ReadOnly _imageExtensions As String() = MediaFormatService.DisplayMediaExtensions ''' ''' Freier Speicherplatz des Laufwerks, auf dem der aktuelle Ordner liegt. Läuft im Hintergrund: @@ -5254,7 +5247,6 @@ Namespace ViewModels Return AppSettingsService.Load().DevelopRawInBatch End Function - Private Shared ReadOnly BatchConvertExcludedExtensions As String() = {".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v", ".svg"} ''' Kann die Stapel-Bildbearbeitung (Groesse/Wasserzeichen/Filter) diese Datei ''' schreiben? Die Menuesichtbarkeit MUSS dieselbe Frage stellen wie ''' GetSelectedBatchEditableImageItems - sonst stehen Eintraege da, die beim Klick still @@ -5283,7 +5275,7 @@ Namespace ViewModels ''' nicht - deren Eintraege blieben sonst wirkungslos sichtbar.) Public Shared Function IsBatchExportable(path As String) As Boolean If String.IsNullOrWhiteSpace(path) Then Return False - Return Not BatchConvertExcludedExtensions.Contains(IO.Path.GetExtension(path).ToLowerInvariant()) + Return Not MediaFormatService.IsVideo(path) AndAlso Not MediaFormatService.IsSvg(path) End Function ''' Formate, die die Stapel-Bildbearbeitung als QUELLE lesen kann. BMP/GIF (Skia) @@ -6084,7 +6076,7 @@ Namespace ViewModels Private Async Function ExportSelectedAsync() As Task Dim targetItems = GetSelectedImageItems(). Where(Function(i) i IsNot Nothing AndAlso Not i.IsFolder). - Where(Function(i) Not BatchConvertExcludedExtensions.Contains(IO.Path.GetExtension(i.FilePath).ToLowerInvariant())). + Where(Function(i) IsBatchExportable(i.FilePath)). ToList() DiagnosticLogService.LogAlways("Gallery.ExportTo", $"selected={GetSelectedImageItems().Count} exportable={targetItems.Count}") If targetItems.Count = 0 Then Return @@ -6242,7 +6234,7 @@ Namespace ViewModels Private Async Sub BatchConvertSelected() Dim targetItems = GetSelectedImageItems(). Where(Function(i) i IsNot Nothing AndAlso Not i.IsFolder). - Where(Function(i) Not BatchConvertExcludedExtensions.Contains(IO.Path.GetExtension(i.FilePath).ToLowerInvariant())). + Where(Function(i) IsBatchExportable(i.FilePath)). ToList() DiagnosticLogService.LogAlways("Gallery.BatchConvert", $"selected={GetSelectedImageItems().Count} convertible={targetItems.Count}") If targetItems.Count = 0 Then Return diff --git a/ViewModels/MainWindowViewModel.vb b/ViewModels/MainWindowViewModel.vb index f18a211..a518a16 100644 --- a/ViewModels/MainWindowViewModel.vb +++ b/ViewModels/MainWindowViewModel.vb @@ -458,7 +458,7 @@ Namespace ViewModels Dim ignored = Gallery.OpenFolderForImage(imagePath) CurrentMode = AppMode.Gallery Case "Editor" - If SvgPreviewService.IsSupportedSvg(imagePath) Then + If MediaFormatService.IsSvg(imagePath) Then Viewer.OpenImage(imagePath) CurrentMode = AppMode.Viewer Else diff --git a/ViewModels/ViewerViewModel.vb b/ViewModels/ViewerViewModel.vb index c108185..1f2203e 100644 --- a/ViewModels/ViewerViewModel.vb +++ b/ViewModels/ViewerViewModel.vb @@ -2013,16 +2013,9 @@ Namespace ViewModels End Sub Private Sub LoadFolderContext(folder As String, currentPath As String) - ' ".fpx" gehört dazu: Projekte blättern im Viewer/Vollbild mit (Anzeige aus dem Composite). - ' Feste Formate plus die kanonischen RAW-Endungen (RawPreviewService.SupportedExtensions). - Dim exts = { - ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".tif", ".webp", ".heic", ".avif", - ".ico", ".svg", ".fpx", ".psd", ".psb", - ".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v" - }.Concat(RawPreviewService.SupportedExtensions).ToArray() Try _folderPaths = Directory.GetFiles(folder). - Where(Function(f) exts.Contains(IO.Path.GetExtension(f).ToLowerInvariant())). + Where(Function(f) MediaFormatService.IsDisplayMedia(f)). OrderBy(Function(f) IO.Path.GetFileName(f)). ToList() _currentIndex = _folderPaths.FindIndex(Function(p) String.Equals(p, currentPath, StringComparison.OrdinalIgnoreCase)) diff --git a/Views/GalleryView.axaml.vb b/Views/GalleryView.axaml.vb index 6c07073..f5b9f28 100644 --- a/Views/GalleryView.axaml.vb +++ b/Views/GalleryView.axaml.vb @@ -884,7 +884,8 @@ Namespace Views Dim mediaType = New Avalonia.Platform.Storage.FilePickerFileType(LocalizationService.T("Bilder & Videos")) With { .Patterns = New List(Of String) From { "*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp", "*.tif", "*.tiff", "*.webp", - "*.heic", "*.heif", "*.avif", "*.mp4", "*.mov", "*.mkv", "*.avi", "*.webm"} + "*.heic", "*.heif", "*.avif" + }.Concat(MediaFormatService.VideoPickerPatterns()).ToList() } Dim files = Await storageProvider.OpenFilePickerAsync(New Avalonia.Platform.Storage.FilePickerOpenOptions With { .Title = LocalizationService.T("Bilder/Videos zum Hochladen wählen"),