diff --git a/src/Makefile.am b/src/Makefile.am index 4e87f915..2436a183 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS=plugins sbin_PROGRAMS = mt-daapd -bin_PROGRAMS = wavstreamer mtd-update +bin_PROGRAMS = wavstreamer if COND_REND_POSIX PRENDSRC=mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c mDNSUNP.c \ @@ -46,20 +46,12 @@ if COND_GDBM GDBM=db-gdbm.c db-gdbm.h endif -mtd_update_SOURCES = mtd-update.c conf.c conf.h ll.c ll.h \ - db-sql.c db-sql.h db-generic.c db-generic.h smart-parser.c \ - smart-parser.h err.c err.h os-unix.c os.h xml-rpc.c xml-rpc.h \ - restart.c restart.h uici.c uici.h ssc.c ssc.h \ - webserver.c webserver.h compat.c compat.h \ - $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) \ - $(SQLITEDB) $(SQLITE3DB) - wavstreamer_SOURCES = wavstreamer.c mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \ mp3-scanner.h mp3-scanner.c rend-unix.h \ - dynamic-art.c dynamic-art.h ssc.c ssc.h \ + dynamic-art.c dynamic-art.h \ db-generic.c db-generic.h dispatch.c dispatch.h \ rxml.c rxml.h redblack.c redblack.h scan-mp3.c scan-mp4.c \ scan-xml.c scan-wma.c scan-aac.c scan-aac.h scan-wav.c scan-url.c \ diff --git a/src/db-sql.c b/src/db-sql.c index 8d96eaa7..f31a8241 100644 --- a/src/db-sql.c +++ b/src/db-sql.c @@ -39,8 +39,8 @@ #include "db-generic.h" #include "db-sql.h" #include "restart.h" -#include "ssc.h" #include "smart-parser.h" +#include "plugin.h" #ifdef HAVE_LIBSQLITE #include "db-sql-sqlite2.h" @@ -1354,7 +1354,7 @@ int db_sql_get_size(DBQUERYINFO *pinfo, SQL_ROW valarray) { case queryTypeItems: case queryTypePlaylistItems: /* essentially the same query */ /* see if this is going to be transcoded */ - transcode = server_side_convert(valarray[37]); + transcode = plugin_ssc_can_transcode(valarray[37]); /* Items that get changed by transcode: * @@ -1530,7 +1530,7 @@ int db_sql_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presul case queryTypeItems: case queryTypePlaylistItems: /* essentially the same query */ /* see if this is going to be transcoded */ - transcode = server_side_convert(valarray[37]); + transcode = plugin_ssc_can_transcode(valarray[37]); /* Items that get changed by transcode: * diff --git a/src/dispatch.c b/src/dispatch.c index 6a4b3558..8a5a242d 100644 --- a/src/dispatch.c +++ b/src/dispatch.c @@ -42,8 +42,8 @@ #include "configfile.h" #include "err.h" #include "mp3-scanner.h" +#include "plugin.h" #include "webserver.h" -#include "ssc.h" #include "dynamic-art.h" #include "restart.h" #include "daapd.h" @@ -739,7 +739,6 @@ char *dispatch_xml_encode(char *original, int len) { void dispatch_stream_id(WS_CONNINFO *pwsc, int session, char *id) { MP3FILE *pmp3; - FILE *file_ptr; int file_fd; int bytes_copied; off_t real_len; @@ -765,84 +764,23 @@ void dispatch_stream_id(WS_CONNINFO *pwsc, int session, char *id) { DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item); config_set_status(pwsc,session,NULL); ws_returnerror(pwsc,404,"File Not Found"); - } else if (server_side_convert(pmp3->codectype)) { + } else if (plugin_ssc_can_transcode(pmp3->codectype)) { /************************ * Server side conversion ************************/ - DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n", - pwsc->threadno,pmp3->path); - file_ptr = server_side_convert_open(pmp3->path, - offset, - pmp3->song_length, - pmp3->codectype); - 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,pmp3->path); - ws_returnerror(pwsc,404,"Not found"); - db_dispose_item(pmp3); - } else { - // The type should really be determined by the transcoding - // function -- it's possible that you want to transcode - // to a lower-bitrate mp3 or something... but for now, - // we'll just assume .wav - ws_addresponseheader(pwsc,"Content-Type","audio/wav"); + config_set_status(pwsc,session, + "Transcoding '%s' (id %d)", + pmp3->title,pmp3->id); - /* - 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"); + DPRINTF(E_LOG,L_WS, + "Session %d: Streaming file '%s' to %s (offset %ld)\n", + session,pmp3->fname, pwsc->hostname,(long)offset); - 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"); - } + bytes_copied = plugin_ssc_transcode(pwsc,pmp3->path,pmp3->codectype, + pmp3->song_length,offset); - ws_emitheaders(pwsc); - - config_set_status(pwsc,session, - "Transcoding '%s' (id %d)", - pmp3->title,pmp3->id); - DPRINTF(E_LOG,L_WS, - "Session %d: Streaming file '%s' to %s (offset %ld)\n", - session,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"); - db_playcount_increment(NULL,pmp3->id); - - } - server_side_convert_close(file_ptr); - config_set_status(pwsc,session,NULL); - db_dispose_item(pmp3); - } + config_set_status(pwsc,session,NULL); + db_dispose_item(pmp3); } else { /********************** * stream file normally diff --git a/src/ff-plugins.h b/src/ff-plugins.h new file mode 100644 index 00000000..738b81e1 --- /dev/null +++ b/src/ff-plugins.h @@ -0,0 +1,146 @@ +/* + * $Id: $ + * Public plug-in interface + * + * Copyright (C) 2006 Ron Pedde (ron@pedde.com) + * + * 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 _FF_PLUGINS_H_ +#define _FF_PLUGINS_H_ + +/* Plugin types */ +#define PLUGIN_OUTPUT 1 +#define PLUGIN_SCANNER 2 +#define PLUGIN_DATABASE 4 +#define PLUGIN_EVENT 8 +#define PLUGIN_TRANSCODE 16 + +/* plugin event types */ +#define PLUGIN_EVENT_LOG 0 +#define PLUGIN_EVENT_FULLSCAN_START 1 +#define PLUGIN_EVENT_FULLSCAN_END 2 +#define PLUGIN_EVENT_STARTING 3 +#define PLUGIN_EVENT_SHUTDOWN 4 +#define PLUGIN_EVENT_STARTSTREAM 5 +#define PLUGIN_EVENT_ABORTSTREAM 6 +#define PLUGIN_EVENT_ENDSTREAM 7 + +#define PLUGIN_VERSION 1 + +#ifndef E_FATAL +# define E_FATAL 0 +# define E_LOG 1 +# define E_INF 5 +# define E_DBG 9 +#endif + + +struct tag_ws_conninfo; + +/* Functions that must be exported by different plugin types */ +typedef struct tag_plugin_output_fn { + void(*handler)(struct tag_ws_conninfo *pwsc); + int(*auth)(struct tag_ws_conninfo *pwsc, char *username, char *pw); +} PLUGIN_OUTPUT_FN; + +typedef struct tag_plugin_event_fn { + void(*handler)(int event_id, int intval, void *vp, int len); +} PLUGIN_EVENT_FN; + +typedef struct tag_plugin_transcode_fn { + void *(*init)(void); + void (*deinit)(void*); + int (*open)(void*, char *, char*, int); + int (*close)(void*); + int (*read)(void*, char*, int); + char *(*error)(void*); +} PLUGIN_TRANSCODE_FN; + +/* info for rendezvous advertising */ +typedef struct tag_plugin_rend_info { + char *type; + char *txt; +} PLUGIN_REND_INFO; + +/* main info struct that plugins must provide */ +typedef struct tag_plugin_info { + int version; /* PLUGIN_VERSION */ + int type; /* PLUGIN_OUTPUT, etc */ + char *server; /* Server/version format */ + char *url; /* regex match of web urls */ + PLUGIN_OUTPUT_FN *output_fns; /* functions for different plugin types */ + PLUGIN_EVENT_FN *event_fns; + PLUGIN_TRANSCODE_FN *transcode_fns; + PLUGIN_REND_INFO *rend_info; /* array of rend announcements */ + char *codeclist; /* comma separated list of codecs */ +} PLUGIN_INFO; + + +#define QUERY_TYPE_ITEMS 0 +#define QUERY_TYPE_PLAYLISTS 1 +#define QUERY_TYPE_DISTINCT 2 + +#define FILTER_TYPE_FIREFLY 0 +#define FILTER_TYPE_APPLE 1 + +typedef struct tag_db_query { + int query_type; + char *distinct_field; + int filter_type; + char *filter; + + int offset; + int limit; + + int playlist_id; /* for items query */ + int totalcount; /* returned total count */ + void *private; +} DB_QUERY; + + +/* version 1 plugin imports */ +typedef struct tag_plugin_input_fn { + /* webserver helpers */ + char* (*ws_uri)(struct tag_ws_conninfo *); + void (*ws_close)(struct tag_ws_conninfo *); + int (*ws_returnerror)(struct tag_ws_conninfo *, int, char *); + char* (*ws_getvar)(struct tag_ws_conninfo *, char *); + int (*ws_writefd)(struct tag_ws_conninfo *, char *, ...); + int (*ws_addresponseheader)(struct tag_ws_conninfo *, char *, char *, ...); + void (*ws_emitheaders)(struct tag_ws_conninfo *); + int (*ws_fd)(struct tag_ws_conninfo *); + char* (*ws_getrequestheader)(struct tag_ws_conninfo *, char *); + int (*ws_writebinary)(struct tag_ws_conninfo *, char *, int); + + /* misc helpers */ + char* (*server_ver)(void); + int (*server_name)(char *, int *); + void (*log)(int, char *, ...); + + int (*db_count)(void); + int (*db_enum_start)(char **, DB_QUERY *); + int (*db_enum_fetch_row)(char **, char ***, DB_QUERY *); + int (*db_enum_end)(char **); + void (*db_enum_dispose)(char **, DB_QUERY*); + void (*stream)(struct tag_ws_conninfo *, char *); + + char *(*conf_alloc_string)(char *section, char *key, char *dflt); + void (*conf_dispose_string)(char *str); +} PLUGIN_INPUT_FN; + + +#endif _FF_PLUGINS_ diff --git a/src/main.c b/src/main.c index 1f6dc33d..a8fd2afc 100644 --- a/src/main.c +++ b/src/main.c @@ -77,7 +77,6 @@ #include "mp3-scanner.h" #include "webserver.h" #include "restart.h" -#include "ssc.h" #include "dynamic-art.h" #include "db-generic.h" #include "os.h" diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index d2e17bc0..9870677b 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -53,7 +53,6 @@ #include "mp3-scanner.h" #include "os.h" #include "restart.h" -#include "ssc.h" /* * Typedefs diff --git a/src/plugin.c b/src/plugin.c index 5fc5c265..a553b104 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -47,12 +47,8 @@ typedef struct tag_pluginentry { void *phandle; - int type; - char *versionstring; regex_t regex; - PLUGIN_OUTPUT_FN *output_fns; - PLUGIN_EVENT_FN *event_fns; - PLUGIN_REND_INFO *rend_info; + PLUGIN_INFO *pinfo; struct tag_pluginentry *next; } PLUGIN_ENTRY; @@ -60,7 +56,7 @@ typedef struct tag_pluginentry { static pthread_key_t _plugin_lock_key; static PLUGIN_ENTRY _plugin_list; static int _plugin_initialized = 0; - +static char *_plugin_ssc_codecs = NULL; static pthread_rwlock_t _plugin_lock; static char* _plugin_error_list[] = { @@ -75,6 +71,7 @@ void _plugin_writelock(void); void _plugin_unlock(void); int _plugin_error(char **pe, int error, ...); void _plugin_free(int *pi); +void _plugin_recalc_codecs(void); /* webserver helpers */ char *pi_ws_uri(WS_CONNINFO *pwsc); @@ -270,6 +267,53 @@ int _plugin_error(char **pe, int error, ...) { return error; } +/** + * walk through the installed plugins and recalculate + * the codec string + */ +void _plugin_recalc_codecs(void) { + PLUGIN_ENTRY *ppi; + int size=0; + + _plugin_writelock(); + + ppi = _plugin_list.next; + while(ppi) { + if(ppi->pinfo->type & PLUGIN_TRANSCODE) { + if(size) size++; + size += strlen(ppi->pinfo->codeclist); + } + ppi=ppi->next; + } + + if(_plugin_ssc_codecs) { + free(_plugin_ssc_codecs); + } + + _plugin_ssc_codecs = (char*)malloc(size+1); + if(!_plugin_ssc_codecs) { + DPRINTF(E_FATAL,L_PLUG,"_plugin_recalc_codecs: malloc\n"); + } + + memset(_plugin_ssc_codecs,0,size+1); + + ppi = _plugin_list.next; + while(ppi) { + if(ppi->pinfo->type & PLUGIN_TRANSCODE) { + if(strlen(_plugin_ssc_codecs)) { + strcat(_plugin_ssc_codecs,","); + } + strcat(_plugin_ssc_codecs,ppi->pinfo->codeclist); + } + ppi=ppi->next; + } + + DPRINTF(E_DBG,L_PLUG,"New transcode codec list: %s\n",_plugin_ssc_codecs); + _plugin_unlock(); + return; + +} + /** * load a specified plugin. * @@ -281,7 +325,7 @@ int _plugin_error(char **pe, int error, ...) { int plugin_load(char **pe, char *path) { PLUGIN_ENTRY *ppi; void *phandle; - PLUGIN_INFO *(*info_func)(void); + PLUGIN_INFO *(*info_func)(PLUGIN_INPUT_FN *); PLUGIN_INFO *pinfo; DPRINTF(E_DBG,L_PLUG,"Attempting to load plugin %s\n",path); @@ -297,30 +341,35 @@ int plugin_load(char **pe, char *path) { ppi->phandle = phandle; - info_func = (PLUGIN_INFO*(*)(void)) os_libfunc(pe, phandle,"plugin_info"); + info_func = (PLUGIN_INFO*(*)(PLUGIN_INPUT_FN*)) os_libfunc(pe, phandle,"plugin_info"); if(info_func == NULL) { DPRINTF(E_INF,L_PLUG,"Couldn't get info_func for %s\n",path); + os_unload(phandle); + free(ppi); return PLUGIN_E_BADFUNCS; } - pinfo = info_func(); + pinfo = info_func(&pi); + ppi->pinfo = pinfo; - ppi->type = pinfo->type; - ppi->versionstring = pinfo->server; - if(ppi->type & PLUGIN_OUTPUT) { + if(!pinfo) { + if(pe) *pe = strdup("plugin declined to load"); + os_unload(phandle); + free(ppi); + return PLUGIN_E_NOLOAD; + } + + if(pinfo->type & PLUGIN_OUTPUT) { /* build the regex */ if(regcomp(&ppi->regex,pinfo->url,REG_EXTENDED | REG_NOSUB)) { DPRINTF(E_LOG,L_PLUG,"Bad regex in %s: %s\n",path,pinfo->url); } } - ppi->output_fns = pinfo->output_fns; - ppi->event_fns = pinfo->event_fns; - ppi->rend_info = pinfo->rend_info; - DPRINTF(E_INF,L_PLUG,"Loaded plugin %s (%s)\n",path,ppi->versionstring); - pinfo->pi = (void*)π - + DPRINTF(E_INF,L_PLUG,"Loaded plugin %s (%s)\n",path,pinfo->server); + _plugin_writelock(); + if(!_plugin_initialized) { _plugin_initialized = 1; memset((void*)&_plugin_list,0,sizeof(_plugin_list)); @@ -331,6 +380,7 @@ int plugin_load(char **pe, char *path) { _plugin_unlock(); + _plugin_recalc_codecs(); return PLUGIN_E_SUCCESS; } @@ -348,7 +398,7 @@ int plugin_url_candispatch(WS_CONNINFO *pwsc) { _plugin_readlock(); ppi = _plugin_list.next; while(ppi) { - if(ppi->type & PLUGIN_OUTPUT) { + if(ppi->pinfo->type & PLUGIN_OUTPUT) { if(!regexec(&ppi->regex,pwsc->uri,0,NULL,0)) { /* we have a winner */ _plugin_unlock(); @@ -375,14 +425,14 @@ void plugin_url_handle(WS_CONNINFO *pwsc) { _plugin_readlock(); ppi = _plugin_list.next; while(ppi) { - if(ppi->type & PLUGIN_OUTPUT) { + if(ppi->pinfo->type & PLUGIN_OUTPUT) { if(!regexec(&ppi->regex,pwsc->uri,0,NULL,0)) { /* we have a winner */ DPRINTF(E_DBG,L_PLUG,"Dispatching %s to %s\n", pwsc->uri, - ppi->versionstring); + ppi->pinfo->server); /* so functions must be a tag_plugin_output_fn */ - disp_fn=(ppi->output_fns)->handler; + disp_fn=(ppi->pinfo->output_fns)->handler; disp_fn(pwsc); _plugin_unlock(); return; @@ -407,7 +457,6 @@ int plugin_rend_register(char *name, int port, char *iface, char *txt) { char *supplied_txt; char *new_name; char *ver; - char *slash; int name_len; @@ -415,9 +464,9 @@ int plugin_rend_register(char *name, int port, char *iface, char *txt) { ppi = _plugin_list.next; while(ppi) { - DPRINTF(E_DBG,L_PLUG,"Checking %s\n",ppi->versionstring); - if(ppi->rend_info) { - pri = ppi->rend_info; + DPRINTF(E_DBG,L_PLUG,"Checking %s\n",ppi->pinfo->server); + if(ppi->pinfo->rend_info) { + pri = ppi->pinfo->rend_info; while(pri->type) { supplied_txt = pri->txt; if(!pri->txt) @@ -425,7 +474,7 @@ int plugin_rend_register(char *name, int port, char *iface, char *txt) { DPRINTF(E_DBG,L_PLUG,"Registering %s\n",pri->type); - name_len = (int)strlen(name) + 4 + (int)strlen(ppi->versionstring); + name_len = (int)strlen(name) + 4 + (int)strlen(ppi->pinfo->server); new_name=(char*)malloc(name_len); if(!new_name) DPRINTF(E_FATAL,L_PLUG,"plugin_rend_register: malloc"); @@ -433,7 +482,7 @@ int plugin_rend_register(char *name, int port, char *iface, char *txt) { memset(new_name,0,name_len); if(conf_get_int("plugins","mangle_rendezvous",1)) { - ver = strdup(ppi->versionstring); + ver = strdup(ppi->pinfo->server); if(strchr(ver,'/')) { *strchr(ver,'/') = '\0'; } @@ -472,14 +521,14 @@ int plugin_auth_handle(WS_CONNINFO *pwsc, char *username, char *pw) { _plugin_readlock(); ppi = _plugin_list.next; while(ppi) { - if(ppi->type & PLUGIN_OUTPUT) { + if(ppi->pinfo->type & PLUGIN_OUTPUT) { if(!regexec(&ppi->regex,pwsc->uri,0,NULL,0)) { /* we have a winner */ DPRINTF(E_DBG,L_PLUG,"Dispatching %s to %s\n", pwsc->uri, - ppi->versionstring); + ppi->pinfo->server); /* so functions must be a tag_plugin_output_fn */ - auth_fn=(ppi->output_fns)->auth; + auth_fn=(ppi->pinfo->output_fns)->auth; if(auth_fn) { result=auth_fn(pwsc,username,pw); _plugin_unlock(); @@ -505,27 +554,150 @@ int plugin_auth_handle(WS_CONNINFO *pwsc, char *username, char *pw) { void plugin_event_dispatch(int event_id, int intval, void *vp, int len) { PLUGIN_ENTRY *ppi; - fprintf(stderr,"entering plugin_event_dispatch\n"); - -// _plugin_readlock(); + _plugin_readlock(); ppi = _plugin_list.next; while(ppi) { - fprintf(stderr,"Checking %s\n",ppi->versionstring); - if(ppi->type & PLUGIN_EVENT) { + fprintf(stderr,"Checking %s\n",ppi->pinfo->server); + if(ppi->pinfo->type & PLUGIN_EVENT) { /* DPRINTF(E_DBG,L_PLUG,"Dispatching event %d to %s\n", event_id,ppi->versionstring); */ - if((ppi->event_fns) && (ppi->event_fns->handler)) { - ppi->event_fns->handler(event_id, intval, vp, len); + if((ppi->pinfo->event_fns) && (ppi->pinfo->event_fns->handler)) { + ppi->pinfo->event_fns->handler(event_id, intval, vp, len); } } ppi=ppi->next; } -// _plugin_unlock(); + _plugin_unlock(); +} + +/** + * check to see if we can transcode + * + * @param codec the codec we are trying to serve + * @returns TRUE if we can transcode, FALSE otherwise + */ +int plugin_ssc_can_transcode(char *codec) { + int result; + + _plugin_readlock(); + result = FALSE; + if(strstr(_plugin_ssc_codecs,codec)) { + result = TRUE; + } + _plugin_unlock(); + return result; } +/** + * stupid helper to copy transcode stream to the fd + */ +int _plugin_ssc_copy(WS_CONNINFO *pwsc, PLUGIN_TRANSCODE_FN *pfn, + void *vp,int offset) { + int bytes_read; + int bytes_to_read; + int total_bytes_read = 0; + char buffer[1024]; + /* first, skip past the offset */ + while(offset) { + bytes_to_read = sizeof(buffer); + if(bytes_to_read > offset) + bytes_to_read = offset; + + bytes_read = pfn->read(vp,buffer,bytes_to_read); + if(bytes_read <= 0) + return bytes_read; + + offset -= bytes_read; + } + + while((bytes_read=pfn->read(vp,buffer,sizeof(buffer))) > 0) { + total_bytes_read += bytes_read; + ws_writebinary(pwsc,buffer,bytes_read); + } + + if(bytes_read < 0) + return bytes_read; + + return total_bytes_read; +} + +/** + * do the transcode, emitting the headers, content type, + * and shoving the file down the wire + * + * @param pwsc connection to transcode to + * @param file file to transcode + * @param codec source codec + * @param duration time in ms + * @returns bytes transferred, or -1 on error + */ +int plugin_ssc_transcode(WS_CONNINFO *pwsc, char *file, char *codec, int duration, int offset) { + PLUGIN_ENTRY *ppi, *ptc=NULL; + PLUGIN_TRANSCODE_FN *pfn = NULL; + void *vp_ssc; + int post_error = 1; + int result = -1; + + /* first, find the plugin that will do the conversion */ + + _plugin_readlock(); + + ppi = _plugin_list.next; + while((ppi) && (!pfn)) { + if(ppi->pinfo->type & PLUGIN_TRANSCODE) { + if(strstr(ppi->pinfo->codeclist,codec)) { + ptc = ppi; + pfn = ppi->pinfo->transcode_fns; + } + } + ppi = ppi->next; + } + + if(pfn) { + DPRINTF(E_DBG,L_PLUG,"Transcoding %s with %s\n",file, + ptc->pinfo->server); + + vp_ssc = pfn->init(); + if(vp_ssc) { + if(pfn->open(vp_ssc,file,codec,duration)) { + /* start reading and throwing */ + ws_addresponseheader(pwsc,"Content-Type","audio/wav"); + ws_addresponseheader(pwsc,"Connection","Close"); + if(!offset) { + ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); + } else { + ws_addresponseheader(pwsc,"Content-Range","bytes %ld-*/*", + (long)offset); + ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n"); + } + ws_emitheaders(pwsc); + + /* start reading/writing */ + result = _plugin_ssc_copy(pwsc,pfn,vp_ssc,offset); + post_error = 0; + pfn->close(vp_ssc); + } else { + DPRINTF(E_LOG,L_PLUG,"Error opening %s for ssc: %s\n", + file,pfn->error(vp_ssc)); + } + pfn->deinit(vp_ssc); + } else { + DPRINTF(E_LOG,L_PLUG,"Error initializing transcoder: %s\n", + ptc->pinfo->server); + } + } + + if(post_error) { + pwsc->error = EPERM; /* ?? */ + ws_returnerror(pwsc,500,"Internal error"); + } + + _plugin_unlock(); + return result; +} /* plugin wrappers for utility functions & stuff * @@ -646,6 +818,8 @@ int pi_db_enum_start(char **pe, DB_QUERY *pinfo) { pqi->playlist_id = pinfo->playlist_id; result = db_enum_start(pe, pqi); pinfo->totalcount = pqi->specifiedtotalcount; + + return DB_E_SUCCESS; } int pi_db_enum_fetch_row(char **pe, char ***row, DB_QUERY *pinfo) { diff --git a/src/plugin.h b/src/plugin.h index e5206250..d7f80305 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -37,6 +37,10 @@ extern int plugin_auth_handle(WS_CONNINFO *pwsc, char *username, char *pw); extern int plugin_rend_register(char *name, int port, char *iface, char *txt); extern void plugin_event_dispatch(int event_id, int intval, void *vp, int len); +/* these should really get rows */ +extern int plugin_ssc_can_transcode(char *codec); +extern int plugin_ssc_transcode(WS_CONNINFO *pwsc, char *file, char *codec, int duration, int offset); + #define PLUGIN_E_SUCCESS 0 #define PLUGIN_E_NOLOAD 1 #define PLUGIN_E_BADFUNCS 2 diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index bab27e79..7a0fac75 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -2,6 +2,7 @@ # rspdir = ${pkgdatadir}/plugins ssc_ffmpegdir = ${pkgdatadir}/plugins +ssc_scriptdir = ${pkgdatadir}/plugins rsp_LTLIBRARIES=rsp.la rsp_la_LDFLAGS=-module -avoid-version @@ -13,7 +14,11 @@ ssc_ffmpeg_la_LDFLAGS=-module -avoid-version ssc_ffmpeg_la_SOURCES=ssc-ffmpeg.c endif -EXTRA_DIST = compat.h rsp.h mtd-plugins.h xml-rpc.h ssc-ffmpeg.c +ssc_script_LTLIBRARIES=ssc-script.la +ssc_script_la_LDFLAGS=-module -avoid-version +ssc_script_la_SOURCES=ssc-script.c + +EXTRA_DIST = compat.h rsp.h mtd-plugins.h xml-rpc.h ssc-ffmpeg.c ssc-script.c AM_CFLAGS = -I.. diff --git a/src/plugins/rsp.c b/src/plugins/rsp.c index 222ff6bb..daeaca03 100644 --- a/src/plugins/rsp.c +++ b/src/plugins/rsp.c @@ -22,7 +22,7 @@ typedef struct tag_rsp_privinfo { } PRIVINFO; /* Forwards */ -PLUGIN_INFO *plugin_info(void); +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *); void plugin_handler(WS_CONNINFO *pwsc); int plugin_auth(WS_CONNINFO *pwsc, char *username, char *pw); void rsp_info(WS_CONNINFO *pwsc, PRIVINFO *ppi); @@ -39,6 +39,8 @@ PLUGIN_REND_INFO _pri[] = { { NULL, NULL } }; +PLUGIN_INPUT_FN *_ppi; + PLUGIN_INFO _pi = { PLUGIN_VERSION, /* version */ PLUGIN_OUTPUT, /* type */ @@ -47,7 +49,6 @@ PLUGIN_INFO _pi = { &_pofn, /* output fns */ NULL, /* event fns */ NULL, /* transcode fns */ - NULL, /* ff functions */ _pri, /* rend info */ NULL /* transcode info */ }; @@ -145,7 +146,8 @@ FIELDSPEC rsp_fields[] = { /** * return info about this plugin module */ -PLUGIN_INFO *plugin_info(void) { +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *ppi) { + _ppi = ppi; return &_pi; } @@ -168,7 +170,7 @@ void plugin_handler(WS_CONNINFO *pwsc) { int index, part; int found; - string = infn->ws_uri(pwsc); + string = _ppi->ws_uri(pwsc); string++; ppi = (PRIVINFO *)malloc(sizeof(PRIVINFO)); @@ -177,27 +179,27 @@ void plugin_handler(WS_CONNINFO *pwsc) { } if(!ppi) { - infn->ws_returnerror(pwsc,500,"Malloc error in plugin_handler"); + _ppi->ws_returnerror(pwsc,500,"Malloc error in plugin_handler"); return; } memset((void*)&ppi->dq,0,sizeof(DB_QUERY)); - infn->log(E_DBG,"Tokenizing url\n"); + _ppi->log(E_DBG,"Tokenizing url\n"); while((ppi->uri_count < 10) && (token=strtok_r(string,"/",&save))) { string=NULL; ppi->uri_sections[ppi->uri_count++] = token; } elements = sizeof(rsp_uri_map) / sizeof(PLUGIN_RESPONSE); - infn->log(E_DBG,"Found %d elements\n",elements); + _ppi->log(E_DBG,"Found %d elements\n",elements); index = 0; found = 0; while((!found) && (index < elements)) { /* test this set */ - infn->log(E_DBG,"Checking reponse %d\n",index); + _ppi->log(E_DBG,"Checking reponse %d\n",index); part=0; while(part < 10) { if((rsp_uri_map[index].uri[part]) && (!ppi->uri_sections[part])) @@ -216,7 +218,7 @@ void plugin_handler(WS_CONNINFO *pwsc) { if(part == 10) { found = 1; - infn->log(E_DBG,"Found it! Index: %d\n",index); + _ppi->log(E_DBG,"Found it! Index: %d\n",index); } else { index++; } @@ -224,13 +226,13 @@ void plugin_handler(WS_CONNINFO *pwsc) { if(found) { rsp_uri_map[index].dispatch(pwsc, ppi); - infn->ws_close(pwsc); + _ppi->ws_close(pwsc); free(ppi); return; } rsp_error(pwsc, ppi, 1, "Bad path"); - infn->ws_close(pwsc); + _ppi->ws_close(pwsc); free(ppi); return; } @@ -243,7 +245,7 @@ void rsp_info(WS_CONNINFO *pwsc, PRIVINFO *ppi) { char servername[256]; int size; - infn->log(E_DBG,"Starting rsp_info\n"); + _ppi->log(E_DBG,"Starting rsp_info\n"); pxml = xml_init(pwsc,1); @@ -257,13 +259,13 @@ void rsp_info(WS_CONNINFO *pwsc, PRIVINFO *ppi) { /* info block */ xml_push(pxml,"info"); - xml_output(pxml,"count","%d",infn->db_count()); + xml_output(pxml,"count","%d",_ppi->db_count()); xml_output(pxml,"rsp-version",RSP_VERSION); - xml_output(pxml,"server-version",infn->server_ver()); + xml_output(pxml,"server-version",_ppi->server_ver()); size = sizeof(servername); - infn->server_name(servername,&size); + _ppi->server_name(servername,&size); xml_output(pxml,"name",servername); xml_pop(pxml); /* info */ @@ -285,9 +287,9 @@ void rsp_db(WS_CONNINFO *pwsc, PRIVINFO *ppi) { ppi->dq.query_type = QUERY_TYPE_PLAYLISTS; - if((err=infn->db_enum_start(&pe,&ppi->dq)) != 0) { + if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) { rsp_error(pwsc, ppi, err | E_DB, pe); - infn->db_enum_dispose(NULL,&ppi->dq); + _ppi->db_enum_dispose(NULL,&ppi->dq); return; } @@ -303,7 +305,7 @@ void rsp_db(WS_CONNINFO *pwsc, PRIVINFO *ppi) { xml_push(pxml,"playlists"); - while((infn->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { + while((_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { xml_push(pxml,"playlist"); rowindex=0; while(rsp_playlist_fields[rowindex].name) { @@ -316,8 +318,8 @@ void rsp_db(WS_CONNINFO *pwsc, PRIVINFO *ppi) { xml_pop(pxml); /* playlist */ } - infn->db_enum_end(NULL); - infn->db_enum_dispose(NULL,&ppi->dq); + _ppi->db_enum_end(NULL); + _ppi->db_enum_dispose(NULL,&ppi->dq); xml_pop(pxml); /* playlists */ xml_pop(pxml); /* response */ @@ -343,7 +345,7 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { // char *user_agent; /* - user_agent = infn->ws_getrequestheader(pwsc,"user-agent"); + user_agent = _ppi->ws_getrequestheader(pwsc,"user-agent"); if(user_agent) { if(strncmp(user_agent,"iTunes",6)==0) { trancode_codecs = "wma,ogg,flac,mpc"; @@ -355,17 +357,17 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { } */ - ppi->dq.filter = infn->ws_getvar(pwsc,"query"); + ppi->dq.filter = _ppi->ws_getvar(pwsc,"query"); ppi->dq.filter_type = FILTER_TYPE_FIREFLY; - if(infn->ws_getvar(pwsc,"offset")) { - ppi->dq.offset = atoi(infn->ws_getvar(pwsc,"offset")); + if(_ppi->ws_getvar(pwsc,"offset")) { + ppi->dq.offset = atoi(_ppi->ws_getvar(pwsc,"offset")); } - if(infn->ws_getvar(pwsc,"limit")) { - ppi->dq.limit = atoi(infn->ws_getvar(pwsc,"limit")); + if(_ppi->ws_getvar(pwsc,"limit")) { + ppi->dq.limit = atoi(_ppi->ws_getvar(pwsc,"limit")); } - browse_type = infn->ws_getvar(pwsc,"type"); + browse_type = _ppi->ws_getvar(pwsc,"type"); type = F_FULL; if(browse_type) { @@ -378,9 +380,9 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { ppi->dq.query_type = QUERY_TYPE_ITEMS; ppi->dq.playlist_id = atoi(ppi->uri_sections[2]); - if((err=infn->db_enum_start(&pe,&ppi->dq)) != 0) { + if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) { rsp_error(pwsc, ppi, err | E_DB, pe); - infn->db_enum_dispose(NULL,&ppi->dq); + _ppi->db_enum_dispose(NULL,&ppi->dq); free(pe); return; } @@ -395,7 +397,7 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { returned = ppi->dq.totalcount - ppi->dq.offset; } - transcode_codecs = infn->conf_alloc_string("general","ssc_codectypes",""); + transcode_codecs = _ppi->conf_alloc_string("general","ssc_codectypes",""); xml_push(pxml,"response"); xml_push(pxml,"status"); @@ -407,7 +409,7 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { xml_push(pxml,"items"); - while((infn->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { + while((_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { xml_push(pxml,"item"); rowindex=0; transcode = 0; @@ -453,8 +455,8 @@ void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) { xml_pop(pxml); /* item */ } - infn->db_enum_end(NULL); - infn->conf_dispose_string(transcode_codecs); + _ppi->db_enum_end(NULL); + _ppi->conf_dispose_string(transcode_codecs); xml_pop(pxml); /* items */ xml_pop(pxml); /* response */ @@ -471,22 +473,22 @@ void rsp_browse(WS_CONNINFO *pwsc, PRIVINFO *ppi) { /* this might fail if an unsupported browse type */ ppi->dq.query_type = QUERY_TYPE_DISTINCT; ppi->dq.distinct_field = ppi->uri_sections[3]; - ppi->dq.filter = infn->ws_getvar(pwsc,"query"); + ppi->dq.filter = _ppi->ws_getvar(pwsc,"query"); ppi->dq.filter_type = FILTER_TYPE_FIREFLY; - if(infn->ws_getvar(pwsc,"offset")) { - ppi->dq.offset = atoi(infn->ws_getvar(pwsc,"offset")); + if(_ppi->ws_getvar(pwsc,"offset")) { + ppi->dq.offset = atoi(_ppi->ws_getvar(pwsc,"offset")); } - if(infn->ws_getvar(pwsc,"limit")) { - ppi->dq.limit = atoi(infn->ws_getvar(pwsc,"limit")); + if(_ppi->ws_getvar(pwsc,"limit")) { + ppi->dq.limit = atoi(_ppi->ws_getvar(pwsc,"limit")); } ppi->dq.playlist_id = atoi(ppi->uri_sections[2]); - if((err=infn->db_enum_start(&pe,&ppi->dq)) != 0) { + if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) { rsp_error(pwsc, ppi, err | E_DB, pe); - infn->db_enum_dispose(NULL,&ppi->dq); + _ppi->db_enum_dispose(NULL,&ppi->dq); return; } @@ -510,12 +512,12 @@ void rsp_browse(WS_CONNINFO *pwsc, PRIVINFO *ppi) { xml_push(pxml,"items"); - while((infn->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { + while((_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) { xml_output(pxml,"item",row[0]); } - infn->db_enum_end(NULL); - infn->db_enum_dispose(NULL,&ppi->dq); + _ppi->db_enum_end(NULL); + _ppi->db_enum_dispose(NULL,&ppi->dq); xml_pop(pxml); /* items */ xml_pop(pxml); /* response */ @@ -523,7 +525,7 @@ void rsp_browse(WS_CONNINFO *pwsc, PRIVINFO *ppi) { } void rsp_stream(WS_CONNINFO *pwsc, PRIVINFO *ppi) { - infn->stream(pwsc, ppi->uri_sections[2]); + _ppi->stream(pwsc, ppi->uri_sections[2]); return; } @@ -540,6 +542,6 @@ void rsp_error(WS_CONNINFO *pwsc, PRIVINFO *ppi, int eno, char *estr) { xml_pop(pxml); /* status */ xml_pop(pxml); /* response */ xml_deinit(pxml); - infn->ws_close(pwsc); + _ppi->ws_close(pwsc); } diff --git a/src/plugins/rsp.h b/src/plugins/rsp.h index 4f244533..302afe41 100644 --- a/src/plugins/rsp.h +++ b/src/plugins/rsp.h @@ -8,7 +8,6 @@ #define RSP_VERSION "1.0" extern PLUGIN_INFO _pi; -#define infn ((PLUGIN_INPUT_FN *)(_pi.pi)) #ifndef TRUE # define TRUE 1 diff --git a/src/plugins/ssc-ffmpeg.c b/src/plugins/ssc-ffmpeg.c index 51f324b8..055ee408 100644 --- a/src/plugins/ssc-ffmpeg.c +++ b/src/plugins/ssc-ffmpeg.c @@ -40,6 +40,8 @@ typedef struct tag_ssc_handle { int buf_remainder_len; int first_frame; + int duration; + int total_decoded; int total_written; @@ -64,19 +66,27 @@ typedef struct tag_ssc_handle { #define SSC_FFMPEG_E_FILEOPEN 3 #define SSC_FFMPEG_E_NOSTREAM 4 #define SSC_FFMPEG_E_NOAUDIO 5 -#define SSC_FFMPEG_E_BADFORMAT 6 + +char *ssc_ffmpeg_errors[] = { + "Success", + "Don't have appropriate codec", + "Can't open codec", + "Cannot open file", + "Cannot find any streams", + "No audio streams" +}; + /* Forwards */ void *ssc_ffmpeg_init(void); void ssc_ffmpeg_deinit(void *pv); -int ssc_ffmpeg_open(void *pv, char *file, char *codec); +int ssc_ffmpeg_open(void *pv, char *file, char *codec, int duration); int ssc_ffmpeg_close(void *pv); int ssc_ffmpeg_read(void *pv, char *buffer, int len); +char *ssc_ffmpeg_error(void *pv); -PLUGIN_INFO *plugin_info(void); - -#define infn ((PLUGIN_INPUT_FN *)(_pi.pi)) +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN*); /* Globals */ PLUGIN_TRANSCODE_FN _ptfn = { @@ -84,9 +94,12 @@ PLUGIN_TRANSCODE_FN _ptfn = { ssc_ffmpeg_deinit, ssc_ffmpeg_open, ssc_ffmpeg_close, - ssc_ffmpeg_read + ssc_ffmpeg_read, + ssc_ffmpeg_error }; +PLUGIN_INPUT_FN *_ppi; + PLUGIN_INFO _pi = { PLUGIN_VERSION, /* version */ PLUGIN_TRANSCODE, /* type */ @@ -95,12 +108,18 @@ PLUGIN_INFO _pi = { NULL, /* output fns */ NULL, /* event fns */ &_ptfn, /* fns */ - NULL, /* functions exported by ff */ NULL, /* rend info */ "flac,alac,ogg,wma" /* codeclist */ }; -PLUGIN_INFO *plugin_info(void) { +char *ssc_ffmpeg_error(void *pv) { + SSCHANDLE *handle = (SSCHANDLE*)pv; + + return ssc_ffmpeg_errors[handle->errno]; +} + +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *ppi) { + _ppi = ppi; av_register_all(); return &_pi; @@ -127,7 +146,7 @@ void ssc_ffmpeg_deinit(void *vp) { return; } -int ssc_ffmpeg_open(void *vp, char *file, char *codec) { +int ssc_ffmpeg_open(void *vp, char *file, char *codec, int duration) { int i; enum CodecID id=CODEC_ID_FLAC; SSCHANDLE *handle = (SSCHANDLE*)vp; @@ -135,10 +154,11 @@ int ssc_ffmpeg_open(void *vp, char *file, char *codec) { if(!handle) return FALSE; + handle->duration = duration; handle->first_frame = 1; handle->raw=0; - infn->log(E_DBG,"opening %s\n",file); + _ppi->log(E_DBG,"opening %s\n",file); if(strcasecmp(codec,"flac") == 0) { handle->raw=1; @@ -146,6 +166,7 @@ int ssc_ffmpeg_open(void *vp, char *file, char *codec) { } if(handle->raw) { + _ppi->log(E_DBG,"opening file raw\n"); handle->pCodec = avcodec_find_decoder(id); if(!handle->pCodec) { handle->errno = SSC_FFMPEG_E_BADCODEC; @@ -166,7 +187,8 @@ int ssc_ffmpeg_open(void *vp, char *file, char *codec) { return TRUE; } - + + _ppi->log(E_DBG,"opening file with format\n"); if(av_open_input_file(&handle->pFmtCtx,file,handle->pFormat,0,NULL) < 0) { handle->errno = SSC_FFMPEG_E_FILEOPEN; return FALSE; @@ -178,7 +200,7 @@ int ssc_ffmpeg_open(void *vp, char *file, char *codec) { return FALSE; } - dump_format(handle->pFmtCtx,0,file,FALSE); + // dump_format(handle->pFmtCtx,0,file,FALSE); handle->audio_stream = -1; for(i=0; i < handle->pFmtCtx->nb_streams; i++) { @@ -226,8 +248,10 @@ int ssc_ffmpeg_close(void *vp) { if(handle->pFrame) av_free(handle->pFrame); - if(handle->pCodecCtx) - avcodec_close(handle->pCodecCtx); + if(handle->raw) { + if(handle->pCodecCtx) + avcodec_close(handle->pCodecCtx); + } if(handle->pFmtCtx) av_close_input_file(handle->pFmtCtx); @@ -393,14 +417,17 @@ int ssc_ffmpeg_read(void *vp, char *buffer, int len) { handle->swab = (bits_per_sample == 16) && (memcmp((void*)&test1,test2,2) == 0); - data_len = (bits_per_sample * sample_rate * channels * (duration/1000)); + if(handle->duration) + duration = handle->duration; + + data_len = ((bits_per_sample * sample_rate * channels / 8) * (duration/1000)); byte_rate = sample_rate * channels * bits_per_sample / 8; block_align = channels * bits_per_sample / 8; - infn->log(E_DBG,"Channels.......: %d\n",channels); - infn->log(E_DBG,"Sample rate....: %d\n",sample_rate); - infn->log(E_DBG,"Bits/Sample....: %d\n",bits_per_sample); - infn->log(E_DBG,"Swab...........: %d\n",handle->swab); + _ppi->log(E_DBG,"Channels.......: %d\n",channels); + _ppi->log(E_DBG,"Sample rate....: %d\n",sample_rate); + _ppi->log(E_DBG,"Bits/Sample....: %d\n",bits_per_sample); + _ppi->log(E_DBG,"Swab...........: %d\n",handle->swab); memcpy(&handle->wav_header[0],"RIFF",4); _ssc_ffmpeg_le32(&handle->wav_header[4],36 + data_len); diff --git a/src/plugins/ssc-script.c b/src/plugins/ssc-script.c new file mode 100644 index 00000000..ac3ea94c --- /dev/null +++ b/src/plugins/ssc-script.c @@ -0,0 +1,206 @@ +/* + * $Id: $ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "ff-plugins.h" + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + +/* Forwards */ +void *ssc_script_init(void); +void ssc_script_deinit(void *vp); +int ssc_script_open(void *vp, char *file, char *codec, int duration); +int ssc_script_close(void *vp); +int ssc_script_read(void *vp, char *buffer, int len); +char *ssc_script_error(void *vp); + +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *); + +#define infn ((PLUGIN_INPUT_FN *)(_pi.pi)) + +/* Globals */ +PLUGIN_TRANSCODE_FN _ptfn = { + ssc_script_init, + ssc_script_deinit, + ssc_script_open, + ssc_script_close, + ssc_script_read, + ssc_script_error +}; + +PLUGIN_INPUT_FN *_ppi; + +PLUGIN_INFO _pi = { + PLUGIN_VERSION, /* version */ + PLUGIN_TRANSCODE, /* type */ + "ssc-script/" VERSION, /* server */ + NULL, /* url */ + NULL, /* output fns */ + NULL, /* event fns */ + &_ptfn, /* fns */ + NULL, /* rend info */ + NULL /* codeclist */ +}; + +typedef struct tag_ssc_handle { + FILE *fin; +} SSCHANDLE; + +static char *_ssc_script_program = NULL; + +/** + * return the plugininfo struct to firefly + */ +PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *ppi) { + char *codeclist; + + _ppi = ppi; + + _ssc_script_program = _ppi->conf_alloc_string("general","ssc_prog",NULL); + if(!_ssc_script_program) { + _ppi->log(E_INF,"No ssc program specified for script transcoder.\n"); + return NULL; + } + + /* FIXME: need an unload function to stop leak */ + codeclist = _ppi->conf_alloc_string("general","ssc_codectypes",NULL); + if(!codeclist) { + _ppi->log(E_INF,"No codectypes specified for script transcoder.\n"); + return NULL; + } + + _pi.codeclist = codeclist; + return &_pi; +} + + +/* + * get a new transcode handle + */ +void *ssc_script_init(void) { + SSCHANDLE *handle; + + handle = (SSCHANDLE*)malloc(sizeof(SSCHANDLE)); + if(handle) { + memset(handle,0,sizeof(SSCHANDLE)); + } + + return (void*)handle; +} + +/** + * FIXME: make register errors in the sschandle + */ +char *ssc_script_error(void *vp) { + SSCHANDLE *handle = (SSCHANDLE*)vp; + + return "Unknown"; +} + + +/** + * dispose of the transocde handle obtained from init + * + * @param pv handle to dispose + */ +void ssc_script_deinit(void *vp) { + SSCHANDLE *handle = (SSCHANDLE*)vp; + + if(handle->fin) { + pclose(handle->fin); + } + + if(handle) + free(handle); +} + +/** + * open a file to transocde + * + * @param pv private sschandle obtained from init + * @param file file name to transcode + * @param codec codec type + * @param duration duration in ms + */ +int ssc_script_open(void *vp, char *file, char *codec, int duration) { + SSCHANDLE *handle = (SSCHANDLE*)vp; + char *cmd; + char *newpath; + char *metachars = "\"\\!(){}#*?$&<>`"; /* More?? */ + char metacount = 0; + char *src,*dst; + + src=file; + while(*src) { + if(strchr(metachars,*src)) + metacount+=5; + src++; + } + + if(metachars) { + newpath = (char*)malloc(strlen(file) + metacount + 1); + if(!newpath) { + _ppi->log(E_FATAL,"ssc_script_open: malloc\n"); + } + src=file; + dst=newpath; + + while(*src) { + if(strchr(metachars,*src)) { + *dst++='"'; + *dst++='\''; + *dst++=*src++; + *dst++='\''; + *dst++='"'; + } else { + *dst++=*src++; + } + } + *dst='\0'; + } else { + newpath = strdup(file); /* becuase it will be freed... */ + } + + /* FIXME: is 64 enough? is there a better way to determine this? */ + cmd=(char *)malloc(strlen(_ssc_script_program) + + strlen(file) + + 64); + sprintf(cmd, "%s \"%s\" 0 %lu.%03lu \"%s\"", + _ssc_script_program, newpath, (unsigned long) duration / 1000, + (unsigned long)duration % 1000, (codec && *codec) ? codec : "*"); + _ppi->log(E_INF,"Executing %s\n",cmd); + handle->fin = popen(cmd, "r"); + free(newpath); + free(cmd); /* should really have in-place expanded the path */ + + return TRUE; +} + +int ssc_script_close(void *vp) { + SSCHANDLE *handle = (SSCHANDLE*)vp; + + if(handle->fin) { + pclose(handle->fin); + handle->fin=NULL; + } + + return TRUE; +} + +int ssc_script_read(void *vp, char *buffer, int len) { + SSCHANDLE *handle = (SSCHANDLE*)vp; + + return fread(buffer,1,len,handle->fin); +} + diff --git a/src/plugins/xml-rpc.c b/src/plugins/xml-rpc.c index b5f3de1a..bb7f4f27 100644 --- a/src/plugins/xml-rpc.c +++ b/src/plugins/xml-rpc.c @@ -41,6 +41,8 @@ struct tag_xmlstruct { XML_STREAMBUFFER *psb; }; +extern PLUGIN_INPUT_FN *_ppi; + /* Forwards */ void xml_get_stats(WS_CONNINFO *pwsc); void xml_set_config(WS_CONNINFO *pwsc); @@ -62,7 +64,7 @@ void xml_write(XMLSTRUCT *pxml, char *fmt, ...) { if(pxml->psb) { xml_stream_write(pxml, buffer); } else { - infn->ws_writefd(pxml->pwsc,"%s",buffer); + _ppi->ws_writefd(pxml->pwsc,"%s",buffer); } } @@ -89,14 +91,14 @@ XML_STREAMBUFFER *xml_stream_open(void) { psb = (XML_STREAMBUFFER*) malloc(sizeof(XML_STREAMBUFFER)); if(!psb) { - infn->log(E_FATAL,"xml_stream_open: malloc\n"); + _ppi->log(E_FATAL,"xml_stream_open: malloc\n"); } psb->out_buffer = (unsigned char*) malloc(XML_STREAM_BLOCK); psb->in_buffer = (unsigned char*) malloc(XML_STREAM_BLOCK); if((!psb->out_buffer) || (!psb->in_buffer)) { - infn->log(E_FATAL,"xml_stream_open: malloc\n"); + _ppi->log(E_FATAL,"xml_stream_open: malloc\n"); } psb->strm.zalloc = Z_NULL; @@ -134,9 +136,9 @@ int xml_stream_write(XMLSTRUCT *pxml, char *out) { while(!done) { result = deflate(&psb->strm, Z_NO_FLUSH); if(result != Z_OK) { - infn->log(E_FATAL,"Error in zlib: %d\n",result); + _ppi->log(E_FATAL,"Error in zlib: %d\n",result); } - infn->ws_writebinary(pxml->pwsc,(char*)psb->out_buffer, + _ppi->ws_writebinary(pxml->pwsc,(char*)psb->out_buffer, XML_STREAM_BLOCK-psb->strm.avail_out); if(psb->strm.avail_out != 0) { done=1; @@ -163,14 +165,14 @@ int xml_stream_close(XMLSTRUCT *pxml) { psb->strm.next_in = psb->in_buffer; deflate(&psb->strm,Z_FINISH); - infn->ws_writebinary(pxml->pwsc,(char*)psb->out_buffer, + _ppi->ws_writebinary(pxml->pwsc,(char*)psb->out_buffer, XML_STREAM_BLOCK - psb->strm.avail_out); if(psb->strm.avail_out != 0) done=1; } - infn->log(E_DBG,"Done sending xml stream\n"); + _ppi->log(E_DBG,"Done sending xml stream\n"); deflateEnd(&psb->strm); if(psb->out_buffer != NULL) free(psb->out_buffer); @@ -196,7 +198,7 @@ XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { pxml=(XMLSTRUCT*)malloc(sizeof(XMLSTRUCT)); if(!pxml) { - infn->log(E_FATAL,"Malloc error\n"); + _ppi->log(E_FATAL,"Malloc error\n"); } memset(pxml,0,sizeof(XMLSTRUCT)); @@ -204,27 +206,27 @@ XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { pxml->pwsc = pwsc; /* should we compress output? */ - nogzip = infn->ws_getvar(pwsc,"nogzip"); - accept = infn->ws_getrequestheader(pwsc,"accept-encoding"); + nogzip = _ppi->ws_getvar(pwsc,"nogzip"); + accept = _ppi->ws_getrequestheader(pwsc,"accept-encoding"); if((!nogzip) && (accept) && (strcasestr(accept,"gzip"))) { - infn->log(E_DBG,"Gzipping output\n"); + _ppi->log(E_DBG,"Gzipping output\n"); pxml->psb = xml_stream_open(); if(pxml->psb) { - infn->ws_addresponseheader(pwsc,"Content-Encoding","gzip"); - infn->ws_addresponseheader(pwsc,"Vary","Accept-Encoding"); - infn->ws_addresponseheader(pwsc,"Connection","Close"); + _ppi->ws_addresponseheader(pwsc,"Content-Encoding","gzip"); + _ppi->ws_addresponseheader(pwsc,"Vary","Accept-Encoding"); + _ppi->ws_addresponseheader(pwsc,"Connection","Close"); } } /* the world would be a wonderful place without ie */ - infn->ws_addresponseheader(pwsc,"Cache-Control","no-cache"); - infn->ws_addresponseheader(pwsc,"Expires","-1"); + _ppi->ws_addresponseheader(pwsc,"Cache-Control","no-cache"); + _ppi->ws_addresponseheader(pwsc,"Expires","-1"); if(emit_header) { - infn->ws_addresponseheader(pwsc,"Content-Type","text/xml; charset=utf-8"); - infn->ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n"); - infn->ws_emitheaders(pwsc); + _ppi->ws_addresponseheader(pwsc,"Content-Type","text/xml; charset=utf-8"); + _ppi->ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n"); + _ppi->ws_emitheaders(pwsc); xml_write(pxml,""); @@ -263,7 +265,7 @@ void xml_pop(XMLSTRUCT *pxml) { pstack=pxml->stack.next; if(!pstack) { - infn->log(E_LOG,"xml_pop: tried to pop an empty stack\n"); + _ppi->log(E_LOG,"xml_pop: tried to pop an empty stack\n"); return; } @@ -310,7 +312,7 @@ void xml_deinit(XMLSTRUCT *pxml) { XMLSTACK *pstack; if(pxml->stack.next) { - infn->log(E_LOG,"xml_deinit: entries still on stack (%s)\n", + _ppi->log(E_LOG,"xml_deinit: entries still on stack (%s)\n", pxml->stack.next->tag); } diff --git a/src/ssc.c b/src/ssc.c deleted file mode 100644 index 8e9af2b2..00000000 --- a/src/ssc.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * $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 - -#ifndef WIN32 -#include /* htons and friends */ -#include /* why here? For osx 10.2, of course! */ -#endif - -#include - -#include "conf.h" -#include "daapd.h" -#include "db-generic.h" -#include "err.h" -#include "mp3-scanner.h" -#include "ssc.h" - -/** - * Check if the file specified by fname should be converted in - * server to wav. Currently it does this by file extension, but - * could in the future decided to transcode based on user agent. - * - * @param codectype codec type of the file we are checking for conversion - * @returns 1 if should be converted. 0 if not - */ -int server_side_convert(char *codectype) { - char *ssc_codectypes; - - ssc_codectypes = conf_alloc_string("general","ssc_codectypes", - "ogg,flac,wma,alac"); - - if ((!conf_isset("general","ssc_codectypes")) || - (!conf_isset("general","ssc_prog")) || - (!codectype)) { - DPRINTF(E_DBG,L_SCAN,"Nope\n"); - free(ssc_codectypes); - return 0; - } - - if(strcasestr(ssc_codectypes, codectype)) { - free(ssc_codectypes); - return 1; - } - - free(ssc_codectypes); - 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, unsigned long len_ms, char *codectype) { - char *cmd; - FILE *f; - char *newpath; - char *metachars = "\"\\!(){}#*?$&<>`"; /* More?? */ - char metacount = 0; - char *src,*dst,*ssc_prog; - - ssc_prog = conf_alloc_string("general","ssc_prog",""); - - if(ssc_prog[0] == '\0') { /* can't happen */ - free(ssc_prog); - return NULL; - } - - src=path; - while(*src) { - if(strchr(metachars,*src)) - metacount+=5; - src++; - } - - if(metachars) { - newpath = (char*)malloc(strlen(path) + metacount + 1); - if(!newpath) { - DPRINTF(E_FATAL,L_SCAN,"Malloc error.\n"); - } - src=path; - dst=newpath; - - while(*src) { - if(strchr(metachars,*src)) { - *dst++='"'; - *dst++='\''; - *dst++=*src++; - *dst++='\''; - *dst++='"'; - } else { - *dst++=*src++; - } - } - *dst='\0'; - } else { - newpath = strdup(path); /* becuase it will be freed... */ - } - - /* FIXME: is 64 enough? is there a better way to determine this? */ - cmd=(char *)malloc(strlen(ssc_prog) + - strlen(path) + - 64); - sprintf(cmd, "%s \"%s\" %ld %lu.%03lu \"%s\"", - ssc_prog, newpath, (long)offset, len_ms / 1000, - len_ms % 1000, (codectype && *codectype) ? codectype : "*"); - DPRINTF(E_INF,L_SCAN,"Executing %s\n",cmd); - f = popen(cmd, "r"); - free(newpath); - free(cmd); /* should really have in-place expanded the path */ - free(ssc_prog); - 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 deleted file mode 100644 index bb701832..00000000 --- a/src/ssc.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * $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(char *codectype); -extern char *server_side_convert_path(char *path); -extern FILE *server_side_convert_open(char *path, - off_t offset, - unsigned long len_ms, - char *codectype); -extern void server_side_convert_close(FILE *f); - -#endif /* _SCC_H_ */ -