diff --git a/CREDITS b/CREDITS index 44179905..e8467253 100644 --- a/CREDITS +++ b/CREDITS @@ -68,3 +68,11 @@ Roger Mundt dirkthedaring2 (?) * fixes for inverted playlist * speedups on connect + +Adrian Schroeter + * fixes for AMD64 + * -Wall cleanups + +Timo J. Rinne + * Server-side format conversion (on-the-fly transcoding) + diff --git a/contrib/mt-daapd.conf b/contrib/mt-daapd.conf index b6b0162e..2b917941 100644 --- a/contrib/mt-daapd.conf +++ b/contrib/mt-daapd.conf @@ -122,7 +122,37 @@ playlist /etc/mt-daapd.playlist # # -extensions .mp3,.m4a,.m4p +extensions .mp3,.m4a,.m4p,.ogg + +# +# ssc_extensions (optional) +# +# List of file extensions belonging to the files daap server +# performs internal format conversion and present to clients +# as WAV files. Extensions must also be present in 'extensions' +# configuration value, or files are not probed in the first +# place. +# + +ssc_extensions .ogg + +# +# ssc_prog (optional) +# +# Program that is used in server side format conversion. +# Program must accept following command line syntax: +# ssc_prog filename offset +# Parameter filename is the real name of the file that is +# to be converted and streamed, offset is number of bytes +# that are skipped from the beginning of the _output_ file +# before streaming is started. The resulting wav file (or +# rest of the file after initial seek) is written to the +# standard output by the ssc_prog program. This is typically +# a script that is a front end for different conversion tools +# handling different formats. +# + +ssc_prog /etc/mt-daapd-ssc-script # # logfile (optional) diff --git a/src/Makefile.am b/src/Makefile.am index a0457fb3..44008cc7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,7 +28,7 @@ mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ mp3-scanner.h mp3-scanner.c playlist.c playlist.h \ rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \ redblack.c redblack.h dynamic-art.c dynamic-art.h query.c query.h \ - xml-rpc.h xml-rpc.c \ + xml-rpc.h xml-rpc.c ssc.c ssc.h \ $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \ diff --git a/src/configfile.c b/src/configfile.c index 92a48939..51307eab 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -117,6 +117,8 @@ CONFIGELEMENT config_elements[] = { { 1,0,0,CONFIG_TYPE_INT,"compress",(void*)&config.compress,config_emit_int }, { 1,0,0,CONFIG_TYPE_STRING,"playlist",(void*)&config.playlist,config_emit_string }, { 1,0,0,CONFIG_TYPE_STRING,"extensions",(void*)&config.extensions,config_emit_string }, + { 1,0,0,CONFIG_TYPE_STRING,"ssc_extensions",(void*)&config.ssc_extensions,config_emit_string }, + { 1,0,0,CONFIG_TYPE_STRING,"ssc_prog",(void*)&config.ssc_prog,config_emit_string }, { 1,0,0,CONFIG_TYPE_STRING,"password",(void*)&config.readpassword, config_emit_string }, { 1,0,0,CONFIG_TYPE_STRING,"logfile",(void*)&config.logfile, config_emit_string }, { 0,0,0,CONFIG_TYPE_SPECIAL,"release",(void*)VERSION,config_emit_literal }, @@ -275,6 +277,8 @@ int config_read(char *file) { /* DWB: use alloced space so it can be freed without errors */ config.extensions=strdup(".mp3"); + config.ssc_extensions=strdup(""); + config.ssc_prog=strdup(""); /* DWB: use alloced space so it can be freed without errors */ config.servername=strdup("mt-daapd " VERSION); @@ -479,6 +483,8 @@ int config_write(WS_CONNINFO *pwsc) { if(ws_getvar(pwsc,"password") && strlen(ws_getvar(pwsc,"password"))) fprintf(configfile,"password\t%s\n",ws_getvar(pwsc,"password")); fprintf(configfile,"extensions\t%s\n",ws_getvar(pwsc,"extensions")); + fprintf(configfile,"ssc_extensions\t%s\n",ws_getvar(pwsc,"ssc_extensions")); + fprintf(configfile,"ssc_prog\t%s\n",ws_getvar(pwsc,"ssc_prog")); fprintf(configfile,"db_dir\t\t%s\n",ws_getvar(pwsc,"db_dir")); fprintf(configfile,"rescan_interval\t%s\n",ws_getvar(pwsc,"rescan_interval")); fprintf(configfile,"scan_type\t%s\n",ws_getvar(pwsc,"scan_type")); diff --git a/src/daapd.h b/src/daapd.h index d47e4eb5..26314f1a 100644 --- a/src/daapd.h +++ b/src/daapd.h @@ -64,6 +64,8 @@ typedef struct tag_config { char *runas; /**< Who to drop privs to (if run as root) */ char *dbdir; /**< Where to put the db file */ char *extensions; /**< What music file extentions to process */ + char *ssc_extensions; /**< What extensions are converted in server */ + char *ssc_prog; /**< Server side music format converter prog */ char *artfilename; /**< What filename to merge coverart with */ char *logfile; /**< What file to use as a logfile */ STATS stats; /**< Stats structure (see above) */ diff --git a/src/mDNSClientAPI.h b/src/mDNSClientAPI.h index 151ce044..c04c1572 100644 --- a/src/mDNSClientAPI.h +++ b/src/mDNSClientAPI.h @@ -23,6 +23,9 @@ Change History (most recent first): $Log$ +Revision 1.5 2005/02/21 08:10:34 rpedde +integrate server-side conversion patches, -Wall cleanups, AMD64 fixes, and xml-rpc cleanups + Revision 1.4 2005/01/10 01:07:01 rpedde Synchronize mDNS to Apples 58.8 drop @@ -523,13 +526,9 @@ typedef signed char mDNSs8; typedef unsigned char mDNSu8; typedef signed short mDNSs16; typedef unsigned short mDNSu16; -#if _LP64 -typedef signed int mDNSs32; -typedef unsigned int mDNSu32; -#else -typedef signed long mDNSs32; -typedef unsigned long mDNSu32; -#endif +#include +typedef int32_t mDNSs32; +typedef u_int32_t mDNSu32; // To enforce useful type checking, we make mDNSInterfaceID be a pointer to a dummy struct // This way, mDNSInterfaceIDs can be assigned, and compared with each other, but not with other types diff --git a/src/mDNSUNP.c b/src/mDNSUNP.c index bb57380f..5509bd5e 100644 --- a/src/mDNSUNP.c +++ b/src/mDNSUNP.c @@ -23,6 +23,9 @@ Change History (most recent first): $Log$ +Revision 1.3 2005/02/21 08:10:34 rpedde +integrate server-side conversion patches, -Wall cleanups, AMD64 fixes, and xml-rpc cleanups + Revision 1.2 2005/01/10 01:07:01 rpedde Synchronize mDNS to Apples 58.8 drop @@ -151,8 +154,7 @@ struct ifi_info *get_ifi_info(int family, int doaliases) for (ptr = buf; ptr < buf + ifc.ifc_len; ) { ifr = (struct ifreq *) ptr; - len = GET_SA_LEN(ifr->ifr_addr); - ptr += sizeof(ifr->ifr_name) + len; /* for next one in buffer */ + ptr += sizeof(*ifr); // fprintf(stderr, "intf %d name=%s AF=%d\n", index, ifr->ifr_name, ifr->ifr_addr.sa_family); diff --git a/src/main.c b/src/main.c index 837cfbed..4b711d5c 100644 --- a/src/main.c +++ b/src/main.c @@ -78,6 +78,7 @@ #include "mp3-scanner.h" #include "webserver.h" #include "playlist.h" +#include "ssc.h" #include "dynamic-art.h" #ifndef WITHOUT_MDNS @@ -184,6 +185,7 @@ void daap_handler(WS_CONNINFO *pwsc) { MP3FILE *pmp3; int file_fd; + FILE *file_ptr; /* for possible conv filter */ int session_id=0; int img_fd; @@ -194,6 +196,8 @@ void daap_handler(WS_CONNINFO *pwsc) { off_t real_len; off_t file_len; + char *real_path; + int bytes_copied=0; GZIP_STREAM *gz; @@ -384,6 +388,77 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!pmp3) { DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item); ws_returnerror(pwsc,404,"File Not Found"); + } else if ((real_path=server_side_convert_path(pmp3->path)) != NULL) { + // The file should be converted in the server side. + DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n", + pwsc->threadno,real_path); + file_ptr=server_side_convert_open(real_path,offset); + if (file_ptr) { + file_fd = fileno(file_ptr); + } else { + file_fd = -1; + } + if(file_fd == -1) { + if (file_ptr) { + server_side_convert_close(file_ptr); + } + pwsc->error=errno; + DPRINTF(E_WARN,L_WS, + "Thread %d: Error opening %s for conversion\n", + pwsc->threadno,real_path); + ws_returnerror(pwsc,404,"Not found"); + config_set_status(pwsc,session_id,NULL); + db_dispose(pmp3); + free(pmp3); + free(real_path); + } else { + // DWB: fix content-type to correctly reflect data + // content type (dmap tagged) should only be used on + // dmap protocol requests, not the actually song data + if(pmp3->type) + ws_addresponseheader(pwsc,"Content-Type","audio/%s", + pmp3->type); + // Also content-length -heade would be nice, but since + // we don't really know it here, so let's leave it out. + ws_addresponseheader(pwsc,"Connection","Close"); + + if(!offset) + ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); + else { + // This is actually against the protocol, since + // range MUST be explicit according to HTTP-standard + // Seems to work at least with iTunes. + ws_addresponseheader(pwsc, + "Content-Range","bytes %ld-*/*", + (long)offset); + ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n"); + } + + ws_emitheaders(pwsc); + + config_set_status(pwsc,session_id, + "Streaming file via convert filter '%s'", + pmp3->fname); + DPRINTF(E_LOG,L_WS, + "Session %d: Streaming file '%s' to %s (offset %ld)\n", + session_id,pmp3->fname, pwsc->hostname,(long)offset); + + if(!offset) + config.stats.songs_served++; /* FIXME: remove stat races */ + if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) { + DPRINTF(E_INF,L_WS, + "Error copying converted file to remote... %s\n", + strerror(errno)); + } else { + DPRINTF(E_INF,L_WS, + "Finished streaming converted file to remote\n"); + } + server_side_convert_close(file_ptr); + config_set_status(pwsc,session_id,NULL); + db_dispose(pmp3); + free(pmp3); + free(real_path); + } } else { /* got the file, let's open and serve it */ file_fd=r_open2(pmp3->path,O_RDONLY); @@ -634,8 +709,8 @@ void *signal_handler(void *arg) { switch(sig) { case SIGCLD: DPRINTF(E_LOG,L_MAIN,"Got CLD signal. Reaping\n"); - while (wait(&status)) { - }; + while (wait3(&status, WNOHANG, NULL) > 0) { + } break; case SIGINT: DPRINTF(E_LOG,L_MAIN,"Got INT signal. Notifying daap server.\n"); diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index c629d91f..872ce7fd 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -50,6 +50,7 @@ #include "err.h" #include "mp3-scanner.h" #include "playlist.h" +#include "ssc.h" #ifndef HAVE_STRCASESTR # include "strcasestr.h" @@ -384,6 +385,7 @@ int scan_path(char *path) { char mp3_path[PATH_MAX]; struct stat sb; int modified_time; + char *ext; if((current_dir=opendir(path)) == NULL) { DPRINTF(E_WARN,L_SCAN,"opendir: %s\n",strerror(errno)); @@ -429,9 +431,8 @@ int scan_path(char *path) { config.process_m3u){ /* we found an m3u file */ scan_static_playlist(path, pde, &sb); - } else if (strcasestr(config.extensions, - (char*)&pde->d_name[strlen(pde->d_name) - 4])) { - + } else if (((ext = strrchr(pde->d_name, '.')) != NULL) && + (strcasestr(config.extensions, ext))) { /* only scan if it's been changed, or empty db */ modified_time=sb.st_mtime; DPRINTF(E_DBG,L_SCAN,"FS Mod time: %d\n",modified_time); @@ -533,8 +534,8 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) { DPRINTF(E_INF,L_SCAN,"Found music file: %s\n",pde->d_name); memset((void*)&mp3file,0,sizeof(mp3file)); - mp3file.path=mp3_path; - mp3file.fname=pde->d_name; + mp3file.path=strdup(mp3_path); + mp3file.fname=strdup(pde->d_name); if(strlen(pde->d_name) > 4) mp3file.type=strdup(strrchr(pde->d_name, '.') + 1); @@ -557,6 +558,8 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) { mp3file.time_added=psb->st_ctime; mp3file.time_modified=psb->st_mtime; + server_side_convert_set(&mp3file); + DPRINTF(E_DBG,L_SCAN," Date Added: %d\n",mp3file.time_added); db_add(&mp3file); @@ -942,6 +945,8 @@ int scan_get_mp3tags(char *file, MP3FILE *pmp3) { * Free up the tags that were dynamically allocated */ int scan_freetags(MP3FILE *pmp3) { + MAYBEFREE(pmp3->path); + MAYBEFREE(pmp3->fname); MAYBEFREE(pmp3->title); MAYBEFREE(pmp3->artist); MAYBEFREE(pmp3->album); @@ -1769,3 +1774,4 @@ void make_composite_tags(MP3FILE *song) /* Ogg used to be set as an item_kind of 4. Dunno why */ song->item_kind = 2; } + diff --git a/src/query.c b/src/query.c index 95445bd5..426c63f1 100644 --- a/src/query.c +++ b/src/query.c @@ -422,6 +422,8 @@ int query_test(query_node_t* query, void* target) case qot_const: return query->left.constant; } + /* should not happen */ + return 0; } void query_free(query_node_t* query) diff --git a/src/ssc.c b/src/ssc.c new file mode 100644 index 00000000..111e337d --- /dev/null +++ b/src/ssc.c @@ -0,0 +1,162 @@ +/* + * $Id$ + * Implementation file for server side format conversion. + * + * Copyright (C) 2005 Timo J. Rinne (tri@iki.fi) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* htons and friends */ +#include +#include /* why here? For osx 10.2, of course! */ + +#include "daapd.h" +#include "db-memory.h" +#include "err.h" +#include "mp3-scanner.h" +#include "ssc.h" + +#ifndef HAVE_STRCASESTR +# include "strcasestr.h" +#endif + +/** + * If path is server side convertable, return the path to real file. + * + * @param path char * to the path in the database. + */ +char *server_side_convert_path(char *path) +{ + char *r = NULL; + + if (path && + (strlen(path) > strlen(SERVER_SIDE_CONVERT_SUFFIX)) && + (strcmp(path + strlen(path) - strlen(SERVER_SIDE_CONVERT_SUFFIX), + SERVER_SIDE_CONVERT_SUFFIX) == 0)) { + /* Lose the artificial suffix. Could use strndup here.*/ + r = strdup(path); + r[strlen(path) - strlen(SERVER_SIDE_CONVERT_SUFFIX)] = '\0'; + } + + return r; +} + +/** + * Check if the file entry (otherwise complete) is such that + * file should be converted in server end to wav-format. + * If so, the info is modified accordingly and non-zero return + * value is returned. + * + * @param song MP3FILE of the file to possibly set to server side conversion + */ +int server_side_convert_set(MP3FILE *pmp3) +{ + char *fname, *path, *description, *ext; + + if ((!config.ssc_extensions) || + (!config.ssc_extensions[0]) || + (!config.ssc_prog) || + (!config.ssc_prog[0]) || + (!pmp3->fname) || + (!pmp3->path) || + (!pmp3->type) || + ((strlen(pmp3->fname) > strlen(SERVER_SIDE_CONVERT_SUFFIX)) && + (strcmp(pmp3->fname + + strlen(pmp3->fname) - + strlen(SERVER_SIDE_CONVERT_SUFFIX), + SERVER_SIDE_CONVERT_SUFFIX) == 0))) { + return 0; + } + + if (((ext = strrchr(pmp3->path, '.')) != NULL) && + (strcasestr(config.ssc_extensions, ext))) { + fname = (char *)malloc(strlen(pmp3->fname) + + strlen(SERVER_SIDE_CONVERT_SUFFIX) + 1); + path = (char *)malloc(strlen(pmp3->path) + + strlen(SERVER_SIDE_CONVERT_SUFFIX) + 1); + description = (char *)malloc(strlen(pmp3->description) + + strlen(SERVER_SIDE_CONVERT_DESCR) + 1); + strcpy(fname, pmp3->fname); + strcat(fname, SERVER_SIDE_CONVERT_SUFFIX); + free(pmp3->fname); + pmp3->fname = fname; + strcpy(path, pmp3->path); + strcat(path, SERVER_SIDE_CONVERT_SUFFIX); + free(pmp3->path); + pmp3->path = path; + strcpy(description, pmp3->description); + strcat(description, SERVER_SIDE_CONVERT_DESCR); + free(pmp3->description); + pmp3->description = description; + free(pmp3->type); + pmp3->type = strdup("wav"); + if (pmp3->samplerate > 0) { + // Here we guess that it's always 16 bit stereo samples, + // which is accurate enough for now. + pmp3->bitrate = (pmp3->samplerate * 4 * 8) / 1000; + } + return 1; + } + return 0; +} + +/** + * Open the source file with convert fiter. + * + * @param path char * to the real filename. + * @param offset off_t to the point in file where the streaming starts. + */ +FILE *server_side_convert_open(char *path, off_t offset) +{ + char *cmd; + FILE *f; + + cmd=(char *)malloc(strlen(config.ssc_prog) + + strlen(path) + + 64); + sprintf(cmd, "%s \"%s\" %ld", + config.ssc_prog, path, (long)offset); + f = popen(cmd, "r"); + return f; +} + +/** + * Open the source file with convert fiter. + * + * @param FILE * returned by server_side_convert_open be closed. + */ +void server_side_convert_close(FILE *f) +{ + if (f) + pclose(f); + return; +} + diff --git a/src/ssc.h b/src/ssc.h new file mode 100644 index 00000000..1479e4c0 --- /dev/null +++ b/src/ssc.h @@ -0,0 +1,34 @@ +/* + * $Id$ + * Implementation file for server side format conversion. + * + * Copyright (C) 2005 Timo J. Rinne (tri@iki.fi) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SCC_H_ +#define _SCC_H_ + +#define SERVER_SIDE_CONVERT_SUFFIX ".-*-ssc-*-.wav" +#define SERVER_SIDE_CONVERT_DESCR " (converted to WAV)" + +extern int server_side_convert_set(MP3FILE *pmp3); +extern char *server_side_convert_path(char *path); +extern FILE *server_side_convert_open(char *path, off_t offset); +extern void server_side_convert_close(FILE *f); + +#endif /* _SCC_H_ */ + diff --git a/src/xml-rpc.c b/src/xml-rpc.c index 2c1d445c..dcf94188 100644 --- a/src/xml-rpc.c +++ b/src/xml-rpc.c @@ -54,7 +54,7 @@ void xml_get_playlists(WS_CONNINFO *pwsc) { ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n"); ws_emitheaders(pwsc); - ws_writefd(pwsc,""); + ws_writefd(pwsc,""); ws_writefd(pwsc,""); /* enumerate all the playlists */ @@ -99,15 +99,18 @@ void xml_get_playlistitems(WS_CONNINFO *pwsc) { playlistid=atoi(playlistnum); + ws_writefd(pwsc,""); ws_writefd(pwsc,""); henum=db_playlist_items_enum_begin(playlistid); while((itemid=db_playlist_items_enum(&henum)) != -1) { current=db_find(itemid); if(0 != current) { - ws_writefd(pwsc,"%lu",itemid); + ws_writefd(pwsc,""); + ws_writefd(pwsc,"%lu",itemid); temp=xml_entity_encode(current->title); ws_writefd(pwsc,"%s",temp); + ws_writefd(pwsc,""); free(temp); db_dispose(current); free(current);