From 74d73037d02c3f858ccd672c1077c6f7e023c64d Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Thu, 22 Jan 2009 00:25:20 +0000 Subject: [PATCH] Lots of changes, but notably: * MiniDLNA can now pass the DLNA Conformance Test! * Dependence on libdlna has been removed, and the ffmpeg libs are used directly. * Lots of unused code has been cleaned up. * File transfers will now be forked off into a new process, so as not to tie up the server when sending data. --- Makefile | 7 +- TODO | 7 +- config.h | 55 ----- genconfig.sh | 45 ++-- getifaddr.c | 40 ++++ getifaddr.h | 3 + metadata.c | 507 ++++++++++++++++++++++++++++++++++++++++------ metadata.h | 11 + minidlna.c | 138 ++++++------- minidlna.conf | 5 - minissdp.c | 8 +- miniupnpdpath.h | 18 -- options.c | 11 +- options.h | 8 +- scanner.c | 108 ++++++---- testupnpdescgen.c | 2 +- upnpdescgen.c | 95 +-------- upnpdescgen.h | 13 +- upnpdescstrings.h | 24 +-- upnpevents.c | 32 +-- upnpevents.h | 6 +- upnpglobalvars.c | 21 +- upnpglobalvars.h | 26 +-- upnphttp.c | 86 ++++---- upnpsoap.c | 101 ++------- 25 files changed, 769 insertions(+), 608 deletions(-) delete mode 100644 config.h diff --git a/Makefile b/Makefile index 76f13a9..d2c4a00 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,10 @@ # #CFLAGS = -Wall -O -D_GNU_SOURCE -g -DDEBUG #CFLAGS = -Wall -g -Os -D_GNU_SOURCE -CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 +CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 \ + -I/usr/include/ffmpeg \ + -I/usr/include/libavutil -I/usr/include/libavcodec -I/usr/include/libavformat \ + -I/usr/include/ffmpeg/libavutil -I/usr/include/ffmpeg/libavcodec -I/usr/include/ffmpeg/libavformat CC = gcc RM = rm -f INSTALL = install @@ -30,7 +33,7 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \ ALLOBJS = $(BASEOBJS) $(LNXOBJS) #LIBS = -liptc -LIBS = -lexif -ltag_c -lsqlite3 -ldlna #-lgd +LIBS = -lexif -ltag_c -lsqlite3 -lavformat -luuid #-lgd TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o diff --git a/TODO b/TODO index 3727b07..43f86e0 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,8 @@ Things left to do: -* Clean up more leftover MiniUPnPd code -* Spawn a new thread for new HTTP connections * Persistent HTTP connection (Keep-Alive) support -* Real Chunked transfer support * PNG image support -* DLNA Profile Name (DLNA.ORG_PN) support for video files -* DLNA Profile Name (DLNA.ORG_PN) support for audio files besides MP3 -* Video metadata support +* DLNA Profile Name (DLNA.ORG_PN) support for more audio files * SortCriteria support * Completely redo the logging scheme. * Update scan support (do not require removing the database) diff --git a/config.h b/config.h deleted file mode 100644 index 530d926..0000000 --- a/config.h +++ /dev/null @@ -1,55 +0,0 @@ -/* MiniUPnP Project - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2008 Thomas Bernard - * generated by ./genconfig.sh on Thu Sep 11 15:05:26 PDT 2008 */ -#ifndef __CONFIG_H__ -#define __CONFIG_H__ - -#define UPNP_VERSION "20070827" -#define USE_NETFILTER 1 -#define OS_NAME "JM" -#define OS_VERSION "Linux/2.6.25.14-108.fc9.i686" -#define OS_URL "http://www.kernel.org/" - -/* syslog facility to be used by miniupnpd */ -#define LOG_MINIUPNPD LOG_DAEMON - -/* Uncomment the following line to allow miniupnpd to be - * controlled by miniupnpdctl */ -/*#define USE_MINIUPNPDCTL*/ - -/* Comment the following line to disable NAT-PMP operations */ -//#define ENABLE_NATPMP - -/* Uncomment the following line to enable generation of - * filter rules with pf */ -/*#define PF_ENABLE_FILTER_RULES*/ - -/* Uncomment the following line to enable caching of results of - * the getifstats() function */ -/*#define ENABLE_GETIFSTATS_CACHING*/ -/* The cache duration is indicated in seconds */ -#define GETIFSTATS_CACHING_DURATION 2 - -/* Uncomment the following line to enable multiple external ip support */ -/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */ -/*#define MULTIPLE_EXTERNAL_IP*/ - -/* Comment the following line to use home made daemonize() func instead - * of BSD daemon() */ -#define USE_DAEMON - -/* Uncomment the following line to enable lease file support */ -/*#define ENABLE_LEASEFILE*/ - -/* Define one or none of the two following macros in order to make some - * clients happy. It will change the XML Root Description of the IGD. - * Enabling the Layer3Forwarding Service seems to be the more compatible - * option. */ -/*#define HAS_DUMMY_SERVICE*/ -#define ENABLE_L3F_SERVICE - -/* Experimental UPnP Events support. */ -#define ENABLE_EVENTS - -#endif diff --git a/genconfig.sh b/genconfig.sh index 3d59456..8224827 100755 --- a/genconfig.sh +++ b/genconfig.sh @@ -13,7 +13,9 @@ CONFIGMACRO="__CONFIG_H__" # version reported in XML descriptions UPNP_VERSION=20070827 # Facility to syslog -LOG_MINIUPNPD="LOG_DAEMON" +LOG_MINIDLNA="LOG_DAEMON" +# Database path +DB_PATH="/tmp/files.db" # detecting the OS name and version OS_NAME=`uname -s` @@ -52,7 +54,6 @@ case $OS_NAME in if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi - echo "#define USE_PF 1" >> ${CONFIGFILE} OS_URL=http://www.openbsd.org/ ;; FreeBSD) @@ -60,25 +61,14 @@ case $OS_NAME in if [ $VER -ge 700049 ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi - if [ -f /usr/include/net/pfvar.h ] ; then - echo "#define USE_PF 1" >> ${CONFIGFILE} - else - echo "#define USE_IPF 1" >> ${CONFIGFILE} - fi OS_URL=http://www.freebsd.org/ ;; pfSense) # we need to detect if PFRULE_INOUT_COUNTS macro is needed - echo "#define USE_PF 1" >> ${CONFIGFILE} OS_URL=http://www.pfsense.com/ ;; NetBSD) OS_URL=http://www.netbsd.org/ - if [ -f /usr/include/net/pfvar.h ] ; then - echo "#define USE_PF 1" >> ${CONFIGFILE} - else - echo "#define USE_IPF 1" >> ${CONFIGFILE} - fi ;; SunOS) echo "#define USE_IPF 1" >> ${CONFIGFILE} @@ -104,7 +94,7 @@ case $OS_NAME in OS_URL=http://www.debian.org/ fi # use lsb_release (Linux Standard Base) when available - LSB_RELEASE=`which lsb_release` + LSB_RELEASE=`which lsb_release 2>/dev/null` if [ 0 -eq $? ]; then OS_NAME=`${LSB_RELEASE} -i -s` OS_VERSION=`${LSB_RELEASE} -r -s` @@ -123,8 +113,12 @@ echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE} echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} +echo "/* full path of the file database */" >> ${CONFIGFILE} +echo "#define DB_PATH \"${DB_PATH}\"" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE} -echo "#define LOG_MINIUPNPD ${LOG_MINIUPNPD}" >> ${CONFIGFILE} +echo "#define LOG_MINIDLNA ${LOG_MINIDLNA}" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE} @@ -132,15 +126,6 @@ echo " * controlled by miniupnpdctl */" >> ${CONFIGFILE} echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Comment the following line to disable NAT-PMP operations */" >> ${CONFIGFILE} -echo "#define ENABLE_NATPMP" >> ${CONFIGFILE} -echo "" >> ${CONFIGFILE} - -echo "/* Uncomment the following line to enable generation of" >> ${CONFIGFILE} -echo " * filter rules with pf */" >> ${CONFIGFILE} -echo "/*#define PF_ENABLE_FILTER_RULES*/">> ${CONFIGFILE} -echo "" >> ${CONFIGFILE} - echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE} echo " * the getifstats() function */" >> ${CONFIGFILE} echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE} @@ -162,16 +147,12 @@ echo "/* Uncomment the following line to enable lease file support */" >> ${CONF echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE} -echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE} -echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE} -echo " * option. */" >> ${CONFIGFILE} -echo "/*#define HAS_DUMMY_SERVICE*/" >> ${CONFIGFILE} -echo "#define ENABLE_L3F_SERVICE" >> ${CONFIGFILE} +echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE} +echo "#define ENABLE_EVENTS" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE} -echo "/*#define ENABLE_EVENTS*/" >> ${CONFIGFILE} +echo "/* Enable NETGEAR ReadyNAS-specific tweaks. */" >> ${CONFIGFILE} +echo "/*#define READYNAS*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE} diff --git a/getifaddr.c b/getifaddr.c index c7d10a0..9737c7d 100644 --- a/getifaddr.c +++ b/getifaddr.c @@ -5,6 +5,8 @@ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ +#include +#include #include #include #include @@ -53,3 +55,41 @@ getifaddr(const char * ifname, char * buf, int len) return 0; } + +int +getifhwaddr(const char * ifname, char * buf, int len) +{ + /* SIOCGIFADDR struct ifreq * */ + int s; + struct ifreq ifr; + int ifrlen; + unsigned char addr[6]; + char mac_string[4]; + int i; + ifrlen = sizeof(ifr); + if( len < 12 ) + { + return -2; + } + s = socket(AF_INET, SOCK_DGRAM, 0); + if(s < 0) + { + syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m"); + return -1; + } + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + if(ioctl(s, SIOCGIFHWADDR, &ifr, &ifrlen) < 0) + { + syslog(LOG_ERR, "ioctl(s, SIOCGIFHWADDR, ...): %m"); + close(s); + return -1; + } + close(s); + + memmove( addr, ifr.ifr_hwaddr.sa_data, 6); + for (i=0; i<6; ++i) { + sprintf(mac_string, "%2.2x", addr[i]); + strcat(buf, mac_string); + } + return 0; +} diff --git a/getifaddr.h b/getifaddr.h index 053c2c1..b769523 100644 --- a/getifaddr.h +++ b/getifaddr.h @@ -14,5 +14,8 @@ int getifaddr(const char * ifname, char * buf, int len); +int +getifhwaddr(const char * ifname, char * buf, int len); + #endif diff --git a/metadata.c b/metadata.c index b09741c..53bd13b 100644 --- a/metadata.c +++ b/metadata.c @@ -15,20 +15,23 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define LIBDLNA_SUPPORT 1 - #include #include #include #include #include +#include +#include +#include +#include + #include #include #include -#if LIBDLNA_SUPPORT -#include -#endif +#include +#include +#include #include "upnpglobalvars.h" #include "metadata.h" @@ -36,6 +39,17 @@ #define FLAG_ARTIST 0x01 +/* Audio profile flags */ +#define MP3 0x00000001 +#define AC3 0x00000002 +#define WMA_BASE 0x00000004 +#define WMA_FULL 0x00000008 +#define WMA_PRO 0x00000010 +#define MP2 0x00000020 +#define PCM 0x00000040 +#define AAC 0x00000100 +#define AAC_MULT5 0x00000200 + int ends_with(const char * haystack, const char * needle) { @@ -111,6 +125,40 @@ strip_ext(char * name) *rindex(name, '.') = '\0'; } +/* This function shamelessly copied from libdlna */ +#define MPEG_TS_SYNC_CODE 0x47 +#define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */ +int +dlna_timestamp_is_present(const char * filename) +{ + unsigned char buffer[2*MPEG_TS_PACKET_LENGTH_DLNA+1]; + int fd, i; + + /* read file header */ + fd = open(filename, O_RDONLY); + read(fd, buffer, MPEG_TS_PACKET_LENGTH_DLNA*2); + close(fd); + for( i=0; i < MPEG_TS_PACKET_LENGTH_DLNA; i++ ) + { + if( buffer[i] == MPEG_TS_SYNC_CODE ) + { + if (buffer[i + MPEG_TS_PACKET_LENGTH_DLNA] == MPEG_TS_SYNC_CODE) + { + if (buffer[i] == 0x00 && buffer [i+1] == 0x00 && + buffer [i+2] == 0x00 && buffer [i+3] == 0x00) + { + break; + } + else + { + return 1; + } + } + } + } + return 0; +} + sqlite_int64 GetFolderMetadata(const char * name, const char * artist) { @@ -239,23 +287,33 @@ GetAudioMetadata(const char * path, char * name) } - if( 1 ) // Switch on audio file type + /* Switch on audio file type */ + if( ends_with(path, ".mp3") ) { strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01"); strcpy(mime, "audio/mpeg"); } + else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") ) + { + strcpy(mime, "audio/x-flac"); + } + else if( ends_with(path, ".m4a") || ends_with(path, ".aac") ) + { + //strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01"); + strcpy(mime, "audio/mp4"); + } sql = sqlite3_mprintf( "INSERT into DETAILS" - " (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," - " TITLE, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " + " (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," + " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " "VALUES" - " (%llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", - size, duration, taglib_audioproperties_channels(properties), + " (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", + path, size, duration, taglib_audioproperties_channels(properties), taglib_audioproperties_bitrate(properties)*1024, taglib_audioproperties_samplerate(properties), strlen(date) ? date : NULL, title, - artist, + artist, artist, album, genre, comment, @@ -388,10 +446,10 @@ GetImageMetadata(const char * path, char * name) asprintf(&m.resolution, "%dx%d", width, height); sql = sqlite3_mprintf( "INSERT into DETAILS" - " (TITLE, SIZE, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " + " (PATH, TITLE, SIZE, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " "VALUES" - " ('%q', %llu, '%s', %Q, %d, '%q', %Q, %Q);", - name, size, date, m.resolution, thumb, model, m.dlna_pn, m.mime); + " (%Q, '%q', %llu, '%s', %Q, %d, '%q', %Q, %Q);", + path, name, size, date, m.resolution, thumb, model, m.dlna_pn, m.mime); //DEBUG printf("SQL: %s\n", sql); if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) { @@ -414,6 +472,36 @@ GetImageMetadata(const char * path, char * name) return ret; } +typedef enum { + AAC_INVALID = 0, + AAC_MAIN = 1, /* AAC Main */ + AAC_LC = 2, /* AAC Low complexity */ + AAC_SSR = 3, /* AAC SSR */ + AAC_LTP = 4, /* AAC Long term prediction */ + AAC_HE = 5, /* AAC High efficiency (SBR) */ + AAC_SCALE = 6, /* Scalable */ + AAC_TWINVQ = 7, /* TwinVQ */ + AAC_CELP = 8, /* CELP */ + AAC_HVXC = 9, /* HVXC */ + AAC_TTSI = 12, /* TTSI */ + AAC_MS = 13, /* Main synthetic */ + AAC_WAVE = 14, /* Wavetable synthesis */ + AAC_MIDI = 15, /* General MIDI */ + AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */ + AAC_LC_ER = 17, /* AAC Low complexity with error recovery */ + AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */ + AAC_SCALE_ER = 20, /* AAC scalable with error recovery */ + AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */ + AAC_BSAC_ER = 22, /* BSAC with error recovery */ + AAC_LD_ER = 23, /* AAC LD with error recovery */ + AAC_CELP_ER = 24, /* CELP with error recovery */ + AAC_HXVC_ER = 25, /* HXVC with error recovery */ + AAC_HILN_ER = 26, /* HILN with error recovery */ + AAC_PARAM_ER = 27, /* Parametric with error recovery */ + AAC_SSC = 28, /* AAC SSC */ + AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */ +} aac_object_type_t; + sqlite_int64 GetVideoMetadata(const char * path, char * name) { @@ -421,78 +509,373 @@ GetVideoMetadata(const char * path, char * name) struct stat file; char *sql; char *zErrMsg = NULL; - int ret; + int ret, i; + struct tm *modtime; + char date[20]; + AVFormatContext *ctx; + int audio_stream = -1, video_stream = -1; + int audio_profile = 0; + ts_timestamp_t ts_timestamp = NONE; + int duration, hours, min, sec, ms; + aac_object_type_t aac_type = 0; metadata_t m; memset(&m, '\0', sizeof(m)); + date[0] = '\0'; //DEBUG printf("Parsing %s...\n", path); if ( stat(path, &file) == 0 ) + { + modtime = localtime(&file.st_mtime); + strftime(date, sizeof(date), "%FT%T", modtime); size = file.st_size; + } strip_ext(name); //DEBUG printf(" * size: %d\n", size); -#if LIBDLNA_SUPPORT - dlna_t *dlna; - dlna_profile_t *p; - dlna_item_t *item; - - dlna = dlna_init(); - dlna_register_all_media_profiles(dlna); - - item = dlna_item_new(dlna, path); - if (item) + av_register_all(); + if( av_open_input_file(&ctx, path, NULL, 0, NULL) == 0 ) { - if (item->properties) + av_find_stream_info(ctx); + //dump_format(ctx, 0, NULL, 0); + for( i=0; inb_streams; i++) { - if( strlen(item->properties->duration) ) - m.duration = strdup(item->properties->duration); - if( item->properties->bitrate ) - asprintf(&m.bitrate, "%d", item->properties->bitrate); - if( item->properties->sample_frequency ) - asprintf(&m.frequency, "%d", item->properties->sample_frequency); - if( item->properties->bps ) - asprintf(&m.bps, "%d", item->properties->bps); - if( item->properties->channels ) - asprintf(&m.channels, "%d", item->properties->channels); - m.resolution = item->properties->resolution; + if( audio_stream == -1 && + ctx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) + { + audio_stream = i; + continue; + } + else if( video_stream == -1 && + ctx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + { + video_stream = i; + continue; + } } - } - - p = dlna_guess_media_profile(dlna, path); - if (p) - { - m.mime = strdup(p->mime); - asprintf(&m.dlna_pn, "%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", p->id); + if( audio_stream >= 0 ) + { + switch( ctx->streams[audio_stream]->codec->codec_id ) + { + case CODEC_ID_MP3: + audio_profile = MP3; + break; + case CODEC_ID_AAC: + if( !ctx->streams[audio_stream]->codec->extradata_size || + !ctx->streams[audio_stream]->codec->extradata ) + printf("No AAC type\n"); + else + aac_type = ctx->streams[audio_stream]->codec->extradata[0] >> 3; + switch( aac_type ) + { + /* AAC Low Complexity variants */ + case AAC_LC: + case AAC_LC_ER: + if( ctx->streams[audio_stream]->codec->sample_rate < 8000 || + ctx->streams[audio_stream]->codec->sample_rate > 48000 ) + { + printf("Unsupported AAC: sample rate is not 8000 < %d < 48000\n", + ctx->streams[audio_stream]->codec->sample_rate); + break; + } + /* AAC @ Level 1/2 */ + if( ctx->streams[audio_stream]->codec->channels <= 2 && + ctx->streams[audio_stream]->codec->bit_rate <= 576000 ) + audio_profile = AAC; + else if( ctx->streams[audio_stream]->codec->channels <= 6 && + ctx->streams[audio_stream]->codec->bit_rate <= 1440000 ) + audio_profile = AAC_MULT5; + else + printf("Unhandled AAC: %d channels, %d bitrate\n", + ctx->streams[audio_stream]->codec->channels, + ctx->streams[audio_stream]->codec->bit_rate); + break; + default: + printf("Unhandled AAC type [%d]\n", aac_type); + break; + } + break; + case CODEC_ID_AC3: + case CODEC_ID_DTS: + audio_profile = AC3; + break; + case CODEC_ID_WMAV1: + case CODEC_ID_WMAV2: + /* WMA Baseline: stereo, up to 48 KHz, up to 192,999 bps */ + if ( ctx->streams[audio_stream]->codec->bit_rate <= 193000 ) + audio_profile = WMA_BASE; + /* WMA Full: stereo, up to 48 KHz, up to 385 Kbps */ + else if ( ctx->streams[audio_stream]->codec->bit_rate <= 385000 ) + audio_profile = WMA_FULL; + break; + case CODEC_ID_WMAPRO: + audio_profile = WMA_PRO; + break; + case CODEC_ID_MP2: + audio_profile = MP2; + break; + default: + if( (ctx->streams[audio_stream]->codec->codec_id >= CODEC_ID_PCM_S16LE) && + (ctx->streams[audio_stream]->codec->codec_id <= CODEC_ID_PCM_F64LE) ) + audio_profile = PCM; + else + printf("Unhandled audio codec [%X]\n", ctx->streams[audio_stream]->codec->codec_id); + break; + } + asprintf(&m.frequency, "%u", ctx->streams[audio_stream]->codec->sample_rate); + #if LIBAVCODEC_VERSION_MAJOR >= 52 + asprintf(&m.bps, "%u", ctx->streams[audio_stream]->codec->bits_per_coded_sample); + #else + asprintf(&m.bps, "%u", ctx->streams[audio_stream]->codec->bits_per_sample); + #endif + asprintf(&m.channels, "%u", ctx->streams[audio_stream]->codec->channels); + } + if( video_stream >= 0 ) + { + //DEBUG printf("Container: '%s' [%s]\n", ctx->iformat->name, path); + asprintf(&m.resolution, "%dx%d", ctx->streams[video_stream]->codec->width, ctx->streams[video_stream]->codec->height); + asprintf(&m.bitrate, "%u", ctx->bit_rate / 8); + if( ctx->duration > 0 ) { + duration = (int)(ctx->duration / AV_TIME_BASE); + hours = (int)(duration / 3600); + min = (int)(duration / 60 % 60); + sec = (int)(duration % 60); + ms = (int)(ctx->duration / (AV_TIME_BASE/1000) % 1000); + asprintf(&m.duration, "%d:%02d:%02d.%03d", hours, min, sec, ms); + } + /* NOTE: The DLNA spec only provides for ASF (WMV), TS, PS, and MP4 containers -- not AVI. */ + switch( ctx->streams[video_stream]->codec->codec_id ) + { + case CODEC_ID_MPEG1VIDEO: + if( strcmp(ctx->iformat->name, "mpeg") == 0 ) + { + asprintf(&m.dlna_pn, "MPEG1;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + asprintf(&m.mime, "video/mpeg"); + } + break; + case CODEC_ID_MPEG2VIDEO: + if( strcmp(ctx->iformat->name, "mpegts") == 0 ) + { + printf("Stream %d of %s is %s MPEG2 TS\n", video_stream, path, m.resolution); + char res; + tsinfo_t * ts = ctx->priv_data; + if( ts->packet_size == 192 ) + { + asprintf(&m.dlna_pn, "MPEG_TS_HD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + asprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); + } + else if( ts->packet_size == 188 ) + { + if( (ctx->streams[video_stream]->codec->width >= 1280) && + (ctx->streams[video_stream]->codec->height >= 720) ) + res = 'H'; + else + res = 'S'; + asprintf(&m.dlna_pn, "MPEG_TS_%cD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0", res); + asprintf(&m.mime, "video/mpeg"); + } + } + else if( strcmp(ctx->iformat->name, "mpeg") == 0 ) + { + printf("Stream %d of %s is %s MPEG2 PS\n", video_stream, path, m.resolution); + char region[5]; + if( (ctx->streams[video_stream]->codec->height == 576) || + (ctx->streams[video_stream]->codec->height == 288) ) + strcpy(region, "PAL"); + else + strcpy(region, "NTSC"); + asprintf(&m.dlna_pn, "MPEG_PS_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", region); + asprintf(&m.mime, "video/mpeg"); + } + else + { + printf("Stream %d of %s [UNKNOWN CONTAINER] is %s MPEG2\n", video_stream, path, m.resolution); + } + break; + case CODEC_ID_H264: + if( strcmp(ctx->iformat->name, "mpegts") == 0 ) + { + tsinfo_t * ts = ctx->priv_data; + if( ts->packet_size == 192 ) + { + if( dlna_timestamp_is_present(path) ) + ts_timestamp = VALID; + else + ts_timestamp = EMPTY; + } + char res = '\0'; + if( ctx->streams[video_stream]->codec->width <= 720 && + ctx->streams[video_stream]->codec->height <= 576 && + ctx->streams[video_stream]->codec->bit_rate <= 10000000 ) + res = 'S'; + else if( ctx->streams[video_stream]->codec->width <= 1920 && + ctx->streams[video_stream]->codec->height <= 1152 && + ctx->streams[video_stream]->codec->bit_rate <= 20000000 ) + res = 'H'; + if( res ) + { + switch( audio_profile ) + { + case MP3: + asprintf(&m.dlna_pn, "AVC_TS_MP_HD_MPEG1_L3%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", + ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":""); + break; + case AC3: + asprintf(&m.dlna_pn, "AVC_TS_MP_HD_AC3%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", + ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":""); + break; + case AAC_MULT5: + asprintf(&m.dlna_pn, "AVC_TS_MP_HD_AAC_MULT5%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", + ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":""); + break; + default: + printf("No DLNA profile found for TS/AVC/%cD file %s\n", res, path); + break; + } + if( m.dlna_pn && (ts_timestamp != NONE) ) + asprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); + } + else + { + printf("Unsupported h.264 video profile! [%dx%d, %dbps]\n", + ctx->streams[video_stream]->codec->width, + ctx->streams[video_stream]->codec->height, + ctx->streams[video_stream]->codec->bit_rate); + } + } + else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) + { + /* AVC wrapped in MP4 only has SD profiles - 10 Mbps max */ + if( ctx->streams[video_stream]->codec->width <= 720 && + ctx->streams[video_stream]->codec->height <= 576 && + ctx->streams[video_stream]->codec->bit_rate <= 10000000 ) + { + switch( audio_profile ) + { + case AC3: + asprintf(&m.dlna_pn, "AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + break; + case AAC_MULT5: + asprintf(&m.dlna_pn, "AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + break; + default: + printf("No DLNA profile found for MP4/AVC/SD file %s\n", path); + break; + } + } + } + printf("Stream %d of %s is h.264\n", video_stream, path); + break; + case CODEC_ID_MPEG4: + if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("XVID") ) + { + printf("Stream %d of %s is %s XViD\n", video_stream, path, m.resolution); + asprintf(&m.mime, "video/divx"); + } + else if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("DX50") ) + { + printf("Stream %d of %s is %s DiVX5\n", video_stream, path, m.resolution); + asprintf(&m.mime, "video/divx"); + } + else if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("DIVX") ) + { + printf("Stream %d of %s is DiVX\n", video_stream, path); + asprintf(&m.mime, "video/divx"); + } + else + { + printf("Stream %d of %s is MPEG4 [%X]\n", video_stream, path, ctx->streams[video_stream]->codec->codec_tag); + } + break; + case CODEC_ID_WMV3: + case CODEC_ID_VC1: + printf("Stream %d of %s is VC1\n", video_stream, path); + char profile[5]; profile[0] = '\0'; + asprintf(&m.mime, "video/x-ms-wmv"); + if( (ctx->streams[video_stream]->codec->width <= 352) && + (ctx->streams[video_stream]->codec->height <= 288) && + (ctx->bit_rate/8 <= 384000) ) + { + if( audio_profile == MP3 ) + strcpy(profile, "MP3"); + else if( audio_profile == WMA_BASE ) + strcpy(profile, "BASE"); + asprintf(&m.dlna_pn, "WMVSPML_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile); + } + else if( (ctx->streams[video_stream]->codec->width <= 720) && + (ctx->streams[video_stream]->codec->height <= 576) && + (ctx->bit_rate/8 <= 10000000) ) + { + if( audio_profile == WMA_PRO ) + strcpy(profile, "PRO"); + else if( audio_profile == WMA_FULL ) + strcpy(profile, "FULL"); + else if( audio_profile == WMA_BASE ) + strcpy(profile, "BASE"); + asprintf(&m.dlna_pn, "WMVMED_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile); + } + else if( (ctx->streams[video_stream]->codec->width <= 1920) && + (ctx->streams[video_stream]->codec->height <= 1080) && + (ctx->bit_rate/8 <= 20000000) ) + { + if( audio_profile == WMA_FULL ) + strcpy(profile, "FULL"); + else if( audio_profile == WMA_PRO ) + strcpy(profile, "PRO"); + asprintf(&m.dlna_pn, "WMVHIGH_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile); + } + break; + case CODEC_ID_XVID: + printf("Stream %d of %s is %s UNKNOWN XVID\n", video_stream, path, m.resolution); + break; + case CODEC_ID_MSMPEG4V1: + printf("Stream %d of %s is %s MS MPEG4 v1\n", video_stream, path, m.resolution); + case CODEC_ID_MSMPEG4V3: + printf("Stream %d of %s is %s MS MPEG4 v3\n", video_stream, path, m.resolution); + asprintf(&m.mime, "video/avi"); + break; + case CODEC_ID_H263I: + printf("Stream %d of %s is h.263i\n", video_stream, path); + break; + case CODEC_ID_MJPEG: + printf("Stream %d of %s is MJPEG\n", video_stream, path); + break; + default: + printf("Stream %d of %s is %d\n", video_stream, path, ctx->streams[video_stream]->codec->codec_id); + break; + } + } + if( !m.mime ) + { + if( strcmp(ctx->iformat->name, "avi") == 0 ) + asprintf(&m.mime, "video/x-msvideo"); + else if( strcmp(ctx->iformat->name, "mpegts") == 0 ) + asprintf(&m.mime, "video/mpeg"); + else if( strcmp(ctx->iformat->name, "mpeg") == 0 ) + asprintf(&m.mime, "video/mpeg"); + else if( strcmp(ctx->iformat->name, "asf") == 0 ) + asprintf(&m.mime, "video/x-ms-wmv"); + else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) + asprintf(&m.mime, "video/mp4"); + } + av_close_input_file(ctx); } else { - printf ("Unknown format [%s]\n", path); - if( ends_with(path, ".mpg") || ends_with(path, ".mpeg") || ends_with(path, ".ts") ) - asprintf(&m.mime, "video/mpeg"); - else if( ends_with(path, ".avi") || ends_with(path, ".divx") ) - asprintf(&m.mime, "video/divx"); + printf("Opening %s failed!\n", path); } sql = sqlite3_mprintf( "INSERT into DETAILS" - " (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," + " (PATH, SIZE, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," " TITLE, DLNA_PN, MIME) " "VALUES" - " (%lld, %Q, %Q, %Q, %Q, %Q, '%q', %Q, '%q');", - size, m.duration, + " (%Q, %lld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, '%q');", + path, size, m.duration, + strlen(date) ? date : NULL, m.channels, m.bitrate, m.frequency, m.resolution, name, m.dlna_pn, m.mime); - dlna_item_free(item); - dlna_uninit(dlna); -#else // LIBDLNA_SUPPORT - sql = sqlite3_mprintf( "INSERT into DETAILS" - " (TITLE, SIZE, MIME) " - "VALUES" - " ('%q', %d, %Q);", - name, size, "video/mpeg");*/ -#endif // LIBDLNA_SUPPORT //DEBUG printf("SQL: %s\n", sql); if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) { @@ -512,6 +895,8 @@ GetVideoMetadata(const char * path, char * name) free(m.mime); if( m.duration ) free(m.duration); + if( m.resolution ) + free(m.resolution); if( m.bitrate ) free(m.bitrate); if( m.frequency ) diff --git a/metadata.h b/metadata.h index fa31f6c..1594527 100644 --- a/metadata.h +++ b/metadata.h @@ -26,6 +26,17 @@ typedef struct metadata_s { char *dlna_pn; } metadata_t; +typedef struct tsinfo_s { + int x; + int packet_size; +} tsinfo_t; + +typedef enum { + NONE, + EMPTY, + VALID +} ts_timestamp_t; + int ends_with(const char * haystack, const char * needle); diff --git a/minidlna.c b/minidlna.c index aa48290..f719c93 100644 --- a/minidlna.c +++ b/minidlna.c @@ -208,6 +208,29 @@ parselanaddr(struct lan_addr_s * lan_addr, const char * str) return 0; } +void +getfriendlyname(char * buf, int len) +{ + char * hn = calloc(1, 256); + if( gethostname(hn, 256) == 0 ) + { + strncpy(buf, hn, len-1); + buf[len] = '\0'; + *strstr(buf, ".") = '\0'; + } + else + { + strcpy(buf, "Unknown"); + } + free(hn); + strcat(buf, ": "); + #ifdef READYNAS + strncat(buf, "ReadyNAS", len-strlen(buf)-1); + #else + strncat(buf, getenv("LOGNAME"), len-strlen(buf)-1); + #endif +} + /* init phase : * 1) read configuration file * 2) read command line arguments @@ -229,6 +252,7 @@ init(int argc, char * * argv, struct runtime_vars * v) /*const char * logfilename = 0;*/ const char * presurl = 0; const char * optionsfile = "/etc/minidlna.conf"; + char * mac_str = calloc(1, 64); /* first check if "-f" option is used */ for(i=2; in_lan_addr = 0;*/ char ext_ip_addr[INET_ADDRSTRLEN]; if( (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && @@ -270,12 +307,6 @@ init(int argc, char * * argv, struct runtime_vars * v) { switch(ary_options[i].id) { - case UPNPEXT_IFNAME: - ext_if_name = ary_options[i].value; - break; - case UPNPEXT_IP: - use_ext_ip_addr = ary_options[i].value; - break; case UPNPLISTENING_IP: if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_lan_addr < MAX_LAN_ADDR)*/ { @@ -313,10 +344,6 @@ init(int argc, char * * argv, struct runtime_vars * v) if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/ break; - case UPNPUUID: - strncpy(uuidvalue+5, ary_options[i].value, - strlen(uuidvalue+5) + 1); - break; case UPNPSERIAL: strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; @@ -331,20 +358,6 @@ init(int argc, char * * argv, struct runtime_vars * v) case UPNPCLEANINTERVAL: v->clean_ruleset_interval = atoi(ary_options[i].value); break; -#ifdef USE_PF - case UPNPQUEUE: - queue = ary_options[i].value; - break; - case UPNPTAG: - tag = ary_options[i].value; - break; -#endif -#ifdef PF_ENABLE_FILTER_RULES - case UPNPQUICKRULES: - if(strcmp(ary_options[i].value, "no") == 0) - SETFLAG(PFNOQUICKRULESMASK); - break; -#endif case UPNPSECUREMODE: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(SECUREMODEMASK); @@ -355,6 +368,10 @@ init(int argc, char * * argv, struct runtime_vars * v) remove(lease_file); break; #endif + case UPNPFRIENDLYNAME: + strncpy(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); + friendly_name[FRIENDLYNAME_MAX_LEN-1] = '\0'; + break; case UPNPMEDIADIR: strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN); media_dir[MEDIADIR_MAX_LEN-1] = '\0'; @@ -375,24 +392,12 @@ init(int argc, char * * argv, struct runtime_vars * v) } else switch(argv[i][1]) { - case 'o': - if(i+1 < argc) - use_ext_ip_addr = argv[++i]; - else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); - break; case 't': if(i+1 < argc) v->notify_interval = atoi(argv[++i]); else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; - case 'u': - if(i+1 < argc) - strncpy(uuidvalue+5, argv[++i], strlen(uuidvalue+5) + 1); - else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); - break; case 's': if(i+1 < argc) strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); @@ -421,26 +426,6 @@ init(int argc, char * * argv, struct runtime_vars * v) case 'S': SETFLAG(SECUREMODEMASK); break; - case 'i': - if(i+1 < argc) - ext_if_name = argv[++i]; - else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); - break; -#ifdef USE_PF - case 'q': - if(i+1 < argc) - queue = argv[++i]; - else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); - break; - case 'T': - if(i+1 < argc) - tag = argv[++i]; - else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); - break; -#endif case 'p': if(i+1 < argc) v->port = atoi(argv[++i]); @@ -510,19 +495,15 @@ init(int argc, char * * argv, struct runtime_vars * v) fprintf(stderr, "Unknown option: %s\n", argv[i]); } } - if(!ext_if_name || (/*v->*/n_lan_addr==0) || v->port<=0) + if( (/*v->*/n_lan_addr==0) || (v->port<=0) ) { fprintf(stderr, "Usage:\n\t" "%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n" "\t\t[-a listening_ip] [-p port] [-d] [-L] [-U] [-S]\n" /*"[-l logfile] " not functionnal */ - "\t\t[-u uuid] [-s serial] [-m model_number] \n" + "\t\t[-s serial] [-m model_number] \n" "\t\t[-t notify_interval] [-P pid_filename]\n" -#ifdef USE_PF - "\t\t[-B down up] [-w url] [-q queue] [-T tag]\n" -#else "\t\t[-B down up] [-w url]\n" -#endif "\nNotes:\n\tThere can be one or several listening_ips.\n" "\tNotify interval is in seconds. Default is 30 seconds.\n" "\tDefault pid file is %s.\n" @@ -533,10 +514,6 @@ init(int argc, char * * argv, struct runtime_vars * v) "of daemon uptime.\n" "\t-B sets bitrates reported by daemon in bits per second.\n" "\t-w sets the presentation url. Default is http address on port 80\n" -#ifdef USE_PF - "\t-q sets the ALTQ queue in pf.\n" - "\t-T sets the tag name in pf.\n" -#endif "", argv[0], pidfilename); return 1; } @@ -563,7 +540,7 @@ init(int argc, char * * argv, struct runtime_vars * v) openlog_option |= LOG_PERROR; /* also log on stderr */ } - openlog("miniupnpd", openlog_option, LOG_MINIUPNPD); + openlog("minidlna", openlog_option, LOG_MINIDLNA); if(!debug_flag) { @@ -573,7 +550,7 @@ init(int argc, char * * argv, struct runtime_vars * v) if(checkforrunning(pidfilename) < 0) { - syslog(LOG_ERR, "MiniUPnPd is already running. EXITING"); + syslog(LOG_ERR, "MiniDLNA is already running. EXITING"); return 1; } @@ -594,9 +571,9 @@ init(int argc, char * * argv, struct runtime_vars * v) } /* set signal handler */ + signal(SIGCLD, SIG_IGN); memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; - if (sigaction(SIGTERM, &sa, NULL)) { syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM"); @@ -641,14 +618,31 @@ main(int argc, char * * argv) LIST_INIT(&upnphttphead); - if( access("/tmp/files.db", F_OK) ) + if( access(DB_PATH, F_OK) ) { - sqlite3_open("/tmp/files.db", &db); + sqlite3_open(DB_PATH, &db); + freopen("/dev/null", "a", stderr); ScanDirectory(media_dir, NULL); + freopen("/proc/self/fd/2", "a", stderr); } else { - sqlite3_open("/tmp/files.db", &db); + char **result; + int rows; + sqlite3_open(DB_PATH, &db); + if( sqlite3_get_table(db, "pragma user_version", &result, &rows, 0, 0) == SQLITE_OK ) + { + if( atoi(result[1]) != DB_VERSION ) { + printf("Database version mismatch; need to recreate...\n"); + sqlite3_close(db); + unlink(DB_PATH); + sqlite3_open(DB_PATH, &db); + freopen("/dev/null", "a", stderr); + ScanDirectory(media_dir, NULL); + freopen("/proc/self/fd/2", "a", stderr); + } + sqlite3_free_table(result); + } } diff --git a/minidlna.conf b/minidlna.conf index 4d129c7..892a70b 100644 --- a/minidlna.conf +++ b/minidlna.conf @@ -1,6 +1,3 @@ -# WAN network interface -ext_ifname=eth0 - # port for HTTP (descriptions and SOAP) traffic port=5555 @@ -31,8 +28,6 @@ system_uptime=no notify_interval=900 clean_ruleset_interval=600 -# uuid : generate your own with "make genuuid" -uuid=fc4ec57e-b051-11db-88f8-0060085db3f6 # serial and model number the daemon will report to clients # in its XML description diff --git a/minissdp.c b/minissdp.c index 262e1a3..87ba70a 100644 --- a/minissdp.c +++ b/minissdp.c @@ -133,15 +133,15 @@ OpenAndConfSSDPNotifySocket(in_addr_t addr) } memset(&sockname, 0, sizeof(struct sockaddr_in)); - sockname.sin_family = AF_INET; - sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ + sockname.sin_family = AF_INET; + sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ - if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) + if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { syslog(LOG_ERR, "bind(udp_notify): %m"); close(s); return -1; - } + } return s; } diff --git a/miniupnpdpath.h b/miniupnpdpath.h index 3f29dd6..546508a 100644 --- a/miniupnpdpath.h +++ b/miniupnpdpath.h @@ -13,18 +13,6 @@ #define ROOTDESC_PATH "/rootDesc.xml" -#ifdef HAS_DUMMY_SERVICE -#define DUMMY_PATH "/dummy.xml" -#endif - -#define WANCFG_PATH "/WANCfg.xml" -#define WANCFG_CONTROLURL "/ctl/CmnIfCfg" -#define WANCFG_EVENTURL "/evt/CmnIfCfg" - -#define WANIPC_PATH "/WANIPCn.xml" -#define WANIPC_CONTROLURL "/ctl/IPConn" -#define WANIPC_EVENTURL "/evt/IPConn" - #define CONTENTDIRECTORY_PATH "/ContentDir.xml" #define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir" #define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir" @@ -37,11 +25,5 @@ #define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar" #define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar" -#ifdef ENABLE_L3F_SERVICE -#define L3F_PATH "/L3F.xml" -#define L3F_CONTROLURL "/ctl/L3F" -#define L3F_EVENTURL "/evt/L3F" -#endif - #endif diff --git a/options.c b/options.c index 5741eea..a6ac7af 100644 --- a/options.c +++ b/options.c @@ -36,20 +36,11 @@ static const struct { { UPNPMODEL_NUMBER, "model_number"}, { UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"}, { UPNPCLEANINTERVAL, "clean_ruleset_interval"}, -#ifdef ENABLE_NATPMP - { UPNPENABLENATPMP, "enable_natpmp"}, -#endif { UPNPENABLE, "enable_upnp"}, -#ifdef USE_PF - { UPNPQUEUE, "queue"}, - { UPNPTAG, "tag"}, -#endif -#ifdef PF_ENABLE_FILTER_RULES - { UPNPQUICKRULES, "quickrules" }, -#endif #ifdef ENABLE_LEASEFILE { UPNPLEASEFILE, "lease_file"}, #endif + { UPNPFRIENDLYNAME, "friendly_name"}, { UPNPMEDIADIR, "media_dir"}, { UPNPSECUREMODE, "secure_mode"} }; diff --git a/options.h b/options.h index adb40c4..f606744 100644 --- a/options.h +++ b/options.h @@ -29,17 +29,11 @@ enum upnpconfigoptions { UPNPCLEANTHRESHOLD, /* clean_ruleset_threshold */ UPNPCLEANINTERVAL, /* clean_ruleset_interval */ UPNPENABLENATPMP, /* enable_natpmp */ -#ifdef USE_PF - UPNPQUEUE, /* queue */ - UPNPTAG, /* tag */ -#endif -#ifdef PF_ENABLE_FILTER_RULES - UPNPQUICKRULES, /* quickrules */ -#endif UPNPSECUREMODE, /* secure_mode */ #ifdef ENABLE_LEASEFILE UPNPLEASEFILE, /* lease_file */ #endif + UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */ UPNPMEDIADIR, /* directory to search for UPnP-A/V content */ UPNPENABLE /* enable_upnp */ }; diff --git a/scanner.c b/scanner.c index aca22f1..54eeef9 100644 --- a/scanner.c +++ b/scanner.c @@ -33,14 +33,19 @@ int is_video(const char * file) { - return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || - ends_with(file, ".ts") || ends_with(file, ".avi")); + return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || + ends_with(file, ".asf") || ends_with(file, ".wmv") || + ends_with(file, ".mp4") || ends_with(file, ".m4v") || + ends_with(file, ".mts") || ends_with(file, ".m2ts") || + ends_with(file, ".vob") || ends_with(file, ".ts") || + ends_with(file, ".avi") || ends_with(file, ".xvid")); } int is_audio(const char * file) { - return (ends_with(file, ".mp3") || + return (ends_with(file, ".mp3") || ends_with(file, ".flac") || + ends_with(file, ".fla") || ends_with(file, ".flc") || ends_with(file, ".m4a") || ends_with(file, ".aac")); } @@ -124,21 +129,27 @@ insert_containers(const char * name, const char *path, const char * refID, const if( strstr(class, "imageItem") ) { - char *date = result[12+cols], *cam = result[16+cols]; + char *date = result[13+cols], *cam = result[16+cols]; char date_taken[11]; date_taken[10] = '\0'; static int last_all_objectID = 0; - if( date ) - strncpy(date_taken, date, 10); if( date ) { + if( strcmp(date, "0000-00-00") == 0 ) + { + strcpy(date_taken, "Unknown"); + } + else + { + strncpy(date_taken, date, 10); + } container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", NULL); parentID = container>>32; objectID = container; sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q, %Q)", - parentID, objectID, parentID, refID, class, detailID, path, name); + " ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q)", + parentID, objectID, parentID, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } @@ -153,26 +164,26 @@ insert_containers(const char * name, const char *path, const char * refID, const int subParentID = subcontainer>>32; int subObjectID = subcontainer; sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q, %Q)", - parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, path, name); + " ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q)", + parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } /* All Images */ sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('3$11$%X', '3$11', '%s', '%s', %lu, %Q, %Q)", - last_all_objectID++, refID, class, detailID, path, name); + " ('3$11$%X', '3$11', '%s', '%s', %lu, %Q)", + last_all_objectID++, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } else if( strstr(class, "audioItem") ) { - char *artist = result[6+cols], *album = result[7+cols], *genre = result[8+cols]; - static char last_artist[1024]; + char *artist = cols ? result[7+cols]:NULL, *album = cols ? result[8+cols]:NULL, *genre = cols ? result[9+cols]:NULL; + static char last_artist[1024] = "0"; static int last_artist_parentID, last_artist_objectID; static char last_album[1024]; static int last_album_parentID, last_album_objectID; @@ -197,10 +208,10 @@ insert_containers(const char * name, const char *path, const char * refID, const last_artist_parentID = parentID; } sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q, %Q)", - parentID, objectID, parentID, refID, class, detailID, path, name); + " ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q)", + parentID, objectID, parentID, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } @@ -221,10 +232,10 @@ insert_containers(const char * name, const char *path, const char * refID, const last_album_parentID = parentID; } sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q, %Q)", - parentID, objectID, parentID, refID, class, detailID, path, name); + " ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q)", + parentID, objectID, parentID, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } @@ -245,19 +256,19 @@ insert_containers(const char * name, const char *path, const char * refID, const last_genre_parentID = parentID; } sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q, %Q)", - parentID, objectID, parentID, refID, class, detailID, path, name); + " ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q)", + parentID, objectID, parentID, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } /* All Music */ sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('1$4$%X', '1$4', '%s', '%s', %lu, %Q, %Q)", - last_all_objectID++, refID, class, detailID, path, name); + " ('1$4$%X', '1$4', '%s', '%s', %lu, %Q)", + last_all_objectID++, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); } @@ -278,10 +289,10 @@ insert_directory(const char * name, const char * path, const char * parentID, in for( i=0; base[i]; i++ ) { sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " "VALUES" - " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q', '%q')", - base[i], parentID, objectID, base[i], parentID, refID, detailID, class, path, name); + " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')", + base[i], parentID, objectID, base[i], parentID, refID, detailID, class, name); //DEBUG printf("SQL: %s\n", sql); ret = sql_exec(db, sql); sqlite3_free(sql); @@ -327,12 +338,14 @@ insert_file(char * name, const char * path, const char * parentID, int object) detailID = GetVideoMetadata(path, name); } //DEBUG printf("Got DetailID %lu!\n", detailID); + if( !detailID ) + return -1; sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('%s', '%s%s', '%s', %lu, '%q', '%q')", - objectID, BROWSEDIR_ID, parentID, class, detailID, path, name); + " ('%s', '%s%s', '%s', %lu, '%q')", + objectID, BROWSEDIR_ID, parentID, class, detailID, name); //DEBUG printf("SQL: %s\n", sql); sql_exec(db, sql); sqlite3_free(sql); @@ -378,6 +391,10 @@ create_database(void) sql_exec(db, "pragma synchronous = OFF;"); sql_exec(db, "pragma cache_size = 8192;"); + //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. + sprintf(sql_buf, "pragma user_version = %d;", DB_VERSION); + sql_exec(db, sql_buf); + ret = sql_exec(db, "CREATE TABLE OBJECTS ( " "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "OBJECT_ID TEXT NOT NULL, " @@ -385,13 +402,13 @@ create_database(void) "REF_ID TEXT DEFAULT NULL, " "CLASS TEXT NOT NULL, " "DETAIL_ID INTEGER DEFAULT NULL, " - "PATH TEXT DEFAULT NULL, " "NAME TEXT DEFAULT NULL" ");"); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, "CREATE TABLE DETAILS ( " "ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "PATH TEXT DEFAULT NULL, " "SIZE INTEGER, " "TITLE TEXT, " "DURATION TEXT, " @@ -433,7 +450,7 @@ create_database(void) sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); - sql_exec(db, "create INDEX IDX_OBJECTS_PATH ON OBJECTS(PATH);"); + sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); @@ -467,6 +484,9 @@ ScanDirectory(const char * dir, const char * parent) char parent_id[PATH_MAX]; char full_path[PATH_MAX]; char * name; +#if USE_FORK + pid_t newpid; +#endif if( !parent ) { @@ -475,12 +495,17 @@ ScanDirectory(const char * dir, const char * parent) fprintf(stderr, "Error creating database!\n"); return; } +#if USE_FORK + newpid = fork(); + if( newpid ) + return; +#endif } setlocale(LC_COLLATE, ""); if( chdir(dir) != 0 ) return; -printf("\nScanning %s\n", dir); + printf("\nScanning %s\n", dir); n = scandir(".", &namelist, filter_media, alphasort); if (n < 0) { fprintf(stderr, "Error scanning %s [scandir]\n", dir); @@ -512,4 +537,11 @@ printf("\nScanning %s\n", dir); } free(namelist); chdir(".."); + if( !parent ) + { + printf("Scanning \"%s\" finished!\n", dir); +#if USE_FORK + _exit(0); +#endif + } } diff --git a/testupnpdescgen.c b/testupnpdescgen.c index d072d7a..833a4ad 100644 --- a/testupnpdescgen.c +++ b/testupnpdescgen.c @@ -13,12 +13,12 @@ #include "upnpdescgen.h" char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd"; +char friendly_name[] = "localhost: system_type"; char serialnumber[] = "12345678"; char modelnumber[] = "1"; char presentationurl[] = "http://192.168.0.1:8080/"; char * use_ext_ip_addr = NULL; -const char * ext_if_name = "eth0"; int getifaddr(const char * ifname, char * buf, int len) { diff --git a/upnpdescgen.c b/upnpdescgen.c index b937b78..d3f0849 100644 --- a/upnpdescgen.c +++ b/upnpdescgen.c @@ -85,10 +85,14 @@ static const char * const upnpallowedvalues[] = "STOPPED", 0, "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," "http-get:*:audio/x-ms-wma:*," "http-get:*:audio/wav:*," @@ -129,7 +133,7 @@ static const struct XMLElt rootDesc[] = {"/major", "1"}, {"/minor", "0"}, {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, - {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ + {"/friendlyName", friendly_name}, /* required */ {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ @@ -175,12 +179,6 @@ static const struct argument AddPortMappingArgs[] = {NULL, 0, 0} }; -static const struct argument GetExternalIPAddressArgs[] = -{ - {NULL, 2, 7}, - {NULL, 0, 0} -}; - static const struct argument DeletePortMappingArgs[] = { {NULL, 1, 11}, @@ -202,14 +200,6 @@ static const struct argument GetConnectionTypeInfoArgs[] = {NULL, 0, 0} }; -static const struct argument GetStatusInfoArgs[] = -{ - {NULL, 2, 2}, - {NULL, 2, 4}, - {NULL, 2, 3}, - {NULL, 0, 0} -}; - static const struct argument GetNATRSIPStatusArgs[] = { {NULL, 2, 5}, @@ -494,71 +484,6 @@ static const struct argument GetTotalPacketsReceivedArgs[] = {NULL, 0, 0} }; -static const struct action WANCfgActions[] = -{ - {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ - {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ - {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ - {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ - {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ - {0, 0} -}; - -/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ -static const struct stateVar WANCfgVars[] = -{ - {"WANAccessType", 0, 0, 1}, - /* Allowed Values : DSL / POTS / Cable / Ethernet - * Default value : empty string */ - {"Layer1UpstreamMaxBitRate", 3, 0}, - {"Layer1DownstreamMaxBitRate", 3, 0}, - {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, - /* allowed values : - * Up / Down / Initializing (optional) / Unavailable (optionnal) - * no Default value - * Evented */ - {"TotalBytesSent", 3, 0}, /* Optional */ - {"TotalBytesReceived", 3, 0}, /* Optional */ - {"TotalPacketsSent", 3, 0}, /* Optional */ - {"TotalPacketsReceived", 3, 0},/* Optional */ - /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ - {0, 0} -}; - -static const struct serviceDesc scpdWANCfg = -{ WANCfgActions, WANCfgVars }; - -#ifdef ENABLE_L3F_SERVICE -/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ -static const struct argument SetDefaultConnectionServiceArgs[] = -{ - {NULL, 1, 0}, /* in */ - {NULL, 0, 0} -}; - -static const struct argument GetDefaultConnectionServiceArgs[] = -{ - {NULL, 2, 0}, /* out */ - {0, 0} -}; - -static const struct action L3FActions[] = -{ - {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ - {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ - {0, 0} -}; - -static const struct stateVar L3FVars[] = -{ - {"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */ - {0, 0} -}; - -static const struct serviceDesc scpdL3F = -{ L3FActions, L3FVars }; -#endif - static const struct serviceDesc scpdContentDirectory = { ContentDirectoryActions, ContentDirectoryVars }; //{ ContentDirectoryActions, ContentDirectoryVars }; @@ -709,9 +634,7 @@ genRootDesc(int * len) char *ret = calloc(1, 8192); sprintf(ret, "\r\n" "10urn:schemas-upnp-org:device:MediaServer:1MiniDLNA (MaggardMachine2)NETGEARhttp://www.netgear.comNETGEAR ReadyNAS NVReadyNASNVhttp://www.netgear.comuuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6dDMS-1.50\r\n" -//"10urn:schemas-upnp-org:device:MediaServer:1MiniDLNA (MaggardMachine2)NETGEARhttp://www.netgear.comNETGEAR ReadyNAS NVReadyNASNVhttp://www.netgear.comuuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6dav-upload,image-upload,create-child-container,audio-uploadDMS-1.50\r\n" "urn:schemas-upnp-org:service:ConnectionManager:1urn:upnp-org:serviceId:ConnectionManager/ConnectionMgr.xml/ctl/ConnectionMgr/evt/ConnectionMgrurn:schemas-upnp-org:service:ContentDirectory:1urn:upnp-org:serviceId:ContentDirectory/ContentDir.xml/ctl/ContentDir/evt/ContentDir"); -//"urn:schemas-upnp-org:service:ConnectionManager:1urn:upnp-org:serviceId:ConnectionManager/ConnectionMgr.xml/ctl/ConnectionMgr/evt/ConnectionMgrurn:schemas-upnp-org:service:ContentDirectory:1urn:upnp-org:serviceId:ContentDirectory/ContentDir.xml/ctl/ContentDir/evt/ContentDirurn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1763f907c-8cfb-11dd-a382-c9c0ad9eae41/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d\r\n"); * len = strlen(ret); return ret; #endif diff --git a/upnpdescgen.h b/upnpdescgen.h index f84da77..054e069 100644 --- a/upnpdescgen.h +++ b/upnpdescgen.h @@ -68,11 +68,6 @@ genWANIPCn(int * len); char * genWANCfg(int * len); -#ifdef ENABLE_L3F_SERVICE -char * -genL3F(int * len); -#endif - #ifdef ENABLE_EVENTS char * getVarsContentDirectory(int * len); @@ -81,13 +76,7 @@ char * getVarsConnectionManager(int * len); char * -getVarsWANIPCn(int * len); - -char * -getVarsWANCfg(int * len); - -char * -getVarsL3F(int * len); +getVarsX_MS_MediaReceiverRegistrar(int * len); #endif #endif diff --git a/upnpdescstrings.h b/upnpdescstrings.h index 57b543e..47e65b7 100644 --- a/upnpdescstrings.h +++ b/upnpdescstrings.h @@ -9,30 +9,14 @@ #include "config.h" /* strings used in the root device xml description */ -#define ROOTDEV_FRIENDLYNAME "MiniDLNA ReadyNAS:" +#ifdef READYNAS #define ROOTDEV_MANUFACTURER "NETGEAR" +#else +#define ROOTDEV_MANUFACTURER "Justin Maggard" +#endif #define ROOTDEV_MANUFACTURERURL OS_URL #define ROOTDEV_MODELNAME "Windows Media Connect compatible (minidlna)" #define ROOTDEV_MODELDESCRIPTION OS_NAME " *ReadyNAS dev DLNA" #define ROOTDEV_MODELURL OS_URL -#define WANDEV_FRIENDLYNAME "WANDevice" -#define WANDEV_MANUFACTURER "MiniUPnP" -#define WANDEV_MANUFACTURERURL "http://miniupnp.free.fr/" -#define WANDEV_MODELNAME "WAN Device" -#define WANDEV_MODELDESCRIPTION "WAN Device" -#define WANDEV_MODELNUMBER UPNP_VERSION -#define WANDEV_MODELURL "http://miniupnp.free.fr/" -#define WANDEV_UPC "MINIUPNPD" - -#define WANCDEV_FRIENDLYNAME "WANConnectionDevice" -#define WANCDEV_MANUFACTURER WANDEV_MANUFACTURER -#define WANCDEV_MANUFACTURERURL WANDEV_MANUFACTURERURL -#define WANCDEV_MODELNAME "MiniUPnPd" -#define WANCDEV_MODELDESCRIPTION "MiniUPnP daemon" -#define WANCDEV_MODELNUMBER UPNP_VERSION -#define WANCDEV_MODELURL "http://miniupnp.free.fr/" -#define WANCDEV_UPC "MINIUPNPD" - #endif - diff --git a/upnpevents.c b/upnpevents.c index 07e06fb..a0e06f2 100644 --- a/upnpevents.c +++ b/upnpevents.c @@ -24,6 +24,12 @@ #include "upnpglobalvars.h" #include "upnpdescgen.h" +#define HAVE_UUID 1 + +#ifdef HAVE_UUID +#include +#endif + #ifdef ENABLE_EVENTS /*enum subscriber_service_enum { EWanCFG = 1, @@ -80,18 +86,12 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen) if(!eventurl || !callback || !callbacklen) return NULL; tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); - if(strcmp(eventurl, WANCFG_EVENTURL)==0) - tmp->service = EWanCFG; - else if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) + if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) tmp->service = EContentDirectory; else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) tmp->service = EConnectionManager; - else if(strcmp(eventurl, WANIPC_EVENTURL)==0) - tmp->service = EWanIPC; -#ifdef ENABLE_L3F_SERVICE - else if(strcmp(eventurl, L3F_EVENTURL)==0) - tmp->service = EL3F; -#endif + else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) + tmp->service = EMSMediaReceiverRegistrar; else { free(tmp); return NULL; @@ -99,10 +99,15 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen) memcpy(tmp->callback, callback, callbacklen); tmp->callback[callbacklen] = '\0'; /* make a dummy uuid */ - /* TODO: improve that */ strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); +#ifdef HAVE_UUID + uuid_t uuid; + uuid_generate_time(uuid); + uuid_unparse_lower(uuid, tmp->uuid+5); +#else tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); +#endif return tmp; } @@ -136,7 +141,7 @@ renewSubscription(const char * sid, int sidlen, int timeout) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { - if(memcmp(sid, sub->uuid, 41)) { + if(memcmp(sid, sub->uuid, 41) == 0) { sub->timeout = (timeout ? time(NULL) + timeout : 0); return 0; } @@ -290,11 +295,14 @@ static void upnp_event_prepare(struct upnp_event_notify * obj) case EConnectionManager: xml = getVarsConnectionManager(&l); break; + case EMSMediaReceiverRegistrar: + xml = getVarsX_MS_MediaReceiverRegistrar(&l); + break; default: xml = NULL; l = 0; } - obj->buffersize = 1536; + obj->buffersize = 2048; obj->buffer = malloc(obj->buffersize); /*if(!obj->buffer) { }*/ diff --git a/upnpevents.h b/upnpevents.h index b4d2566..b685538 100644 --- a/upnpevents.h +++ b/upnpevents.h @@ -8,11 +8,9 @@ #define __UPNPEVENTS_H__ #ifdef ENABLE_EVENTS enum subscriber_service_enum { - EWanCFG = 1, - EContentDirectory, + EContentDirectory = 1, EConnectionManager, - EWanIPC, - EL3F + EMSMediaReceiverRegistrar }; void diff --git a/upnpglobalvars.c b/upnpglobalvars.c index 0459352..8d99a00 100644 --- a/upnpglobalvars.c +++ b/upnpglobalvars.c @@ -11,9 +11,6 @@ #include "config.h" #include "upnpglobalvars.h" -/* network interface for internet */ -const char * ext_if_name = 0; - /* file to store leases */ #ifdef ENABLE_LEASEFILE const char* lease_file = 0; @@ -39,9 +36,6 @@ int sysuptime = 0; /* log packets flag */ int logpackets = 0; -#ifdef ENABLE_NATPMP -int enablenatpmp = 0; -#endif #endif int runtime_flags = 0; @@ -61,21 +55,10 @@ char presentationurl[PRESENTATIONURL_MAX_LEN]; struct upnpperm * upnppermlist = 0; unsigned int num_upnpperm = 0; -#ifdef ENABLE_NATPMP -/* NAT-PMP */ -unsigned int nextnatpmptoclean_timestamp = 0; -unsigned short nextnatpmptoclean_eport = 0; -unsigned short nextnatpmptoclean_proto = 0; -#endif - -#ifdef USE_PF -const char * queue = 0; -const char * tag = 0; -#endif - int n_lan_addr = 0; struct lan_addr_s lan_addr[MAX_LAN_ADDR]; /* UPnP-A/V [DLNA] */ sqlite3 *db; -char media_dir[256]; +char media_dir[MEDIADIR_MAX_LEN]; +char friendly_name[FRIENDLYNAME_MAX_LEN]; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 4d32f9f..ad8d52a 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -13,8 +13,8 @@ #include -/* name of the network interface used to acces internet */ -extern const char * ext_if_name; +#define USE_FORK 1 +#define DB_VERSION 1 /* file to store all leases */ #ifdef ENABLE_LEASEFILE @@ -36,16 +36,9 @@ extern time_t startup_time; extern int runtime_flags; #define LOGPACKETSMASK 0x0001 #define SYSUPTIMEMASK 0x0002 -#ifdef ENABLE_NATPMP -#define ENABLENATPMPMASK 0x0004 -#endif #define CHECKCLIENTIPMASK 0x0008 #define SECUREMODEMASK 0x0010 -#ifdef PF_ENABLE_FILTER_RULES -#define PFNOQUICKRULESMASK 0x0040 -#endif - #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) runtime_flags & mask #define CLEARFLAG(mask) runtime_flags &= ~mask @@ -67,19 +60,6 @@ extern char presentationurl[]; extern struct upnpperm * upnppermlist; extern unsigned int num_upnpperm; -#ifdef ENABLE_NATPMP -/* NAT-PMP */ -extern unsigned int nextnatpmptoclean_timestamp; -extern unsigned short nextnatpmptoclean_eport; -extern unsigned short nextnatpmptoclean_proto; -#endif - -#ifdef USE_PF -/* queue and tag for PF rules */ -extern const char * queue; -extern const char * tag; -#endif - /* lan addresses */ /* MAX_LAN_ADDR : maximum number of interfaces * to listen to SSDP traffic */ @@ -91,5 +71,7 @@ extern struct lan_addr_s lan_addr[]; extern sqlite3 *db; #define MEDIADIR_MAX_LEN (256) extern char media_dir[]; +#define FRIENDLYNAME_MAX_LEN (64) +extern char friendly_name[]; #endif diff --git a/upnphttp.c b/upnphttp.c index 2b75984..9b4df54 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -367,25 +367,6 @@ findendheaders(const char * s, int len) return NULL; } -#ifdef HAS_DUMMY_SERVICE -static void -sendDummyDesc(struct upnphttp * h) -{ - static const char xml_desc[] = "\r\n" - "" - " " - " 1" - " 0" - " " - " " - " " - "\r\n"; - BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); -} -#endif - /* Sends the description generated by the parameter */ static void sendXMLdesc(struct upnphttp * h, char * (f)(int *)) @@ -551,17 +532,31 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) HttpVer[i] = '\0'; syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", HttpCommand, HttpUrl, HttpVer); - //DEBUG printf("HTTP REQUEST:\n%s\n", h->req_buf); + //DEBUG printf("HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf); ParseHttpHeaders(h); - if( (h->reqflags & FLAG_CHUNKED) && (h->req_chunklen > (h->req_buflen - h->req_contentoff) || h->req_chunklen < 0) ) + /* see if we need to wait for remaining data */ + if( (h->reqflags & FLAG_CHUNKED) ) { - /* waiting for remaining data */ - printf("*** %d < %d\n", (h->req_buflen - h->req_contentoff), h->req_contentlen); - printf("Chunked request [%ld]. Need more input.\n", h->req_chunklen); + char * chunkstart = h->req_buf+h->req_contentoff; + char * numstart; h->state = 2; + while( h->req_chunklen ) + { + if( chunkstart >= (h->req_buf+h->req_buflen) ) + return; + numstart = chunkstart+h->req_chunklen+2; + h->req_chunklen = strtol(numstart, &chunkstart, 16); + if( !h->req_chunklen && (chunkstart == numstart) ) + { + printf("Chunked request needs more input.\n"); + return; + } + chunkstart = chunkstart+2; + } + h->state = 100; } - else if(strcmp("POST", HttpCommand) == 0) + if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); @@ -602,12 +597,6 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) { sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); } -#ifdef HAS_DUMMY_SERVICE - else if(strcmp(DUMMY_PATH, HttpUrl) == 0) - { - sendDummyDesc(h); - } -#endif else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) { SendResp_dlnafile(h, HttpUrl+12); @@ -864,6 +853,7 @@ SendResp_thumbnail(struct upnphttp * h, char * object) char header[1500]; char sql_buf[256]; char **result; + int rows; char date[30]; time_t curtime = time(NULL); int n; @@ -879,8 +869,14 @@ SendResp_thumbnail(struct upnphttp * h, char * object) return; } - sprintf(sql_buf, "SELECT PATH from OBJECTS where OBJECT_ID = '%s'", object); - sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); + sprintf(sql_buf, "SELECT d.PATH from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); + if( !rows ) + { + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); + Send404(h); + goto error; + } printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]); if( access(result[1], F_OK) == 0 ) @@ -976,7 +972,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object) return; } - sprintf(sql_buf, "SELECT o.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sprintf(sql_buf, "SELECT d.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); printf("Serving up resized image for ObjectId: %s [%s]\n", object, result[1]); @@ -1073,17 +1069,26 @@ SendResp_dlnafile(struct upnphttp * h, char * object) time_t curtime = time(NULL); off_t total, send_size; char *path, *mime, *dlna; - +#if USE_FORK + pid_t newpid = 0; +#endif + memset(header, 0, 1500); - sprintf(sql_buf, "SELECT o.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sprintf(sql_buf, "SELECT d.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); Send404(h); - goto error; + sqlite3_free_table(result); + return; } +#if USE_FORK + newpid = fork(); + if( newpid ) + return; +#endif path = result[3]; mime = result[4]; @@ -1186,8 +1191,8 @@ SendResp_dlnafile(struct upnphttp * h, char * object) } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { - if( (strncmp(mime, "vide", 4) == 0) || - (strncmp(mime, "audi", 4) == 0) ) + if( (strncmp(mime, "video", 5) == 0) || + (strncmp(mime, "audio", 5) == 0) ) strcat(header, "transferMode.dlna.org: Streaming\r\n"); else strcat(header, "transferMode.dlna.org: Interactive\r\n"); @@ -1240,4 +1245,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object) } error: sqlite3_free_table(result); +#if USE_FORK + _exit(0); +#endif } diff --git a/upnpsoap.c b/upnpsoap.c index cd73310..50caf40 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -64,28 +64,6 @@ BuildSendAndCloseSoapResp(struct upnphttp * h, CloseSocket_upnphttp(h); } -static void -GetStatusInfo(struct upnphttp * h, const char * action) -{ - static const char resp[] = - "" - "Connected" - "ERROR_NONE" - "%ld" - ""; - - char body[512]; - int bodylen; - time_t uptime; - - uptime = (time(NULL) - startup_time); - bodylen = snprintf(body, sizeof(body), resp, - action, "urn:schemas-upnp-org:service:WANIPConnection:1", - (long)uptime, action); - BuildSendAndCloseSoapResp(h, body, bodylen); -} - static void GetSystemUpdateID(struct upnphttp * h, const char * action) { @@ -129,16 +107,15 @@ GetProtocolInfo(struct upnphttp * h, const char * action) "" "" - /*"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01,"*/ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," + "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," "http-get:*:audio/x-ms-wma:*," "http-get:*:audio/wav:*," @@ -212,7 +189,7 @@ GetCurrentConnectionIDs(struct upnphttp * h, const char * action) static const char resp[] = "" - "-1" + "0" ""; char body[512]; @@ -378,13 +355,16 @@ static int callback(void *args, int argc, char **argv, char **azColName) sprintf(str_buf, "childCount=\"%s\"", result[1]); strcat(passed_args->resp, str_buf); } - /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s */ + /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */ if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) ) { - strcat(passed_args->resp, ">" - "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>" - "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>" - "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass"); + if( !passed_args->filter || strstr(passed_args->filter, "upnp:searchClass") ) + { + strcat(passed_args->resp, ">" + "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>" + "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>" + "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass"); + } } sprintf(str_buf, ">" "<dc:title>%s</dc:title>" @@ -575,49 +555,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action) free(resp); } -static void -GetExternalIPAddress(struct upnphttp * h, const char * action) -{ - static const char resp[] = - "" - "%s" - ""; - - char body[512]; - int bodylen; - char ext_ip_addr[INET_ADDRSTRLEN]; - -#ifndef MULTIPLE_EXTERNAL_IP - if(use_ext_ip_addr) - { - strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); - } - else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0) - { - syslog(LOG_ERR, "Failed to get ip address for interface %s", - ext_if_name); - strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); - } -#else - int i; - strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); - for(i = 0; iclientaddr.s_addr & lan_addr[i].mask.s_addr) - == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) - { - strncpy(ext_ip_addr, lan_addr[i].ext_ip_str, INET_ADDRSTRLEN); - break; - } - } -#endif - bodylen = snprintf(body, sizeof(body), resp, - action, "urn:schemas-upnp-org:service:WANIPConnection:1", - ext_ip_addr, action); - BuildSendAndCloseSoapResp(h, body, bodylen); -} - /* If a control point calls QueryStateVariable on a state variable that is not buffered in memory within (or otherwise available from) the service, @@ -659,7 +596,7 @@ QueryStateVariable(struct upnphttp * h, const char * action) BuildSendAndCloseSoapResp(h, body, bodylen); } #if 0 - /* not usefull */ + /* not useful */ else if(strcmp(var_name, "ConnectionType") == 0) { bodylen = snprintf(body, sizeof(body), resp, "IP_Routed"); @@ -687,9 +624,7 @@ static const struct } soapMethods[] = { - { "GetExternalIPAddress", GetExternalIPAddress}, { "QueryStateVariable", QueryStateVariable}, - { "GetStatusInfo", GetStatusInfo}, { "Browse", BrowseContentDirectory}, { "Search", SearchContentDirectory}, { "GetSearchCapabilities", GetSearchCapabilities},