1 /* 2 * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "cdda.h" 8 9 #include <KernelExport.h> 10 #include <device/scsi.h> 11 12 #include <ctype.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 17 struct cdtext_pack_data { 18 uint8 id; 19 uint8 track; 20 uint8 number; 21 uint8 character_position : 4; 22 uint8 block_number : 3; 23 uint8 double_byte : 1; 24 char text[12]; 25 uint8 crc[2]; 26 } _PACKED; 27 28 enum { 29 kTrackID = 0x80, 30 kArtistID = 0x81, 31 kMessageID = 0x85, 32 }; 33 34 static const uint32 kBufferSize = 16384; 35 static const uint32 kSenseSize = 1024; 36 37 38 // #pragma mark - string functions 39 40 41 static char * 42 copy_string(const char *string) 43 { 44 if (string == NULL || !string[0]) 45 return NULL; 46 47 return strdup(string); 48 } 49 50 51 static bool 52 is_garbage(char c) 53 { 54 return isspace(c) || c == '-' || c == '/' || c == '\\'; 55 } 56 57 58 static void 59 sanitize_string(char *&string) 60 { 61 if (string == NULL) 62 return; 63 64 // strip garbage at the start 65 66 uint32 length = strlen(string); 67 uint32 garbage = 0; 68 while (is_garbage(string[garbage])) { 69 garbage++; 70 } 71 72 length -= garbage; 73 if (garbage) 74 memmove(string, string + garbage, length + 1); 75 76 // strip garbage from the end 77 78 while (length > 1 && isspace(string[length - 1])) { 79 string[--length] = '\0'; 80 } 81 82 if (!string[0]) { 83 // free string if it's empty 84 free(string); 85 string = NULL; 86 } 87 } 88 89 90 //! Finds the first occurrence of \a find in \a string, ignores case. 91 static char* 92 find_string(const char *string, const char *find) 93 { 94 if (string == NULL || find == NULL) 95 return NULL; 96 97 char first = tolower(find[0]); 98 if (first == '\0') 99 return (char *)string; 100 101 int32 findLength = strlen(find) - 1; 102 find++; 103 104 for (; string[0]; string++) { 105 if (tolower(string[0]) != first) 106 continue; 107 if (strncasecmp(string + 1, find, findLength) == 0) 108 return (char *)string; 109 } 110 111 return NULL; 112 } 113 114 115 static void 116 cut_string(char *string, char *cut) 117 { 118 if (string == NULL || cut == NULL) 119 return; 120 121 char *found = find_string(string, cut); 122 if (found != NULL) { 123 uint32 foundLength = strlen(found); 124 uint32 cutLength = strlen(cut); 125 memmove(found, found + cutLength, foundLength + 1 - cutLength); 126 } 127 } 128 129 130 static void 131 sanitize_album(cdtext &text) 132 { 133 cut_string(text.album, text.artist); 134 sanitize_string(text.album); 135 136 if (text.album != NULL && !strcasecmp(text.album, "My CD")) { 137 // don't laugh, people really do that! 138 free(text.album); 139 text.album = NULL; 140 } 141 142 if ((text.artist == NULL || text.artist[0] == NULL) && text.album != NULL) { 143 // try to extract artist from album 144 char *space = strstr(text.album, " "); 145 if (space != NULL) { 146 space[0] = '\0'; 147 text.artist = text.album; 148 text.album = copy_string(space + 2); 149 150 sanitize_string(text.artist); 151 sanitize_string(text.album); 152 } 153 } 154 } 155 156 157 static void 158 sanitize_titles(cdtext &text) 159 { 160 for (uint8 i = 0; i < text.track_count; i++) { 161 cut_string(text.titles[i], "(Album Version)"); 162 sanitize_string(text.titles[i]); 163 sanitize_string(text.artists[i]); 164 165 if (text.artists[i] != NULL && text.artist != NULL 166 && !strcasecmp(text.artists[i], text.artist)) { 167 // if the title artist is the same as the main artist, remove it 168 free(text.artists[i]); 169 text.artists[i] = NULL; 170 } 171 172 if (text.titles[i] != NULL && text.titles[i][0] == '\t' && i > 0) 173 text.titles[i] = copy_string(text.titles[i - 1]); 174 } 175 } 176 177 178 static bool 179 single_case(const char *string, bool &upper, bool &first) 180 { 181 if (string == NULL) 182 return true; 183 184 while (string[0]) { 185 while (!isalpha(string[0])) { 186 string++; 187 } 188 189 if (first) { 190 upper = isupper(string[0]) != 0; 191 first = false; 192 } else if ((isupper(string[0]) != 0) ^ upper) 193 return false; 194 195 string++; 196 } 197 198 return true; 199 } 200 201 202 static void 203 capitalize_string(char *string) 204 { 205 if (string == NULL) 206 return; 207 208 bool newWord = isalpha(string[0]) || isspace(string[0]); 209 while (string[0]) { 210 if (isalpha(string[0])) { 211 if (newWord) { 212 string[0] = toupper(string[0]); 213 newWord = false; 214 } else 215 string[0] = tolower(string[0]); 216 } else if (string[0] != '\'') 217 newWord = true; 218 219 string++; 220 } 221 } 222 223 224 static void 225 correct_case(cdtext &text) 226 { 227 // check if all titles share a single case 228 bool first = true; 229 bool upper; 230 if (!single_case(text.album, upper, first) 231 || !single_case(text.artist, upper, first)) 232 return; 233 234 for (int32 i = 0; i < text.track_count; i++) { 235 if (!single_case(text.titles[i], upper, first) 236 || !single_case(text.artists[i], upper, first)) 237 return; 238 } 239 240 // If we get here, everything has a single case; we fix that 241 // and capitalize each word 242 243 capitalize_string(text.album); 244 capitalize_string(text.artist); 245 for (int32 i = 0; i < text.track_count; i++) { 246 capitalize_string(text.titles[i]); 247 capitalize_string(text.artists[i]); 248 } 249 } 250 251 252 // #pragma mark - CD-Text 253 254 255 cdtext::cdtext() 256 : 257 artist(NULL), 258 album(NULL), 259 genre(NULL), 260 track_count(0) 261 { 262 memset(titles, 0, sizeof(titles)); 263 memset(artists, 0, sizeof(artists)); 264 } 265 266 267 cdtext::~cdtext() 268 { 269 free(album); 270 free(artist); 271 free(genre); 272 273 for (uint8 i = 0; i < track_count; i++) { 274 free(titles[i]); 275 free(artists[i]); 276 } 277 } 278 279 280 static bool 281 is_string_id(uint8 id) 282 { 283 return id >= kTrackID && id <= kMessageID; 284 } 285 286 287 static bool 288 parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft, 289 cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state, 290 char *buffer, size_t &length) 291 { 292 if (packLeft < sizeof(cdtext_pack_data)) 293 return false; 294 295 uint8 number = pack->number; 296 size_t size = length; 297 298 if (state != 0) { 299 // we had a terminated string and a missing track 300 track++; 301 memcpy(buffer, lastPack->text + state, 12 - state); 302 if (pack->track - track == 1) 303 state = 0; 304 else 305 state += strnlen(buffer, 12 - state); 306 return true; 307 } 308 309 id = pack->id; 310 track = pack->track; 311 buffer[0] = '\0'; 312 length = 0; 313 314 size_t position = pack->character_position; 315 if (position > 0 && lastPack != NULL) { 316 memcpy(buffer, &lastPack->text[12 - position], position); 317 length = position; 318 } 319 320 while (id == pack->id && track == pack->track) { 321 #if 1 322 dprintf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number, 323 pack->double_byte, pack->block_number, pack->character_position); 324 for (int32 i = 0; i < 12; i++) { 325 if (isprint(pack->text[i])) 326 dprintf("%c", pack->text[i]); 327 } 328 dprintf("\n"); 329 #endif 330 if (is_string_id(id)) { 331 // TODO: support double byte characters 332 if (length + 12 < size) { 333 memcpy(buffer + length, pack->text, 12); 334 length += 12; 335 } 336 } 337 338 packLeft -= sizeof(cdtext_pack_data); 339 if (packLeft < sizeof(cdtext_pack_data)) 340 return false; 341 342 lastPack = pack; 343 number++; 344 pack++; 345 346 if (pack->number != number) 347 return false; 348 } 349 350 if (id == pack->id) { 351 length -= pack->character_position; 352 if (length >= size) 353 length = size - 1; 354 buffer[length] = '\0'; 355 356 if (pack->track > lastPack->track + 1) { 357 // there is a missing track 358 for (int32 i = 0; i < 12; i++) { 359 if (lastPack->text[i] == '\0') { 360 state = i + (lastPack->double_byte ? 2 : 1); 361 break; 362 } 363 } 364 } 365 } 366 367 // TODO: convert text to UTF-8 368 return true; 369 } 370 371 372 static void 373 dump_cdtext(cdtext &text) 374 { 375 if (text.album) 376 dprintf("Album: \"%s\"\n", text.album); 377 if (text.artist) 378 dprintf("Artist: \"%s\"\n", text.artist); 379 for (uint8 i = 0; i < text.track_count; i++) { 380 dprintf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i], 381 text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "", 382 text.artists[i] ? ")" : ""); 383 } 384 } 385 386 387 static void 388 dump_toc(scsi_toc_toc *toc) 389 { 390 int32 numTracks = toc->last_track + 1 - toc->first_track; 391 392 for (int32 i = 0; i < numTracks; i++) { 393 scsi_toc_track& track = toc->tracks[i]; 394 scsi_cd_msf& next = toc->tracks[i + 1].start.time; 395 // the last track is always lead-out 396 scsi_cd_msf& start = toc->tracks[i].start.time; 397 scsi_cd_msf length; 398 399 uint64 diff = next.minute * kFramesPerMinute 400 + next.second * kFramesPerSecond + next.frame 401 - start.minute * kFramesPerMinute 402 - start.second * kFramesPerSecond - start.frame; 403 length.minute = diff / kFramesPerMinute; 404 length.second = (diff % kFramesPerMinute) / kFramesPerSecond; 405 length.frame = diff % kFramesPerSecond; 406 407 dprintf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n", 408 track.track_number, start.minute, start.second, start.frame, 409 length.minute, length.second, length.frame); 410 } 411 } 412 413 414 static status_t 415 read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer, 416 size_t bufferSize) 417 { 418 raw_device_command raw; 419 uint8 *senseData = (uint8 *)malloc(kSenseSize); 420 if (senseData == NULL) 421 return B_NO_MEMORY; 422 423 memset(&raw, 0, sizeof(raw_device_command)); 424 memset(senseData, 0, kSenseSize); 425 memset(buffer, 0, bufferSize); 426 427 scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command; 428 toc.opcode = SCSI_OP_READ_TOC; 429 toc.time = 1; 430 toc.format = format; 431 toc.track = track; 432 toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize); 433 434 raw.command_length = 10; 435 raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL 436 | B_RAW_DEVICE_SHORT_READ_VALID; 437 raw.scsi_status = 0; 438 raw.cam_status = 0; 439 raw.data = buffer; 440 raw.data_length = bufferSize; 441 raw.timeout = 10000000LL; // 10 secs 442 raw.sense_data = senseData; 443 raw.sense_data_length = sizeof(kSenseSize); 444 445 if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0 446 && raw.scsi_status == 0 && raw.cam_status == 1) { 447 free(senseData); 448 return B_OK; 449 } 450 451 free(senseData); 452 return B_ERROR; 453 } 454 455 456 // #pragma mark - exported functions 457 458 459 status_t 460 read_cdtext(int fd, struct cdtext &cdtext) 461 { 462 uint8 *buffer = (uint8 *)malloc(kBufferSize); 463 if (buffer == NULL) 464 return B_NO_MEMORY; 465 466 // do it twice, just in case... 467 // (at least my CD-ROM sometimes returned broken data on first try) 468 read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, 469 kBufferSize); 470 if (read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, 471 kBufferSize) < B_OK) { 472 free(buffer); 473 return B_ERROR; 474 } 475 476 scsi_toc_general *header = (scsi_toc_general *)buffer; 477 478 size_t packLength = B_BENDIAN_TO_HOST_INT16(header->data_length) - 2; 479 cdtext_pack_data *pack = (cdtext_pack_data *)(header + 1); 480 cdtext_pack_data *lastPack = NULL; 481 uint8 state = 0; 482 char text[256]; 483 484 while (true) { 485 size_t length = sizeof(text); 486 uint8 id, track; 487 488 if (!parse_pack_data(pack, packLength, lastPack, id, track, 489 state, text, length)) 490 break; 491 492 switch (id) { 493 case kTrackID: 494 if (track == 0) { 495 if (cdtext.album == NULL) 496 cdtext.album = copy_string(text); 497 } else if (track <= kMaxTracks) { 498 if (cdtext.titles[track - 1] == NULL) 499 cdtext.titles[track - 1] = copy_string(text); 500 if (track > cdtext.track_count) 501 cdtext.track_count = track; 502 } 503 break; 504 505 case kArtistID: 506 if (track == 0) { 507 if (cdtext.artist == NULL) 508 cdtext.artist = copy_string(text); 509 } else if (track <= kMaxTracks) { 510 if (cdtext.artists[track - 1] == NULL) 511 cdtext.artists[track - 1] = copy_string(text); 512 } 513 break; 514 515 default: 516 if (is_string_id(id)) 517 dprintf("UNKNOWN %u: \"%s\"\n", id, text); 518 break; 519 } 520 } 521 522 free(buffer); 523 524 sanitize_string(cdtext.artist); 525 sanitize_album(cdtext); 526 sanitize_titles(cdtext); 527 correct_case(cdtext); 528 529 dump_cdtext(cdtext); 530 return B_OK; 531 } 532 533 534 status_t 535 read_table_of_contents(int fd, scsi_toc_toc *toc, size_t length) 536 { 537 status_t status = read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC, 538 (uint8*)toc, length); 539 if (status < B_OK) 540 return status; 541 542 dump_toc(toc); 543 return B_OK; 544 } 545 546