Mark Williams 14 Posted December 11, 2020 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
Remy Lebeau 1409 Posted December 12, 2020 (edited) 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 December 12, 2020 by Remy Lebeau Share this post Link to post
Guest Posted December 12, 2020 (edited) 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 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 December 12, 2020 by Guest Share this post Link to post
Guest Posted December 12, 2020 (edited) my tip: IRRELEVANT ... SORRY I CANNOT DELETE POSTS! DONT CREATE A LONG PROCEDURE/FUNCTION... break it in pieces! it's better for analize! Edited December 12, 2020 by Guest Share this post Link to post
Mark Williams 14 Posted December 12, 2020 (edited) 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 December 12, 2020 by Mark Williams Further testing of code Share this post Link to post
Mark Williams 14 Posted December 12, 2020 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 Posted December 12, 2020 (edited) 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 December 12, 2020 by Guest Share this post Link to post
Mark Williams 14 Posted December 13, 2020 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
Remy Lebeau 1409 Posted December 13, 2020 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
Mark Williams 14 Posted December 14, 2020 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
Remy Lebeau 1409 Posted December 14, 2020 (edited) 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 December 14, 2020 by Remy Lebeau Share this post Link to post
Mark Williams 14 Posted December 14, 2020 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