diff options
author | Daniel Caujolle-Bert <f1rmb@users.sourceforge.net> | 2001-12-08 03:01:40 +0000 |
---|---|---|
committer | Daniel Caujolle-Bert <f1rmb@users.sourceforge.net> | 2001-12-08 03:01:40 +0000 |
commit | 52bbd9a237392fa9a5f8428ac6a2ee2111f0a4ba (patch) | |
tree | a3335ffcd4fbdac588a1825314453383a2def688 | |
parent | 2887e16506cf5bcb6bf848e2155216b470ef67f0 (diff) | |
download | xine-lib-52bbd9a237392fa9a5f8428ac6a2ee2111f0a4ba.tar.gz xine-lib-52bbd9a237392fa9a5f8428ac6a2ee2111f0a4ba.tar.bz2 |
Add CDDB support.
CVS patchset: 1179
CVS date: 2001/12/08 03:01:40
-rw-r--r-- | src/input/input_cda.c | 488 |
1 files changed, 482 insertions, 6 deletions
diff --git a/src/input/input_cda.c b/src/input/input_cda.c index 4dd7914bd..8d24a0275 100644 --- a/src/input/input_cda.c +++ b/src/input/input_cda.c @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * - * $Id: input_cda.c,v 1.1 2001/12/06 23:53:20 f1rmb Exp $ + * $Id: input_cda.c,v 1.2 2001/12/08 03:01:40 f1rmb Exp $ */ #ifdef HAVE_CONFIG_H @@ -26,6 +26,15 @@ #include <stdio.h> #include <stdlib.h> + +#include <netdb.h> +#include <signal.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include <pwd.h> +#include <sys/types.h> + #include <sys/stat.h> #include <unistd.h> #include <errno.h> @@ -45,7 +54,6 @@ #if ! defined (HAVE_LINUX_CDROM_H) && ! defined (HAVE_SYS_CDIO_H) #error "you need to add cdrom / CDA support for your platform to input_cda and configure.in" #endif - #include "xine_internal.h" #include "xineutils.h" #include "input_plugin.h" @@ -57,6 +65,9 @@ #define CDROM "/dev/cdaudio" +#define CDDB_SERVER "freedb.freedb.org" +#define CDDB_PORT 8880 + /* for type */ #define CDAUDIO 1 #define CDDATA 2 @@ -76,6 +87,7 @@ typedef struct { int length; int start; int track; + char *title; } trackinfo_t; typedef struct { @@ -87,6 +99,11 @@ typedef struct { int num_tracks; int first_track; int length; + unsigned long disc_id; + char *title; + char *category; + char *cdiscid; + char ui_title[256]; trackinfo_t *track; } cdainfo_t; @@ -100,6 +117,12 @@ typedef struct { uint32_t speed; char *mrl; + + struct { + char *server; + int port; + int fd; + } cddb; cdainfo_t *cda; @@ -117,6 +140,385 @@ static void _cda_stop_cd(cdainfo_t *); */ /* + * Return user name. + */ +static char *_cda_get_username_safe(void) { + char *un; + struct passwd *pw; + + pw = getpwuid(geteuid()); + + un = strdup(((pw && pw->pw_name) ? strdup(pw->pw_name) : "unknown")); + + return un; +} + +/* + * Return host name. + */ +static char *_cda_get_hostname_safe(void) { + char *hn; + char buf[2048]; + + memset(&buf, 0, sizeof(buf)); + if(gethostname(&buf[0], sizeof(buf)) == 0) + hn = strdup(buf); + else + hn = strdup("unknown"); + + return hn; +} + +/* + * ************* CDDB ********************* + */ +/* + * Small sighandler ;-) + */ +static void die(int signal) { + exit(signal); +} + +/* + * Read from socket, fill char *s, return size length. + */ +static int _cda_cddb_socket_read(char *s, int size, int socket) { + int i = 0, r; + char c; + + alarm(20); + signal(SIGALRM, die); + + while((r=recv(socket, &c, 1, 0)) != 0) { + if(c == '\r' || c == '\n') + break; + if(i > size) + break; + s[i] = c; + i++; + } + s[i] = '\n'; + s[i+1] = 0; + recv(socket, &c, 1, 0); + + alarm(0); + signal(SIGALRM, SIG_DFL); + + s[i] = 0; + return r; +} + +/* + * Send a command to socket + */ +static int _cda_cddb_send_command(cda_input_plugin_t *this, char *cmd) { + + if((this == NULL) || (this->cddb.fd < 0) || (cmd == NULL)) + return -1; + + return (send(this->cddb.fd, cmd, strlen(cmd), 0)); +} + +/* + * Handle return code od a command result. + */ +static int _cda_cddb_handle_code(char *buf) { + int rcode, fdig, sdig, tdig; + int err = -1; + + if(sscanf(buf, "%d", &rcode) == 1) { + + fdig = rcode / 100; + sdig = (rcode - (fdig * 100)) / 10; + tdig = (rcode - (fdig * 100) - (sdig * 10)); + + /* + printf(" %d--\n", fdig); + printf(" -%d-\n", sdig); + printf(" --%d\n", tdig); + */ + switch(fdig) { + case 1: + // printf("Informative message\n"); + err = 0; + break; + case 2: + // printf("Command OK\n"); + err = 0; + break; + case 3: + // printf("Command OK so far, continue\n"); + err = 0; + break; + case 4: + // printf("Command OK, but cannot be performed for some specified reasons\n"); + err = -1; + break; + case 5: + // printf("Command unimplemented, incorrect, or program error\n"); + err = -1; + break; + default: + // printf("Unhandled case %d\n", fdig); + err = -1; + break; + } + + switch(sdig) { + case 0: + // printf("Ready for further commands\n"); + err = 0; + break; + case 1: + // printf("More server-to-client output follows (until terminating marker)\n"); + err = 0; + break; + case 2: + // printf("More client-to-server input follows (until terminating marker)\n"); + err = 0; + break; + case 3: + // printf("Connection will close\n"); + err = -1; + break; + default: + // printf("Unhandled case %d\n", sdig); + err = -1; + break; + } + + if(err >= 0) + err = rcode; + } + + return err; +} + +/* + * Try to talk with CDDB server (to retrieve disc/tracks titles). + */ +static void _cda_cddb_retrieve(cda_input_plugin_t *this) { + char buffer[2048]; + char *username, *hostname; + int err, i; + + if((this == NULL) || (this->cddb.fd < 0)) + return; + + username = _cda_get_username_safe(); + hostname = _cda_get_hostname_safe(); + + memset(&buffer, 0, sizeof(buffer)); + + /* Get welcome message */ + if(_cda_cddb_socket_read(&buffer[0], 2047, this->cddb.fd)) { + if((err = _cda_cddb_handle_code(buffer)) >= 0) { + /* send hello */ + memset(&buffer, 0, sizeof(buffer)); + sprintf(buffer, "cddb hello %s %s xine %s\n", username, hostname, VERSION); + if((err = _cda_cddb_send_command(this, buffer)) > 0) { + /* Get answer from hello */ + memset(&buffer, 0, sizeof(buffer)); + if(_cda_cddb_socket_read(&buffer[0], 2047, this->cddb.fd)) { + /* Parse returned code */ + if((err = _cda_cddb_handle_code(buffer)) >= 0) { + /* We are logged, query disc */ + memset(&buffer, 0, sizeof(buffer)); + sprintf(buffer, "cddb query %08lx %d ", this->cda->disc_id, this->cda->num_tracks); + for(i = 0; i < this->cda->num_tracks; i++) { + sprintf(buffer, "%s%d ", buffer, this->cda->track[i].start); + } + sprintf(buffer, "%s%d\n", buffer, this->cda->track[i].length); + if((err = _cda_cddb_send_command(this, buffer)) > 0) { + memset(&buffer, 0, sizeof(buffer)); + if(_cda_cddb_socket_read(&buffer[0], 2047, this->cddb.fd)) { + /* Parse returned code */ + if((err = _cda_cddb_handle_code(buffer)) == 200) { + /* Disc entry exist */ + char *m = NULL, *p = buffer; + int f = 0; + + while((f <= 2) && ((m = xine_strsep(&p, " ")) != NULL)) { + if(f == 1) + this->cda->category = strdup(m); + else if(f == 2) + this->cda->cdiscid = strdup(m); + f++; + } + } + + /* Now, grab track titles */ + memset(&buffer, 0, sizeof(buffer)); + sprintf(buffer, "cddb read %s %s\n", this->cda->category, this->cda->cdiscid); + if((err = _cda_cddb_send_command(this, buffer)) > 0) { + /* Get answer from read */ + memset(&buffer, 0, sizeof(buffer)); + if(_cda_cddb_socket_read(&buffer[0], 2047, this->cddb.fd)) { + /* Great, now we will have track titles */ + if((err = _cda_cddb_handle_code(buffer)) == 210) { + char buf[2048]; + unsigned char *pt; + int tnum; + + while(strcmp(buffer, ".")) { + memset(&buffer, 0, sizeof(buffer)); + _cda_cddb_socket_read(&buffer[0], 2047, this->cddb.fd); + if(sscanf(buffer, "DTITLE=%s", &buf[0]) == 1) { + pt = strrchr(buffer, '='); + if(pt) pt++; + this->cda->title = strdup(pt); + } + else if(sscanf(buffer, "TTITLE%d=%s", &tnum, &buf[0]) == 2) { + pt = strrchr(buffer, '='); + if(pt) pt++; + this->cda->track[tnum].title = strdup(pt); + } + } + } + } + } + } + } + } + } + } + } + } + + free(username); + free(hostname); +} + +/* + * Open a socket. + */ +static int _cda_cddb_socket_open(cda_input_plugin_t *this) { + int sockfd; + struct hostent *he; + struct sockaddr_in their_addr; + + if(this == NULL) + return -1; + + this->cddb.server = + this->config->register_string(this->config, "input.cda_cddb_server", CDDB_SERVER, + "cddbp server name", NULL, NULL, NULL); + + this->cddb.port = + this->config->register_num(this->config, "input.cda_cddb_port", CDDB_PORT, + "cddbp server port", NULL, NULL, NULL); + + + alarm(15); + signal(SIGALRM, die); + if((he=gethostbyname(this->cddb.server)) == NULL) { + alarm(0); + signal(SIGALRM, SIG_DFL); + return -1; + } + + if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + alarm(0); + signal(SIGALRM, SIG_DFL); + return -1; + } + + their_addr.sin_family = AF_INET; + their_addr.sin_port = htons(this->cddb.port); + their_addr.sin_addr = *((struct in_addr *)he->h_addr); + memset(&(their_addr.sin_zero), 0, 8); + + if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { + alarm(0); + signal(SIGALRM, SIG_DFL); + return -1; + } + alarm(0); + signal(SIGALRM, SIG_DFL); + + return sockfd; +} + +/* + * Close the socket + */ +static void _cda_cddb_socket_close(cda_input_plugin_t *this) { + + if((this == NULL) || (this->cddb.fd < 0)) + return; + + close(this->cddb.fd); + this->cddb.fd = -1; +} + +/* + * + */ +static unsigned int _cda_cddb_sum(int n) { + unsigned int ret = 0; + + while(n > 0) { + ret += (n % 10); + n /= 10; + } + return ret; +} + +/* + * Compute cddb disc compliant id + */ +static unsigned long _cda_calc_cddb_id(cdainfo_t *cda) { + int i, tsum = 0; + + if(cda == NULL || (cda->num_tracks <= 0)) + return 0; + + for(i = 0; i < cda->num_tracks; i++) + tsum += _cda_cddb_sum((cda->track[i].start / 75)); + + return ((tsum % 0xff) << 24 + | (cda->track[cda->num_tracks].length - (cda->track[0].start / 75)) << 8 + | cda->num_tracks); +} + +/* + * return cbbd disc id. + */ +static unsigned long _cda_get_cddb_id(cdainfo_t *cda) { + + if(cda == NULL || (cda->num_tracks <= 0)) + return 0; + + return _cda_calc_cddb_id(cda); +} + +/* + * grab (try) titles from cddb server. + */ +static void _cda_cbbd_grab_infos(cda_input_plugin_t *this) { + + if(this == NULL) + return; + + this->cddb.fd = _cda_cddb_socket_open(this); + if(this->cddb.fd >= 0) { + printf("input_cda: server '%s:%d' successfuly connected.\n", + this->cddb.server, this->cddb.port); + + _cda_cddb_retrieve(this); + } + else + printf("input_cda: opening server '%s:%d' failed: %s\n", + this->cddb.server, this->cddb.port, strerror(errno)); + + _cda_cddb_socket_close(this); +} + +/* + * **************** CDDB END ********************* + */ + +/* * Get CDA status (pos, cur track, status) * This function was grabbed and adapted from workbone (thanks to this team). */ @@ -420,8 +822,24 @@ static int _cda_read_toc_cd(cdainfo_t *cda) { cda->first_track = hdr.cdth_trk0; cda->num_tracks = hdr.cdth_trk1; - if(cda->track) + if(cda->track) { + /* Freeing old track/disc titles */ + for(i = 0; i < cda->num_tracks; i++) { + if(cda->track[i].title) + free(cda->track[i].title); + } + + if(cda->title) + free(cda->title); + + if(cda->category) + free(cda->category); + + if(cda->cdiscid) + free(cda->cdiscid); + cda->track = (trackinfo_t *) realloc(cda->track, (cda->num_tracks + 1) * sizeof(trackinfo_t)); + } else cda->track = (trackinfo_t *) malloc((cda->num_tracks + 1) * sizeof(trackinfo_t)); @@ -441,6 +859,7 @@ static int _cda_read_toc_cd(cdainfo_t *cda) { cda->track[i].type = (entry.cdte_ctrl & CDROM_DATA_TRACK) ? CDDATA : CDAUDIO; cda->track[i].length = entry.cdte_addr.msf.minute * 60 + entry.cdte_addr.msf.second; cda->track[i].start = cda->track[i].length * 75 + entry.cdte_addr.msf.frame; + cda->track[i].title = NULL; } /* compute real track length */ @@ -452,12 +871,27 @@ static int _cda_read_toc_cd(cdainfo_t *cda) { cda->track[i].length = (cda->track[i + 1].start - cda->track[i].start) * 2; } - cda->length = cda->track[cda->num_tracks].length; + cda->length = cda->track[cda->num_tracks].length; + cda->disc_id = _cda_get_cddb_id(cda); + cda->title = NULL; + cda->cdiscid = NULL; + cda->category = NULL; #ifdef DEBUG_DISC printf("Disc have %d track(s), first track is %d, length %d (%02d:%02d:%02d)\n", cda->num_tracks, cda->first_track, cda->length, (cda->length / (60 * 60)), ((cda->length / 60) % 60), (cda->length %60)); + + { /* CDDB infos */ + int t; + printf("CDDB disc ID is %08lx\n", cda->disc_id); + printf("%d, ", cda->num_tracks); + for(t = 0; t < cda->num_tracks; t++) { + printf("%d, ", cda->track[t].start); + } + printf("%d\n", cda->track[t].length); + } + for(i = 0; i < cda->num_tracks; i++) { printf("Track %2d, %s type, length %3d seconds(%02d:%02d:%02d), start at %3d secs\n", i, @@ -468,6 +902,13 @@ static int _cda_read_toc_cd(cdainfo_t *cda) { (cda->track[i].length %60), cda->track[i].start); } + printf("LEADOUT (%2d), length %3d seconds(%02d:%02d:%02d), start at %3d secs\n", + i, + cda->track[i].length, + (cda->track[i].length / (60 * 60)), + ((cda->track[i].length / 60) % 60), + (cda->track[i].length %60), + cda->track[i].start); #endif return 1; @@ -530,6 +971,26 @@ static void _cda_free_cda(cdainfo_t *cda) { } /* + * Change title in UI. + */ +static void _cda_update_ui_title(cda_input_plugin_t *this) { + xine_ui_event_t uievent; + + if((this == NULL) + || (this->cda->title == NULL) || (this->cda->track[this->cda->cur_track - 1].title == NULL)) + return; + + memset(&this->cda->ui_title, 0, sizeof(this->cda->ui_title)); + snprintf(this->cda->ui_title, 255, "%s -*- %d: %s", this->cda->title, + this->cda->cur_track, this->cda->track[this->cda->cur_track - 1].title); + + uievent.event.type = XINE_EVENT_UI_SET_TITLE; + uievent.data = this->cda->ui_title; + + xine_send_event(this->xine, &uievent.event); +} + +/* * *************************** END OF PRIVATES ************************************ */ @@ -554,6 +1015,8 @@ static int cda_plugin_open (input_plugin_t *this_gen, char *mrl) { _cda_free_cda(this->cda); return 0; } + + _cda_cbbd_grab_infos(this); filename = (char *) &mrl[6]; @@ -631,6 +1094,7 @@ static off_t cda_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin switch (origin) { case SEEK_SET: _cda_play_track_from_pos(this->cda, this->cda->cur_track, (int) (offset/CDA_BLOCKSIZE)); + _cda_update_ui_title(this); break; default: @@ -757,6 +1221,8 @@ static mrl_t **cda_plugin_get_dir (input_plugin_t *this_gen, if(!this->cda->num_tracks) return NULL; + _cda_cbbd_grab_infos(this); + *nEntries = this->cda->num_tracks; for(i=1; i <= this->cda->num_tracks; i++) { @@ -828,6 +1294,8 @@ static char **cda_plugin_get_autoplay_list (input_plugin_t *this_gen, int *nFile if(!this->cda->num_tracks) return NULL; + _cda_cbbd_grab_infos(this); + *nFiles = this->cda->num_tracks; for(i = 1; i <= this->cda->num_tracks; i++) @@ -843,7 +1311,7 @@ static char **cda_plugin_get_autoplay_list (input_plugin_t *this_gen, int *nFile */ static char* cda_plugin_get_mrl (input_plugin_t *this_gen) { cda_input_plugin_t *this = (cda_input_plugin_t *) this_gen; - + return this->mrl; } @@ -901,7 +1369,7 @@ input_plugin_t *init_input_plugin (int iface, xine_t *xine) { this->xine = xine; this->config = config; - + this->mrl = NULL; this->cda = (cdainfo_t *) xine_xmalloc(sizeof(cdainfo_t)); @@ -912,6 +1380,14 @@ input_plugin_t *init_input_plugin (int iface, xine_t *xine) { "path to your local cd audio device file", NULL, NULL, NULL)); + this->cddb.server = config->register_string(config, "input.cda_cddb_server", CDDB_SERVER, + "cddbp server name", NULL, NULL, NULL); + + this->cddb.port = config->register_num(config, "input.cda_cddb_port", CDDB_PORT, + "cddbp server port", NULL, NULL, NULL); + + this->cddb.fd = -1; + this->mrls = (mrl_t **) xine_xmalloc(sizeof(mrl_t*)); this->mrls_allocated_entries = 0; |