{"id":3293,"date":"2013-09-09T07:00:00","date_gmt":"2013-09-09T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/09\/09\/programmatically-editing-the-metadata-of-an-audio-file\/"},"modified":"2013-09-09T07:00:00","modified_gmt":"2013-09-09T07:00:00","slug":"programmatically-editing-the-metadata-of-an-audio-file","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130909-00\/?p=3293","title":{"rendered":"Programmatically editing the metadata of an audio file"},"content":{"rendered":"<p>\nToday&#8217;s Little Program edits the metadata of an audio file,\nostensibly to correct a spelling error,\nbut really just to show how it&#8217;s done.\n<\/p>\n<p>\nToday&#8217;s smart pointer class library is&#8230; (rolls dice)&#8230; CComPtr!\n<\/p>\n<p>\nWe open with two helper functions which encapsulate the patterns\n<\/p>\n<ul>\n<li>Get property from property store\n<ol>\n<li>Call <code>IProperty&shy;Store::Get&shy;Value<\/code>\n<li>Convert <code>PROPVARIANT<\/code> into desired final type\n<li>Destroy the <code>PROPVARIANT<\/code>\n    <\/ol>\n<li>Set property in property store\n<ol>\n<li>Create a <code>PROPVARIANT<\/code>\n<li>Call <code>IProperty&shy;Store::Set&shy;Value<\/code>\n<li>Destroy the <code>PROPVARIANT<\/code>\n    <\/ol>\n<\/ul>\n<pre>\n#define STRICT\n#include &lt;windows.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;shlobj.h&gt;\n#include &lt;propkey.h&gt;\n#include &lt;propvarutil.h&gt;\n#include &lt;atlbase.h&gt;\n#include &lt;atlalloc.h&gt;\ntemplate&lt;typename TLambda&gt;\nHRESULT GetPropertyAsLambda(IPropertyStore *pps, REFPROPERTYKEY key,\n                             TLambda lambda)\n{\n  PROPVARIANT pvar;\n  HRESULT hr = pps-&gt;GetValue(key, &amp;pvar);\n  if (SUCCEEDED(hr)) {\n    hr = lambda(pvar);\n    PropVariantClear(&amp;pvar);\n  }\n  return hr;\n}\ntemplate&lt;typename TLambda&gt;\nHRESULT SetPropertyAsLambda(IPropertyStore *pps, REFPROPERTYKEY key,\n                            TLambda lambda)\n{\n  PROPVARIANT pvar;\n  HRESULT hr = lambda(&amp;pvar);\n  if (SUCCEEDED(hr)) {\n    hr = pps-&gt;SetValue(key, pvar);\n    PropVariantClear(&amp;pvar);\n  }\n  return hr;\n}\n<\/pre>\n<p>\nBoth functions use a lambda to do the type-specific work.\n<\/p>\n<p>\nHere are some functions that will use the helpers:\n<\/p>\n<pre>\nHRESULT GetPropertyAsString(\n    IPropertyStore *pps, REFPROPERTYKEY key, PWSTR *ppszValue)\n{\n  *ppszValue = nullptr;\n  return GetPropertyAsLambda(pps, key, [=](REFPROPVARIANT pvar) {\n    return PropVariantToStringAlloc(pvar, ppszValue);\n  });\n}\nHRESULT SetPropertyAsString(\n    IPropertyStore *pps, REFPROPERTYKEY key, PCWSTR pszValue)\n{\n  return SetPropertyAsLambda(pps, key, [=](PROPVARIANT *ppvar) {\n    return InitPropVariantFromString(pszValue, ppvar);\n  });\n}\nHRESULT GetPropertyAsStringVector(\n    IPropertyStore *pps, REFPROPERTYKEY key,\n    PWSTR **pprgsz, ULONG *pcElem)\n{\n  *pprgsz = nullptr;\n  *pcElem = 0;\n  return GetPropertyAsLambda(pps, key, [=](REFPROPVARIANT pvar) {\n    return PropVariantToStringVectorAlloc(pvar, pprgsz, pcElem);\n  });\n}\nHRESULT SetPropertyAsStringVector(\n    IPropertyStore *pps, REFPROPERTYKEY key,\n    PCWSTR *prgsz, ULONG cElems)\n{\n  return SetPropertyAsLambda(pps, key, [=](PROPVARIANT *ppvar) {\n    return InitPropVariantFromStringVector(prgsz, cElems, ppvar);\n  });\n}\n<\/pre>\n<p>\nThe <code>Prop&shy;Variant&shy;To&shy;String&shy;Vector&shy;Alloc<\/code>\nfunction returns an array of pointers to memory allocated via\n<code>Co&shy;Task&shy;Mem&shy;Alloc<\/code>,\nand the array itself was also allocated by the same function.\nHere&#8217;s a helper function to free the memory and the array:\n<\/p>\n<pre>\ntemplate&lt;typename T&gt;\nvoid CoTaskMemFreeArray(T **prgElem, ULONG cElem)\n{\n    for (ULONG i = 0; i &lt; cElem; i++) {\n        CoTaskMemFree(prgElem[i]);\n    }\n    CoTaskMemFree(prgElem);\n}\n<\/pre>\n<p>\nOkay, we&#8217;re ready to write our main program.\nRemember, Little Programs do little to no error checking.\nIn a real program, you would check that your function calls succeeded.\n<\/p>\n<pre>\nint __cdecl wmain(int argc, wchar_t **argv)\n{\n  <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/05\/20\/135841.aspx\">CCoInitialize<\/a> init;\n  CComPtr&lt;IPropertyStore&gt; spps;\n  SHGetPropertyStoreFromParsingName(argv[1], nullptr,\n    GPS_READWRITE, IID_PPV_ARGS(&amp;spps));\n  \/\/ Get the existing composers\n  PWSTR *rgpszComposers;\n  ULONG cComposers;\n  GetPropertyAsStringVector(spps, <a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/bb787298(v=vs.85).aspx\">PKEY_Music_Composer<\/a>,\n    &amp;rgpszComposers, &amp;cComposers);\n  \/\/ Look for \"Dvorak, Antonin\" and add diacritics\n  for (ULONG ulPos = 0; ulPos &lt; cComposers; ulPos++) {\n    if (wcscmp(rgpszComposers[ulPos], L\"Dvorak, Antonin\") == 0) {\n      \/\/ Swap in the new name\n      PWSTR pszOld = rgpszComposers[ulPos];\n      rgpszComposers[ulPos] = L\"Dvo\\x0159\\x00E1k, Anton\\x00EDn\";\n      \/\/ Write out the new list of composers\n      SetPropertyAsStringVector(spps, PKEY_Music_Composer, (PCWSTR *)rgpszComposers, cComposers);\n      \/\/ Swap it back so we can free it\n      rgpszComposers[ulPos] = pszOld;\n      \/\/ Add a little graffiti just because\n      SetPropertyAsString(spps, PKEY_Comment, L\"Kilroy was here\");\n      spps-&gt;Commit();\n      break;\n    }\n  }\n  CoTaskMemFreeArray(rgpszComposers, cComposers);\n  return 0;\n}\n<\/pre>\n<p>\nOkay, what just happened here?\n<\/p>\n<p>\nFirst, we took the file whose name was passed on the command\nline (fully-qualified path, please)\nand obtained its property store.\n<\/p>\n<p>\nNext, we queried the property store for the\n<code>System.&shy;Music.&shy;Composer<\/code> property.\nThis property is typed as a multiple-valued string,\nso we read and write the value in the form of a string vector.\nYou could also read and write it as a single string:\nThe\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/bb776560(v=vs.85).aspx\">\n<code>Prop&shy;Variant&shy;To&shy;String&shy;Alloc<\/code><\/a>\nfunction represents string arrays by joining the strings together,\nseparating them with <code>\"; \"<\/code> (semicolon and space).\nHowever, we access it as an array because that makes it easier\nto insert and remove individual entries.\n<\/p>\n<p>\nOnce we get the list of composers, we look for one that says\n<code>\"Dvorak, Antonin\"<\/code>.\nIf we find it, then we change that entry to\n<code>\"Dvo&#x159;&#xe1;k, Anton&#xed;n\"<\/code>\nand write out the new vector.\n<\/p>\n<p>\nAnd then just to show that I know how to write out a string\nproperty too,\nI&#8217;ll put some graffiti in the Comment field.\n<\/p>\n<p>\nCommit the changes and break the loop now that we found what\nwe&#8217;re looking for.\n(This assumes that the song was not a collaboration between\nAnton&#xed;n Dvo&#x159;&#xe1;k and himself!)\n<\/p>\n<p>\nSo there you have it,\na little program that modifies metadata.\nObviously, this program is not particularly useful by itself,\nbut it illustrates what you need to do to do something more\nuseful in general.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today&#8217;s Little Program edits the metadata of an audio file, ostensibly to correct a spelling error, but really just to show how it&#8217;s done. Today&#8217;s smart pointer class library is&#8230; (rolls dice)&#8230; CComPtr! We open with two helper functions which encapsulate the patterns Get property from property store Call IProperty&shy;Store::Get&shy;Value Convert PROPVARIANT into desired final [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[26],"class_list":["post-3293","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-other"],"acf":[],"blog_post_summary":"<p>Today&#8217;s Little Program edits the metadata of an audio file, ostensibly to correct a spelling error, but really just to show how it&#8217;s done. Today&#8217;s smart pointer class library is&#8230; (rolls dice)&#8230; CComPtr! We open with two helper functions which encapsulate the patterns Get property from property store Call IProperty&shy;Store::Get&shy;Value Convert PROPVARIANT into desired final [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/3293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=3293"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/3293\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=3293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=3293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=3293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}