diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index b369066821..e5c4b5ef87 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -113,6 +113,7 @@ arsinh
artanh
arw
asdf
+asf
AShortcut
ASingle
ASSOCCHANGED
@@ -225,8 +226,8 @@ bytearray
CABD
CALG
callbackptr
-cameligo
calpwstr
+cameligo
Cangjie
CANRENAME
CAPTUREBLT
@@ -771,6 +772,7 @@ google
gpedit
gpo
GPOCA
+gpp
GPT
gpu
graphql
@@ -1121,13 +1123,13 @@ LPCTSTR
LPCWSTR
lpdw
lpfn
-lpmi
LPINPUT
+lpmi
LPMINMAXINFO
LPMONITORINFO
LPOSVERSIONINFOEXW
-lprc
LPPOINT
+lprc
LPRECT
LPSAFEARRAY
LPSTR
@@ -1230,6 +1232,7 @@ Miracast
mjpg
mkd
mkdn
+mkv
mlcfg
mmc
mmcexe
@@ -1511,7 +1514,6 @@ pft
pgp
pgsql
pguid
-pkey
PHANDLE
phbm
phbmp
@@ -1525,6 +1527,7 @@ pinfo
pinvoke
pipename
PKBDLLHOOKSTRUCT
+pkey
PKEY
plib
PLK
diff --git a/src/modules/peek/Peek.Common/Extensions/DispatcherExtensions.cs b/src/modules/peek/Peek.Common/Extensions/DispatcherExtensions.cs
index d6793d5316..c90dd90115 100644
--- a/src/modules/peek/Peek.Common/Extensions/DispatcherExtensions.cs
+++ b/src/modules/peek/Peek.Common/Extensions/DispatcherExtensions.cs
@@ -33,5 +33,29 @@ namespace Peek.Common.Extensions
return tcs.Task;
}
+
+ ///
+ /// Run work on UI thread safely.
+ ///
+ /// True if the work was run successfully, False otherwise.
+ public static Task RunOnUiThread(this DispatcherQueue dispatcher, Action work)
+ {
+ var tcs = new TaskCompletionSource();
+ dispatcher.TryEnqueue(() =>
+ {
+ try
+ {
+ work();
+
+ tcs.SetResult();
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ }
+ });
+
+ return tcs.Task;
+ }
}
}
diff --git a/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs b/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs
index 50cc7f9c93..0d976fd288 100644
--- a/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs
+++ b/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs
@@ -8,6 +8,7 @@ using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
+using Peek.Common.Helpers;
using Peek.Common.Models;
using Scripting;
using Windows.Foundation;
@@ -19,17 +20,12 @@ namespace Peek.Common.Extensions
{
public static Size? GetImageSize(this IFileSystemItem item)
{
- Size? size = null;
+ return PropertyStoreHelper.TryGetUintSizeProperty(item.Path, PropertyKey.ImageHorizontalSize, PropertyKey.ImageVerticalSize);
+ }
- var width = item.Width;
- var height = item.Height;
-
- if (width != null && height != null)
- {
- size = new Size((int)width, (int)height);
- }
-
- return size;
+ public static Size? GetVideoSize(this IFileSystemItem item)
+ {
+ return PropertyStoreHelper.TryGetUintSizeProperty(item.Path, PropertyKey.FrameWidth, PropertyKey.FrameHeight);
}
public static Size? GetSvgSize(this IFileSystemItem item)
diff --git a/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs b/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs
index 2ca58fe7f2..392e347ad1 100644
--- a/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs
+++ b/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs
@@ -7,24 +7,13 @@ using System.Globalization;
using System.Runtime.InteropServices;
using Peek.Common.Extensions;
using Peek.Common.Models;
+using Windows.Foundation;
using Windows.Win32.UI.Shell.PropertiesSystem;
namespace Peek.Common.Helpers
{
public static partial class PropertyStoreHelper
{
- ///
- /// Gets a uint type value from PropertyStore from the given item.
- ///
- /// The file/folder path
- /// The property key
- /// a nullable uint
- public static uint? TryGetUintProperty(string path, PropertyKey key)
- {
- using DisposablePropertyStore propertyStore = GetPropertyStoreFromPath(path);
- return propertyStore.TryGetUInt(key);
- }
-
///
/// Gets a ulong type value from PropertyStore from the given item.
///
@@ -49,6 +38,28 @@ namespace Peek.Common.Helpers
return propertyStore.TryGetString(key);
}
+ ///
+ /// Gets Size composed of weight (uint) and height (uint) from PropertyStore from the given item.
+ ///
+ /// The file/folder path
+ /// The property key for width
+ /// The property key for height
+ /// a nullable string
+ public static Size? TryGetUintSizeProperty(string path, PropertyKey widthKey, PropertyKey heightKey)
+ {
+ Size? size = null;
+ using DisposablePropertyStore propertyStore = GetPropertyStoreFromPath(path);
+ uint? width = propertyStore.TryGetUInt(widthKey);
+ uint? height = propertyStore.TryGetUInt(heightKey);
+
+ if (width != null && height != null)
+ {
+ size = new Size((float)width, (float)height);
+ }
+
+ return size;
+ }
+
///
/// Gets a IPropertyStore interface (wrapped in DisposablePropertyStore) from the given path.
///
@@ -73,7 +84,7 @@ namespace Peek.Common.Helpers
if (hr != 0)
{
- throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "GetPropertyStore returned hresult={0}", hr));
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "GetPropertyStore returned hresult={0}, errorMessage={1}", hr, Marshal.GetExceptionForHR(hr)!.Message));
}
return new DisposablePropertyStore((IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore));
diff --git a/src/modules/peek/Peek.Common/Models/IFileSystemItem.cs b/src/modules/peek/Peek.Common/Models/IFileSystemItem.cs
index e455ca243d..d030cf90cb 100644
--- a/src/modules/peek/Peek.Common/Models/IFileSystemItem.cs
+++ b/src/modules/peek/Peek.Common/Models/IFileSystemItem.cs
@@ -5,7 +5,6 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
-using Peek.Common.Extensions;
using Peek.Common.Helpers;
using Windows.Storage;
@@ -39,10 +38,6 @@ namespace Peek.Common.Models
public string Path { get; init; }
- public uint? Width => PropertyStoreHelper.TryGetUintProperty(Path, PropertyKey.ImageHorizontalSize);
-
- public uint? Height => PropertyStoreHelper.TryGetUintProperty(Path, PropertyKey.ImageVerticalSize);
-
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
public string FileType => PropertyStoreHelper.TryGetStringProperty(Path, PropertyKey.FileType) ?? string.Empty;
diff --git a/src/modules/peek/Peek.Common/Models/Win32/PropertyKey.cs b/src/modules/peek/Peek.Common/Models/Win32/PropertyKey.cs
index e21c9c7820..3611f3c82d 100644
--- a/src/modules/peek/Peek.Common/Models/Win32/PropertyKey.cs
+++ b/src/modules/peek/Peek.Common/Models/Win32/PropertyKey.cs
@@ -61,5 +61,7 @@ namespace Peek.Common.Models
public static readonly PropertyKey ImageVerticalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4);
public static readonly PropertyKey FileSizeBytes = new PropertyKey(new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), 12);
public static readonly PropertyKey FileType = new PropertyKey(new Guid(0xd5cdd502, 0x2e9c, 0x101b, 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae), 26);
+ public static readonly PropertyKey FrameWidth = new PropertyKey(new Guid(0x64440491, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 3);
+ public static readonly PropertyKey FrameHeight = new PropertyKey(new Guid(0x64440491, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4);
}
}
diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
index c64410551d..f5035fab7f 100644
--- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
+++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
@@ -26,6 +26,22 @@
ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(ImagePreviewer, Previewer.State), Mode=OneWay}" />
+
+
+
+
+
+
Previewer as IImagePreviewer;
+ public IVideoPreviewer? VideoPreviewer => Previewer as IVideoPreviewer;
+
public IBrowserPreviewer? BrowserPreviewer => Previewer as IBrowserPreviewer;
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
@@ -140,6 +143,7 @@ namespace Peek.FilePreviewer
{
Previewer = null;
ImagePreview.Visibility = Visibility.Collapsed;
+ VideoPreview.Visibility = Visibility.Collapsed;
BrowserPreview.Visibility = Visibility.Collapsed;
UnsupportedFilePreview.Visibility = Visibility.Collapsed;
return;
@@ -199,6 +203,8 @@ namespace Peek.FilePreviewer
partial void OnPreviewerChanging(IPreviewer? value)
{
+ VideoPreview.MediaPlayer.Pause();
+
if (Previewer != null)
{
Previewer.PropertyChanged -= Previewer_PropertyChanged;
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IVideoPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IVideoPreviewer.cs
new file mode 100644
index 0000000000..ae7d26d1b1
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IVideoPreviewer.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Media.Core;
+
+namespace Peek.FilePreviewer.Previewers.Interfaces
+{
+ public interface IVideoPreviewer : IPreviewer
+ {
+ public MediaSource? Preview { get; }
+ }
+}
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/NativeMethods.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs
similarity index 100%
rename from src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/NativeMethods.cs
rename to src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/ThumbnailHelper.cs
similarity index 100%
rename from src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs
rename to src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/ThumbnailHelper.cs
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/WICHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/WICHelper.cs
similarity index 100%
rename from src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/WICHelper.cs
rename to src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/WICHelper.cs
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/ImagePreviewer.cs
similarity index 100%
rename from src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs
rename to src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/ImagePreviewer.cs
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs
new file mode 100644
index 0000000000..7f5e6ae87f
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Dispatching;
+using Peek.Common.Extensions;
+using Peek.Common.Helpers;
+using Peek.Common.Models;
+using Peek.FilePreviewer.Previewers.Interfaces;
+using Windows.Foundation;
+using Windows.Media.Core;
+using Windows.Storage;
+
+namespace Peek.FilePreviewer.Previewers
+{
+ public partial class VideoPreviewer : ObservableObject, IVideoPreviewer, IDisposable
+ {
+ [ObservableProperty]
+ private MediaSource? preview;
+
+ [ObservableProperty]
+ private PreviewState state;
+
+ [ObservableProperty]
+ private Size videoSize;
+
+ public VideoPreviewer(IFileSystemItem file)
+ {
+ Item = file;
+ Dispatcher = DispatcherQueue.GetForCurrentThread();
+ }
+
+ private IFileSystemItem Item { get; }
+
+ private DispatcherQueue Dispatcher { get; }
+
+ private Task? VideoTask { get; set; }
+
+ public static bool IsFileTypeSupported(string fileExt)
+ {
+ return _supportedFileTypes.Contains(fileExt);
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ }
+
+ public async Task LoadPreviewAsync(CancellationToken cancellationToken)
+ {
+ State = PreviewState.Loading;
+ VideoTask = LoadVideoAsync(cancellationToken);
+ cancellationToken.ThrowIfCancellationRequested();
+ await VideoTask;
+
+ if (Preview == null && HasFailedLoadingPreview())
+ {
+ State = PreviewState.Error;
+ }
+ }
+
+ partial void OnPreviewChanged(MediaSource? value)
+ {
+ if (Preview != null)
+ {
+ State = PreviewState.Loaded;
+ }
+ }
+
+ public async Task GetPreviewSizeAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return await Task.Run(Item.GetVideoSize);
+ }
+
+ public async Task CopyAsync()
+ {
+ await Dispatcher.RunOnUiThread(async () =>
+ {
+ var storageItem = await Item.GetStorageItemAsync();
+ ClipboardHelper.SaveToClipboard(storageItem);
+ });
+ }
+
+ private Task LoadVideoAsync(CancellationToken cancellationToken)
+ {
+ return TaskExtension.RunSafe(async () =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var storageFile = await Item.GetStorageItemAsync() as StorageFile;
+
+ await Dispatcher.RunOnUiThread(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ Preview = MediaSource.CreateFromStorageFile(storageFile);
+ });
+ });
+ }
+
+ private bool HasFailedLoadingPreview()
+ {
+ return !(VideoTask?.Result ?? true);
+ }
+
+ private static readonly HashSet _supportedFileTypes = new()
+ {
+ ".mp4", ".3g2", ".3gp", ".3gp2", ".3gpp", ".asf", ".avi", ".m2t", ".m2ts",
+ ".m4v", ".mkv", ".mov", ".mp4", ".mp4v", ".mts", ".wm", ".wmv",
+ };
+ }
+}
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
index 8a8b0becdc..f03a8435be 100644
--- a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
@@ -16,6 +16,10 @@ namespace Peek.FilePreviewer.Previewers
{
return new ImagePreviewer(file);
}
+ else if (VideoPreviewer.IsFileTypeSupported(file.Extension))
+ {
+ return new VideoPreviewer(file);
+ }
else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension))
{
return new WebBrowserPreviewer(file);