Date: | September 9, 2013 / year-entry #239 |
Tags: | other |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20130909-00/?p=3293 |
Comments: | 12 |
Summary: | Today's Little Program edits the metadata of an audio file, ostensibly to correct a spelling error, but really just to show how it's done. Today's smart pointer class library is... (rolls dice)... CComPtr! We open with two helper functions which encapsulate the patterns Get property from property store Call IPropertyStore::GetValue Convert PROPVARIANT into desired final... |
Today's Little Program edits the metadata of an audio file, ostensibly to correct a spelling error, but really just to show how it's done. Today's smart pointer class library is... (rolls dice)... CComPtr! We open with two helper functions which encapsulate the patterns
#define STRICT #include <windows.h> #include <stdio.h> #include <shlobj.h> #include <propkey.h> #include <propvarutil.h> #include <atlbase.h> #include <atlalloc.h> template<typename TLambda> HRESULT GetPropertyAsLambda(IPropertyStore *pps, REFPROPERTYKEY key, TLambda lambda) { PROPVARIANT pvar; HRESULT hr = pps->GetValue(key, &pvar); if (SUCCEEDED(hr)) { hr = lambda(pvar); PropVariantClear(&pvar); } return hr; } template<typename TLambda> HRESULT SetPropertyAsLambda(IPropertyStore *pps, REFPROPERTYKEY key, TLambda lambda) { PROPVARIANT pvar; HRESULT hr = lambda(&pvar); if (SUCCEEDED(hr)) { hr = pps->SetValue(key, pvar); PropVariantClear(&pvar); } return hr; } Both functions use a lambda to do the type-specific work. Here are some functions that will use the helpers: HRESULT GetPropertyAsString( IPropertyStore *pps, REFPROPERTYKEY key, PWSTR *ppszValue) { *ppszValue = nullptr; return GetPropertyAsLambda(pps, key, [=](REFPROPVARIANT pvar) { return PropVariantToStringAlloc(pvar, ppszValue); }); } HRESULT SetPropertyAsString( IPropertyStore *pps, REFPROPERTYKEY key, PCWSTR pszValue) { return SetPropertyAsLambda(pps, key, [=](PROPVARIANT *ppvar) { return InitPropVariantFromString(pszValue, ppvar); }); } HRESULT GetPropertyAsStringVector( IPropertyStore *pps, REFPROPERTYKEY key, PWSTR **pprgsz, ULONG *pcElem) { *pprgsz = nullptr; *pcElem = 0; return GetPropertyAsLambda(pps, key, [=](REFPROPVARIANT pvar) { return PropVariantToStringVectorAlloc(pvar, pprgsz, pcElem); }); } HRESULT SetPropertyAsStringVector( IPropertyStore *pps, REFPROPERTYKEY key, PCWSTR *prgsz, ULONG cElems) { return SetPropertyAsLambda(pps, key, [=](PROPVARIANT *ppvar) { return InitPropVariantFromStringVector(prgsz, cElems, ppvar); }); }
The template<typename T> void CoTaskMemFreeArray(T **prgElem, ULONG cElem) { for (ULONG i = 0; i < cElem; i++) { CoTaskMemFree(prgElem[i]); } CoTaskMemFree(prgElem); } Okay, we're ready to write our main program. Remember, Little Programs do little to no error checking. In a real program, you would check that your function calls succeeded. int __cdecl wmain(int argc, wchar_t **argv) { CCoInitialize init; CComPtr<IPropertyStore> spps; SHGetPropertyStoreFromParsingName(argv[1], nullptr, GPS_READWRITE, IID_PPV_ARGS(&spps)); // Get the existing composers PWSTR *rgpszComposers; ULONG cComposers; GetPropertyAsStringVector(spps, PKEY_Music_Composer, &rgpszComposers, &cComposers); // Look for "Dvorak, Antonin" and add diacritics for (ULONG ulPos = 0; ulPos < cComposers; ulPos++) { if (wcscmp(rgpszComposers[ulPos], L"Dvorak, Antonin") == 0) { // Swap in the new name PWSTR pszOld = rgpszComposers[ulPos]; rgpszComposers[ulPos] = L"Dvo\x0159\x00E1k, Anton\x00EDn"; // Write out the new list of composers SetPropertyAsStringVector(spps, PKEY_Music_Composer, (PCWSTR *)rgpszComposers, cComposers); // Swap it back so we can free it rgpszComposers[ulPos] = pszOld; // Add a little graffiti just because SetPropertyAsString(spps, PKEY_Comment, L"Kilroy was here"); spps->Commit(); break; } } CoTaskMemFreeArray(rgpszComposers, cComposers); return 0; } Okay, what just happened here? First, we took the file whose name was passed on the command line (fully-qualified path, please) and obtained its property store.
Next, we queried the property store for the
Once we get the list of composers, we look for one that says
And then just to show that I know how to write out a string property too, I'll put some graffiti in the Comment field. Commit the changes and break the loop now that we found what we're looking for. (This assumes that the song was not a collaboration between Antonín Dvořák and himself!) So there you have it, a little program that modifies metadata. Obviously, this program is not particularly useful by itself, but it illustrates what you need to do to do something more useful in general. |
Comments (12)
Comments are closed. |
You can use the wonderful new feature of Visual C, and save your source file as Unicode, so you don't have to enter non-ASCII characters by their hex codes.
alegr1: and then it stops displaying properly on some terminals, doesn't round trip through some command-line programs and mail clients, and makes it awkward to reason about the memory contents. Better to pull the strings out into a separate file if you want to edit them that way.
is that a for-if loop I spy, Raymond? :o)
I know, little program, example purposes only — but once you explained that as an anti-pattern, it was like a switch being flipped in my head, and I wind up doing my best to make any for-ifs that I find more sensible.
hopefully that doesnt have the nasty side effect with txxx tags that has been in there since 2009…
The for-if antipattern looks like this:
for (i = some range of values) {
____ if (i == some specific value) { do something with i }
}
This can be replaced with the more performant
do something with (the specific value)
That is not the case in this example. What we have here is:
for (i = some range of values) {
____ if (array[i] == some specific value) { do something with i }
}
There is no way to replace this with a single statement. If we were guaranteed that the "composers" array were sorted we could do a binary search instead of a linear search, but no such guarantee exists.
I think he meant not seeing the general usefulness of it due to unsupported formats that are commonly used.
Then again he might have confused it with the metadata stored in alternative streams or with metadata locked in to individual user accounts. Dealing with COM, metadata can mean a lot of things. Like sending your boss that photoshopped picture of him that you made. Lovely bugs and office installed. I've run windows 7 for years I never knew it supported flac. I just tested it. I'm surprised. Must be something I installed, surely. It do still however not support mkv or ogg. As for me I did go nutsy, windows media player, ugh, unusable and slow! After that episode I installed an open source player.
// Swap in the new name
PWSTR pszOld = rgpszComposers[ulPos];
rgpszComposers[ulPos] = L"Dvox0159x00E1k, Antonx00EDn";
…
// Swap it back so we can free it
rgpszComposers[ulPos] = pszOld;
Swapping pointers to allocated memory make me nervous.
Swapping a pointer with static string when talking to com makes me worried
rgpszComposers[ulPos] = L"Dvox0159x00E1k, Antonx00EDn";
"but it illustrates what you need to do to do something more useful in general."
No, I'm not seeing it. An example would have been nice.
I only see problems: Since when can windows read flac and mkv metadata? Can it even read ogg?
Windows version of "metadata", *cough* time wasting lock-in *cough* is not compatible when you move the files to another computer eg Linux or even sometimes another windows machine.
@Jodi:
The formats are third party, so Windows doesn't support them out of the box.
There is, however, nothing stopping third party developers from providing the same level of integration to these formats that the likes of MP3 and WMA have.
For Direct Show, LAV Filters provides splitters and filters for a lot of out of box formats like flac/mkv/ogg and more. This doesn't add general support for the tags that these formats use though. As Raymond said, someone needs to write a property provider for these formats, and that is just something that people haven't done.
How appropriate that the Classical Music Library Free Download of the Week for this week (or week-like period) is Dvořák's Symphony No. 9.
> "but it illustrates what you need to do to do something more useful in general."
> No, I'm not seeing it. An example would have been nice.
As an example of a practical use of this technique, here's a tool I use to groom the audio metadata in my collection (mostly to set the album title on content which spans multiple CDs.)
blogs.msdn.com/…/shellproperty-exe-set-read-string-properties-on-a-file-from-the-command-line.aspx
ITT: People complaining that relatively obscure* formats are not supported out of the box by Windows. Oh the humanity! How will I ever properly tag my APE files now?
*In sheer numbers MP3 will be here today, here tomorrow, here forever. The wise-man however always archives with FLAC then transcodes to the flavor of the week, but how many people are that forward thinking?
The solution, as usual is to look the the wide world of third party utilities that are what makes the Windows Ecosystem the Goliath it has been for years in the desktop world. Personally I enjoy Mp3Tag.de which supports *some* of these obscure formats.
Did you actually roll dice, or did you use http://xkcd.com/221/ ?