Help with folder structure


Ok I have been looking for days now to find away to do this!
My script so far works but needs tweaking any help would be much appreciated!
This is my script.

$if(%album%,$if($eq($or(%originaldate%,%date%),1),%album% [$left($if2(%originaldate%,%date%),4)]/,)

$if($gt(%totaldiscs%,1),$if(%discsubtitle%,CD %discnumber% [%discsubtitle%]/, CD %discnumber%/),)

$noop(Track Name)
$num(%tracknumber%,2) - %title%)

This is what it dose.
~\Music\Artist Name\Album Name\01 - song.mp3
~\Music\Compilations\Album Name\01 - song.mp3
~\Music\Soundtrack\Album Name\01 - song.mp3

I would like to have subfolders in Compilations for artist with greatest hits albums in it.
I would also like to have a folder for Live Albums with artist subfolders.
One for studio albums and so on.


My biggest question is how you’re currently getting the “Compilations” and “Soundtrack” folders in the first place. Have you overridden the album artist field with that info? If you haven’t (or if you return the album artist to what it should be), you should be able to properly categorize the albums by putting the following line at the beginning of your script.

  • Make sure to replace the FIRST at the end with two backslashes (\\) followed by a 1 – for whatever reason the forum renders that as \\1, which is obviously not helpful.
  • The script won’t stumble over multiple secondary types (say if an album is both “compilation” and “remix”), but it might not choose the one you want. It just takes the first in the list, which I think will be the one earliest in the alphabet. Working around that is definitely possible, but it would make the code quite a bit more complex.

Hope this works for you, and let me know if you do want preferential treatment for “Live” or anything. I’d be happy to add that code, I’d just need to know what order you wanted them to be checked in.


Thank you for your help I’m not to sure on how I get Soundtracks and Compilations ether. I have as I said been playing with this days now. I did use some scripts I had found in the forums and some I found with google. One that comes to mind is named Magic Script and I have installed the lastfmng plugin as well. So I could have vary well overridden some fields. I’m not really sure as to what I am doing here at all and more just playing around. So if I add your script to mine if I followed you right it looks like this.


Witch gives me.
~\Music\Live–\Various Artists\Album\01 - song.mp3
~\Musci\Live–\Artist\Album\01 - song.mp3
So on from there I can clean it up a bit to get it closer like this.

$upper($firstalphachar($if2(%_secondaryreleasetype%,Studio Albums)))$rreplace($if2(%_secondaryreleasetype%,Studio Albums),.\([^;]*\).*,\\1)/

Witch gives me.
~\Music\Live\Various Artists\Album [year]\01 - song.mp3
~\Musci\Live\Artist\Album [year]\01 - song.mp3
~\Music\Studio Albums\Artist\Album [year]\01 - song.mp3
~\Music\Soundtrack\Soundtrack\Album [year]\01 - song.mp3

I have lost Compilations folder But I think it would not be hard to get back. No clue as to way the two soundtrack folders. Would like Live to be Live Albums but I still need to play some more. If you could enplane what you did it might added in my fight! Again many many thanks for your help I am in the long run going for the. Give a man a fish he eats for today. Teach him how to fish he eats for life.


By the by how do you add code boxes in to your post?


Found the post I got the Magic script from if it helps any



The code in line with text is from surrounding it with backticks (the ` which is under the ~ on at least US keyboards) – it looks like you’ve figured out the four-spaces-at-the-front for doing the entire line, which automatically joins multiple lines into larger blocks. How’d you get the colors on yours, though? That’s something I don’t know. And sorry about the two dashes; I meant for them to just be an en-dash separating the second part of the sentence.

Not sure why the Compilations folder has disappeared – that (with no ‘s’) was actually what most of my tests wound up being sorted with – but the second soundtrack looks like it might be due to the album artist having been overridden; it’s in the same “slot” as the artists for the other release types. Do both artist fields show up correctly in the bottom pane of Picard?

EDIT: There’s a chance that has something to do with MagicScript, since it doesn’t include artists in the soundtrack hierarchy, but it doesn’t save anything over the tags so unless you have something in your file namer that doesn’t show up in your original post, I’m not sure why it would affect anything

And definitely happy to explain it! One resource I’ve found invaluable, if you haven’t seen it already, is the scripting documentation page. Not necessarily a fun read, but it’s really nice to take twenty minutes and just figure out everything that’s possible.


%_secondaryreleasetype% unsurprisingly contains a list of all the secondary types on the release group, in lowercase. You can find a list of everything you can use on the tags page, but my favorite way of finding something helpful is using the “View script variables” plugin. Anyway, when this list variable’s used normally (as in, not through one of the $...multi functions), the types are separated by a semicolon and a space; this will come into play later. For now, all we care about is if the release group has any of them set, so we use an $if2(...) to select the entire list if it’s not empty, or the text “studio” if it is. The function can take pretty much as many comma-separated arguments as you want to give it, and returns the first one that’s not empty.


From that result, take only the first character and turn it into uppercase. This would normally cause unexpected behaviour if the first character wasn’t a letter, but since none of the secondary types use anything but letters in their name, we don’t have to worry about it. As a note, the first “Albums” you added to your line (and, in fact, everything after the s) doesn’t do anything because $firstalphachar drops it. That’s also why I had studio in all lowercase – it’s automatically capitalized, so if I match what’s in %_secondaryreleasetype, it’s less likely to do anything weird if I edit the script in the future.


(Don’t copy that, there’s a zero-width space between the two backslashes.) This, admittedly, looks a bit like magic. $rreplace([...]) runs a regex on some text, and those are rather infamously difficult to read. It isn’t helped at all by the fact that Picard parses the script before the regex is run, so there’s more backslashes in there than normal (for a digression on this, check out this page and look for sections talking about the title text). At a high level, the expression between the commas is matched to parts of the text before the first comma, and those matches are replaced with the value after the second comma. To take it an element at a time:

  • . - This matches any single character. Since regexes are greedy (matching at the first possible point and not going to the next part of the expression until they have to), this will always be the first character in the string, If we were using something else or wanted to be particularly careful, we would put a ^ at the beginning of the expression, which only matches the beginning of the string. The . is in here because we already have the first character from the $upper([...]) expression.

  • \( - I’ll explain this later, but it’s escaped (the backslash) because a plain ( has a special meaning in the script that we don’t want to invoke.

  • [^;] - Square brackets usually mean “match any of these characters”, but the ^ being right after the [ makes it “match everything except these characters” (as opposed to what ^ means at the start of a regex). Therefore, this matches any (single) character that’s not a semicolon – what separates the elements of the list, remember.

  • * - This takes the last element (in this case, the [^;]) and matches it any number of times, zero to theoretical infinity. Again, regexes are greedy, so if we want it to stop at some point before the end of the line, we have to tell it when to stop before the *: in this case, when it hits the first semicolon (if there’s no semicolon in the string, it just takes everything until the end of the input). This will then be “the first element of the list, ignoring the first character” since that was already matched.

  • \) - Again, escaped due to having another meaning; the pair would typically be read ([^;]*). Doesn’t match anything on its own, but saves everything between the parentheses into memory to be referred back to later.

  • .* - I assume you’ve been paying attention and can figure this out from the above, but for completeness, match any number of any character; basically, “match the entire rest of the line”. Like with ^, if we weren’t using such a broad wildcard or we wanted to be careful, we could add a $ to the end to be sure we match the end of the line, but it’s not necessary after .*.

  • \​\1 - (Like above, retype this, don’t copy it.) This is how we retrieve the value saved by ( and ) – or, more generally, \1, but the backslash also needs to be escaped here. Since this is after the second comma, we’ve switched from matching text to replacing whatever was matched. Because we made sure to get the entire line, this takes whatever we gave as input and replaces it with the first saved group, essentially discarding the first character, the first semicolon, and everything after that.

    $rreplace([…]) Albums/
    If I may make another suggestion, this is what I’d do to add the “Albums” part of the name, as long as you don’t mind “Compilation Albums” and “Soundtrack Albums”. You get it applied across the board, and don’t run into “Studio Albums” behaving differently than everything else. If you do want folders without the second word, you can always do something like $if($ne($left(%_secondaryreleasetype%,10),soundtrack), Albums,) to only add it if the first 10 characters of the first release type in the list don’t match “soundtrack” (or, more correctly, to add an empty string if they do), and if you’re being lazy, you can even replace 10 with $len(soundtrack) to do the counting for you.


For the code boxes with colors in was not done on purpose in any way at all. I did however copy and past from Picard and think that’s how it came up that way. Thank you for your time and knowledge it is a big help and has given some things to relearn. It also brings up many more questions but before I ask I need to do some home work. I’m thinking I may need to clean my music and re-scrape my metadata in order to move forward. Witch is not a big deal as the only reason I started out with this is my NAS died on me and I lost my media. I when I set out to regain years of lost data I wanted it cleaned and organized from the get go. TV Shows and Movies are easy but when it comes to Music oh boy look out. The “View script variables” plugin not so sure on how to use this or any plugin for that matter. What others do you use and could you point me to so documentation on them. If i have not found any or miss something before you get back to me.


In an effort to keep this straight and to have it written down some were for when I forget what it was I did. Here we go I uninstalled picard deleted any trace of it and reinstalled it as to start fresh. I’m starting with my initial code to clean up my files. As you guessed I had over written not some but a lot of the fields and this time around. There was not a compilation folder or one for soundtracks I did however get one for various artist.

$if(%album%,$if($eq($or(%originaldate%,%date%),1),%album% [$left($if2(%originaldate%,%date%),4)]/,)
$if($gt(%totaldiscs%,1),$if(%discsubtitle%,CD %discnumber% [%discsubtitle%]/, CD %discnumber%/),)
$noop(Track Name)
$num(%tracknumber%,2) - %title%)

With this I get the following folder structure.
~\Music\Led Zeppelin\The Song Remains the Same [1976]\CD 1\01 - Rock and Roll.mp3
~\Music\Various Artists\Dirty Dancing [1987]\01 - (I’ve Had) The Time of My Life.mp3
A good starting point I think! Now its time to fill my head with info so I can get ripping and have all my vinyl back in mp3 format. Now when I add the code from your post I get the top level folders taken from the releasetype field.



$if(%album%,$if($eq($or(%originaldate%,%date%),1),%album% [$left($if2(%originaldate%,%date%),4)]/,)

$if($gt(%totaldiscs%,1),$if(%discsubtitle%,CD %discnumber% [%discsubtitle%]/, CD %discnumber%/),)

$noop(Track Name)
$num(%tracknumber%,2) - %title%)

like this.

~\Music\Live–\The Rolling Stones\Got Live If You Want It! [1966]\01 - Under My Thumb.mp3
~\Music\Live–\Various Artists\Harry Chapin Tribute [1991]\01 - Circle.mp3
~\Music\Soundtrack–\Led Zeppelin\The Song Remains the Same [1976]\CD 1\01 - Rock and Roll.mp3
witch is much better if I remove the – after the 1 in your code. On to the novel’s of info you have pointed me too. I am now starting to understand this and not just copy and pasting my way through. Thank you again for your help.


All right I just for life of can’t get this I do only have the one plugin running right now “View script variables” but do not think its working. I’m running picard v1.4 if that helps any. Do you happen to now under options scripts tager scripts what it should do? Is it just a place to store scripts or can I call on it to run scripts?


Glad it’s working out for you! I got into this to reorganize my existing music, and though that’s definitely easier than starting from scratch, I have some idea of how large a task you’re looking at. Not quite sure what I’d do if I lost everything.

The plugins are kind of hit-and-miss; many were written for older versions of Picard and no longer work, and the documentation on all of them is pretty lacking. If I remember correctly, Picard 1.4 comes with the list in the Options window filled out (though you might have to hit the reload button), and from there you can simply click the “Install” on the line of whatever you’re interested in. Otherwise, you can go to the plugins page, download what you want, and use the “Install plugin…” button below the list. Installing doesn’t actually enable the plugin, though – you have to check the box next to the name (and probably restart Picard) to get them running. After that, it’s figuring out where its features have showed up. Some of them add new fields (whether to the bottom pane or to the hidden list, which is why I like “View script variables”), and the others seem to show up in the right-click menu of either – but not usually both – a release or a track in the right pane, in the “Plugins” submenu. “View script variables” is one of the latter – once it’s installed and running, right-clicking on any track and selecting it from the list opens a new window showing everything that could be used in a script.

I also have “AcousticBrainz Tonal-Rhythm” (adds fields for key and BPM), “Generate M3U playlist” (on the albums, but doesn’t seem to work), “Standardise Performers”, “No release” (no idea if they even do anything in 1.4), and “ReplayGain” (very helpfully gives an error message about not working in 1.4) running, but as you can tell, I don’t tend to use most of them.

And you posted your last as I was writing that. Let me know if the end of the second paragraph doesn’t help.


I will let you now how I far latter on My youngest and are going getting our camping gear ready for the summer. I also need to walk away from this for a bit I’m getting frustrated with it and wanting to through my computer out the windows then drive over it with my van. I do see the plugin working from what I saw it should be a help. Thank you again for your help.


I am starting to think adding Albums to folder names is not possible at all to display in the way I want.
I can get it to come up as Compilation Albums, Soundtarck Albums, Studio Albums, and Live Albums. Or for that matter add Albums after any name that shows up in the secondary release type field using your suggestion. Compilation Albums I can live with and pretty much any other combo it has spit out so far. On the other hand and I may just be being picky here but Soundtrack Albums just sounds and looks wrong to me.

That said I am starting to understand this a lot better now and figuring out how to play with the order the script goes. For example if I move some code around I get a new folder stucture like so.


$if(%album%,$if($eq($or(%originaldate%,%date%),1),%album% [$left($if2(%originaldate%,%date%),4)]/,)

$if($gt(%totaldiscs%,1),$if(%discsubtitle%,CD %discnumber% [%discsubtitle%]/, CD %discnumber%/),)

$noop(Track Name)
$num(%tracknumber%,2) - %title%)

This lets me sort the secondary release type in the artist folder in stead of the root. Or if I add in a media filter like so.


$if(%album%,$if($eq($or(%originaldate%,%date%),1),%album% [$left($if2(%originaldate%,%date%),4)]/,)

$if($gt(%totaldiscs%,1),$if(%discsubtitle%,CD %discnumber% [%discsubtitle%]/, CD %discnumber%/),)

$noop(Track Name)
$num(%tracknumber%,2) - %title%)

The albums are sorted by the media type in there release type folder. I can see my O.C.D. kicking in here. Any way if you know of a way to add Albums like I was after in the first place let me know. I have all but given up on it for now. Thank you again for your help


I did kind of bury that at the end of the overly-long explanation. After the closing parenthesis of the $rreplace function (and right before the /), add the following line to append “Albums” to everything except “Soundtrack”:

$if($ne($left(%_secondaryreleasetype%,10),soundtrack), Albums,)

$left take the first n (in this case, 10) characters from a string (in this case, the list of secondary release types), $ne compares one string to another (in this case, roughly the first type in the list and the word “soundtrack”), and $if essentially takes one string ("​ Albums") if a condition is true or non-empty and another (an empty string) if it’s not. The comma after Albums is not actually necessary here since the “false” value is an empty string by default, but I like putting it in because I think it’s clearer.

You can easily extend this to other disallowed types with the same behavior by using $and, which performs a Boolean and on multiple conditions:

$if($and($ne($left(%_secondaryreleasetype%,10),soundtrack),$ne($left(%_secondaryreleasetype%,11),compilation)), Albums,s)

This one checks that both conditions are true (the list starts with neither “soundtrack” nor “compilation”) and sticks an s on the end if either is the case. Note that the position of the space is important: putting one before the $if and none between the comma and Albums would probably have worked for the first line in the post, but doing so with this second line would give Compilation s rather than Compilations.

If you do want Compilations rather than Compilation Albums but don’t like the look of Soundtracks, you can nest another $if expression, but it starts looking a bit less clean:

$if($eq($left(%_secondaryreleasetype%,10),soundtrack),,$if($eq($left(%_secondaryreleasetype%,11),compilation),s, Albums))

Note that I’ve replaced $ne with $eq which checks if the values are the same and switched the results of the $if because it’s harder to read what the extra comma at the end is for if there’s another big expression in the middle. Unlike having an empty string as the “false” result, we have to include both the commas in ,, because otherwise the script would think we meant "if the type list starts with both soundtrack and compilation".


Ok ran that through today and it works also after rereading it makes sense now the only thing left after much playing around. Is .\([^;]*\).*,\)` part wich you had pointed me to and I need to look over again to full understand it. I do have to say I would have never ever gotten that. I can’t find any thing about on the scripts page or any other I may add.


Yeah, the Picard documentation assumes you’re familiar with regular expressions (commonly called “regexes”, singular “regex”), and people typically aren’t unless they have a background in either Linux or programming, and even the second isn’t a complete guarantee. They’re tricky to read even if you know how, and the one I used for your script is definitely not a simple one. You might want to start trying to figure out regexes with something a bit more sane. Search for “regex tutorial”, and see what site presents it in a way that clicks for you; from a quick look around, this document seems like a pretty good introduction, but you could easily find that someone else’s writeup makes more sense. Either way, the Python regex page has information on the version Picard uses (most environments have slight differences in how they use the particularly rare characters), though it’s not the best first introduction to them and tends to document the functions needed to use regexes in raw Python more than the regexes themselves.


[quote=“leighdog, post:15, topic:230717”].\([^;]**\).*,\)[/quote]The regex-tool [Expresso]( explain that part as
Any character
Literal (
Any character that is ^ NOT in this class: [;], any number of repetitions
,``Literal ) Any character, any number of repetitions , Literal
With the closing ) this part is “unbalanced”, because you have two closing ), but only one opening ( parenthesis.
As part of the “($if2…” command, this syntax is OK.


Copying to the forums (and escaping characters to embed in Picard’s script) obscures things a bit: the two underlying regexes are .([^;]*).* and \1. The most confusing part is that the backslash in the replace-with has to be escaped, but the forums render backslash-backslash-one as backslash-grave within the code blocks. I also broke it down further up the thread, but that’s a handy-looking tool. Thanks!