diff stream/stream_cddb.c @ 19272:e53b30cd047f

renamed cddX stream interface to stream_cddX for consistency
author ben
date Mon, 31 Jul 2006 17:48:50 +0000
parents stream/cddb.c@64d82a45a05d
children ff7b00afaa20
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stream/stream_cddb.c	Mon Jul 31 17:48:50 2006 +0000
@@ -0,0 +1,878 @@
+/*
+ * CDDB HTTP protocol 
+ * by Bertrand Baudet <bertrand_baudet@yahoo.com>
+ * (C) 2002, MPlayer team.
+ *
+ * Implementation follow the freedb.howto1.06.txt specification
+ * from http://freedb.freedb.org
+ * 
+ * discid computation by Jeremy D. Zawodny
+ *	 Copyright (c) 1998-2000 Jeremy D. Zawodny <Jeremy@Zawodny.com>
+ *	 Code release under GPL
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef WIN32
+#ifdef __MINGW32__
+#define mkdir(a,b) mkdir(a)
+#endif
+#include <windows.h>
+#ifdef HAVE_WINSOCK2
+#include <winsock2.h>
+#endif
+#else
+#include <netdb.h>
+#include <sys/ioctl.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "mp_msg.h"
+#include "help_mp.h"
+
+#if defined(__linux__)
+	#include <linux/cdrom.h>
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+	#include <sys/cdio.h>
+#elif defined(WIN32)
+        #include <ddk/ntddcdrm.h>
+#elif (__bsdi__)
+        #include <dvd.h>
+#endif
+
+#include "cdd.h"
+#include "version.h"
+#include "stream.h"
+#include "network.h"
+
+#define DEFAULT_FREEDB_SERVER	"freedb.freedb.org"
+#define DEFAULT_CACHE_DIR	"/.cddb/"
+
+stream_t* open_cdda(char *dev, char *track);
+
+static cd_toc_t cdtoc[100];
+static int cdtoc_last_track;
+
+#if defined(__linux__) || defined(__bsdi__)
+int 
+read_toc(const char *dev) {
+	int drive;
+	struct cdrom_tochdr tochdr;
+	struct cdrom_tocentry tocentry;
+	int i;
+
+	drive = open(dev, O_RDONLY | O_NONBLOCK);
+	if( drive<0 ) {
+		return drive;
+	}
+	
+	ioctl(drive, CDROMREADTOCHDR, &tochdr);
+	for (i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1; i++) {
+		tocentry.cdte_track = i;
+		tocentry.cdte_format = CDROM_MSF;
+		ioctl(drive, CDROMREADTOCENTRY, &tocentry);
+		cdtoc[i-1].min = tocentry.cdte_addr.msf.minute;
+		cdtoc[i-1].sec = tocentry.cdte_addr.msf.second;
+		cdtoc[i-1].frame = tocentry.cdte_addr.msf.frame;
+		cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
+		cdtoc[i-1].frame += cdtoc[i-1].sec*75;
+	}
+	tocentry.cdte_track = 0xAA;
+	tocentry.cdte_format = CDROM_MSF;
+	ioctl(drive, CDROMREADTOCENTRY, &tocentry);
+	cdtoc[tochdr.cdth_trk1].min = tocentry.cdte_addr.msf.minute;
+	cdtoc[tochdr.cdth_trk1].sec = tocentry.cdte_addr.msf.second;
+	cdtoc[tochdr.cdth_trk1].frame = tocentry.cdte_addr.msf.frame;
+	cdtoc[tochdr.cdth_trk1].frame += cdtoc[tochdr.cdth_trk1].min*60*75;
+	cdtoc[tochdr.cdth_trk1].frame += cdtoc[tochdr.cdth_trk1].sec*75;
+	close(drive);
+	return tochdr.cdth_trk1;
+}
+
+#elif defined(WIN32)
+int
+read_toc(const char *dev) {
+        HANDLE drive;
+        DWORD r;
+        CDROM_TOC toc;
+        char device[10];
+        int i;
+
+        sprintf(device, "\\\\.\\%s", dev);
+        drive = CreateFile(device, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
+
+        if(!DeviceIoControl(drive, IOCTL_CDROM_READ_TOC, NULL, 0, &toc, sizeof(CDROM_TOC), &r, 0)) {
+                mp_msg(MSGT_OPEN, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToReadTOC);
+                return 0;
+        }
+
+        for (i = toc.FirstTrack; i <= toc.LastTrack; i++) {
+                toc.FirstTrack = i;
+		cdtoc[i-1].min = toc.TrackData[i - 1].Address[1];
+		cdtoc[i-1].sec = toc.TrackData[i - 1].Address[2];
+		cdtoc[i-1].frame = toc.TrackData[i - 1].Address[3];
+		cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
+		cdtoc[i-1].frame += cdtoc[i-1].sec*75;
+        }
+        toc.FirstTrack = 0xAA;
+        cdtoc[toc.LastTrack].min = toc.TrackData[toc.LastTrack].Address[1];
+        cdtoc[toc.LastTrack].sec = toc.TrackData[toc.LastTrack].Address[2];
+        cdtoc[toc.LastTrack].frame = toc.TrackData[toc.LastTrack].Address[3];
+        cdtoc[toc.LastTrack].frame += cdtoc[toc.LastTrack].min*60*75;
+        cdtoc[toc.LastTrack].frame += cdtoc[toc.LastTrack].sec*75;
+        CloseHandle(drive);
+        return toc.LastTrack;
+}
+
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+int 
+read_toc(const char *dev) {
+	int drive;
+	struct ioc_toc_header tochdr;
+	struct ioc_read_toc_single_entry tocentry;
+	int i;
+
+	drive = open(dev, O_RDONLY | O_NONBLOCK);
+	if( drive<0 ) {
+		return drive;
+	}
+
+	ioctl(drive, CDIOREADTOCHEADER, &tochdr);
+	for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) {
+		tocentry.track = i;
+		tocentry.address_format = CD_MSF_FORMAT;
+		ioctl(drive, CDIOREADTOCENTRY, &tocentry);
+		cdtoc[i-1].min = tocentry.entry.addr.msf.minute;
+		cdtoc[i-1].sec = tocentry.entry.addr.msf.second;
+		cdtoc[i-1].frame = tocentry.entry.addr.msf.frame;
+		cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
+		cdtoc[i-1].frame += cdtoc[i-1].sec*75;
+	}
+	tocentry.track = 0xAA;
+	tocentry.address_format = CD_MSF_FORMAT;
+	ioctl(drive, CDIOREADTOCENTRY, &tocentry);
+	cdtoc[tochdr.ending_track].min = tocentry.entry.addr.msf.minute;
+	cdtoc[tochdr.ending_track].sec = tocentry.entry.addr.msf.second;
+	cdtoc[tochdr.ending_track].frame = tocentry.entry.addr.msf.frame;
+	cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].min*60*75;
+	cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].sec*75;
+	close(drive);
+	return tochdr.ending_track;
+}
+
+#elif defined(__NetBSD__) || defined(__OpenBSD__)
+int
+read_toc(const char *dev) {
+	int drive;
+	struct ioc_toc_header tochdr;
+	struct ioc_read_toc_entry tocentry;
+	int i;
+	struct cd_toc_entry toc_buffer;
+
+	drive = open(dev, O_RDONLY | O_NONBLOCK);
+	if( drive<0 ) {
+		return drive;
+	}
+
+	ioctl(drive, CDIOREADTOCHEADER, &tochdr);
+	for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) {
+		tocentry.starting_track = i;
+		tocentry.address_format = CD_MSF_FORMAT;
+		tocentry.data = &toc_buffer;
+		tocentry.data_len = sizeof(toc_buffer);
+		ioctl(drive, CDIOREADTOCENTRYS, &tocentry);
+		cdtoc[i-1].min = toc_buffer.addr.msf.minute;
+		cdtoc[i-1].sec = toc_buffer.addr.msf.second;
+		cdtoc[i-1].frame = toc_buffer.addr.msf.frame;
+		cdtoc[i-1].frame += cdtoc[i-1].min*60*75;
+		cdtoc[i-1].frame += cdtoc[i-1].sec*75;
+	}
+	tocentry.starting_track = 0xAA;
+	tocentry.address_format = CD_MSF_FORMAT;
+	ioctl(drive, CDIOREADTOCENTRYS, &tocentry);
+	cdtoc[tochdr.ending_track].min = toc_buffer.addr.msf.minute;
+	cdtoc[tochdr.ending_track].sec = toc_buffer.addr.msf.second;
+	cdtoc[tochdr.ending_track].frame = toc_buffer.addr.msf.frame;
+	cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].min*60*75;
+	cdtoc[tochdr.ending_track].frame += cdtoc[tochdr.ending_track].sec*75;
+	close(drive);
+	return tochdr.ending_track;
+}
+#endif
+
+/** 
+\brief Reads TOC from CD in the given device and prints the number of tracks
+       and the length of each track in minute:second:frame format.
+\param *dev the device to analyse
+\return if the command line -identify is given, returns the last track of
+        the TOC or -1 if the TOC can't be read,
+        otherwise just returns 0 and let cddb_resolve the TOC
+*/
+int cdd_identify(const char *dev)
+{
+	cdtoc_last_track = 0;
+	if (mp_msg_test(MSGT_IDENTIFY, MSGL_INFO))
+	{
+		int i, min, sec, frame;
+		cdtoc_last_track = read_toc(dev);
+		if (cdtoc_last_track < 0) {
+			mp_msg(MSGT_OPEN, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToOpenDevice, dev);
+			return -1;
+		}
+		mp_msg(MSGT_GLOBAL, MSGL_INFO, "ID_CDDA_TRACKS=%d\n", cdtoc_last_track);
+		for (i = 1; i <= cdtoc_last_track; i++)
+		{
+			frame = cdtoc[i].frame - cdtoc[i-1].frame;
+			sec = frame / 75;
+			frame -= sec * 75;
+			min = sec / 60;
+			sec -= min * 60;
+			mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CDDA_TRACK_%d_MSF=%02d:%02d:%02d\n", i, min, sec, frame);
+		}
+	}
+	return cdtoc_last_track;
+}
+
+unsigned int 
+cddb_sum(int n) {
+	unsigned int ret;
+
+	ret = 0;
+	while (n > 0) {
+		ret += (n % 10);
+		n /= 10;
+	}
+	return ret;
+}
+
+unsigned long 
+cddb_discid(int tot_trks) {
+	unsigned int i, t = 0, n = 0;
+
+	i = 0;
+	while (i < (unsigned int)tot_trks) {
+		n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec);
+		i++;
+	}
+	t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -
+		((cdtoc[0].min * 60) + cdtoc[0].sec);
+	return ((n % 0xff) << 24 | t << 8 | tot_trks);
+}
+
+
+
+int
+cddb_http_request(char *command, int (*reply_parser)(HTTP_header_t*,cddb_data_t*), cddb_data_t *cddb_data) {
+	char request[4096];
+	int fd, ret = 0;
+	URL_t *url;
+	HTTP_header_t *http_hdr;
+	
+	if( reply_parser==NULL || command==NULL || cddb_data==NULL ) return -1;
+	
+	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 );
+	mp_msg(MSGT_OPEN, MSGL_INFO,"Request[%s]\n", request );
+
+	url = url_new(request);
+	if( url==NULL ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_NotAValidURL);
+		return -1;
+	}
+	
+	fd = http_send_request(url,0);
+	if( fd<0 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToSendHTTPRequest);
+		return -1;
+	}
+
+	http_hdr = http_read_response( fd );
+	if( http_hdr==NULL ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToReadHTTPResponse);
+		return -1;
+	}
+
+	http_debug_hdr(http_hdr);
+	mp_msg(MSGT_OPEN, MSGL_INFO,"body=[%s]\n", http_hdr->body );
+
+	switch(http_hdr->status_code) {
+		case 200:
+			ret = reply_parser(http_hdr, cddb_data);
+			break;
+		case 400:
+			mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_HTTPErrorNOTFOUND);
+			break;
+		default:
+			mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_HTTPErrorUnknown);
+	}
+
+	http_free( http_hdr );
+	url_free( url );
+	
+	return ret;
+}
+
+int
+cddb_read_cache(cddb_data_t *cddb_data) {
+	char file_name[100];
+	struct stat stats;
+	int file_fd, ret;
+	size_t file_size;
+
+	if( cddb_data==NULL || cddb_data->cache_dir==NULL ) return -1;
+	
+	sprintf( file_name, "%s%08lx", cddb_data->cache_dir, cddb_data->disc_id);
+	
+	file_fd = open(file_name, O_RDONLY
+#ifdef WIN32
+	| O_BINARY
+#endif
+	);
+	if( file_fd<0 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_NoCacheFound);
+		return -1;
+	}
+
+	ret = fstat( file_fd, &stats );
+	if( ret<0 ) {
+		perror("fstat");
+		file_size = 4096;
+	} else {
+		file_size = stats.st_size;
+	}
+	
+	cddb_data->xmcd_file = malloc(file_size);
+	if( cddb_data->xmcd_file==NULL ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MemAllocFailed);
+		close(file_fd);
+		return -1;
+	}
+	cddb_data->xmcd_file_size = read(file_fd, cddb_data->xmcd_file, file_size);
+	if( cddb_data->xmcd_file_size!=file_size ) {
+		mp_msg(MSGT_DEMUX, MSGL_WARN, MSGTR_MPDEMUX_CDDB_NotAllXMCDFileHasBeenRead);
+		close(file_fd);
+		return -1;
+	}
+	
+	close(file_fd);
+	
+	return 0;
+}
+
+int
+cddb_write_cache(cddb_data_t *cddb_data) {
+	// We have the file, save it for cache.
+	struct stat file_stat;
+	char file_name[100];
+	int file_fd, ret;
+	int wrote=0;
+
+	if( cddb_data==NULL || cddb_data->cache_dir==NULL ) return -1;
+
+	// Check if the CDDB cache dir exist
+	ret = stat( cddb_data->cache_dir, &file_stat );
+	if( ret<0 ) {
+		// Directory not present, create it.
+		ret = mkdir( cddb_data->cache_dir, 0755 );
+#ifdef __MINGW32__
+		if( ret<0 && errno != EEXIST ) {
+#else
+		if( ret<0 ) {
+#endif
+			perror("mkdir");
+			mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToCreateDirectory, cddb_data->cache_dir);
+			return -1;
+		}
+	}
+	
+	sprintf( file_name, "%s%08lx", cddb_data->cache_dir, cddb_data->disc_id );
+	
+	file_fd = creat(file_name, S_IREAD|S_IWRITE);
+	if( file_fd<0 ) {
+		perror("create");
+		return -1;
+	}
+	
+	wrote = write(file_fd, cddb_data->xmcd_file, cddb_data->xmcd_file_size);
+	if( wrote<0 ) {
+		perror("write");
+		close(file_fd);
+		return -1;
+	}
+	if( (unsigned int)wrote!=cddb_data->xmcd_file_size ) {
+		mp_msg(MSGT_DEMUX, MSGL_WARN, MSGTR_MPDEMUX_CDDB_NotAllXMCDFileHasBeenWritten);
+		close(file_fd);
+		return -1;
+	}
+	
+	close(file_fd);
+
+	return 0;
+}
+
+int
+cddb_read_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
+	unsigned long disc_id;
+	char category[100];
+	char *ptr=NULL, *ptr2=NULL;
+	int ret, status;
+
+	if( http_hdr==NULL || cddb_data==NULL ) return -1;
+	
+	ret = sscanf( http_hdr->body, "%d ", &status);
+	if( ret!=1 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+		return -1;
+	}
+
+	switch(status) {
+		case 210:
+			ret = sscanf( http_hdr->body, "%d %s %08lx", &status, category, &disc_id);
+			if( ret!=3 ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+				return -1;
+			}
+			// Check if it's a xmcd database file
+			ptr = strstr(http_hdr->body, "# xmcd");
+			if( ptr==NULL ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_InvalidXMCDDatabaseReturned);
+				return -1;
+			}
+			// Ok found the beginning of the file
+			// look for the end
+			ptr2 = strstr(ptr, "\r\n.\r\n");
+			if( ptr2==NULL ) {
+				ptr2 = strstr(ptr, "\n.\n");
+				if( ptr2==NULL ) {
+					mp_msg(MSGT_DEMUX, MSGL_FIXME, "Unable to find '.'\n");
+					ptr2=ptr+strlen(ptr); //return -1;
+				}
+			}
+			// Ok found the end
+			// do a sanity check
+			if( http_hdr->body_size<(unsigned int)(ptr2-ptr) ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_UnexpectedFIXME);
+				return -1;
+			}
+			cddb_data->xmcd_file = ptr;
+			cddb_data->xmcd_file_size = ptr2-ptr+2;
+			cddb_data->xmcd_file[cddb_data->xmcd_file_size] = '\0';
+			// Avoid the http_free function to free the xmcd file...save a mempcy...
+			http_hdr->body = NULL;
+			http_hdr->body_size = 0;
+			return cddb_write_cache(cddb_data);
+		default:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_UnhandledCode);
+	}
+	return 0;
+}
+
+int
+cddb_request_titles(cddb_data_t *cddb_data) {
+	char command[1024];
+	sprintf( command, "cddb+read+%s+%08lx", cddb_data->category, cddb_data->disc_id);
+	return cddb_http_request(command, cddb_read_parse, cddb_data); 
+}
+
+int
+cddb_parse_matches_list(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
+	char album_title[100];
+	char *ptr = NULL;
+	int ret;
+	
+	ptr = strstr(http_hdr->body, "\n");
+	if( ptr==NULL ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_UnableToFindEOL);
+		return -1;
+	}
+	ptr++;
+	// We have a list of exact/inexact matches, so which one do we use?
+	// So let's take the first one.
+	ret = sscanf(ptr, "%s %08lx %s", cddb_data->category, &(cddb_data->disc_id), album_title);
+	if( ret!=3 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+		return -1;
+	}
+	ptr = strstr(http_hdr->body, album_title);
+	if( ptr!=NULL ) {
+		char *ptr2;
+		int len;
+		ptr2 = strstr(ptr, "\n");
+		if( ptr2==NULL ) {
+			len = (http_hdr->body_size)-(ptr-(http_hdr->body));
+		} else {
+			len = ptr2-ptr+1;
+		}
+		strncpy(album_title, ptr, len);
+		album_title[len-2]='\0';
+	}
+	mp_msg(MSGT_DEMUX, MSGL_STATUS, MSGTR_MPDEMUX_CDDB_ParseOKFoundAlbumTitle, album_title);
+	return 0;
+}
+
+int
+cddb_query_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
+	char album_title[100];
+	char *ptr = NULL;
+	int ret, status;
+	
+	ret = sscanf( http_hdr->body, "%d ", &status);
+	if( ret!=1 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+		return -1;
+	}
+
+	switch(status) {
+		case 200:
+			// Found exact match
+			ret = sscanf(http_hdr->body, "%d %s %08lx %s", &status, cddb_data->category, &(cddb_data->disc_id), album_title);
+			if( ret!=4 ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+				return -1;
+			}
+			ptr = strstr(http_hdr->body, album_title);
+			if( ptr!=NULL ) {
+				char *ptr2;
+				int len;
+				ptr2 = strstr(ptr, "\n");
+				if( ptr2==NULL ) {
+					len = (http_hdr->body_size)-(ptr-(http_hdr->body));
+				} else {
+					len = ptr2-ptr+1;
+				}
+				strncpy(album_title, ptr, len);
+				album_title[len-2]='\0';
+			}
+			mp_msg(MSGT_DEMUX, MSGL_STATUS, MSGTR_MPDEMUX_CDDB_ParseOKFoundAlbumTitle, album_title);
+			return cddb_request_titles(cddb_data);
+		case 202:
+			// No match found
+			mp_msg(MSGT_DEMUX, MSGL_WARN, MSGTR_MPDEMUX_CDDB_AlbumNotFound);
+			break;
+		case 210:
+			// Found exact matches, list follows
+			cddb_parse_matches_list(http_hdr, cddb_data);
+			return cddb_request_titles(cddb_data);
+/*
+body=[210 Found exact matches, list follows (until terminating `.')
+misc c711930d Santana / Supernatural
+rock c711930d Santana / Supernatural
+blues c711930d Santana / Supernatural
+.]
+*/	
+		case 211:
+			// Found inexact matches, list follows
+			cddb_parse_matches_list(http_hdr, cddb_data);
+			return cddb_request_titles(cddb_data);
+		case 500:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_ServerReturnsCommandSyntaxErr);
+			break;
+		default:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_UnhandledCode);	
+	}
+	return -1;
+}
+
+int
+cddb_proto_level_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
+	int max;
+	int ret, status;
+	char *ptr;
+	
+	ret = sscanf( http_hdr->body, "%d ", &status);
+	if( ret!=1 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);	
+		return -1;
+	}
+
+	switch(status) {
+		case 210:
+			ptr = strstr(http_hdr->body, "max proto:");
+			if( ptr==NULL ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+				return -1;
+			}
+			ret = sscanf(ptr, "max proto: %d", &max);
+			if( ret!=1 ) {
+				mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+				return -1;
+			}
+			cddb_data->freedb_proto_level = max;
+			return 0;
+		default:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_UnhandledCode);	
+	}
+	return -1;
+}
+
+int
+cddb_get_proto_level(cddb_data_t *cddb_data) {
+	return cddb_http_request("stat", cddb_proto_level_parse, cddb_data);
+}
+
+int
+cddb_freedb_sites_parse(HTTP_header_t *http_hdr, cddb_data_t *cddb_data) {
+	int ret, status;
+
+	ret = sscanf( http_hdr->body, "%d ", &status);
+	if( ret!=1 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_ParseError);
+		return -1;
+	}
+
+	switch(status) {
+		case 210:
+			// TODO: Parse the sites
+			ret = cddb_data->anonymous;	// For gcc complaining about unused parameter.
+			return 0;
+		case 401:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_NoSitesInfoAvailable);
+			break;
+		default:
+			mp_msg(MSGT_DEMUX, MSGL_FIXME, MSGTR_MPDEMUX_CDDB_UnhandledCode);
+	}
+	return -1;
+}
+
+int
+cddb_get_freedb_sites(cddb_data_t *cddb_data) {
+	return cddb_http_request("sites", cddb_freedb_sites_parse, cddb_data);
+}
+
+void
+cddb_create_hello(cddb_data_t *cddb_data) {
+	char host_name[51];
+	char *user_name;
+	
+	if( cddb_data->anonymous ) {	// Default is anonymous
+		/* Note from Eduardo Pérez Ureta <eperez@it.uc3m.es> : 
+		 * We don't send current user/host name in hello to prevent spam.
+		 * Software that sends this is considered spyware
+		 * that most people don't like.
+		 */
+		user_name = "anonymous";
+		strcpy(host_name, "localhost");
+	} else {
+		if( gethostname(host_name, 50)<0 ) {
+			strcpy(host_name, "localhost");
+		}
+		user_name = getenv("LOGNAME");
+	}
+	sprintf( cddb_data->cddb_hello, "&hello=%s+%s+%s+%s", user_name, host_name, "MPlayer", VERSION );
+}
+
+int 
+cddb_retrieve(cddb_data_t *cddb_data) {
+	char offsets[1024], command[1024];
+	char *ptr;
+	unsigned int i, time_len;
+	int ret;
+
+	ptr = offsets;
+	for( i=0; i<cddb_data->tracks ; i++ ) {
+		ptr += sprintf(ptr, "%d+", cdtoc[i].frame );
+		if (ptr-offsets > sizeof offsets - 40) break;
+	}
+	ptr[0]=0;
+	time_len = (cdtoc[cddb_data->tracks].frame)/75;
+	
+	cddb_data->freedb_server = DEFAULT_FREEDB_SERVER;
+	cddb_data->freedb_proto_level = 1;
+	cddb_data->xmcd_file = NULL;
+
+	cddb_create_hello(cddb_data);
+	if( cddb_get_proto_level(cddb_data)<0 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToGetProtocolLevel);
+		return -1;
+	}
+
+	//cddb_get_freedb_sites(&cddb_data);
+
+	sprintf(command, "cddb+query+%08lx+%d+%s%d", cddb_data->disc_id, cddb_data->tracks, offsets, time_len );
+	ret = cddb_http_request(command, cddb_query_parse, cddb_data);
+	if( ret<0 ) return -1;
+
+	if( cddb_data->cache_dir!=NULL ) {
+		free(cddb_data->cache_dir);
+	}
+	return 0;
+}
+
+int
+cddb_resolve(const char *dev, char **xmcd_file) {
+	char cddb_cache_dir[] = DEFAULT_CACHE_DIR;
+	char *home_dir = NULL;
+	cddb_data_t cddb_data;
+
+	if (cdtoc_last_track <= 0)
+	{
+	    cdtoc_last_track = read_toc(dev);
+	    if (cdtoc_last_track < 0) {
+		mp_msg(MSGT_OPEN, MSGL_ERR, MSGTR_MPDEMUX_CDDB_FailedToOpenDevice, dev);
+		return -1;
+	    }
+	}
+	cddb_data.tracks = cdtoc_last_track;
+	cddb_data.disc_id = cddb_discid(cddb_data.tracks);
+	cddb_data.anonymous = 1;	// Don't send user info by default
+
+	// Check if there is a CD in the drive
+	// FIXME: That's not really a good way to check
+	if( cddb_data.disc_id==0 ) {
+		mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MPDEMUX_CDDB_NoCDInDrive);
+		return -1;
+	}
+	
+	home_dir = getenv("HOME");
+#ifdef __MINGW32__
+	if( home_dir==NULL ) home_dir = getenv("USERPROFILE");
+	if( home_dir==NULL ) home_dir = getenv("HOMEPATH");
+	// Last resort, store the cddb cache in the mplayer directory
+	if( home_dir==NULL ) home_dir = (char *)get_path("");
+#endif
+	if( home_dir==NULL ) {
+		cddb_data.cache_dir = NULL;
+	} else {
+		cddb_data.cache_dir = malloc(strlen(home_dir)+strlen(cddb_cache_dir)+1);
+		if( cddb_data.cache_dir==NULL ) {
+			mp_msg(MSGT_DEMUX, MSGL_ERR, MSGTR_MemAllocFailed);
+			return -1;
+		}
+		sprintf(cddb_data.cache_dir, "%s%s", home_dir, cddb_cache_dir );
+	}
+
+	// Check for a cached file
+	if( cddb_read_cache(&cddb_data)<0 ) {
+		// No Cache found
+		if( cddb_retrieve(&cddb_data)<0 ) {
+			return -1;
+		}
+	}
+
+	if( cddb_data.xmcd_file!=NULL ) {
+//		printf("%s\n", cddb_data.xmcd_file );
+		*xmcd_file = cddb_data.xmcd_file;
+		return 0;
+	}
+	
+	return -1;
+}
+
+/*******************************************************************************************************************
+ *
+ * xmcd parser
+ *
+ *******************************************************************************************************************/
+char*
+xmcd_parse_dtitle(cd_info_t *cd_info, char *line) {
+	char *ptr, *album;
+	ptr = strstr(line, "DTITLE=");
+	if( ptr!=NULL ) {
+		ptr += 7;
+		album = strstr(ptr, "/");
+		if( album==NULL ) return NULL;
+		cd_info->album = malloc(strlen(album+2)+1);
+		if( cd_info->album==NULL ) {
+			return NULL;
+		}
+		strcpy( cd_info->album, album+2 );
+		album--;
+		album[0] = '\0';
+		cd_info->artist = malloc(strlen(ptr)+1);
+		if( cd_info->artist==NULL ) {
+			return NULL;
+		}
+		strcpy( cd_info->artist, ptr );
+	}
+	return ptr;
+}
+
+char*
+xmcd_parse_dgenre(cd_info_t *cd_info, char *line) {
+	char *ptr;
+	ptr = strstr(line, "DGENRE=");
+	if( ptr!=NULL ) {
+		ptr += 7;
+		cd_info->genre = malloc(strlen(ptr)+1);
+		if( cd_info->genre==NULL ) {
+			return NULL;
+		}
+		strcpy( cd_info->genre, ptr );
+	}
+	return ptr;
+}
+
+char*
+xmcd_parse_ttitle(cd_info_t *cd_info, char *line) {
+	unsigned int track_nb;
+	unsigned long sec, off;
+	char *ptr;
+	ptr = strstr(line, "TTITLE");
+	if( ptr!=NULL ) {
+		ptr += 6;
+		// Here we point to the track number
+		track_nb = atoi(ptr);
+		ptr = strstr(ptr, "=");
+		if( ptr==NULL ) return NULL;
+		ptr++;
+		
+		sec = cdtoc[track_nb].frame;
+		off = cdtoc[track_nb+1].frame-sec+1;
+
+		cd_info_add_track( cd_info, ptr, track_nb+1, (unsigned int)(off/(60*75)), (unsigned int)((off/75)%60), (unsigned int)(off%75), sec, off );
+	}
+	return ptr;
+}
+
+cd_info_t*
+cddb_parse_xmcd(char *xmcd_file) {
+	cd_info_t *cd_info = NULL;
+	int length, pos = 0;
+	char *ptr, *ptr2;
+	unsigned int audiolen;
+	if( xmcd_file==NULL ) return NULL;
+	
+	cd_info = cd_info_new();
+	if( cd_info==NULL ) {
+		return NULL;
+	}
+	
+	length = strlen(xmcd_file);
+	ptr = xmcd_file;
+	while( ptr!=NULL && pos<length ) {
+		// Read a line
+		ptr2 = ptr;
+		while( ptr2[0]!='\0' && ptr2[0]!='\r' && ptr2[0]!='\n' ) ptr2++;
+		if( ptr2[0]=='\0' ) {
+			break;
+		}
+		ptr2[0] = '\0';
+		// Ignore comments
+		if( ptr[0]!='#' ) {
+			// Search for the album title
+			if( xmcd_parse_dtitle(cd_info, ptr) );
+			// Search for the genre
+			else if( xmcd_parse_dgenre(cd_info, ptr) );
+			// Search for a track title
+			else if( xmcd_parse_ttitle(cd_info, ptr) ) audiolen++;	// <-- audiolen++ to shut up gcc warning
+		}
+		if( ptr2[1]=='\n' ) ptr2++;
+		pos = (ptr2+1)-ptr;
+		ptr = ptr2+1;
+	}
+
+	audiolen = cdtoc[cd_info->nb_tracks].frame-cdtoc[0].frame;	
+	cd_info->min  = (unsigned int)(audiolen/(60*75));
+	cd_info->sec  = (unsigned int)((audiolen/75)%60);
+	cd_info->msec = (unsigned int)(audiolen%75);
+	
+	return cd_info;
+}