| main | console | Legend - Hand of God | web | contact |
| cd.* | music.* | ogg.* |
| applyTracklist | dbIndex | makeMp3 | makeOgg | makeWav |
This script is called with directories as parameter music.applyTracklist dir_1 dir_2 .. dir_n. In every directory it takes the content of the file "index" and writes the track information it finds therein into the music (mp3 or ogg) files. It will also rename the files so that the filename reflects the song title.
For the sake of clarity, here's an index file:
Artist: Killswitch Engage Title: The End of Heartache Genre: Metal Year: 2004 01: A Bid Farewell 02: Take This Oath 03: When Darkness Falls 04: Rose Of Sharyn 05: Inhale 06: Breathe Life 07: The End Of Heartache 08: Declaration 09: World Ablaze 10: And Embers Rise 11: Wasted Sacrifice 12: Hope Is ...
For each track it expects to find a file called track(nr).cdda.ogg or track(nr).cdda.wav.mp3. If one of the files is missing, or not every tag (e.g. the artist) is specified, the script will abort before changing any file.
Note: The cd.makeOgg will create files compatible to this scheme, so you don't need to worry.
When all necesarry information is available, all tags are written into the files and they will be renamed to (nr)_(title).(ogg,mp3)
The script also knows the parameter --update. If you use that (e.g. music.applyTracklist --update dir_1), it will use just the first two characters of each filename to find the tracks.
This way you can quickly rename files like 06-Track06.ogg to 06_breathe_life.ogg
In Update Mode, only those tracks will be touched, whose tags are really different from those in the index file.
If some (or all) artists of a song differ from the "global" album artist, you can specify him as well by enclosing him in ## as can be seen in this example:
Artist: Various Title: Matrix Genre: Soundtrack Year: 1999 01: ##Marilyn Manson## Rock Is Dead 02: ##Propellerheads## Spybreak! (short one) 03: ##Ministry## Bad Blood ..
In this case the files will be renamed according to the scheme (nr)_(artist)_(title).(mp3,ogg).
Note1: The script does only support track numbers up to 99 and will not check for higher numbers.
Note2: For ogg files, the genre can be an arbitrary string. If you use mp3 files, your choice is somewhat limited. In that case, use id3v2 -L to see a list of valid options.
1#!/bin/sh 2# the next line restarts using tclsh \ 3exec tclsh "$0" "$@" 4 5# 6# needed programs: 7# id3v2 8# metaflac 9# ogg.displayTags (script) 10# ogg.writeTags (script) 11# 12 13 14# 15# do we do an update? 16# 17set do_update no 18set i [lsearch $argv "--update"] 19if {$i > -1} then { 20 # we do an update 21 set do_update yes 22 set argv [lreplace $argv $i $i] 23} 24unset i 25 26 27# 28# exit if nothing to do 29# 30if {[llength $argv] == 0} then { 31 puts "Usage: [file tail $argv0] \[option\] \[directory..\]\n" 32 33 puts "\t--update\tupdate existing files\n" 34 exit 1 35} 36 37 38 39# 40# prepare the list of genres (mp3 files need number instead of plain names) 41# 42 43#read genre list 44set genre_list [exec id3v2 -L] 45 46# transform into tcl list 47regsub -all ": " $genre_list " \"" genre_list 48regsub -all "\n" $genre_list "\"\}\n\{" genre_list 49 50set genre_list "\{$genre_list\"\}" 51 52# this function finds the corresponding number to a genre 53proc genre_nr {val} { 54 global genre_list 55 56 set return "-1" 57 58 foreach item $genre_list { 59 set nr [lindex $item 0] 60 set name [lindex $item 1] 61 62 if {[string compare -nocase $name $val] == 0} then { 63 set return $nr 64 unset nr name 65 break 66 } 67 68 unset nr name 69 } 70 71 return $return 72} 73 74 75# 76# First, check for correct tracklists 77# 78 79set errors "no" 80 81 82foreach folder [lsort -dictionary $argv] { 83 set folder [file normalize $folder] 84 85 if {![file isdirectory $folder]} then { 86 unset folder 87 continue 88 } 89 90 # 91 # check for missing tracklists 92 # 93 set tracklist_file [file join $folder index] 94 95 if {![file exists $tracklist_file]} then { 96 puts "no tracklist in $folder" 97 98 set errors yes 99 unset tracklist_file 100 continue 101 } 102 103 # 104 # check, whether there's a tracklist entry for each file 105 # 106 107 # reads tracklist into an array 108 109 array set contents {} 110 111 set fd [open $tracklist_file r] 112 while {![eof $fd]} { 113 set line [split [gets $fd] :] 114 115 if {[llength $line] >= 2} then { 116 set name [string trim [lindex $line 0]] 117 set value [string trim [join [lrange $line 1 end] ":"]] 118 119 array set contents [list $name $value] 120 unset name value 121 } 122 unset line 123 } 124 125 close $fd 126 unset fd tracklist_file 127 128 129 # does an album title exist 130 if {[array get contents Title] == ""} then { 131 puts "tracklist in $folder has no title" 132 array unset contents 133 unset folder 134 135 set errors yes 136 continue 137 } 138 139 # does an artist exist 140 if {[array get contents Artist] == ""} then { 141 puts "tracklist in $folder has no artist" 142 array unset contents 143 unset folder 144 145 set errors yes 146 continue 147 } 148 149 # does an genre exist 150 if {[array get contents Genre] == ""} then { 151 puts "tracklist in $folder has no genre" 152 array unset contents 153 unset folder 154 155 set errors yes 156 continue 157 } 158 159 # is this genre an valid one 160 # (only necessary if there are mp3 files in this directory, since 161 # oggs can have arbitrary genres) 162 if {[glob -nocomplain -types f [file join $folder *.mp3]] != ""} then { 163 if {[genre_nr $contents(Genre)] == "-1"} then { 164 puts "tracklist in $folder uses unknown genre" 165 array unset contents 166 unset folder 167 168 set errors yes 169 continue 170 } 171 } 172 173 # does an year exist 174 if {[array get contents Year] == ""} then { 175 puts "tracklist in $folder has no year" 176 array unset contents 177 unset folder 178 179 set errors yes 180 continue 181 } 182 183 # exists a title for all tracks? 184 if {$do_update} then { 185 foreach file [lsort [glob -nocomplain -types f [file join $folder *.{ogg,mp3,flac}]]] { 186 187 # file is '$folder/07*.ogg', we only need the track nr 188 set nr [string range [file tail $file] 0 1] 189 190 if {[array get contents $nr] == ""} then { 191 puts "[file tail $file] in $folder has no title" 192 set errors yes 193 } 194 unset nr file 195 } 196 } else { 197 foreach file [lsort [glob -nocomplain -types f [file join $folder *.cdda.{wav.mp3,ogg,flac}]]] { 198 # file is '$folder/track07.cdda.ogg', we only need the track nr 199 set nr [string range [file tail $file] 5 6] 200 201 if {[array get contents $nr] == ""} then { 202 puts "[file tail $file] in $folder has no title" 203 set errors yes 204 } 205 unset nr file 206 } 207 } 208 209 # exists a title in the tracklist, but no such file? 210 foreach nr [lsort [array names contents]] { 211 if {$nr == "Title" || $nr == "Artist" || $nr == "Genre" || $nr == "Year"} then {unset nr; continue} 212 213 if {$do_update} then { 214 if {[glob -nocomplain -types f [file join $folder "[set nr]{_,-, }*.{ogg,mp3,flac}"]] == ""} then { 215 puts "no file for track $nr in $folder" 216 set errors yes 217 } 218 } else { 219 if {[glob -nocomplain -types f [file join $folder "track[set nr].cdda.{ogg,wav.mp3,flac}"]] == ""} then { 220 puts "no file for track $nr in $folder" 221 set errors yes 222 } 223 } 224 unset nr 225 } 226 227 228 array unset contents 229 unset folder 230} 231 232if {$errors} then {unset errors; exit} 233unset errors 234 235 236########################################### 237# Tracklists are OK, now rename the files # 238########################################### 239 240foreach folder [lsort -dictionary $argv] { 241 set folder [file normalize $folder] 242 243 if {![file isdirectory $folder]} then { 244 unset folder 245 continue 246 } 247 248 puts "$folder/" 249 250 # set tracklist 251 set tracklist_file [file join $folder index] 252 253 # reads tracklist into an array 254 array set contents {} 255 256 set fd [open $tracklist_file r] 257 while {![eof $fd]} { 258 set line [split [gets $fd] :] 259 260 if {[llength $line] >= 2} then { 261 set name [string trim [lindex $line 0]] 262 set value [string trim [join [lrange $line 1 end] ":"]] 263 264 array set contents [list $name $value] 265 unset name value 266 } 267 unset line 268 } 269 270 close $fd 271 unset fd tracklist_file 272 273 274 # rename files 275 if {$do_update} then { 276 set l [lsort [glob -nocomplain -types f [file join $folder *.{ogg,mp3,flac}]]] 277 } else { 278 set l [lsort [glob -nocomplain -types f [file join $folder *.cdda.{wav.mp3,ogg,flac}]]] 279 } 280 281 foreach oldfile $l { 282 if {$do_update} then { 283 # file is '$folder/07*.ogg', we only need the track nr 284 set nr [string range [file tail $oldfile] 0 1] 285 } else { 286 # file is '$folder/track07.cdda.ogg', we only need the track nr 287 set nr [string range [file tail $oldfile] 5 6] 288 } 289 290 set artist [lindex [array get contents Artist] 1] 291 set album [lindex [array get contents Title] 1] 292 set genre [lindex [array get contents Genre] 1] 293 set year [lindex [array get contents Year] 1] 294 set title [lindex [array get contents $nr] 1] 295 296 # each track can have it's own artist (e.g. "01: ##Creed## Are You Ready?") 297 # here we need to extract it 298 set alternate_artist no 299 if {[string range $title 0 1] == "##"} then { 300 set artist [lindex [split $title "#"] 2] 301 set title [string trim [join [lrange [split $title "#"] 4 end] "#"]] 302 set alternate_artist yes 303 } 304 305 set ext [file extension $oldfile] 306 307 # get old tag information 308 if {$ext == ".ogg"} then { 309 set old_infos [exec ogg.displayTags $oldfile] 310 311 set old_album "" 312 set old_artist "" 313 set old_year "" 314 set old_genre "" 315 set old_title "" 316 set old_nr "" 317 318 foreach line [split $old_infos \n] { 319 set line [split $line =] 320 set tag [string trim [lindex $line 0]] 321 set val [string trim [join [lrange $line 1 end] =]] 322 323 switch $tag { 324 "ALBUM" { 325 set old_album $val 326 } 327 "ARTIST" { 328 set old_artist $val 329 } 330 "DATE" { 331 set old_year $val 332 } 333 "GENRE" { 334 set old_genre $val 335 } 336 "TITLE" { 337 set old_title $val 338 } 339 "TRACKNR" { 340 set old_nr $val 341 } 342 } 343 unset line tag val 344 } 345 unset old_infos 346 347 } elseif {$ext == ".flac"} then { 348 set old_album [join [lrange [split [exec metaflac --show-tag=ALBUM $oldfile] =] 1 end]] 349 set old_artist [join [lrange [split [exec metaflac --show-tag=ARTIST $oldfile] =] 1 end]] 350 set old_year [join [lrange [split [exec metaflac --show-tag=DATE $oldfile] =] 1 end]] 351 set old_genre [join [lrange [split [exec metaflac --show-tag=GENRE $oldfile] =] 1 end]] 352 set old_title [join [lrange [split [exec metaflac --show-tag=TITLE $oldfile] =] 1 end]] 353 set old_nr [join [lrange [split [exec metaflac --show-tag=TRACKNUMBER $oldfile] =] 1 end]] 354 } else { 355 set old_infos [exec id3v2 -l $oldfile] 356 357 set old_album "" 358 set old_artist "" 359 set old_year "" 360 set old_genre "" 361 set old_title "" 362 set old_nr "" 363 364 foreach line [split $old_infos \n] { 365 if {[string range $line 0 3] == "TALB"} then {set old_album [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 366 if {[string range $line 0 3] == "TPE1"} then {set old_artist [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 367 if {[string range $line 0 3] == "TYER"} then {set old_year [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 368 if {[string range $line 0 3] == "TCON"} then {set old_genre [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 369 if {[string range $line 0 3] == "TIT2"} then {set old_title [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 370 if {[string range $line 0 3] == "TRCK"} then {set old_nr [string trim [join [lrange [split $line ":"] 1 end] ":"]]} 371 unset line 372 } 373 374 # genre is of format "genre name (nr)".. remove the nr 375 set old_genre [string trim [lindex [split $old_genre (] 0]] 376 377 unset old_infos 378 } 379 380 381 # if tags are the same, we can stop here 382 if {[string match $old_album $album] && \ 383 [string match $old_artist $artist] && \ 384 [string match $old_year $year] && \ 385 [string match $old_genre $genre] && \ 386 [string match $old_title $title] && \ 387 [string match $old_nr $nr]} then { 388 389 unset old_album old_artist old_year old_genre old_title old_nr 390 unset nr artist album genre year title alternate_artist oldfile ext 391 continue 392 } 393 394 395 # set new tags, if they differ 396 if {$ext == ".ogg"} then { 397 # set OGG tags 398 399 # be sure to escape quotation marks in tags (otherwise ogg.writeTags would choke) 400 foreach var {title artist album} { 401 regsub -all {\"} [set $var] {\\"} $var 402 unset var 403 } 404 405 # write the tags 406 exec ogg.writeTags --album $album --artist $artist --date $year --genre $genre --title $title --tracknr $nr $oldfile 407 408 # restore the escaped quotation marks again 409 foreach var {title artist album} { 410 regsub -all {\\"} [set $var] "\"" $var 411 unset var 412 } 413 414 } elseif {$ext == ".flac"} then { 415 # delete all existing tags 416 exec metaflac --remove-all-tags $oldfile 417 418 # set tags 419 exec metaflac --set-tag=ALBUM=$album --set-tag=ARTIST=$artist --set-tag=DATE=$year --set-tag=GENRE=$genre --set-tag=TITLE=$title --set-tag=TRACKNUMBER=$nr $oldfile 420 421 } else { 422 # delete all exisiting tags 423 exec id3v2 -D $oldfile 424 425 # set ID3 tags 426 exec id3v2 -a $artist -A $album -t $title -T $nr $oldfile -y $year -g [genre_nr $genre] 427 } 428 429 # prepare title and artist for the new filename 430 foreach var {title artist album} { 431 regsub -all {\.} [set $var] "" $var 432 regsub -all {\"} [set $var] "" $var 433 regsub -all {'} [set $var] "" $var 434 regsub -all {\?} [set $var] "" $var 435 regsub -all {,} [set $var] "" $var 436 regsub -all {!} [set $var] "" $var 437 regsub -all {\;} [set $var] "" $var 438 regsub -all {\(} [set $var] "" $var 439 regsub -all {\)} [set $var] "" $var 440 441 regsub -all {\*} [set $var] " " $var 442 regsub -all {:} [set $var] " " $var 443 regsub -all {#} [set $var] " " $var 444 regsub -all {@} [set $var] " " $var 445 446 regsub -all {<} [set $var] "-" $var 447 regsub -all {>} [set $var] "-" $var 448 regsub -all {/} [set $var] "-" $var 449 450 regsub -all {Ä} [set $var] "A" $var 451 regsub -all {ä} [set $var] "a" $var 452 regsub -all {Ö} [set $var] "O" $var 453 regsub -all {ö} [set $var] "o" $var 454 regsub -all {Ü} [set $var] "U" $var 455 regsub -all {ü} [set $var] "u" $var 456 regsub -all {ß} [set $var] "ss" $var 457 458 regsub -all {&} [set $var] "and" $var 459 regsub -all {\+} [set $var] "and" $var 460 regsub -all { } [set $var] "_" $var 461 462 set $var [string tolower [set $var]] 463 unset var 464 } 465 466 # rename the file 467 if {$alternate_artist} then { 468 # alternate artist should appear in filename too 469 set newfile [file join $folder "[set nr]_[set artist]-[set title][set ext]"] 470 } else { 471 set newfile [file join $folder "[set nr]_[set title][set ext]"] 472 } 473 474 if {![string match $oldfile $newfile]} then { 475 file rename -force $oldfile $newfile 476 } 477 478 puts " [file tail $newfile]" 479 480 unset old_album old_artist old_year old_genre old_title old_nr 481 unset nr artist album genre year title alternate_artist oldfile newfile ext 482 } 483 unset l 484 485 array unset contents 486 unset folder 487} 488 489unset do_update 490 491exit 0