locked
WMP SDK, AddAttribute and WM/Picture format in C#? RRS feed

  • Question

  • I've been trying to find some sample code that illustrates how to use managed code to update binary tags in MP3 files that the WMP SDK chooses to treat as 'special' (i.e. rather than giving an opaque byte[] array, the SDK translates it into a C++ structure).

    Specifically, I'm trying to read and/or write the image in an MP3 file. I've found an example at http://www.eggheadcafe.com/software/aspnet/29508784/embedding-pictures-to-wma-files-with-c.aspx, but it seems to be not only awkward, but also requires modification of the SetAttribute / AddAttribute interface definition in the WMFSDKWrapper class provided in the samples.

    Obviously, I recognise that, sometimes, a sample needs to be altered in order to remain useful, but I'm wondering if there is another, better, way to write and/or read WM_PICTURE data in a managed app.

    Saturday, November 13, 2010 11:58 PM

All replies

  • I am not too familiar with the FSDK metadata editing functionality, so I do not have much to suggest there.  However, if I were going to try to update picture metadata in an MP3 file from C#, I would probably use PInvoke/COM interop to talk to the shell property system.  SHCreateItemFromParsingName creates an IShellItem2 from a file path, and then IShellItem2::GetProperty for PKEY_ThumbnailStream will get a PROPVARIANT that contains an IStream for the picture metadata.  You can then read from the IStream to get the picture data or write to it to replace the picture data.
    Wednesday, December 8, 2010 2:14 AM
  • I didn't find any good explanations online of how to do this, and certainly couldn't find any good examples in source. Most of the examples I saw seemed to give the impression that these metadata properties are read-only, which is specifically unhelpful in this case.

    Here's the code I used:

    {
      Image newImage = Bitmap.FromFile(jpglocation);
      ImageConverter imageConverter = new ImageConverter();
      WMPicture picture = new WMPicture();
    
      picture.pwszMIMEType = Marshal.StringToCoTaskMemUni("image/jpeg\0");
      picture.pwszDescription = Marshal.StringToCoTaskMemUni("AlbumArt\0");
      picture.bPictureType = 3;
    
      byte[] data = (byte[])imageConverter.ConvertTo(newImage, typeof(byte[]));
      newImage.Dispose();
      picture.dwDataLen = data.Length;
    
      picture.pbData = Marshal.AllocCoTaskMem(picture.dwDataLen);
    
      Marshal.Copy(data, 0, picture.pbData, picture.dwDataLen);
    
      IntPtr pictureParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(picture));
      Marshal.StructureToPtr(picture, pictureParam, false);
      byte[] picbytes = new byte[Marshal.SizeOf(picture)];
      Marshal.Copy(pictureParam, picbytes, 0, Marshal.SizeOf(picture));
      SetAttributeString("WM/Picture", picbytes);
      Marshal.FreeCoTaskMem(pictureParam);
      Marshal.FreeCoTaskMem(picture.pbData);
      Marshal.FreeCoTaskMem(picture.pwszDescription);
      Marshal.FreeCoTaskMem(picture.pwszMIMEType);
    }
    

    "SetAttributeString" is my function, which takes a byte[] array, and puts it into a WMT_TYPE_BINARY field in the MP3 file. using the WMFSDKWrapper definitions. It seems to work, so far.

    However, I'd really like to see the ability to use shell functions to set metadata, because the method I'm using here failse completely when writing to MP4 files. If you've seen any examples of setting metadata, including the thumbnail, or album art, for MP4 files, I'd be really grateful for the pointer.

    Monday, August 22, 2011 12:24 AM
  • I do not know what it would look like from managed code, but here's what using the shell to set metadata would look like in native code:
    HGLOBAL hMem = GlobalAlloc(GMEM_FIXED, cbMyImageSize);
    
    CComPtr<IStream> spStream;
    CreateStreamFromHGlobal(hMem, TRUE, &spStream);
    spStream->Write(pbMyImageData, cbMyImageSize, nullptr);
    
    CComPtr<IShellItem2> spShellItem;
    SHCreateItemFromParsingName(L"C:\\Path\\To\\Item.mp4", nullptr, IID_PPV_ARGS(&spShellItem);
    
    CComPtr<IPropertyStore> spStore;
    spShellItem->GetPropertyStore(GPS_READWRITE, IID_PPV_ARGS(&spStore));
    
    PROPVARIANT varStream;
    varStream.vt = VT_STREAM;
    varStream.pStream = spStream;
    spStore->SetValue(PKEY_ThumbnailStream, &varStream);
    spStore->Commit();
    
     Obviously no error checking here.  Also, the property handlers for media content store thumbnails as JPEGs, so if you have the image available as a JPEG it will avoid an image transcode.

    Monday, September 19, 2011 7:03 PM
  • Yeah, that's the same sort of thing that I've been looking at doing.

    As you say, you have no idea how it would look in managed code - and neither do I.

    I don't suppose anyone reading can give me some pointers to the creation of an IStream in C# from, let's say, a Stream that I've pulled out of a call to HttpResponse.GetResponseStream? [And then how to pass that as a PropVariant with type VT_STREAM]

    I've tried pulling the PropVariant class from the Windows API Code Pack, but that doesn't do VT_STREAM, nor even VT_CF, which I might use for System.Thumbnail.

    Saturday, September 24, 2011 11:24 PM