generate.pike (12838B)
1 #! /usr/bin/env pike 2 #pike 7.5 3 4 // 5 // A small program that generates a ID3v1/ID3v1.1 testsuite. 6 // Copyright (c) 2003 Martin Nilsson 7 // 8 // This code is far from good looking, but the code itself isn't 9 // really the interesting thing here... 10 // 11 12 #if !constant(ADT.Struct) 13 #error This Pike is too old for this application. 14 #endif 15 16 // 64Kbit/s, 32kHz, ~222.22 frames/s, 288 bytes/frame 17 constant silence_lead_in = "˙ûXÄ\0\0\0\0\1¤\0\0\0\0\0\0""4\200\0\0\0" + 18 ("˙"*267) + "˙ûXÄ\205\200\2ā\1¤\0\0\0\0\0\0""4\200\0\0\0" + ("˙"*267); 19 constant silence_frame = "˙ûXÄ˙\200!`\1¤\0\0\0\0\0\0""4\200\0\0\0" + ("˙"*267); 20 21 // Generate silent MP3 data, either as a returned string or 22 // outputted on @[f]. The length is either @[s] seconds or 23 // three frames, in case @[s] is zero or omitted. 24 void|string generate_silence(void|int s, void|Stdio.File f) { 25 int frames = (int)(64000/288.0 * s); 26 frames = max(frames-2, 0); 27 if(f) { 28 f->write(silence_lead_in); 29 for(; frames; frames--) 30 f->write(silence_frame); 31 return; 32 } 33 return silence_lead_in + silence_frame * frames; 34 } 35 36 // The structure of an ID3v1 tag. 37 class ID3_1 { 38 inherit ADT.Struct; 39 Item head = Chars(3, "TAG"); 40 Item title = Chars(30, "\0"*30); 41 Item artist = Chars(30, "\0"*30); 42 Item album = Chars(30, "\0"*30); 43 Item year = Chars(4, "2003"); 44 Item comment = Chars(30, "\0"*30); 45 Item genre = Byte(0); 46 } 47 48 // The structure of an ID3v1.1 tag. 49 class ID3_11 { 50 inherit ADT.Struct; 51 Item head = Chars(3, "TAG"); 52 Item title = Chars(30, "\0"*30); 53 Item artist = Chars(30, "\0"*30); 54 Item album = Chars(30, "\0"*30); 55 Item year = Chars(4, "2003"); 56 Item comment = Chars(28, "\0"*28); 57 Item null = Byte(0); 58 Item track = Byte(0); 59 Item genre = Byte(0); 60 } 61 62 // Pads a string with null to @[size] characters. Default is 30. 63 string pad(string in, void|int size) { 64 if(!size) size=30; 65 if(sizeof(in)>size) error("String longer than %d chars.\n", size); 66 return in+("\0"*(size-sizeof(in))); 67 } 68 69 // Removes the null padding from a string. 70 string strip_pad(string in) { 71 sscanf(reverse(in), "%*[\0]%s", in); 72 return reverse(in); 73 } 74 75 // Prints out information about an ID3 tag on stdout. 76 void show_tag(string data) { 77 if(sizeof(data)!=128) error("Wrong tag size.\n"); 78 79 #define ITEM(X) write("%-7s: %O\n", #X, strip_pad(tag->X)) 80 object tag; 81 if(data[-3]==0 && data[-2]!=0) 82 tag = ID3_11(data); 83 else 84 tag = ID3_1(data); 85 86 write("%-7s: %s\n", "version", tag->track?"1.1":"1.0"); 87 ITEM(head); 88 ITEM(title); 89 ITEM(artist); 90 ITEM(album); 91 ITEM(year); 92 ITEM(comment); 93 if(tag->track) 94 write("%-7s: %O\n", "track", tag->track); 95 string genre; 96 catch( genre = id3_genres[tag->genre] ); 97 write("%-7s: %O (%s)\n", "genre", tag->genre, 98 genre||"unknown"); 99 } 100 101 array(string) id3_genres = ({ 102 "Blues", // 0 103 "Classic Rock", 104 "Country", 105 "Dance", 106 "Disco", 107 "Funk", 108 "Grunge", 109 "Hip-Hop", 110 "Jazz", 111 "Metal", 112 "New Age", 113 "Oldies", 114 "Other", 115 "Pop", 116 "R&B", 117 "Rap", 118 "Reggae", 119 "Rock", 120 "Techno", 121 "Industrial", 122 "Alternative", 123 "Ska", 124 "Death Metal", 125 "Pranks", 126 "Soundtrack", 127 "Euro-Techno", 128 "Ambient", 129 "Trip-Hop", 130 "Vocal", 131 "Jazz+Funk", 132 "Fusion", 133 "Trance", 134 "Classical", 135 "Instrumental", 136 "Acid", 137 "House", 138 "Game", 139 "Sound Clip", 140 "Gospel", 141 "Noise", 142 "AlternRock", 143 "Bass", 144 "Soul", 145 "Punk", 146 "Space", 147 "Meditative", 148 "Instrumental Pop", 149 "Instrumental Rock", 150 "Ethnic", 151 "Gothic", 152 "Darkwave", 153 "Techno-Industrial", 154 "Electronic", 155 "Pop-Folk", 156 "Eurodance", 157 "Dream", 158 "Southern Rock", 159 "Comedy", 160 "Cult", 161 "Gangsta", 162 "Top 40", 163 "Christian Rap", 164 "Pop/Funk", 165 "Jungle", 166 "Native American", 167 "Cabaret", 168 "New Wave", 169 "Psychadelic", 170 "Rave", 171 "Showtunes", 172 "Trailer", 173 "Lo-Fi", 174 "Tribal", 175 "Acid Punk", 176 "Acid Jazz", 177 "Polka", 178 "Retro", 179 "Musical", 180 "Rock & Roll", 181 "Hard Rock", // 79 182 "Folk", 183 "Folk-Rock", 184 "National Folk", 185 "Swing", 186 "Fast Fusion", 187 "Bebob", 188 "Latin", 189 "Revival", 190 "Celtic", 191 "Bluegrass", 192 "Avantgarde", 193 "Gothic Rock", 194 "Progressive Rock", 195 "Psychedelic Rock", 196 "Symphonic Rock", 197 "Slow Rock", 198 "Big Band", 199 "Chorus", 200 "Easy Listening", 201 "Acoustic", 202 "Humour", 203 "Speech", 204 "Chanson", 205 "Opera", 206 "Chamber Music", 207 "Sonata", 208 "Symphony", 209 "Booty Bass", 210 "Primus", 211 "Porn Groove", 212 "Satire", 213 "Slow Jam", 214 "Club", 215 "Tango", 216 "Samba", 217 "Folklore", 218 "Ballad", 219 "Power Ballad", 220 "Rhythmic Soul", 221 "Freestyle", 222 "Duet", 223 "Punk Rock", 224 "Drum Solo", 225 "A capella", 226 "Euro-House", 227 "Dance Hall", // 125 228 "Goa", 229 "Drum & Bass", 230 "Club-House", 231 "Hardcore", 232 "Terror", 233 "Indie", 234 "BritPop", 235 "Negerpunk", 236 "Polsk Punk", 237 "Beat", 238 "Christian", 239 "Heavy Metal", 240 "Black Metal", 241 "Crossover", 242 "Contemporary", 243 "Christian Rock", 244 "Merengue", 245 "Salsa", 246 "Thrash Metal", 247 "Anime", 248 "JPop", 249 "Synthpop", 250 }); 251 252 int global_test_counter; 253 string path = "id3v1/"; 254 array(string) m3u = ({}); 255 256 // Test template 257 class tt { 258 string desc; 259 int complience; 260 261 string fn() { 262 string c = ""; 263 if(complience==1) c="_W"; // Warning 264 if(complience>1) c="_F"; // Failure 265 return sprintf("id3v1_%03d_%s%s.mp3", global_test_counter, sect, c); 266 } 267 268 string tag() { return ""; }; 269 270 void create() { 271 global_test_counter++; 272 m3u += ({ fn() }); 273 Stdio.write_file( path + fn(), 274 generate_silence()+tag() ); 275 write("Test case %d\n", global_test_counter); 276 write("Generated test file %O\n", fn()); 277 werror("Generated test file %O\n", fn()); 278 if(!desc) error("Missing description.\n"); 279 write("%-=70s\n", desc); 280 if(complience>1) write("Test case should generate a decoding failure.\n"); 281 if(complience==1) write("Test case might generate a decoding warning.\n"); 282 write("Tag structure\n"); 283 show_tag(tag()); 284 write("\n"); 285 } 286 } 287 288 string sect; 289 290 void tests() { 291 292 sect = "basic"; 293 write("Test cases that tests basic tag capabilities.\n\n"); 294 295 class { 296 inherit tt; 297 string desc = "An ordinary ID3v1 tag with all fields set to a " 298 "plauseble value."; 299 string tag() { 300 object tag = ID3_1(); 301 tag->title = pad("Title"); 302 tag->artist = pad("Artist"); 303 tag->album = pad("Album"); 304 tag->year = "2003"; 305 tag->genre = 7; 306 tag->comment = pad("Comment"); 307 return (string)tag; 308 } 309 }(); 310 311 class { 312 inherit tt; 313 string desc = "An ordinary ID3v1.1 tag with all fields set to a " 314 "plauseble value."; 315 string tag() { 316 object tag = ID3_11(); 317 tag->title = pad("Title"); 318 tag->artist = pad("Artist"); 319 tag->album = pad("Album"); 320 tag->year = "2003"; 321 tag->genre = 7; 322 tag->comment = pad("Comment", 28); 323 tag->track = 12; 324 return (string)tag; 325 } 326 }(); 327 328 class { 329 inherit tt; 330 string desc = "An ID3 tag with its header in the wrong case."; 331 int complience = 2; 332 string tag() { 333 object tag = ID3_1(); 334 tag->head = "tag"; 335 return (string)tag; 336 } 337 }(); 338 339 class { 340 inherit tt; 341 string desc = "An ID3 tag with all fields set to shortest legal value."; 342 string tag() { 343 object tag = ID3_1(); 344 return (string)tag; 345 } 346 }(); 347 348 class { 349 inherit tt; 350 string desc = "An ID3v1 tag with all fields set to longest value."; 351 string tag() { 352 object tag = ID3_1(); 353 tag->title = "a"*29+"A"; 354 tag->artist = "b"*29+"B"; 355 tag->album = "c"*29+"C"; 356 tag->comment = "d"*29+"D"; 357 return (string)tag; 358 } 359 }(); 360 361 class { 362 inherit tt; 363 string desc = "An ID3v1.1 tag with all fields set to longest value."; 364 string tag() { 365 object tag = ID3_11(); 366 tag->title = "a"*29+"A"; 367 tag->artist = "b"*29+"B"; 368 tag->album = "c"*29+"C"; 369 tag->comment = "d"*27+"D"; 370 tag->track = 1; 371 return (string)tag; 372 } 373 }(); 374 375 class { 376 inherit tt; 377 string desc = "An ID3v1 tag with junk after string terminator. " 378 "The junk should not show up for the user (i.e. only the string " 379 "12345 should show up)."; 380 int complience = 1; 381 string tag() { 382 object tag = ID3_1(); 383 tag->title = "12345" + "\0"*21 + "junk"; 384 tag->artist = "12345" + "\0"*21 + "junk"; 385 tag->album = "12345" + "\0"*21 + "junk"; 386 tag->comment = "12345" + "\0"*21 + "junk"; 387 return (string)tag; 388 } 389 }(); 390 391 class { 392 inherit tt; 393 string desc = "An ID3v1 tag with junk after string terminator. " 394 "The junk should not show up for the user (i.e. only the string " 395 "12345 should show up)."; 396 int complience = 1; 397 string tag() { 398 object tag = ID3_11(); 399 tag->title = "12345" + "\0"*21 + "junk"; 400 tag->artist = "12345" + "\0"*21 + "junk"; 401 tag->album = "12345" + "\0"*21 + "junk"; 402 tag->comment = "12345" + "\0"*19 + "junk"; 403 tag->track = 1; 404 return (string)tag; 405 } 406 }(); 407 408 class { 409 inherit tt; 410 string desc = "An ID3 tag with the track number set to max (255)."; 411 string tag() { 412 object tag = ID3_11(); 413 tag->track = 255; 414 return (string)tag; 415 } 416 }(); 417 418 sect = "year"; 419 write("\nDifferent tests that tries to break the year parser.\n\n"); 420 421 class Year { 422 inherit tt; 423 string year; 424 string tag() { 425 object tag = ID3_1(); 426 tag->year = year; 427 return (string)tag; 428 } 429 }; 430 431 class { 432 inherit Year; 433 string desc = "An ID3 tag with the year set to 0000.\n"; 434 string year = "0000"; 435 }(); 436 437 class { 438 inherit Year; 439 string desc = "An ID3 tag with the year set to 9999.\n"; 440 string year = "9999"; 441 }(); 442 443 class { 444 inherit Year; 445 string desc = "An ID3 tag with the year set to \" 3\".\n"; 446 int complience = 2; 447 string year = " 3"; 448 }(); 449 450 class { 451 inherit Year; 452 string desc = "An ID3 tag with the year set to \"112\\0\".\n"; 453 int complience = 2; 454 string year = "112\0"; 455 }(); 456 457 class { 458 inherit Year; 459 string desc = "An ID3 tag with the year set to NULL.\n"; 460 int complience = 2; 461 string year = "\0\0\0\0"; 462 }(); 463 464 sect = "genre"; 465 write("\nTests that tests the genre capabilities.\n\n"); 466 foreach(id3_genres; int i; string name) { 467 class { 468 inherit tt; 469 string name; 470 int genre; 471 string desc = "An ID3 tag with genre set to "; 472 string tag() { 473 object tag = ID3_1(); 474 tag->title = pad(name); 475 tag->genre = genre; 476 return (string)tag; 477 } 478 void create(string _name, int _genre) { 479 name = _name; 480 genre = _genre; 481 desc += name+"."; 482 if(genre>79) { 483 complience = 1; 484 desc += " Only the first 80 genres are defined in the original ID3."; 485 } 486 ::create(); 487 } 488 }(name, i); 489 } 490 491 for(int i=sizeof(id3_genres); i<256; i++) 492 class { 493 inherit tt; 494 string desc = "An ID3 tag with genre set to "; 495 int complience = 2; 496 int g; 497 string tag() { 498 object tag = ID3_1(); 499 tag->title = pad("Unknown/"+g); 500 tag->genre = g; 501 return (string)tag; 502 } 503 void create(int _g) { 504 g = _g; 505 desc += g + "."; 506 ::create(); 507 } 508 }(i); 509 510 sect = "extra"; 511 write("\nTests to test charset decoding and similar optional " 512 "capabilities.\n\n"); 513 514 class { 515 inherit tt; 516 string desc = "Title with 8-bit iso-8859-1 characters (would be written " 517 "as räksmörgås in HTML)."; 518 string tag() { 519 object tag = ID3_1(); 520 tag->title = pad("räksmörgås"); 521 tag->artist = pad("räksmörgås"); 522 tag->album = pad("räksmörgås"); 523 tag->comment = pad("räksmörgås"); 524 return (string)tag; 525 } 526 }(); 527 528 class { 529 inherit tt; 530 string desc = "Title with utf-8-encoded 8-bit string (would be written " 531 "as räksmörgås in HTML)."; 532 string tag() { 533 object tag = ID3_1(); 534 tag->title = pad(string_to_utf8("räksmörgås")); 535 tag->artist = pad(string_to_utf8("räksmörgås")); 536 tag->album = pad(string_to_utf8("räksmörgås")); 537 tag->comment = pad(string_to_utf8("räksmörgås")); 538 return (string)tag; 539 } 540 }(); 541 542 class { 543 inherit tt; 544 string desc = "Comment field with http://-style URL."; 545 string tag() { 546 object tag = ID3_1(); 547 tag->comment = pad("http://www.id3.org/"); 548 return (string)tag; 549 } 550 }(); 551 552 class { 553 inherit tt; 554 string desc = "Comment field with unprefixed URL."; 555 string tag() { 556 object tag = ID3_1(); 557 tag->comment = pad("www.id3.org/"); 558 return (string)tag; 559 } 560 }(); 561 } 562 563 #define TEE(X...) do { werror(X); write(X); } while(0) 564 565 void main(int num, array args) { 566 567 // FIXME: 568 // --output-dir Where to put the files 569 // --only-correct Don't create W or F files. 570 // --length How many seconds of MP3 silence 571 572 TEE("ID3v1/ID3v1.1 test suite\n"); 573 TEE("Copyright (c) 2003 Martin Nilsson\n"); 574 TEE("Output generated %s\n", Calendar.now()->format_mtime()); 575 TEE("Generated with %s\n\n", version()); 576 tests(); 577 Stdio.write_file(path+"tags.m3u", m3u*"\n"); 578 }