在Windows 10中用C#的Shell32获取视频缺失的帧宽帧高属性
解决Shell32获取视频分辨率返回空的问题
我之前也碰到过一模一样的情况——用GetDetailsOf能拿到视频长度,但分辨率(Frame width/height)总是返回空字符串,哪怕资源管理器里明明能看到这些信息。核心原因是:GetDetailsOf严重依赖Windows Shell的属性缓存,而视频分辨率属于内容属性,需要Shell的属性处理程序主动解析文件内容后才能生成,不像文件长度这种文件系统属性是直接从NTFS元数据读取的。如果缓存没被触发更新,GetDetailsOf就拿不到值。
下面给你一个不需要第三方库、更可靠的解决方案:
直接访问Shell Property Store
我们可以绕过GetDetailsOf,直接通过IShellItem2接口访问Windows属性系统,这个方式会主动触发视频内容的解析(如果缓存中没有的话),稳定性更高。
实现代码
首先确保你的项目引用了Shell32和Microsoft.CSharp,然后添加以下工具类:
using Shell32; using System; using System.IO; using System.Runtime.InteropServices; public static class VideoResolutionHelper { // Windows属性系统中视频分辨率的标准属性键 private static readonly Guid PropertySystemGuid = new Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"); private const int PID_FrameWidth = 100; private const int PID_FrameHeight = 101; public static (int? Width, int? Height) GetVideoResolution(FileInfo videoFile) { if (!videoFile.Exists) return (null, null); Shell shellInstance = new Shell(); Folder targetFolder = shellInstance.NameSpace(videoFile.DirectoryName); FolderItem videoItem = targetFolder.ParseName(videoFile.Name); // 尝试转换为IShellItem2接口,直接访问属性系统 if (videoItem is IShellItem2 shellItem) { PropVariant widthVariant = new PropVariant(); PropVariant heightVariant = new PropVariant(); try { // 获取帧宽度属性 int resultCode = shellItem.GetProperty(ref PropertySystemGuid, PID_FrameWidth, ref widthVariant); if (resultCode == 0 && widthVariant.vt == VarEnum.VT_I4) { // 获取帧高度属性 resultCode = shellItem.GetProperty(ref PropertySystemGuid, PID_FrameHeight, ref heightVariant); if (resultCode == 0 && heightVariant.vt == VarEnum.VT_I4) { return ((int)widthVariant.intVal, (int)heightVariant.intVal); } } } finally { // 释放非托管资源 Marshal.FreeCoTaskMem(widthVariant.pointer); Marshal.FreeCoTaskMem(heightVariant.pointer); } } return (null, null); } // 手动定义IShellItem2接口(Shell32互操作库未包含这个接口的定义) [ComImport] [Guid("7E9FB0D3-919F-4307-AB2E-9B1860310C93")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellItem2 { [PreserveSig] int GetProperty(ref Guid propKey, int propertyId, ref PropVariant propertyValue); } // 定义PropVariant结构,用于接收属性值 [StructLayout(LayoutKind.Explicit)] private struct PropVariant { [FieldOffset(0)] public VarEnum vt; [FieldOffset(8)] public int intVal; [FieldOffset(8)] public IntPtr pointer; } }
使用方式
调用这个工具类非常简单:
FileInfo videoFile = new FileInfo(@"C:\Path\To\Your\Video.mp4"); var resolution = VideoResolutionHelper.GetVideoResolution(videoFile); if (resolution.Width.HasValue && resolution.Height.HasValue) { Console.WriteLine($"视频分辨率:{resolution.Width}x{resolution.Height}"); } else { Console.WriteLine("无法获取视频分辨率,请检查文件是否正常或系统编解码器是否完整"); }
为什么这个方案有效?
- 主动触发解析:
IShellItem2.GetProperty会直接调用Windows属性系统,如果属性缓存不存在,会立即触发视频文件的内容解析,而GetDetailsOf只会读取已有的缓存数据。 - 属性键更稳定:我们用的是Windows属性系统定义的标准属性GUID+PID,不会像
GetDetailsOf的索引那样随系统语言、版本变化(你提到的索引301/303只是你当前系统的临时值,换个系统可能就变了)。
额外排查点
如果还是拿不到分辨率,可以检查这两点:
- 系统编解码器是否完整:打开资源管理器,右键视频文件→属性→详细信息,如果这里也看不到分辨率,说明系统缺少对应视频格式的编解码器,需要安装。
- 权限是否足够:如果视频文件在受保护目录(比如
Program Files),请以管理员身份运行你的程序。
内容的提问来源于stack exchange,提问作者Gildon
相关产品推荐
相关产品推荐

