You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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("无法获取视频分辨率,请检查文件是否正常或系统编解码器是否完整");
}

为什么这个方案有效?

  1. 主动触发解析:IShellItem2.GetProperty会直接调用Windows属性系统,如果属性缓存不存在,会立即触发视频文件的内容解析,而GetDetailsOf只会读取已有的缓存数据。
  2. 属性键更稳定:我们用的是Windows属性系统定义的标准属性GUID+PID,不会像GetDetailsOf的索引那样随系统语言、版本变化(你提到的索引301/303只是你当前系统的临时值,换个系统可能就变了)。

额外排查点

如果还是拿不到分辨率,可以检查这两点:

  • 系统编解码器是否完整:打开资源管理器,右键视频文件→属性→详细信息,如果这里也看不到分辨率,说明系统缺少对应视频格式的编解码器,需要安装。
  • 权限是否足够:如果视频文件在受保护目录(比如Program Files),请以管理员身份运行你的程序。

内容的提问来源于stack exchange,提问作者Gildon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:36:44