{"id":66844,"date":"2022-08-30T09:00:49","date_gmt":"2022-08-30T09:00:49","guid":{"rendered":"https:\/\/www.cryptocabaret.com\/?p=66844"},"modified":"2022-08-30T09:00:49","modified_gmt":"2022-08-30T09:00:49","slug":"clean-up-music-tags-with-a-groovy-script","status":"publish","type":"post","link":"https:\/\/www.cryptocabaret.com\/?p=66844","title":{"rendered":"Clean up music tags with a Groovy script"},"content":{"rendered":"<p><span class=\"field field--name-title field--type-string field--label-hidden\">Clean up music tags with a Groovy script<\/span><br \/>\n<span class=\"field field--name-uid field--type-entity-reference field--label-hidden\"><a title=\"View user profile.\" href=\"https:\/\/opensource.com\/users\/clhermansen\" class=\"username\">Chris Hermansen<\/a><\/span><br \/>\n<span class=\"field field--name-created field--type-created field--label-hidden\">Tue, 08\/30\/2022 &#8211; 03:00<\/span><\/p>\n<div data-drupal-selector=\"rate-node-70175\" class=\"rate-widget-thumbs-up\" title=\"Register or Login to like.\">\n<div class=\"rate-thumbs-up-btn-up vote-pending\">1 reader likes this<\/div>\n<div class=\"rate-score\">1 reader likes this<\/div>\n<\/div>\n<div class=\"clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item\">\n<p>Lately, I&#8217;ve been looking at how Groovy streamlines Java. In this series, I&#8217;m developing several scripts to help in cleaning up my music collection. In my last article, I used the framework developed previously to create a list of unique file names and counts of occurrences of those file names in the music collection directory. I then used the Linux <code>find<\/code> command to get rid of files I didn&#8217;t want.<\/p>\n<p>In this article, I demonstrate a Groovy script to clean up the motley assembly of tag fields.<\/p>\n<p>WARNING: This script alters music tags, so it is vital that you make a backup of the music collection you test your code on.<\/p>\n<h2>Back to the problem<\/h2>\n<p>If you haven&#8217;t read the previous articles is this series, do that now before continuing so you understand the intended structure of the music directory, the framework I&#8217;ve created, and how to detect and use FLAC, MP3, and OGG files.<\/p>\n<ul>\n<li><a href=\"https:\/\/opensource.com\/article\/22\/8\/groovy-script-java-music\" target=\"_blank\" rel=\"noopener\">How I analyze my music directory with Groovy<\/a><\/li>\n<li><a href=\"https:\/\/opensource.com\/article\/22\/8\/analyze-music-files-jaudiotagger\" target=\"_blank\" rel=\"noopener\">My favorite open source library for analyzing music files<\/a><\/li>\n<li><a href=\"https:\/\/opensource.com\/article\/22\/8\/groovy-scripting-analyzing-music-directory-part-3\" target=\"_blank\" rel=\"noopener\">How I use Groovy to analyze album art in my music directory<\/a><\/li>\n<li><a href=\"https:\/\/opensource.com\/article\/22\/8\/remove-files-music-directory-groovy\" target=\"_blank\" rel=\"noopener\">Clean up unwanted files in your music directory using Groovy<\/a><\/li>\n<\/ul>\n<h2>Vorbis and ID3 tags<\/h2>\n<p>I don&#8217;t have many MP3 music files. Generally, I prefer to use FLAC. But sometimes only MP3 versions are available, or a free MP3 download comes with a vinyl purchase. So in this script, I have to be able to handle both. One thing I&#8217;ve learned as I have become familiar with <a href=\"http:\/\/jthink.net\/jaudiotagger\/index.jsp\" target=\"_blank\" rel=\"noopener\">JAudiotagger<\/a> is what ID3 tags (used by MP3) look like, and I discovered that some of those &#8220;unwanted&#8221; field tag IDs I uncovered in part 2 of this series are actually very useful.<\/p>\n<p>Now it&#8217;s time to use this framework to get a list of all the tag field IDs in a music collection, with their counts, to begin deciding what belongs and what doesn&#8217;t:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\"><span class=\"nu0\">1<\/span> \u00a0 \u00a0 \u00a0 \u00a0@Grab<span class=\"br0\">(<\/span><span class=\"st0\">'net.jthink:jaudiotagger:3.0.1'<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">2<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.audio.*<\/span><br><span class=\"nu0\">3<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.tag.*<\/span><br><span class=\"nu0\">4<\/span> \u00a0 \u00a0 \u00a0 \u00a0def logger <span class=\"sy0\">=<\/span> java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Logger<\/span>.<span class=\"me1\">getLogger<\/span><span class=\"br0\">(<\/span><span class=\"st0\">'org.jaudiotagger'<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><br><span class=\"nu0\">5<\/span> \u00a0 \u00a0 \u00a0 \u00a0logger.<span class=\"me1\">setLevel<\/span><span class=\"br0\">(<\/span>java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Level<\/span>.<span class=\"me1\">OFF<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><br><span class=\"nu0\">6<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Define the music library directory<\/span><br><span class=\"nu0\">7<\/span> \u00a0 \u00a0 \u00a0 \u00a0def musicLibraryDirName <span class=\"sy0\">=<\/span> <span class=\"st0\">'\/var\/lib\/mpd\/music'<\/span><br><span class=\"nu0\">8<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Define the tag field id accumulation map<\/span><br><span class=\"nu0\">9<\/span> \u00a0 \u00a0 \u00a0 \u00a0def tagFieldIdCounts <span class=\"sy0\">=<\/span> <span class=\"br0\">[<\/span><span class=\"sy0\">:<\/span><span class=\"br0\">]<\/span><br><span class=\"nu0\">10<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Print the CSV file header<\/span><br><span class=\"nu0\">11<\/span> \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"tagFieldId|count\"<\/span><br><span class=\"nu0\">12<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the music libary directory<\/span><br><span class=\"nu0\">13<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be artist directories<\/span><br><span class=\"nu0\">14<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">new<\/span> <a href=\"http:\/\/www.google.com\/search?hl=en&amp;q=allinurl%3Adocs.oracle.com+javase+docs+api+file\"><span class=\"kw3\">File<\/span><\/a><span class=\"br0\">(<\/span>musicLibraryDirName<span class=\"br0\">)<\/span>.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> artistDir <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">15<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the artist directory<\/span><br><span class=\"nu0\">16<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be album directories<\/span><br><span class=\"nu0\">17<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0artistDir.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> albumDir <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">18<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each file in the album directory<\/span><br><span class=\"nu0\">19<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be content or related<\/span><br><span class=\"nu0\">20<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ (cover.jpg, PDFs with liner notes etc)<\/span><br><span class=\"nu0\">21<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0albumDir.<span class=\"me1\">eachFile<\/span> <span class=\"br0\">{<\/span> contentFile <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">22<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Analyze the file and print the analysis<\/span><br><span class=\"nu0\">23<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>contentFile.<span class=\"me1\">name<\/span> <span class=\"sy0\">==<\/span>~ <span class=\"sy0\">\/<\/span>.<span class=\"sy0\">*<\/span>.<span class=\"br0\">(<\/span>flac<span class=\"sy0\">|<\/span>mp3<span class=\"sy0\">|<\/span>ogg<span class=\"br0\">)<\/span><span class=\"sy0\">\/<\/span><span class=\"br0\">)<\/span> <span class=\"br0\">{<\/span><br><span class=\"nu0\">24<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def af <span class=\"sy0\">=<\/span> AudioFileIO.<span class=\"me1\">read<\/span><span class=\"br0\">(<\/span>contentFile<span class=\"br0\">)<\/span><br><span class=\"nu0\">25<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0af.<span class=\"me1\">tag<\/span>.<span class=\"me1\">fields<\/span>.<span class=\"me1\">each<\/span> <span class=\"br0\">{<\/span> tagField <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">26<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0tagFieldIdCounts<span class=\"br0\">[<\/span>tagField.<span class=\"me1\">id<\/span><span class=\"br0\">]<\/span> <span class=\"sy0\">=<\/span> tagFieldIdCounts.<span class=\"me1\">containsKey<\/span><span class=\"br0\">(<\/span>tagField.<span class=\"me1\">id<\/span><span class=\"br0\">)<\/span> <span class=\"sy0\">?<\/span> tagFieldIdCounts<span class=\"br0\">[<\/span>tagField.<span class=\"me1\">id<\/span><span class=\"br0\">]<\/span> <span class=\"sy0\">+<\/span> <span class=\"nu0\">1<\/span> <span class=\"sy0\">:<\/span> <span class=\"nu0\">1<\/span><br><span class=\"nu0\">27<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">28<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">29<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">30<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">31<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">32<\/span> \u00a0 \u00a0 \u00a0 \u00a0tagFieldIdCounts.<span class=\"me1\">each<\/span> <span class=\"br0\">{<\/span> key, value <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">33<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"$key|$value\"<\/span><br><span class=\"nu0\">34<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><\/div><\/div><\/pre>\n<p>Lines 1-7 originally appeared in part 2 of this series.<\/p>\n<p>Lines 8-9 define a map for accumulating tag field IDs and counts of occurrences.<\/p>\n<p>Lines 10-21 also appeared in previous articles. They get down to the level of the individual content files.<\/p>\n<p>Lines 23-28 ensures that the files being used are FLAC, MP3, or OGG. Line 23 uses a Groovy match operator <code>==~<\/code> with a slashy regular expression to filter out wanted files.<\/p>\n<p>Line 24 uses <code>org.jaudiotagger.audio.AudioFileIO.read()<\/code> to get the tag body from the content file.<\/p>\n<p>Lines 25-27 use <code>org.jaudiotagger.tag.Tag.getFields()<\/code> to get all the <code>TagField<\/code> instances in the tag body and the Groovy <code>each()<\/code> method to iterate over that list of instances.<\/p>\n<p>Line 27 accumulates the count of each <code>tagField.id<\/code> into the <code>tagFieldIdCounts<\/code> map.<\/p>\n<p>Finally, lines 32-24 iterate over the <code>tagFieldIdCounts<\/code> map printing out the keys (the tag field IDs found) and the values (the count of occurrences of each tag field ID).<\/p>\n<p>I run this script as follows:<\/p>\n<pre>\n<span class=\"geshifilter\"><code class=\"bash geshifilter-bash\"><span class=\"co4\">$ <\/span>groovy TagAnalyzer5b.groovy <span class=\"sy0\">&gt;<\/span> tagAnalysis5b.csv<\/code><\/span><\/pre>\n<p>Then I load the results into a <a href=\"https:\/\/opensource.com\/article\/21\/9\/libreoffice-tips\" target=\"_blank\" rel=\"noopener\">LibreOffice<\/a> or <a href=\"https:\/\/opensource.com\/article\/20\/12\/onlyoffice-docs\" target=\"_blank\" rel=\"noopener\">OnlyOffice<\/a> spreadsheet. In my case, this script takes quite a long time to run (several minutes) and the loaded data, sorted in descending order of the second column (count) looks like this:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/08\/creenshot2520of2520first2520few2520rows2520of2520tagAnalysis5b.csv2520in2520LibreOffice2520Calc.png\" width=\"392\" height=\"699\" alt=\"Image of a screenshot of the first few row of tagAnalysis in LibreOffic Calc\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(Chris Hermansen, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>On row 2, you can see that there are 8,696 occurrences of the TITLE field tag ID, which is the ID that FLAC files (and Vorbis, generally) uses for a song title. Down on row 28, you also see 348 occurrences of the TIT2 field tag ID, which is the ID3 tag field that contains the &#8220;actual&#8221; name of the song. At this point, it&#8217;s worth going away to look at <a href=\"http:\/\/www.jthink.net\/jaudiotagger\/javadoc\/index.html\" target=\"_blank\" rel=\"noopener\">the JavaDoc<\/a> for <code>org.jaudiotagger.tag.ide.framebody.FrameBodyTIT2<\/code> to learn more about this tag and the way in which JAudiotagger recognizes it. There, you also see the mechanisms to handle other ID3 tag fields.<\/p>\n<p>In that list of field tag IDs, there are lots that I&#8217;m not interested in and that could affect the ability of various music players to display my music collection in what I consider to be a reasonable order.<\/p>\n<\/p>\n<div class=\"embedded-resource-list callout-float-right\">\n<div class=\"field field--name-title field--type-string field--label-hidden field__item\">More on Java<\/div>\n<div class=\"field field--name-links field--type-link field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/developers.redhat.com\/topics\/enterprise-java\/?intcmp=7013a000002Cxq6AAC\">What is enterprise Java programming?<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/developers.redhat.com\/products\/openjdk\/overview?intcmp=7013a000002Cxq6AAC\">Red Hat build of OpenJDK<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/downloads\/java-cheat-sheet?intcmp=7013a000002Cxq6AAC\">Java cheat sheet<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/services\/training\/do092-developing-cloud-native-applications-microservices-architectures?intcmp=7013a000002Cxq6AAC\">Free online course: Developing cloud-native applications with microservices<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/java?intcmp=7013a000002Cxq6AAC\">Fresh Java articles<\/a><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h2>The org.jaudiotagger.tag.Tag interface<\/h2>\n<p>I&#8217;m going to take a moment to explore the way JAudiotagger provides a generic mechanism to access tag fields. This mechanism is described in <a href=\"http:\/\/www.jthink.net\/jaudiotagger\/javadoc\/index.html\" target=\"_blank\" rel=\"noopener\">the JavaDocs<\/a> for <code>org.jaudiotagger.tag.Tag<\/code>. There are two methods that would help clean up the tag field situation:<\/p>\n<pre>\n<span class=\"geshifilter\"><code class=\"java geshifilter-java\"><span class=\"kw4\">void<\/span> setField<span class=\"br0\">(<\/span>FieldKey genericKey,<a href=\"http:\/\/www.google.com\/search?hl=en&amp;q=allinurl%3Adocs.oracle.com+javase+docs+api+string\"><span class=\"kw3\">String<\/span><\/a> value<span class=\"br0\">)<\/span><\/code><\/span><\/pre>\n<p>This is used to set the value for a particular tag field.<\/p>\n<p>This line is used to delete all instances of a particular tag field (turns out some tag fields in some tagging schemes permit multiple occurrences).<\/p>\n<pre>\n<span class=\"geshifilter\"><code class=\"java geshifilter-java\"><span class=\"kw4\">void<\/span> deleteField<span class=\"br0\">(<\/span>FieldKey fieldKey<span class=\"br0\">)<\/span><\/code><\/span><\/pre>\n<p>However, this particular <code>deleteField()<\/code> method requires us to supply a <code>FieldKey<\/code> value, and as I have discovered, not all field key IDs in my music collection correspond to a known <code>FieldKey<\/code> value.<\/p>\n<p>Looking around the JavaDocs, I see there&#8217;s a <code>FlacTag<\/code> which &#8220;uses Vorbis Comment for most of its metadata,&#8221; and declares its tag field to be of type <code>VorbisCommentTag<\/code>.<\/p>\n<p><code>VorbisCommentTag<\/code> itself extends <code>org.jaudiotagger.audio.generic.AbstractTag<\/code>, which offers:<\/p>\n<pre>\n<span class=\"geshifilter\"><code class=\"java geshifilter-java\"><span class=\"kw1\">protected<\/span> <span class=\"kw4\">void<\/span> deleteField<span class=\"br0\">(<\/span><a href=\"http:\/\/www.google.com\/search?hl=en&amp;q=allinurl%3Adocs.oracle.com+javase+docs+api+string\"><span class=\"kw3\">String<\/span><\/a> key<span class=\"br0\">)<\/span><\/code><\/span><\/pre>\n<p>As it turns out, this is accessible from the tag instance returned by <code>AudioFileIO.read(f).getTag()<\/code>, at least for FLAC and MP3 tag bodies.<\/p>\n<p>In theory, it should be possible to do this:<\/p>\n<ol>\n<li>\n<p>Get the tag body using<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">def af <span class=\"sy0\">=<\/span> AudioFileIO.<span class=\"me1\">read<\/span><span class=\"br0\">(<\/span>contentFile<span class=\"br0\">)<\/span><br>\ndef tagBody <span class=\"sy0\">=<\/span> af.<span class=\"me1\">tag<\/span><\/div><\/div><\/pre>\n<\/li>\n<li>\n<p>Get the values of the (known) tag fields I want using:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">def album <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM<\/span><span class=\"br0\">)<\/span><br>\ndef artist <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST<\/span><span class=\"br0\">)<\/span><br><span class=\"co1\">\/\/ etc<\/span><\/div><\/div><\/pre>\n<\/li>\n<li>\n<p>Delete all tag fields (both wanted and unwanted) using:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">def originalTagFieldIdList <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">fields<\/span>.<span class=\"me1\">collect<\/span> <span class=\"br0\">{<\/span> tagField <span class=\"sy0\">-&gt;<\/span><br>\ntagField.<span class=\"me1\">id<\/span><br><span class=\"br0\">}<\/span><br>\noriginalTagFieldIdList.<span class=\"me1\">each<\/span> <span class=\"br0\">{<\/span> tagFieldId <span class=\"sy0\">-&gt;<\/span><br>\ntagBody.<span class=\"me1\">deleteField<\/span><span class=\"br0\">(<\/span>tagFieldId<span class=\"br0\">)<\/span><br><span class=\"br0\">}<\/span><\/div><\/div><\/pre>\n<\/li>\n<li>\n<p>Put only the desired tag fields back:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM<\/span>, album<span class=\"br0\">)<\/span><br>\ntagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST<\/span>, artist<span class=\"br0\">)<\/span><br><span class=\"co1\">\/\/ etc<\/span><\/div><\/div><\/pre>\n<\/li>\n<\/ol>\n<p>Of course there are few wrinkles here.<\/p>\n<p>First, notice the use of the <code>originalTagFieldIdList<\/code>. I can&#8217;t use <code>each()<\/code> to iterate over the iterator returned by <code>tagBody.getFields()<\/code> at the same time I modify those fields; so I get the tag field IDs into a list using <code>collect()<\/code>, then iterate over that list of tag field IDs to do the deletions.<\/p>\n<p>Second, not all files are going to have all the tag fields I want. For example, some files might not have <code>ALBUM_SORT_ORDER<\/code> defined, and so on. I might not wish to write those tag fields in with empty values. Additionally, I can probably safely default some fields. For example, if <code>ALBUM_ARTIST<\/code> isn&#8217;t defined, I can set it to ARTIST.<\/p>\n<p>Third, and for me most obscure, is that Vorbis Comment tags always include a VENDOR field tag ID; if I try to delete it, I end up simply unsetting the value. Huh.<\/p>\n<h2>Trying it all out<\/h2>\n<p>Considering these lessons, I decided to create a test music directory that contains just a few artists and their albums (because I don&#8217;t want to wipe out my music collection.)<\/p>\n<p>WARNING: Because this script will alter music tags it is very important to have a backup of the music collection so that when I discover I have deleted an essential tag, I can recover the backup, modify the script and rerun it.<\/p>\n<p>Here&#8217;s the script:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\"><span class=\"nu0\">1<\/span> \u00a0 \u00a0 \u00a0 \u00a0@Grab<span class=\"br0\">(<\/span><span class=\"st0\">'net.jthink:jaudiotagger:3.0.1'<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">2<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.audio.*<\/span><br><span class=\"nu0\">3<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.tag.*<\/span><br><span class=\"nu0\">4<\/span> \u00a0 \u00a0 \u00a0 \u00a0def logger <span class=\"sy0\">=<\/span> java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Logger<\/span>.<span class=\"me1\">getLogger<\/span><span class=\"br0\">(<\/span><span class=\"st0\">'org.jaudiotagger'<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><span class=\"nu0\">5<\/span> \u00a0 \u00a0 \u00a0 \u00a0logger.<span class=\"me1\">setLevel<\/span><span class=\"br0\">(<\/span>java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Level<\/span>.<span class=\"me1\">OFF<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><br><span class=\"nu0\">6<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Define the music library directory<\/span><br><span class=\"nu0\">7<\/span> \u00a0 \u00a0 \u00a0 \u00a0def musicLibraryDirName <span class=\"sy0\">=<\/span> <span class=\"st0\">'\/work\/Test\/Music'<\/span><br><span class=\"nu0\">8<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Print the CSV file header<\/span><br><span class=\"nu0\">9<\/span> \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"artistDir|albumDir|contentFile|tagField.id|tagField.toString()\"<\/span><br><span class=\"nu0\">10<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the music libary directory<\/span><br><span class=\"nu0\">11<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be artist directories<\/span><br><span class=\"nu0\">12<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">new<\/span> <a href=\"http:\/\/www.google.com\/search?hl=en&amp;q=allinurl%3Adocs.oracle.com+javase+docs+api+file\"><span class=\"kw3\">File<\/span><\/a><span class=\"br0\">(<\/span>musicLibraryDirName<span class=\"br0\">)<\/span>.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> artistDir <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">13<\/span> \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the artist directory<\/span><br><span class=\"nu0\">14<\/span> \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed o be album directories<\/span><br><span class=\"nu0\">15<\/span> \u00a0 \u00a0artistDir.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> albumDir <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">16<\/span> \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each file in the album directory<\/span><br><span class=\"nu0\">17<\/span> \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be content or related18 \u00a0 \u00a0\/\/ (cover.jpg, PDFs with liner notes etc)<\/span><br><span class=\"nu0\">19<\/span> \u00a0 \u00a0albumDir.<span class=\"me1\">eachFile<\/span> <span class=\"br0\">{<\/span> contentFile <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">20<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Analyze the file and print the analysis<\/span><br><span class=\"nu0\">21<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>contentFile.<span class=\"me1\">name<\/span> <span class=\"sy0\">==<\/span>~ <span class=\"sy0\">\/<\/span>.<span class=\"sy0\">*<\/span>.<span class=\"br0\">(<\/span>flac<span class=\"sy0\">|<\/span>mp3<span class=\"sy0\">|<\/span>ogg<span class=\"br0\">)<\/span><span class=\"sy0\">\/<\/span><span class=\"br0\">)<\/span> <span class=\"br0\">{<\/span><br><span class=\"nu0\">22<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def af <span class=\"sy0\">=<\/span> AudioFileIO.<span class=\"me1\">read<\/span><span class=\"br0\">(<\/span>contentFile<span class=\"br0\">)<\/span><br><span class=\"nu0\">23<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def tagBody <span class=\"sy0\">=<\/span> af.<span class=\"me1\">tag<\/span><br><span class=\"nu0\">24<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def album <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">25<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def albumArtist <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM_ARTIST<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">26<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def albumArtistSort <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM_ARTIST_SORT<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">27<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def artist <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">28<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def artistSort <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST_SORT<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">29<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def composer <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">COMPOSER<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">30<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def composerSort <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">COMPOSER_SORT<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">31<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def genre <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">GENRE<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">32<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def title <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TITLE<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">33<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def titleSort <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TITLE_SORT<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">34<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def track <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TRACK<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">35<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def trackTotal <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TRACK_TOTAL<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">36<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def year <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">getFirst<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">YEAR<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">37<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span><span class=\"sy0\">!<\/span>albumArtist<span class=\"br0\">)<\/span> albumArtist <span class=\"sy0\">=<\/span> artist<br><span class=\"nu0\">38<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span><span class=\"sy0\">!<\/span>albumArtistSort<span class=\"br0\">)<\/span> albumArtistSort <span class=\"sy0\">=<\/span> albumArtist<br><span class=\"nu0\">39<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span><span class=\"sy0\">!<\/span>artistSort<span class=\"br0\">)<\/span> artistSort <span class=\"sy0\">=<\/span> artist<br><span class=\"nu0\">40<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span><span class=\"sy0\">!<\/span>composerSort<span class=\"br0\">)<\/span> composerSort <span class=\"sy0\">=<\/span> composer<br><span class=\"nu0\">41<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span><span class=\"sy0\">!<\/span>titleSort<span class=\"br0\">)<\/span> titleSort <span class=\"sy0\">=<\/span> title<br><span class=\"nu0\">42<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.ALBUM|${album}\"<\/span><br><span class=\"nu0\">43<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.ALBUM_ARTIST|${albumArtist}\"<\/span><br><span class=\"nu0\">44<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.ALBUM_ARTIST_SORT|${albumArtistSort}\"<\/span><br><span class=\"nu0\">45<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.ARTIST|${artist}\"<\/span><br><span class=\"nu0\">46<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.ARTIST_SORT|${artistSort}\"<\/span><br><span class=\"nu0\">47<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.COMPOSER|${composer}\"<\/span><br><span class=\"nu0\">48<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}<br>\n|FieldKey.COMPOSER_SORT|${composerSort}\"<\/span><br><span class=\"nu0\">49<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.GENRE|${genre}\"<\/span><br><span class=\"nu0\">50<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.TITLE|${title}\"<\/span><br><span class=\"nu0\">51<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.TITLE_SORT|${titleSort}\"<\/span><br><span class=\"nu0\">52<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.TRACK|${track}\"<\/span><br><span class=\"nu0\">53<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.TRACK_TOTAL|${trackTotal}\"<\/span><br><span class=\"nu0\">54<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|FieldKey.YEAR|${year}\"<\/span><br><span class=\"nu0\">55<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def originalTagIdList <span class=\"sy0\">=<\/span> tagBody.<span class=\"me1\">fields<\/span>.<span class=\"me1\">collect<\/span> <span class=\"br0\">{<\/span><br><span class=\"nu0\">56<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0tagField <span class=\"sy0\">-&gt;<\/span> tagField.<span class=\"me1\">id<\/span><br><span class=\"nu0\">57<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">58<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0originalTagIdList.<span class=\"me1\">each<\/span> <span class=\"br0\">{<\/span> tagFieldId <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">59<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${contentFile.name}|${tagFieldId}|XXX\"<\/span><br><span class=\"nu0\">60<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>tagFieldId <span class=\"sy0\">!=<\/span> <span class=\"st0\">'VENDOR'<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">61<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0tagBody.<span class=\"me1\">deleteField<\/span><span class=\"br0\">(<\/span>tagFieldId<span class=\"br0\">)<\/span><br><span class=\"nu0\">62<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">63<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>album<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM<\/span>, album<span class=\"br0\">)<\/span><br><span class=\"nu0\">64<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>albumArtist<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM_ARTIST<\/span>, albumArtist<span class=\"br0\">)<\/span><br><span class=\"nu0\">65<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>albumArtistSort<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ALBUM_ARTIST_SORT<\/span>, albumArtistSort<span class=\"br0\">)<\/span><br><span class=\"nu0\">66<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>artist<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST<\/span>, artist<span class=\"br0\">)<\/span><br><span class=\"nu0\">67<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>artistSort<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">ARTIST_SORT<\/span>, artistSort<span class=\"br0\">)<\/span><br><span class=\"nu0\">68<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>composer<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">COMPOSER<\/span>, composer<span class=\"br0\">)<\/span><br><span class=\"nu0\">69<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>composerSort<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">COMPOSER_SORT<\/span>, composerSort<span class=\"br0\">)<\/span><br><span class=\"nu0\">70<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>genre<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">GENRE<\/span>, genre<span class=\"br0\">)<\/span><br><span class=\"nu0\">71<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>title<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TITLE<\/span>, title<span class=\"br0\">)<\/span><br><span class=\"nu0\">72<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>titleSort<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TITLE_SORT<\/span>, titleSort<span class=\"br0\">)<\/span><br><span class=\"nu0\">73<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>track<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TRACK<\/span>, track<span class=\"br0\">)<\/span><br><span class=\"nu0\">74<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>trackTotal<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">TRACK_TOTAL<\/span>, trackTotal<span class=\"br0\">)<\/span><br><span class=\"nu0\">75<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>year<span class=\"br0\">)<\/span> tagBody.<span class=\"me1\">setField<\/span><span class=\"br0\">(<\/span>FieldKey.<span class=\"me1\">YEAR<\/span>, year<span class=\"br0\">)<\/span><br><span class=\"nu0\">76<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0af.<span class=\"me1\">commit<\/span><span class=\"br0\">(<\/span><span class=\"br0\">)<\/span><span class=\"nu0\">77<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">78<\/span> \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">79<\/span> \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">80<\/span> \u00a0<span class=\"br0\">}<\/span><\/div><\/div><\/pre>\n<p>Lines 1-21 are already familiar. Note that my music directory defined in line 7 refers to a test directory though!<\/p>\n<p>Lines 22-23 get the tag body.<\/p>\n<p>Lines 24-36 get the fields of interest to me (but maybe not the fields of interest to you, so feel free to adjust for your own requirements!)<\/p>\n<p>Lines 37-41 adjust some values for missing ALBUM_ARTIST and sort order.<\/p>\n<p>Lines 42-54 print out each tag field key and adjusted value for posterity.<\/p>\n<p>Lines 55-57 get the list of all tag field IDs.<\/p>\n<p>Lines 58-62 prints out each tag field id <em>and deletes it<\/em>, with the exception of the VENDOR tag field ID.<\/p>\n<p>Lines 63-75 set the desired tag field values using the known tag field keys.<\/p>\n<p>Finally, line 76 <em>commits the changes to the file<\/em>.<\/p>\n<p>The script produces output that can be imported into a spreadsheet.<\/p>\n<p>I&#8217;m just going to mention one more time that this script alters music tags! It is very important to have a backup of the music collection so that when you discover you&#8217;ve deleted an essential tag, or somehow otherwise trashed your music files, you can recover the backup, modify the script, and rerun it.<\/p>\n<h2>Check the results with this Groovy script<\/h2>\n<p>I have a handy little Groovy script to check the results:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\"><span class=\"nu0\">1<\/span> \u00a0 \u00a0 \u00a0 \u00a0@Grab<span class=\"br0\">(<\/span><span class=\"st0\">'net.jthink:jaudiotagger:3.0.1'<\/span><span class=\"br0\">)<\/span><br><span class=\"nu0\">2<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.audio.*<\/span><br><span class=\"nu0\">3<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">import<\/span> <span class=\"co2\">org.jaudiotagger.tag.*<\/span><br>\n\u00a0 <br><span class=\"nu0\">4<\/span> \u00a0 \u00a0 \u00a0 \u00a0def logger <span class=\"sy0\">=<\/span> java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Logger<\/span>.<span class=\"me1\">getLogger<\/span><span class=\"br0\">(<\/span><span class=\"st0\">'org.jaudiotagger'<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><br><span class=\"nu0\">5<\/span> \u00a0 \u00a0 \u00a0 \u00a0logger.<span class=\"me1\">setLevel<\/span><span class=\"br0\">(<\/span>java.<span class=\"me1\">util<\/span>.<span class=\"me1\">logging<\/span>.<span class=\"me1\">Level<\/span>.<span class=\"me1\">OFF<\/span><span class=\"br0\">)<\/span><span class=\"sy0\">;<\/span><br>\n\u00a0 <br><span class=\"nu0\">6<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Define the music libary directory<\/span><br>\n\u00a0 <br><span class=\"nu0\">7<\/span> \u00a0 \u00a0 \u00a0 \u00a0def musicLibraryDirName <span class=\"sy0\">=<\/span> <span class=\"st0\">'\/work\/Test\/Music'<\/span><br>\n\u00a0 <br><span class=\"nu0\">8<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Print the CSV file header<\/span><br>\n\u00a0 <br><span class=\"nu0\">9<\/span> \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"artistDir|albumDir|tagField.id|tagField.toString()\"<\/span><br>\n\u00a0 <br><span class=\"nu0\">10<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the music libary directory<\/span><br><span class=\"nu0\">11<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be artist directories<\/span><br>\n\u00a0 <br><span class=\"nu0\">12<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">new<\/span> <a href=\"http:\/\/www.google.com\/search?hl=en&amp;q=allinurl%3Adocs.oracle.com+javase+docs+api+file\"><span class=\"kw3\">File<\/span><\/a><span class=\"br0\">(<\/span>musicLibraryDirName<span class=\"br0\">)<\/span>.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> artistDir <span class=\"sy0\">-&gt;<\/span><br>\n\u00a0 <br><span class=\"nu0\">13<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each directory in the artist directory<\/span><br><span class=\"nu0\">14<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be album directories<\/span><br>\n\u00a0 <br><span class=\"nu0\">15<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0artistDir.<span class=\"me1\">eachDir<\/span> <span class=\"br0\">{<\/span> albumDir <span class=\"sy0\">-&gt;<\/span><br>\n\u00a0 <br><span class=\"nu0\">16<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Iterate over each file in the album directory<\/span><br><span class=\"nu0\">17<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ These are assumed to be content or related<\/span><br><span class=\"nu0\">18<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ (cover.jpg, PDFs with liner notes etc)<\/span><br>\n\u00a0 <br><span class=\"nu0\">19<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0albumDir.<span class=\"me1\">eachFile<\/span> <span class=\"br0\">{<\/span> contentFile <span class=\"sy0\">-&gt;<\/span><br>\n\u00a0 <br><span class=\"nu0\">20<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"co1\">\/\/ Analyze the file and print the analysis<\/span><br>\n\u00a0 <br><span class=\"nu0\">21<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"kw1\">if<\/span> <span class=\"br0\">(<\/span>contentFile.<span class=\"me1\">name<\/span> <span class=\"sy0\">==<\/span>~ <span class=\"sy0\">\/<\/span>.<span class=\"sy0\">*<\/span>.<span class=\"br0\">(<\/span>flac<span class=\"sy0\">|<\/span>mp3<span class=\"sy0\">|<\/span>ogg<span class=\"br0\">)<\/span><span class=\"sy0\">\/<\/span><span class=\"br0\">)<\/span> <span class=\"br0\">{<\/span><br><span class=\"nu0\">22<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0def af <span class=\"sy0\">=<\/span> AudioFileIO.<span class=\"me1\">read<\/span><span class=\"br0\">(<\/span>contentFile<span class=\"br0\">)<\/span><br><span class=\"nu0\">23<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0af.<span class=\"me1\">tag<\/span>.<span class=\"me1\">fields<\/span>.<span class=\"me1\">each<\/span> <span class=\"br0\">{<\/span> tagField <span class=\"sy0\">-&gt;<\/span><br><span class=\"nu0\">24<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0println <span class=\"st0\">\"${artistDir.name}|${albumDir.name}|${tagField.id}|${tagField.toString()}\"<\/span><br><span class=\"nu0\">25<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">26<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br>\n\u00a0 <br><span class=\"nu0\">27<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">28<\/span> \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><br><span class=\"nu0\">29<\/span> \u00a0 \u00a0 \u00a0 \u00a0<span class=\"br0\">}<\/span><\/div><\/div><\/pre>\n<p>This should look pretty familiar by now!<\/p>\n<p>Running it produces results like this before running the fixer script in the previous section:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">St Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>VENDOR<span class=\"sy0\">|<\/span>reference libFLAC 1.1.4 <span class=\"nu0\">20070213<\/span><br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TITLE<span class=\"sy0\">|<\/span>Land Of...<br><span class=\"me1\">St<\/span> Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ARTIST<span class=\"sy0\">|<\/span>St Germain<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ALBUM<span class=\"sy0\">|<\/span>Tourist<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TRACKNUMBER<span class=\"sy0\">|<\/span>04<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TRACKTOTAL<span class=\"sy0\">|<\/span>09<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>GENRE<span class=\"sy0\">|<\/span>Electronica<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>DISCID<span class=\"sy0\">|<\/span>730e0809<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>MUSICBRAINZ_DISCID<span class=\"sy0\">|<\/span>jdWlcpnr5MSZE9H0eibpRfeZtt0<span class=\"sy0\">-<\/span><br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>MUSICBRAINZ_SORTNAME<span class=\"sy0\">|<\/span>St Germain<\/div><\/div><\/pre>\n<p>Once the fixer script is run, it produces results like this:<\/p>\n<pre>\n<div class=\"geshifilter\"><div class=\"java geshifilter-java\">St Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>VENDOR<span class=\"sy0\">|<\/span>reference libFLAC 1.1.4 <span class=\"nu0\">20070213<\/span><br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ALBUM<span class=\"sy0\">|<\/span>Tourist<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ALBUMARTIST<span class=\"sy0\">|<\/span>St Germain<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ALBUMARTISTSORT<span class=\"sy0\">|<\/span>St Germain<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ARTIST<span class=\"sy0\">|<\/span>St Germain<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>ARTISTSORT<span class=\"sy0\">|<\/span>St Germain<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>GENRE<span class=\"sy0\">|<\/span>Electronica<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TITLE<span class=\"sy0\">|<\/span>Land Of...<br><span class=\"me1\">St<\/span> Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TITLESORT<span class=\"sy0\">|<\/span>Land Of...<br><span class=\"me1\">St<\/span> Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TRACKNUMBER<span class=\"sy0\">|<\/span>04<br>\nSt Germain<span class=\"sy0\">|<\/span>Tourist<span class=\"sy0\">|<\/span>TRACKTOTAL<span class=\"sy0\">|<\/span>09<\/div><\/div><\/pre>\n<p>That&#8217;s it! Now I just have to work up the nerve to run my fixer script on my full music library\u2026<\/p>\n<\/div>\n<div class=\"clearfix text-formatted field field--name-field-article-subhead field--type-text-long field--label-hidden field__item\">\n<p>I demonstrate a Groovy script to clean up the motley assembly of tag fields.<\/p>\n<\/div>\n<div class=\"field field--name-field-lead-image field--type-entity-reference field--label-hidden field__item\">\n<article class=\"media media--type-image media--view-mode-caption\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/08\/programming-code-keyboard-laptop-music-headphones-1.png\" width=\"520\" height=\"292\" alt=\"Woman programming\" title=\"Woman programming\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>WOCinTech Chat. Modified by Opensource.com. CC BY-SA 4.0<\/p>\n<\/div>\n<\/article>\n<\/div>\n<div class=\"field field--name-field-tags field--type-entity-reference field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/java\" hreflang=\"en\">Java<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/audio-and-music\" hreflang=\"en\">Audio and music<\/a><\/div>\n<\/p><\/div>\n<div class=\"hidden field field--name-field-listicle-title field--type-string field--label-hidden field__item\">What to read next<\/div>\n<div class=\"field field--name-field-default-license field--type-list-string field--label-hidden field__item\"><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/\"><br \/>\n        <img decoding=\"async\" alt=\"Creative Commons License\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/08\/cc-by-sa--44.png\" title=\"This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.\"><\/a>This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.<\/div>\n<section class=\"field field--name-field-comments field--type-comment field--label-hidden comment-wrapper\">\n<div class=\"comments__count\">\n<div class=\"login\"><a href=\"https:\/\/opensource.com\/user\/register?absolute=1\">Register<\/a> or <a href=\"https:\/\/opensource.com\/user\/login?current=\/rss.xml&amp;absolute=1\">Login<\/a> to post a comment.<\/div>\n<\/p><\/div>\n<\/section>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\" rel=\"noopener\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Clean up music tags with a Groovy script Chris Hermansen Tue, 08\/30\/2022 &#8211; 03:00 1 reader likes this 1 reader likes this Lately, I&#8217;ve been looking at how Groovy streamlines Java. In this series, I&#8217;m developing several scripts to help in cleaning up my music collection. In my last article, I used the framework developed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":66845,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[307],"tags":[],"class_list":["post-66844","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-open-source"],"_links":{"self":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/66844","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=66844"}],"version-history":[{"count":0,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/66844\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/media\/66845"}],"wp:attachment":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=66844"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=66844"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=66844"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}