00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00035 #include "playlist.h"
00036 #include "html.h"
00037
00046 mu_ent *new_ent(apr_pool_t *pool, mu_ent *head)
00047 {
00048 mu_ent *p = (mu_ent *) ap_pcalloc(pool, sizeof(mu_ent));
00049 memset(p, 0, sizeof(mu_ent));
00050 p->next = head;
00051 return p;
00052 }
00053
00057 #undef DETECT_BY_CONTENT_NOT_BORKEN
00058 #ifdef DETECT_BY_CONTENT_NOT_BORKEN
00059
00060
00061
00062
00063 static unsigned long mpg123_convert_to_header(unsigned char * buf)
00064 {
00065 return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
00066 }
00067
00077 static short mpg123_head_check(unsigned long head)
00078 {
00079 if ((head & 0xffe00000) != 0xffe00000)
00080 return FALSE;
00081 if (!((head >> 17) & 3))
00082 return FALSE;
00083 if (((head >> 12) & 0xf) == 0xf)
00084 return FALSE;
00085 if (!((head >> 12) & 0xf))
00086 return FALSE;
00087 if (((head >> 10) & 0x3) == 0x3)
00088 return FALSE;
00089 if (((head >> 19) & 1) == 1 && ((head >> 17) & 3) == 3 && ((head >> 16) & 1) == 1)
00090 return FALSE;
00091 if ((head & 0xffff0000) == 0xfffe0000)
00092 return FALSE;
00093
00094 return TRUE;
00095 }
00096 #endif
00097
00101 const char *mpg123_id3_genres[GENRE_MAX] = {
00102 "Blues", "Classic Rock", "Country", "Dance",
00103 "Disco", "Funk", "Grunge", "Hip-Hop",
00104 "Jazz", "Metal", "New Age", "Oldies",
00105 "Other", "Pop", "R&B", "Rap", "Reggae",
00106 "Rock", "Techno", "Industrial", "Alternative",
00107 "Ska", "Death Metal", "Pranks", "Soundtrack",
00108 "Euro-Techno", "Ambient", "Trip-Hop", "Vocal",
00109 "Jazz+Funk", "Fusion", "Trance", "Classical",
00110 "Instrumental", "Acid", "House", "Game",
00111 "Sound Clip", "Gospel", "Noise", "Alt",
00112 "Bass", "Soul", "Punk", "Space",
00113 "Meditative", "Instrumental Pop",
00114 "Instrumental Rock", "Ethnic", "Gothic",
00115 "Darkwave", "Techno-Industrial", "Electronic",
00116 "Pop-Folk", "Eurodance", "Dream",
00117 "Southern Rock", "Comedy", "Cult",
00118 "Gangsta Rap", "Top 40", "Christian Rap",
00119 "Pop/Funk", "Jungle", "Native American",
00120 "Cabaret", "New Wave", "Psychedelic", "Rave",
00121 "Showtunes", "Trailer", "Lo-Fi", "Tribal",
00122 "Acid Punk", "Acid Jazz", "Polka", "Retro",
00123 "Musical", "Rock & Roll", "Hard Rock", "Folk",
00124 "Folk/Rock", "National Folk", "Swing",
00125 "Fast-Fusion", "Bebob", "Latin", "Revival",
00126 "Celtic", "Bluegrass", "Avantgarde",
00127 "Gothic Rock", "Progressive Rock",
00128 "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
00129 "Big Band", "Chorus", "Easy Listening",
00130 "Acoustic", "Humour", "Speech", "Chanson",
00131 "Opera", "Chamber Music", "Sonata", "Symphony",
00132 "Booty Bass", "Primus", "Porn Groove",
00133 "Satire", "Slow Jam", "Club", "Tango",
00134 "Samba", "Folklore", "Ballad", "Power Ballad",
00135 "Rhythmic Soul", "Freestyle", "Duet",
00136 "Punk Rock", "Drum Solo", "A Cappella",
00137 "Euro-House", "Dance Hall", "Goa",
00138 "Drum & Bass", "Club-House", "Hardcore",
00139 "Terror", "Indie", "BritPop", "Negerpunk",
00140 "Polsk Punk", "Beat", "Christian Gangsta Rap",
00141 "Heavy Metal", "Black Metal", "Crossover",
00142 "Contemporary Christian", "Christian Rock",
00143 "Merengue", "Salsa", "Thrash Metal",
00144 "Anime", "JPop", "Synthpop"
00145 };
00146
00154 static short mpg123_mp3_ext_check(char *filename)
00155 {
00156 char *ext = strrchr(filename, '.');
00157 if (ext && (!strncasecmp(ext, ".mp2", 4) || !strncasecmp(ext, ".mp3", 4)))
00158 return FALSE;
00159 return TRUE;
00160 }
00161
00176 static id3_utf8_t *utf8_id3tag_findframe(struct id3_tag *tag, char const *frameid,
00177 unsigned short index)
00178 {
00179 const struct id3_frame *frame = NULL;
00180 const union id3_field *field = NULL;
00181 const id3_ucs4_t *ucs4 = NULL;
00182
00183 if ((frame = id3_tag_findframe (tag, frameid, index))) {
00184 field = &frame->fields[1];
00185 if (id3_field_getnstrings(field)
00186 && (ucs4 = id3_field_getstrings(field, index))) {
00187 return(id3_ucs4_utf8duplicate(ucs4));
00188
00189
00190 }
00191 }
00192 return NULL;
00193 }
00194
00211 mu_ent *make_ogg_entry(apr_pool_t *pool, mu_ent *head,
00212 FILE *in, mu_config * conf, mu_ent_names *names, request_rec * r)
00213 {
00214 mu_ent *p = head;
00215 OggVorbis_File vf;
00216 vorbis_comment *comment = NULL;
00217 char *t;
00218
00219 if (!(ov_open(in, &vf, NULL, 0))) {
00220 p = new_ent(pool, head);
00221 p->filetype = FT_OGG;
00222
00223 if ((comment = ov_comment(&vf, -1))) {
00224 if ((t = vorbis_comment_query(comment, "album", 0)))
00225 p->album = ap_pstrdup(pool, t);
00226 if ((t = vorbis_comment_query(comment, "artist", 0)))
00227 p->artist = ap_pstrdup(pool, t);
00228 if ((t = vorbis_comment_query(comment, "title", 0)))
00229 p->title = ap_pstrdup(pool, t);
00230 if ((t = vorbis_comment_query(comment, "tracknumber", 0)))
00231 p->track = atoi(t);
00232 if ((t = vorbis_comment_query(comment, "date", 0)))
00233 p->date = atoi(t);
00234 if ((t = vorbis_comment_query(comment, "tpos", 0)))
00235 p->posn = atoi(t);
00236 if ((t = vorbis_comment_query(comment, "genre", 0)))
00237 p->genre = ap_pstrdup(pool, t);
00238 }
00239
00240 if (conf->options & (MI_QUICKPL)) {
00241 p->bitrate = p->length = 0;
00242 }
00243 else {
00244 p->bitrate = (long)ov_bitrate(&vf, -1);
00245 p->length = (long)ov_time_total(&vf, -1);
00246 }
00247
00248 ov_clear(&vf);
00249
00250 }
00251
00252 return p;
00253 }
00254
00273 mu_ent *make_mp3_entry(apr_pool_t *pool, mu_ent *head,
00274 FILE *in, mu_config * conf, mu_ent_names *names, request_rec * r)
00275 {
00276 mu_ent *p = head;
00277
00278 struct id3_file *id3struct = NULL;
00279 struct id3_tag *tag = NULL;
00280 id3_utf8_t *utf8 = NULL;
00281
00282 struct stat filestat;
00283
00284 struct mad_stream madstream;
00285 struct mad_frame madframe;
00286 unsigned char madinput_buffer[INPUT_BUFFER_SIZE];
00287 unsigned short mp3gid = 0, isd = 0;
00288
00289 #ifdef DETECT_BY_CONTENT_NOT_BORKEN
00290 unsigned char tmp[4];
00291
00292 if (fread(tmp, 1, 4, in) != 4)
00293 goto exit;
00294
00295 if (mpg123_head_check(mpg123_convert_to_header(tmp)))
00296 #else
00297 if (mpg123_mp3_ext_check(names->filename))
00298 #endif
00299 goto exit;
00300
00301 mad_stream_init(&madstream);
00302 mad_frame_init(&madframe);
00303
00304 fstat(fileno(in), &filestat);
00305
00306 p = new_ent(pool, head);
00307 p->filetype = FT_MP3;
00308 p->size = filestat.st_size;
00309
00310 if (conf->options & (MI_QUICKPL)) {
00311 p->bitrate = p->length = 0;
00312 }
00313 else {
00314
00315
00316 do {
00317 if (madstream.buffer == NULL || madstream.error == MAD_ERROR_BUFLEN) {
00318 size_t madread_size, remaining;
00319 unsigned char *madread_start;
00320
00321 if (madstream.next_frame != NULL) {
00322 remaining = madstream.bufend-madstream.next_frame;
00323 memmove(madinput_buffer, madstream.next_frame, remaining);
00324 madread_start=madinput_buffer + remaining;
00325 madread_size = INPUT_BUFFER_SIZE - remaining;
00326 }
00327 else {
00328 madread_size = INPUT_BUFFER_SIZE;
00329 madread_start = madinput_buffer;
00330 remaining = 0;
00331 }
00332
00333 madread_size = fread(madread_start, 1, madread_size, in);
00334 if (madread_size <= 0) {
00335 ap_log_printf(r->server, "[musicindex] DBG: maderror madread_size <= 0 on %s", names->filename);
00336 goto exit;
00337
00338 }
00339
00340 mad_stream_buffer(&madstream, madinput_buffer, madread_size + remaining);
00341 madstream.error = 0;
00342 }
00343
00344
00345
00346 if (mad_frame_decode(&madframe,&madstream)) {
00347 if (MAD_RECOVERABLE(madstream.error)) {
00348
00349 continue;
00350 }
00351 else {
00352 if (madstream.error == MAD_ERROR_BUFLEN)
00353 continue;
00354 else {
00355 ap_log_printf(r->server, "[musicindex] DBG: maderror madstream.error unrecoverable on %s", names->filename);
00356
00357 goto exit;
00358 }
00359 }
00360 }
00361
00362 p->bitrate = madframe.header.bitrate * 1.024;
00363 p->length = filestat.st_size / (madframe.header.bitrate / 8);
00364 break;
00365 } while (1);
00366 }
00367
00368
00369 if ((id3struct = id3_file_open(names->filename, ID3_FILE_MODE_READONLY))) {
00370 if ((tag = id3_file_tag(id3struct)) && (tag->frames)) {
00371 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_TITLE, 0))) {
00372 p->title = ap_pstrdup(pool, (char *)utf8);
00373 free(utf8);
00374 }
00375
00376 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_ARTIST, 0))) {
00377 p->artist = ap_pstrdup(pool, (char *)utf8);
00378 free(utf8);
00379 }
00380
00381 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_ALBUM, 0))) {
00382 p->album = ap_pstrdup(pool, (char *)utf8);
00383 free(utf8);
00384 }
00385
00386 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_YEAR, 0))) {
00387 p->date = atoi((char *)utf8);
00388 free(utf8);
00389 }
00390
00391 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_TRACK, 0))) {
00392 p->track = atoi((char *)utf8);
00393 free(utf8);
00394 }
00395
00396 if ((utf8 = utf8_id3tag_findframe(tag, "TPOS", 0))) {
00397 p->posn = atoi((char *)utf8);
00398 free(utf8);
00399 }
00400
00401 if ((utf8 = utf8_id3tag_findframe(tag, "TLEN", 0))) {
00402 if ((atoi((char *)utf8) / 1000) > 0)
00403 p->length = atoi((char *)utf8) / 1000;
00404 free(utf8);
00405 }
00406 if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_GENRE, 0))) {
00407 if (*utf8 == '(') {
00408 (*utf8)++;
00409 mp3gid = atoi((char *)utf8);
00410 (*utf8)--;
00411 isd = 1;
00412 }
00413 else if ((isd = isdigit(*utf8)))
00414 mp3gid = atoi((char *)utf8);
00415
00416 if (isd && (mp3gid < GENRE_MAX))
00417 p->genre = ap_pstrdup(pool, mpg123_id3_genres[mp3gid]);
00418 else
00419 p->genre = ap_pstrdup(pool, (char *)utf8);
00420
00421 free(utf8);
00422 }
00423
00424 }
00425 id3_file_close (id3struct);
00426 }
00427
00428 exit:
00429 return p;
00430 }
00431
00450 mu_ent *make_music_entry(apr_pool_t *pool, request_rec * r,
00451 mu_ent *head, mu_config * conf, mu_ent_names *names)
00452 {
00453 DIR *dir;
00454 struct DIR_TYPE *dstruct;
00455 mu_ent *p = head;
00456 FILE *in = NULL;
00457 unsigned short i, checkresult = 0;
00458 char *t, *fn, *uri;
00459 #ifndef NO_CACHE
00460 mu_cache_data *cachedata = NULL;
00461 #endif
00462
00463 if (!names) {
00464 names = ap_palloc(r->pool, sizeof(mu_ent_names));
00465 names->filename = ap_palloc(r->pool, MAX_STRING);
00466 names->uri = ap_palloc(r->pool, MAX_STRING);
00467 strcpy(names->filename, r->filename);
00468 strcpy(names->uri, r->parsed_uri.path);
00469 }
00470
00471 t = names->uri + strlen(names->uri)-1;
00472 while (*t != '/')
00473 t--;
00474 t++;
00475 if (*t == '.')
00476 return p;
00477
00478 if ((in = fopen(names->filename, "r"))) {
00479 #ifndef NO_CACHE
00480 cachedata = ap_palloc(r->pool, sizeof(mu_cache_data));
00481 cachedata->name = names->filename;
00482 #endif
00483 if ((ap_is_directory(names->filename)) ) {
00484 fn = names->filename + strlen(names->filename);
00485 *fn++ = '/';
00486 *fn = '\0';
00487
00488 uri = names->uri + strlen(names->uri) - 1;
00489 if (*uri++ != '/')
00490 *uri++ = '/';
00491 *uri = '\0';
00492
00493 if (conf->options & MI_RECURSIVE) {
00494 conf->options &= (conf->play_recursive);
00495
00496 if (!(dir = opendir(names->filename)))
00497 return head;
00498 #ifndef NO_CACHE
00499 if ((conf->cache_path) && (cache_check_dir(r, conf, cachedata)))
00500 ap_log_error_old("[musicindex] DBG: cache_check_dir failed", r->server);
00501 #endif
00502 while ((dstruct = readdir(dir))) {
00503 strcpy(fn, dstruct->d_name);
00504 strcpy(uri, dstruct->d_name);
00505 p = make_music_entry(pool, r, p, conf, names);
00506 }
00507 closedir(dir);
00508 fclose(in);
00509 return p;
00510 }
00511 else if (!(conf->options & MI_PLAYLIST)) {
00512 p = new_ent(pool, head);
00513 p->filetype = FT_DIR;
00514 }
00515 fclose(in);
00516 }
00517 #ifndef NO_CACHE
00518
00519 else if ((conf->cache_path) && !(checkresult = cache_check_file(r, conf, cachedata))
00520 && ((p = cache_read_file(r, head, conf, cachedata)) != head))
00521 fclose(in);
00522 #endif
00523 else if ((p = make_ogg_entry(pool, head, in, conf, names, r)) != head);
00524
00525
00526
00527 else if ((p = make_mp3_entry(pool, head, in, conf, names, r)) != head)
00528 fclose(in);
00529 else
00530 fclose(in);
00531 }
00532
00533 if (p == head)
00534 return p;
00535
00536 p->uri = ap_pstrdup(pool, names->uri);
00537 p->file = p->uri + strlen(r->parsed_uri.path);
00538
00539 if (p->filetype == FT_DIR)
00540 return p;
00541
00542 if (!(p->title)) {
00543 #ifdef NO_TITLE_STRIP
00544 p->title = p->file;
00545 #else
00546
00547 p->title = ap_pstrndup(pool, p->file, strlen(p->file) - 4);
00548 for (i=0; p->title[i] != '\0'; i++)
00549 if (p->title[i] == '_')
00550 p->title[i] = ' ';
00551 #endif
00552
00553 if (strrchr(p->title, '/')) {
00554 p->title = strrchr(p->title, '/');
00555 (p->title)++;
00556 }
00557
00558 }
00559
00560 #ifndef NO_CACHE
00561
00562
00563 if (checkresult == CA_CREATE)
00564 cache_write_file(r, p, conf, cachedata);
00565 #endif
00566
00567 if (!(conf->options & MI_SEARCH))
00568 return p;
00569
00570
00571
00572 if (p->file && (ap_strcasestr(p->file, conf->search)))
00573 return p;
00574 if (p->artist && (ap_strcasestr(p->artist, conf->search)))
00575 return p;
00576 if (p->album && (ap_strcasestr(p->album, conf->search)))
00577 return p;
00578 if (p->title && (ap_strcasestr(p->title, conf->search)))
00579 return p;
00580
00581 return head;
00582 }
00583
00592 short musicindex_directory(request_rec * r, mu_config * conf)
00593 {
00594 DIR *dir;
00595 mu_ent *head = NULL;
00596
00597 if (!(dir = ap_popendir(r->pool, r->filename))) {
00598 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
00599 "Can't open directory for music index: %s",
00600 r->filename);
00601 return HTTP_FORBIDDEN;
00602 }
00603
00604 r->content_type = "text/html; charset=UTF-8";
00605 ap_send_http_header(r);
00606
00607 if (r->header_only) {
00608 ap_pclosedir(r->pool, dir);
00609 return 0;
00610 }
00611 ap_hard_timeout("send music list", r);
00612
00613 head = make_music_entry(r->pool, r, NULL, conf, NULL);
00614
00615 head = quicksort(head, NULL, conf);
00616
00617 send_head(r, conf);
00618
00619 if (!(conf->options & MI_SEARCH))
00620 send_directories(r, head, conf);
00621
00622 send_tracks(r, head, conf);
00623
00624 send_foot(r, conf);
00625
00626 ap_pclosedir(r->pool, dir);
00627
00628 ap_kill_timeout(r);
00629 return 0;
00630 }
00631
00643 short playlist_directory(request_rec * r, mu_config * conf)
00644 {
00645 DIR *dir;
00646 mu_ent *head = NULL;
00647
00648 if (!(dir = ap_popendir(r->pool, r->filename)))
00649 return HTTP_FORBIDDEN;
00650
00651 r->content_type = "audio/mpegurl";
00652
00653 ap_send_http_header(r);
00654
00655 conf->options |= MI_PLAYLIST;
00656
00657 if (r->header_only) {
00658 ap_pclosedir(r->pool, dir);
00659 return 0;
00660 }
00661 ap_hard_timeout("send playlist", r);
00662
00663 head = make_music_entry(r->pool, r, head, conf, NULL);
00664
00665 head = quicksort(head, NULL, conf);
00666
00667 send_playlist(r, head, conf);
00668
00669 ap_pclosedir(r->pool, dir);
00670
00671 ap_kill_timeout(r);
00672 return 0;
00673 }
00674
00691 short playlist_selected(request_rec * r, mu_config * conf)
00692 {
00693 const char *args = r->args;
00694 char *p;
00695 unsigned short i;
00696
00697 char *fn, *uri;
00698 mu_ent_names *names;
00699 mu_ent *head = NULL;
00700
00701 r->content_type = "audio/mpegurl";
00702
00703 ap_send_http_header(r);
00704
00705 if (r->header_only)
00706 return 0;
00707 ap_hard_timeout("send playlist", r);
00708
00709 if ((args == NULL) || (*args == '\0')) {
00710 ap_kill_timeout(r);
00711 return 0;
00712 }
00713
00714 names = ap_palloc(r->pool, sizeof(mu_ent_names));
00715 names->filename = ap_palloc(r->pool, MAX_STRING);
00716 names->uri = ap_palloc(r->pool, MAX_STRING);
00717 strcpy(names->filename, r->filename);
00718 strcpy(names->uri, r->parsed_uri.path);
00719
00720 fn = names->filename + strlen(names->filename);
00721 *fn++ = '/';
00722 *fn = '\0';
00723
00724 uri = names->uri + strlen(names->uri) - 1;
00725 if (*uri++ != '/')
00726 *uri++ = '/';
00727 *uri = '\0';
00728
00729 while (args[0]){
00730 p = ap_getword(r->pool, &args, '&');
00731 if (!strncmp(p, "file=", 5))
00732 {
00733 p += 5;
00734 strcpy(fn, p);
00735 for (i=0;fn[i]; i++) {
00736 if (fn[i] == '+')
00737 fn[i] = ' ';
00738 }
00739 ap_unescape_url(fn);
00740 strcpy(uri, fn);
00741 head = make_music_entry(r->pool, r, head, conf, names);
00742 }
00743 }
00744
00745 head = quicksort(head, NULL, conf);
00746 send_playlist(r, head, conf);
00747
00748 ap_kill_timeout(r);
00749 return 0;
00750 }