With Win32 API/Intefaces, it is done with IPropertyStore
Test reading/writing Subtitle on a random .mp4 :
{
string sFileName = @"E:\Phone265.mp4";
IPropertyStore pPropertyStore = null;
Guid PropertyStoreGuid = typeof(IPropertyStore).GUID;
HRESULT hr = SHGetPropertyStoreFromParsingName(sFileName, IntPtr.Zero, GETPROPERTYSTOREFLAGS.GPS_READWRITE, ref PropertyStoreGuid, out pPropertyStore);
if (hr == HRESULT.S_OK)
{
var pv = new PROPVARIANT();
hr = pPropertyStore.GetValue(PKEY_Title, out pv);
if (hr == HRESULT.S_OK)
{
string sTitle = Marshal.PtrToStringUni(pv.pwszVal);
Console.WriteLine("Original Title : {0}", sTitle);
}
hr = pPropertyStore.GetValue(PKEY_Media_SubTitle, out pv);
if (hr == HRESULT.S_OK)
{
string sSubTitle = Marshal.PtrToStringUni(pv.pwszVal);
Console.WriteLine("Original SubTitle : {0}", sSubTitle);
}
string sNewSubTitle = "New SubTitle";
PROPVARIANT pvNewSubTitle = new PROPVARIANT();
pvNewSubTitle.varType = (ushort)VARENUM.VT_LPWSTR;
pvNewSubTitle.pwszVal = Marshal.StringToHGlobalUni(sNewSubTitle);
hr = pPropertyStore.SetValue(PKEY_Media_SubTitle, pvNewSubTitle);
if (hr == HRESULT.S_OK)
hr = pPropertyStore.Commit();
if (hr == HRESULT.S_OK)
{
hr = pPropertyStore.GetValue(PKEY_Media_SubTitle, out pv);
string sSubTitle = Marshal.PtrToStringUni(pv.pwszVal);
Console.WriteLine("New SubTitle : {0}", sSubTitle);
}
Marshal.ReleaseComObject(pPropertyStore);
}
}
With declarations :
public enum HRESULT : int
{
S_OK = 0,
S_FALSE = 1,
E_NOINTERFACE = unchecked((int)0x80004002),
E_NOTIMPL = unchecked((int)0x80004001),
E_FAIL = unchecked((int)0x80004005),
E_UNEXPECTED = unchecked((int)0x8000FFFFL)
}
[DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern HRESULT SHGetPropertyStoreFromParsingName(string pszPath, IntPtr pbc, GETPROPERTYSTOREFLAGS flags, ref Guid iid, [MarshalAs(UnmanagedType.Interface)] out IPropertyStore propertyStore);
public enum GETPROPERTYSTOREFLAGS
{
GPS_DEFAULT = 0,
GPS_HANDLERPROPERTIESONLY = 0x1,
GPS_READWRITE = 0x2,
GPS_TEMPORARY = 0x4,
GPS_FASTPROPERTIESONLY = 0x8,
GPS_OPENSLOWITEM = 0x10,
GPS_DELAYCREATION = 0x20,
GPS_BESTEFFORT = 0x40,
GPS_NO_OPLOCK = 0x80,
GPS_PREFERQUERYPROPERTIES = 0x100,
GPS_EXTRINSICPROPERTIES = 0x200,
GPS_EXTRINSICPROPERTIESONLY = 0x400,
GPS_VOLATILEPROPERTIES = 0x800,
GPS_VOLATILEPROPERTIESONLY = 0x1000,
GPS_MASK_VALID = 0x1FFF
}
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStore
{
HRESULT GetCount([Out] out uint propertyCount);
HRESULT GetAt([In] uint propertyIndex, [Out, MarshalAs(UnmanagedType.Struct)] out PROPERTYKEY key);
HRESULT GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [Out, MarshalAs(UnmanagedType.Struct)] out PROPVARIANT pv);
HRESULT SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [In, MarshalAs(UnmanagedType.Struct)] ref PROPVARIANT pv);
HRESULT Commit();
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PROPERTYKEY
{
private readonly Guid _fmtid;
private readonly uint _pid;
public PROPERTYKEY(Guid fmtid, uint pid)
{
_fmtid = fmtid;
_pid = pid;
}
}
public static readonly PROPERTYKEY PKEY_Title = new PROPERTYKEY(new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9"), 2);
public static readonly PROPERTYKEY PKEY_Keywords = new PROPERTYKEY(new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9"), 5);
public static readonly PROPERTYKEY PKEY_Media_SubTitle = new PROPERTYKEY(new Guid("56A3372E-CE9C-11D2-9F0E-006097C686F6"), 38);
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct PROPARRAY
{
public UInt32 cElems;
public IntPtr pElems;
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct PROPVARIANT
{
[FieldOffset(0)]
public ushort varType;
[FieldOffset(2)]
public ushort wReserved1;
[FieldOffset(4)]
public ushort wReserved2;
[FieldOffset(6)]
public ushort wReserved3;
[FieldOffset(8)]
public byte bVal;
[FieldOffset(8)]
public sbyte cVal;
[FieldOffset(8)]
public ushort uiVal;
[FieldOffset(8)]
public short iVal;
[FieldOffset(8)]
public UInt32 uintVal;
[FieldOffset(8)]
public Int32 intVal;
[FieldOffset(8)]
public UInt64 ulVal;
[FieldOffset(8)]
public Int64 lVal;
[FieldOffset(8)]
public float fltVal;
[FieldOffset(8)]
public double dblVal;
[FieldOffset(8)]
public short boolVal;
[FieldOffset(8)]
public IntPtr pclsidVal; // GUID ID pointer
[FieldOffset(8)]
public IntPtr pszVal; // Ansi string pointer
[FieldOffset(8)]
public IntPtr pwszVal; // Unicode string pointer
[FieldOffset(8)]
public IntPtr punkVal; // punkVal (interface pointer)
[FieldOffset(8)]
public PROPARRAY ca;
[FieldOffset(8)]
public System.Runtime.InteropServices.ComTypes.FILETIME filetime;
}
public enum VARENUM
{
VT_EMPTY = 0,
VT_NULL = 1,
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_VARIANT = 12,
VT_UNKNOWN = 13,
VT_DECIMAL = 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_VOID = 24,
VT_HRESULT = 25,
VT_PTR = 26,
VT_SAFEARRAY = 27,
VT_CARRAY = 28,
VT_USERDEFINED = 29,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_RECORD = 36,
VT_INT_PTR = 37,
VT_UINT_PTR = 38,
VT_FILETIME = 64,
VT_BLOB = 65,
VT_STREAM = 66,
VT_STORAGE = 67,
VT_STREAMED_OBJECT = 68,
VT_STORED_OBJECT = 69,
VT_BLOB_OBJECT = 70,
VT_CF = 71,
VT_CLSID = 72,
VT_VERSIONED_STREAM = 73,
VT_BSTR_BLOB = 0xfff,
VT_VECTOR = 0x1000,
VT_ARRAY = 0x2000,
VT_BYREF = 0x4000,
VT_RESERVED = 0x8000,
VT_ILLEGAL = 0xffff,
VT_ILLEGALMASKED = 0xfff,
VT_TYPEMASK = 0xfff
};