Jump to content
Mark Williams

IPropertyStore MultiValue Strings

Recommended Posts

I am trying to extract meta data from media files using the IPropertyStore interface. So far I have no problem extracting most property values, but I am having difficulties with certain variant types. 

 

Below is part of the code used.

Procedure ExtractMediaTags(fileName : String);

  type TMetaDataTagType = (mdtString, mdtInteger, mdtLongInt, mdtDateTime);

  var
    Store : IPropertyStore;

  Function GetMetaDataValueAsString(Const MetaDataTag : TPropertyKey; MetaDataTagType : TMetaDataTagType) :String;
    var
      v : PropVariant;
      
  begin
    try
      OlECheck(store.GetValue(MetaDataTag, v));        
      try
        case MetaDataTagType of
          mdtString : if assigned(v.bstrVal) then Result := v.bstrVal;
          mdtInteger : Result := v.iVal.ToString;
          mdtLongInt : Result := v.hVal.QuadPart.ToString;
          mdtDateTime :Result := FileTimeToDateTimeStr(v.filetime);
        end;
      finally
        PropVariantClear(v);
      end;
    except
    end;
  end;

  var
    v : PropVariant;    
    s : string;
begin
    if SHGetPropertyStoreFromParsingName(pWideChar(FileName),
    Nil, GPS_READWRITE, IPropertyStore, store)<>S_OK then
      exit;
    
    AddMetaData(pd.DocProps, '70', GetMetaDataValueAsString(PKEY_Music_Artist, mdtString));
    AddMetaData(pd.DocProps, '74', GetMetaDataValueAsString(PKEY_Music_AlbumArtist, mdtString));
    
end;

For example, I am able to extract the value for PKEY_MUSIC_ALBUMARTIST. But not for PKEY_MUSIC_ARTIST.

 

The MS docs describe both variant values as being of type VT_LPWSTR (here).

 

However, more detailed remarks state that PKEY_MUSIC_AlbumArtist references a string value (here) and PKEY_Music_Artist references a "Multivalue String" (here).

 

I have queried the TVarType of PKEY_Music_Artist and it doesn't register as any of the specified TVarTypes not even varUnknown. Although PKEY_MUSIC_AlbumArtist also doesn't register as any TVarType.

 

Does anyone have any idea what type "Multivalue String" is and how I would read it?

Share this post


Link to post
1 hour ago, Mark Williams said:

I am trying to extract meta data from media files using the IPropertyStore interface. So far I have no problem extracting most property values, but I am having difficulties with certain variant types.

PROPVARIANT has similarities to VARIANT, but they are actually separate types, so you should treat them that way.

Quote

 Below is part of the code used.

Why are you passing in MetaDataTagType as an input parameter?  You should be looking at the vt member of the PROPVARIANT instead, which tells you the actual data type of the variant data, eg:

procedure ExtractMediaTags(const FileName : String);
var
  Store : IPropertyStore;

  function GetMetaDataValueAsString(const MetaDataTag : TPropertyKey): String;
  const
    VT_LPWSTR_VECTOR = VT_LPWSTR or VT_VECTOR;
  var
    v : PropVariant;
    i: UInt32;
  begin
    Result := '';
    try
      OleCheck(store.GetValue(MetaDataTag, v));
      try
        case v.vt of
          VT_BSTR: Result := v.bstrVal;
          VT_I2: Result := v.iVal.ToString;
          VT_I4: Result := v.lVal.ToString;
          VT_I8: Result := v.hVal.QuadPart.ToString;
          VT_FILETIME: Result := FileTimeToDateTimeStr(v.filetime);
          VT_LPWSTR: Result := v.pwszVal;
          // etc...
          VT_LPWSTR_VECTOR: begin
            if v.calpwstr.cElems > 0 then begin
              Result := v.calpwstr.pElems[0];
              for i := 1 to v.calpwstr.cElems-1 do begin
                Result := ', ' + string(v.calpwstr.pElems[i]);
              end;
            end;
          end;
          // etc...
        end;
      finally
        PropVariantClear(v);
      end;
    except
    end;
  end;

begin
  if SHGetPropertyStoreFromParsingName(PChar(FileName), nil, GPS_READWRITE, IPropertyStore, store) <> S_OK then
    Exit;
    
  AddMetaData(pd.DocProps, '70', GetMetaDataValueAsString(PKEY_Music_Artist));
  AddMetaData(pd.DocProps, '74', GetMetaDataValueAsString(PKEY_Music_AlbumArtist));
end;

Since you want to extract the PROPVARIANT data as a String, consider using functions like PropVariantToString() or PropVariantGetStringElem() instead, which handle these kind of details for you.  See PROPVARIANT and VARIANT Functions for a more complete list of functions available.

Quote

For example, I am able to extract the value for PKEY_MUSIC_ALBUMARTIST. But not for PKEY_MUSIC_ARTIST.

PKEY_MUSIC_ALBUMARTIST is the single primary artist.  PKEY_MUSIC_ARTIST is a list of all of the artists.

Quote

I have queried the TVarType of PKEY_Music_Artist and it doesn't register as any of the specified TVarTypes not even varUnknown.

What is the actual numeric value of the PROPVARIANT.vt member?  I'm guessing it is $101F (4127), which is an array of VT_LPWSTR strings.

Quote

Although PKEY_MUSIC_AlbumArtist also doesn't register as any TVarType.

TVarType is meant for VARIANT, not for PROPVARIANT.  Although they share many common types, PROPVARIANT has types that VARIANT does not, like VT_VECTOR (VARIANT uses VT_ARRAY instead).

 

Edited by Remy Lebeau

Share this post


Link to post
Guest

hi Mark,

 

I DONT KNOW ABOUT THIS FUNCTION... but, I think that you needs read a little more about your language programming... ok?

 

I dont changed all code, just the "point" for read the "ARTIST" list in a my "Tada.MP3" (by MSWindows sounds) for test..

  • the Artist list, is COMMA separated in my MP3... then 

 

image.thumb.png.00ca90e7e281c5f5dbf313c7ae56c374.png

implementation

{$R *.dfm}

uses
  Winapi.PropKey,
  Winapi.PropSys,
  System.Win.ComObj,
  Winapi.ActiveX,
  Winapi.ShlObj;

type
  TMetaDataTagType = (mdtString, mdtInteger, mdtLongInt, mdtDateTime);

var
  Store: IPropertyStore;

function FileTimeToDateTimeStr(F: TFileTime): string;
var
  LocalFileTime: TFileTime;
  SystemTime   : TSystemTime;
  DateTime     : TDateTime;
begin
  if Comp(F) = 0 then
    Result := '-'
  else
  begin
    FileTimeToLocalFileTime(F, LocalFileTime);
    FileTimeToSystemTime(LocalFileTime, SystemTime);
    with SystemTime do
      DateTime := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
    Result     := DateTimeToStr(DateTime);
  end;
end;

procedure ExtractMediaTags(fileName: string);
//
  function GetMetaDataValueAsString(const MetaDataTag: TPropertyKey): string;
  var
    MetaDataTagType: TMetaDataTagType;
    v              : PropVariant;
  begin
    Result := '!-nothing-!';
    //
    try
      OlECheck(Store.GetValue(MetaDataTag, v));
      //
      try
        case MetaDataTagType of
          mdtString:
            // if v.bstrVal  then { <----  PWideChar }
            Result := v.bstrVal;
          mdtInteger:
            Result := v.iVal.ToString;
          mdtLongInt:
            Result := v.hVal.QuadPart.ToString;
          mdtDateTime:
            Result := FileTimeToDateTimeStr(v.filetime);
        end;
      except
        on E: Exception do
          ShowMessage('My Error: ' + E.Message);
      end;
    finally
      PropVariantClear(v);
    end;
  end;

var
  // v: PropVariant;
  s: string;
  //
  lMyTStrList: TStringList; // for example for easy get it my list comma-separated
  i          : integer;
begin
  Store := nil;
  //
  if SHGetPropertyStoreFromParsingName(PWideChar(fileName), nil, GPS_READWRITE, IPropertyStore, Store) <> S_OK then
  begin
    ShowMessage('bye...');
    exit;
  end;
  //
  // for didatic passes
  //
  lMyTStrList := TStringList.Create;
  try
    s := GetMetaDataValueAsString(PKEY_Music_AlbumArtist); // , mdtString);
    //
    lMyTStrList.Delimiter     := ',';
    lMyTStrList.DelimitedText := s;
    //
    for i := 0 to Pred(lMyTStrList.Count) do
      ShowMessage(lMyTStrList.Strings[i]);
  finally
    lMyTStrList.Free;
  end;


  //

  // AddMetaData(pd.DocProps, '70', GetMetaDataValueAsString(PKEY_Music_Artist, mdtString));
  // AddMetaData(pd.DocProps, '74', GetMetaDataValueAsString(PKEY_Music_AlbumArtist, mdtString));

end;

procedure TfrmFormMain.Button1Click(Sender: TObject);
begin
  // ExtractMediaTags('..\..\tada.mp3'); // dont accept relative-path
  ExtractMediaTags('D:\RADRIOTests\zzzzzzzzzzzzzzzzzzzzzzzzx\tada.mp3');
end;

end.

hug

Edited by Guest

Share this post


Link to post
Guest

my tip:  IRRELEVANT ... SORRY I CANNOT DELETE POSTS!

  • DONT CREATE A LONG PROCEDURE/FUNCTION... break it in pieces!
    • it's better for analize!
Edited by Guest

Share this post


Link to post
14 hours ago, Remy Lebeau said:

I'm guessing it is $101F (4127), which is an array of VT_LPWSTR strings.

Yes. You guess right! 

 

And for anyone interested in how to extract the info from type 4127 the following seems to do the job ok.

 

   v, v2 : PropVariant;
   k : cardinal;      
  begin
    try
      OlECheck(store.GetValue(MetaDataTag, v));

     if metaDataTag = PKEY_Music_Artist then
      begin   
		if PropVariantGetElementCount(v) > 0 then //for some reason never exits loop unless you query this first
		  begin
            for k := 0 to PropVariantGetElementCount(v) -1 do
              begin
                if PropVariantGetElem(v, k, v2) = S_OK then                       
                 Result :=Result + v2.bstrVal + ',' 
              end;
          end;

NB the index for PropVariantGetElem is 0 based.

 

Interestingly (and rather annoyingly) after testing I have realised that some files have properties stored as multivalue strings in an array with only one item, but with comma separated values! Really does seem to defeat the purpose of the array type, but possibly explains why @emailx45 possibly got a comma separated array of values from a single string PropVariant (see below).

Edited by Mark Williams
Further testing of code

Share this post


Link to post
13 hours ago, emailx45 said:

GetMetaDataValueAsString(PKEY_Music_AlbumArtist);

It is the PKEY_MUSIC_ARTIST that I was having difficulties with. I can extract PKEY_Music_AlbumArtist) no problem via the bStrVal value. 

 

However, did you get comma delimited values from PKEY_Music_AlbumArtist? If so, it's a bit odd that MS should declare PKEY_Music_AlbumArtist as a string value instead of an array value.

Share this post


Link to post
Guest

in fact, all is a "string" (in base types) for better "transport" (for exchange of systems). the use of an array we have some like "table-index" ... i dont know if you understand my poor english explain?

 

if you look a property in a Mp3 (for example) for particular prop we have values separated by "some delimiter, normally, comma" then the programmer can create a list or array of this value for better presents it or use it... but, it still being a "string" list of values. in another prop this "comma", can DONT BE a separator, but it's "part" of string. you see?

 

my english is horrible, and my brain too!  :))))))

 

hug

Edited by Guest

Share this post


Link to post

A slight but important update to the code sample in my initial entry.

 

Original code:

if SHGetPropertyStoreFromParsingName(PWideChar(fileName), nil, GPS_READWRITE, IPropertyStore, Store) <> S_OK 

Whilst using the GETPROPERTYSTOREFLAG of GPS_READWRITE will work fine for media files that Windows can both read and write, it will fail to open media files which Windows can only read. Obvious really, but caused me a half hour of consternation before I realised why I couldn't read the tags for certain other files for eg AVI. In such cases using the GPS_READWRITE tag will get you an "Access Denied" result. If all you want to do is read the tags then use GPS_DEFAULT.

Share this post


Link to post
On 12/12/2020 at 6:49 AM, Mark Williams said:

Yes. You guess right! 

 

And for anyone interested in how to extract the info from type 4127 the following seems to do the job ok.

Is there a reason why you seem to have completely ignored the code I gave you for this exact purpose?

Share this post


Link to post
12 hours ago, Remy Lebeau said:

Is there a reason why you seem to have completely ignored the code I gave you for this exact purpose?

Sorry I didn't see any code. Looking back at your post, do you mean the suggestion to use PropVariant's vt member to ascertain type? If so, I haven't ignored it. The simple example I posted was just to show, for anyone interested, how to extract array type info. The example doesn't pass in the MetaDataTagType parameter nor does it reference the vt member as it didn't need to given that the tagtype PKEY_Music_Artist is known to be an array type.

Share this post


Link to post
7 hours ago, Mark Williams said:

Sorry I didn't see any code. Looking back at your post, do you mean the suggestion to use PropVariant's vt member to ascertain type?

Yes.  And how to access the array of LPWSTR string pointers for any string-vector tag, like PKEY_Music_Artist.

Quote

The example doesn't pass in the MetaDataTagType parameter nor does it reference the vt member as it didn't need to given that the tagtype PKEY_Music_Artist is known to be an array type.

But that example makes the code very tag-specific, whereas my example should work for any tag generically.

Edited by Remy Lebeau

Share this post


Link to post
22 minutes ago, Remy Lebeau said:

But that example makes the code very tag-specific, whereas my example should work for any tag generically.

Of course. I was just posting an example to show how to handle array data. I wasn't (at least not intentionally) suggesting that it is a good idea to repeat that code for every tag. My own code attempts to extract dozens of tag values and that is done by passing the tags into a separate function.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×