comparison libmpdemux/cddb.c @ 6474:654b26c941e5

CDDB support added.
author bertrand
date Fri, 21 Jun 2002 06:15:36 +0000
parents
children 837ca6fd4adf
comparison
equal deleted inserted replaced
6473:6c45b8bf9a3e 6474:654b26c941e5
1 /*
2 * CDDB HTTP protocol
3 * by Bertrand Baudet <bertrand_baudet@yahoo.com>
4 * (C) 2002, MPlayer team.
5 *
6 * Implementation follow the freedb.howto1.06.txt specification
7 * from http://freedb.freedb.org
8 *
9 * discid computation by Jeremy D. Zawodny
10 * Copyright (c) 1998-2000 Jeremy D. Zawodny <Jeremy@Zawodny.com>
11 * Code release under GPL
12 *
13 * TODO:
14 * * do a xmcd parser
15 */
16
17 #include "config.h"
18
19 #if defined(HAVE_CDDA) && defined(STREAMING)
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #include <stdarg.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32
33 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__bsdi__)
34 #define SYS_BSD 1
35 #endif
36
37 #if defined(__linux__)
38 #include <linux/cdrom.h>
39 #elif defined(SYS_BSD)
40 #include <sys/cdio.h>
41 #endif
42
43 #include "../version.h"
44 #include "stream.h"
45 #include "network.h"
46
47 #define DEFAULT_FREEDB_SERVER "freedb.freedb.org"
48 #define DEFAULT_CACHE_DIR "/.cddb/"
49
50 stream_t* open_cdda(char *dev, char *track);
51
52 typedef struct {
53 char cddb_hello[1024];
54 unsigned long disc_id;
55 unsigned int tracks;
56 char *cache_dir;
57 char *freedb_server;
58 int freedb_proto_level;
59 char category[100];
60 char *xmcd_file;
61 size_t xmcd_file_size;
62 void *user_data;
63 } cddb_data_t;
64
65
66 struct toc {
67 int min, sec, frame;
68 } cdtoc[100];
69
70 #if defined(__linux__)
71 int
72 read_toc(void) {
73 int drive = open("/dev/cdrom", O_RDONLY | O_NONBLOCK);
74 struct cdrom_tochdr tochdr;
75 struct cdrom_tocentry tocentry;
76 int i;
77
78 ioctl(drive, CDROMREADTOCHDR, &tochdr);
79 for (i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1; i++) {
80 tocentry.cdte_track = i;
81 tocentry.cdte_format = CDROM_MSF;
82 ioctl(drive, CDROMREADTOCENTRY, &tocentry);
83 cdtoc[i-1].min = tocentry.cdte_addr.msf.minute;
84 cdtoc[i-1].sec = tocentry.cdte_addr.msf.second;
85 cdtoc[i-1].frame = tocentry.cdte_addr.msf.frame;
86 cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
87 cdtoc[i-1].frame += cdtoc[i-1].sec*75;
88 }
89 tocentry.cdte_track = 0xAA;
90 tocentry.cdte_format = CDROM_MSF;
91 ioctl(drive, CDROMREADTOCENTRY, &tocentry);
92 cdtoc[tochdr.cdth_trk1].min = tocentry.cdte_addr.msf.minute;
93 cdtoc[tochdr.cdth_trk1].sec = tocentry.cdte_addr.msf.second;
94 cdtoc[tochdr.cdth_trk1].frame = tocentry.cdte_addr.msf.frame;
95 cdtoc[tochdr.cdth_trk1].frame += cdtoc[tochdr.cdth_trk1].min*60*75;
96 cdtoc[tochdr.cdth_trk1].frame += cdtoc[tochdr.cdth_trk1].sec*75;
97 close(drive);
98 return tochdr.cdth_trk1;
99 }
100
101 #elif defined(SYS_BSD)
102 int
103 read_toc(void) {
104 int drive = open("/dev/acd0c", O_RDONLY | O_NONBLOCK);
105 struct ioc_toc_header tochdr;
106 struct ioc_read_toc_single_entry tocentry;
107 int i;
108
109 ioctl(drive, CDIOREADTOCHEADER, &tochdr);
110 for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) {
111 tocentry.track = i;
112 tocentry.address_format = CD_MSF_FORMAT;
113 ioctl(drive, CDIOREADTOCENTRY, &tocentry);
114 cdtoc[i-1].min = tocentry.entry.addr.msf.minute;
115 cdtoc[i-1].sec = tocentry.entry.addr.msf.second;
116 cdtoc[i-1].frame = tocentry.entry.addr.msf.frame;
117 cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
118 cdtoc[i-1].frame += cdtoc[i-1].sec*75;
119 }
120 tocentry.track = 0xAA;
121 tocentry.address_format = CD_MSF_FORMAT;
122 ioctl(drive, CDIOREADTOCENTRY, &tocentry);
123 cdtoc[tochdr.ending_track].min = tocentry.entry.addr.msf.minute;
124 cdtoc[tochdr.ending_track].sec = tocentry.entry.addr.msf.second;
125 cdtoc[tochdr.ending_track].frame = tocentry.entry.addr.msf.frame;
126 cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].min*60*75;
127 cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].sec*75;
128 close(drive);
129 return tochdr.ending_track;
130 }
131 #endif
132
133 unsigned int
134 cddb_sum(int n) {
135 unsigned int ret;
136
137 ret = 0;
138 while (n > 0) {
139 ret += (n % 10);
140 n /= 10;
141 }
142 return ret;
143 }
144
145 unsigned long
146 cddb_discid(int tot_trks) {
147 unsigned int i, t = 0, n = 0;
148
149 i = 0;
150 while (i < tot_trks) {
151 n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec);
152 i++;
153 }
154 t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -
155 ((cdtoc[0].min * 60) + cdtoc[0].sec);
156 return ((n % 0xff) << 24 | t << 8 | tot_trks);
157 }
158
159
160
161 int
162 cddb_http_request(char *command, int (*reply_parser)(HTTP_header_t*,cddb_data_t*), cddb_data_t *cddb_data) {
163 char request[4096];
164 int fd, ret = 0;
165 URL_t *url;
166 HTTP_header_t *http_hdr;
167
168 if( reply_parser==NULL || command==NULL || cddb_data==NULL ) return -1;
169
170 sprintf( request, "http://%s/~cddb/cddb.cgi?cmd=%s%s&proto=%d", cddb_data->freedb_server, command, cddb_data->cddb_hello, cddb_data->freedb_proto_level );
171 printf("Request[%s]\n", request );
172
173 url = url_new(request);
174 if( url==NULL ) {
175 printf("Not a valid URL\n");
176 return -1;
177 }
178
179 fd = http_send_request(url);
180 if( fd<0 ) {
181 printf("failed to send the http request\n");
182 return -1;
183 }
184
185 http_hdr = http_read_response( fd );
186 if( http_hdr==NULL ) {
187 printf("Failed to read the http response\n");
188 return -1;
189 }
190
191 http_debug_hdr(http_hdr);
192 printf("body=[%s]\n", http_hdr->body );
193
194 switch(http_hdr->status_code) {
195 case 200:
196 ret = reply_parser(http_hdr, cddb_data);
197 break;
198 case 400:
199 printf("Not Found\n");
200 break;
201 default:
202 printf("Unknown Error code\n");
203 }
204
205 http_free( http_hdr );
206 url_free( url );
207
208 return ret;
209 }
210
211 int
212 cddb_read_cache(cddb_data_t *cddb_data) {
213 char file_name[100];
214 struct stat stats;
215 int file_fd, ret;
216 size_t file_size;
217
218 if( cddb_data==NULL || cddb_data->cache_dir==NULL ) return -1;
219
220 sprintf( file_name, "%s%08lx", cddb_data->cache_dir, cddb_data->disc_id);
221
222 file_fd = open(file_name, O_RDONLY);
223 if( file_fd<0 ) {
224 printf("No cache found\n");
225 return -1;
226 }
227
228 ret = fstat( file_fd, &stats );
229 if( ret<0 ) {
230 perror("fstat");
231 file_size = 4096;
232 } else {
233 file_size = stats.st_size;
234 }
235
236 cddb_data->xmcd_file = (char*)malloc(file_size);
237 if( cddb_data->xmcd_file==NULL ) {
238 printf("Memory allocation failed\n");
239 close(file_fd);
240 return -1;
241 }
242 cddb_data->xmcd_file_size = read(file_fd, cddb_data->xmcd_file, file_size);
243 if( cddb_data->xmcd_file_size!=file_size ) {
244 printf("Not all the xmcd file has been read\n");
245 close(file_fd);
246 return -1;
247 }
248
249 close(file_fd);
250
251 return 0;
252 }
253
254 int
255 cddb_write_cache(cddb_data_t *cddb_data) {
256 // We have the file, save it for cache.
257 char file_name[100];
258 int file_fd;
259 size_t wrote=0;
260
261 if( cddb_data==NULL || cddb_data->cache_dir==NULL ) return -1;
262
263 sprintf( file_name, "%s%08lx", cddb_data->cache_dir, cddb_data->disc_id);
264
265 file_fd = creat(file_name, S_IREAD|S_IWRITE);
266 if( file_fd<0 ) {
267 perror("open");
268 return -1;
269 }
270
271 wrote = write(file_fd, cddb_data->xmcd_file, cddb_data->xmcd_file_size);
272 if( wrote<0 ) {
273 perror("write");
274 close(file_fd);
275 return -1;
276 }
277 if( wrote!=cddb_data->xmcd_file_size ) {
278 printf("Not all the xmcd file has been written\n");
279 close(file_fd);
280 return -1;
281 }
282
283 close(file_fd);
284
285 return 0;
286 }
287
288 int
289 cddb_read_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
290 unsigned long disc_id;
291 char category[100];
292 char *ptr=NULL, *ptr2=NULL;
293 int ret, status;
294
295 if( http_hdr==NULL || cddb_data==NULL ) return -1;
296
297 ret = sscanf( http_hdr->body, "%d ", &status);
298 if( ret!=1 ) {
299 printf("Parse error\n");
300 return -1;
301 }
302
303 switch(status) {
304 case 210:
305 ret = sscanf( http_hdr->body, "%d %s %08lx", &status, category, &disc_id);
306 if( ret!=3 ) {
307 printf("Parse error\n");
308 return -1;
309 }
310 // Check if it's a xmcd database file
311 ptr = strstr(http_hdr->body, "# xmcd");
312 if( ptr==NULL ) {
313 printf("Invalid xmcd database file returned\n");
314 return -1;
315 }
316 // Ok found the beginning of the file
317 // look for the end
318 ptr2 = strstr(ptr, "\r\n.\r\n");
319 if( ptr2==NULL ) {
320 ptr2 = strstr(ptr, "\n.\n");
321 if( ptr2==NULL ) {
322 printf("Unable to find '.'\n");
323 return -1;
324 }
325 }
326 // Ok found the end
327 // do a sanity check
328 if( http_hdr->body_size<(ptr2-ptr) ) {
329 printf("Unexpected fix me\n");
330 return -1;
331 }
332 cddb_data->xmcd_file = ptr;
333 cddb_data->xmcd_file_size = ptr2-ptr+2;
334 cddb_data->xmcd_file[cddb_data->xmcd_file_size] = '\0';
335 // Avoid the http_free function to free the xmcd file...save a mempcy...
336 http_hdr->body = NULL;
337 http_hdr->body_size = 0;
338 return cddb_write_cache(cddb_data);
339 default:
340 printf("Unhandled code\n");
341 }
342 return 0;
343 }
344
345 int
346 cddb_request_titles(cddb_data_t *cddb_data) {
347 char command[1024];
348 sprintf( command, "cddb+read+%s+%08lx", cddb_data->category, cddb_data->disc_id);
349 return cddb_http_request(command, cddb_read_parse, cddb_data);
350 }
351
352 int
353 cddb_query_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
354 char album_title[100];
355 char *ptr = NULL;
356 int ret, status;
357
358 ret = sscanf( http_hdr->body, "%d ", &status);
359 if( ret!=1 ) {
360 printf("Parse error\n");
361 return -1;
362 }
363
364 switch(status) {
365 case 200:
366 // Found exact match
367 ret = sscanf(http_hdr->body, "%d %s %08lx %s", &status, cddb_data->category, &(cddb_data->disc_id), album_title);
368 if( ret!=4 ) {
369 printf("Parse error\n");
370 return -1;
371 }
372 ptr = strstr(http_hdr->body, album_title);
373 if( ptr!=NULL ) {
374 char *ptr2;
375 int len;
376 ptr2 = strstr(ptr, "\n");
377 if( ptr2==NULL ) {
378 len = (http_hdr->body_size)-(ptr-(http_hdr->body));
379 } else {
380 len = ptr2-ptr+1;
381 }
382 strncpy(album_title, ptr, len);
383 album_title[len-2]='\0';
384 }
385 printf("Parse OK, found: %s\n", album_title);
386 return cddb_request_titles(cddb_data);
387 case 202:
388 // No match found
389 printf("Album not found\n");
390 break;
391 case 210:
392 // Found exact matches, list follows
393 ptr = strstr(http_hdr->body, "\n");
394 if( ptr==NULL ) {
395 printf("Unable to find end of line\n");
396 return -1;
397 }
398 ptr++;
399 // We have a list of exact matches, so which one do
400 // we use? So let's take the first one.
401 ret = sscanf(ptr, "%s %08lx %s", cddb_data->category, &(cddb_data->disc_id), album_title);
402 if( ret!=3 ) {
403 printf("Parse error\n");
404 return -1;
405 }
406 ptr = strstr(http_hdr->body, album_title);
407 if( ptr!=NULL ) {
408 char *ptr2;
409 int len;
410 ptr2 = strstr(ptr, "\n");
411 if( ptr2==NULL ) {
412 len = (http_hdr->body_size)-(ptr-(http_hdr->body));
413 } else {
414 len = ptr2-ptr+1;
415 }
416 strncpy(album_title, ptr, len);
417 album_title[len-2]='\0';
418 }
419 printf("Parse OK, found: %s\n", album_title);
420 return cddb_request_titles(cddb_data);
421 /*
422 body=[210 Found exact matches, list follows (until terminating `.')
423 misc c711930d Santana / Supernatural
424 rock c711930d Santana / Supernatural
425 blues c711930d Santana / Supernatural
426 .]
427 */
428 case 211:
429 // Found inexact matches, list follows
430 printf("No exact matches found, list follows\n");
431 break;
432 default:
433 printf("Unhandled code\n");
434 }
435 return -1;
436 }
437
438 int
439 cddb_proto_level_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
440 int max;
441 int ret, status;
442 char *ptr;
443
444 ret = sscanf( http_hdr->body, "%d ", &status);
445 if( ret!=1 ) {
446 printf("Parse error\n");
447 return -1;
448 }
449
450 switch(status) {
451 case 210:
452 ptr = strstr(http_hdr->body, "max proto:");
453 if( ptr==NULL ) {
454 printf("Parse error\n");
455 return -1;
456 }
457 ret = sscanf(ptr, "max proto: %d", &max);
458 if( ret!=1 ) {
459 printf("Parse error\n");
460 return -1;
461 }
462 cddb_data->freedb_proto_level = max;
463 return 0;
464 default:
465 printf("Unhandled code\n");
466 }
467 return -1;
468 }
469
470 int
471 cddb_get_proto_level(cddb_data_t *cddb_data) {
472 return cddb_http_request("stat", cddb_proto_level_parse, cddb_data);
473 }
474
475 int
476 cddb_freedb_sites_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
477 int ret, status;
478
479 ret = sscanf( http_hdr->body, "%d ", &status);
480 if( ret!=1 ) {
481 printf("Parse error\n");
482 return -1;
483 }
484
485 switch(status) {
486 case 210:
487 // Parse the sites
488 return 0;
489 case 401:
490 printf("No sites information available\n");
491 break;
492 default:
493 printf("Unhandled code\n");
494 }
495 return -1;
496 }
497
498 int
499 cddb_get_freedb_sites(cddb_data_t *cddb_data) {
500 return cddb_http_request("sites", cddb_freedb_sites_parse, cddb_data);
501 }
502
503 void
504 cddb_create_hello(cddb_data_t *cddb_data) {
505 char host_name[51];
506 char *user_name;
507
508 if( gethostname(host_name, 50)<0 ) {
509 strcpy(host_name, "localhost");
510 }
511 user_name = getenv("LOGNAME");
512 sprintf( cddb_data->cddb_hello, "&hello=%s+%s+%s+%s", user_name, host_name, "MPlayer", VERSION );
513 }
514
515 int
516 cddb_retrieve(cddb_data_t *cddb_data) {
517 char offsets[1024], command[1024];
518 char *ptr;
519 int i, time_len;
520
521 ptr = offsets;
522 for( i=0; i<cddb_data->tracks ; i++ ) {
523 ptr += sprintf(ptr, "%d+", cdtoc[i].frame );
524 }
525 time_len = (cdtoc[cddb_data->tracks].frame)/75;
526
527 cddb_data->freedb_server = DEFAULT_FREEDB_SERVER;
528 cddb_data->freedb_proto_level = 1;
529 cddb_data->xmcd_file = NULL;
530
531 cddb_create_hello(cddb_data);
532 cddb_get_proto_level(cddb_data);
533
534 //cddb_get_freedb_sites(&cddb_data);
535
536 sprintf(command, "cddb+query+%08lx+%d+%s%d", cddb_data->disc_id, cddb_data->tracks, offsets, time_len );
537 i = cddb_http_request(command, cddb_query_parse, cddb_data);
538 if( i<0 ) return -1;
539
540 if( cddb_data->cache_dir!=NULL ) {
541 free(cddb_data->cache_dir);
542 }
543 return 0;
544 }
545
546 int
547 cddb_resolve(char *xmcd_file) {
548 char cddb_cache_dir[] = DEFAULT_CACHE_DIR;
549 char *home_dir = NULL;
550 cddb_data_t cddb_data;
551
552 cddb_data.tracks = read_toc();
553 cddb_data.disc_id = cddb_discid(cddb_data.tracks);
554
555 home_dir = getenv("HOME");
556 if( home_dir==NULL ) {
557 cddb_data.cache_dir = NULL;
558 } else {
559 cddb_data.cache_dir = (char*)malloc(strlen(home_dir)+strlen(cddb_cache_dir)+1);
560 if( cddb_data.cache_dir==NULL ) {
561 printf("Memory allocation failed\n");
562 return -1;
563 }
564 sprintf(cddb_data.cache_dir, "%s%s", home_dir, cddb_cache_dir );
565 }
566
567 // Check for a cached file
568 if( cddb_read_cache(&cddb_data)<0 ) {
569 // No Cache found
570 if( cddb_retrieve(&cddb_data)<0 ) {
571 return -1;
572 }
573 }
574
575 if( cddb_data.xmcd_file!=NULL ) {
576 printf("%s\n", cddb_data.xmcd_file );
577 xmcd_file = cddb_data.xmcd_file;
578 }
579
580 return 0;
581 }
582
583 stream_t*
584 cddb_open(char *dev, char *track) {
585 char *xmcd_file;
586 cddb_resolve(xmcd_file);
587
588 return open_cdda(dev, track);
589 }
590
591 #endif