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.
This commit is contained in:
Justin Maggard 2009-01-22 00:25:20 +00:00
parent 9867def383
commit 74d73037d0
25 changed files with 769 additions and 608 deletions

View File

@ -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

7
TODO
View File

@ -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)

View File

@ -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

View File

@ -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}

View File

@ -5,6 +5,8 @@
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
@ -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;
}

View File

@ -14,5 +14,8 @@
int
getifaddr(const char * ifname, char * buf, int len);
int
getifhwaddr(const char * ifname, char * buf, int len);
#endif

View File

@ -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 <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <taglib/tag_c.h>
#include <libexif/exif-loader.h>
#if LIBDLNA_SUPPORT
#include <dlna.h>
#endif
#include <avutil.h>
#include <avcodec.h>
#include <avformat.h>
#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; i<ctx->nb_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 )

View File

@ -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);

View File

@ -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; i<argc; i++)
@ -241,6 +265,19 @@ init(int argc, char * * argv, struct runtime_vars * v)
}
}
/* set up uuid based on mac address */
if( (getifhwaddr("eth0", mac_str, 64) < 0) &&
(getifhwaddr("eth1", mac_str, 64) < 0) )
{
printf("No MAC addresses found!\n");
strcpy(mac_str, "554e4b4e4f57");
}
strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-");
strncat(uuidvalue, mac_str, 12);
free(mac_str);
getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN);
/*v->n_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);
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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"}
};

View File

@ -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 */
};

108
scanner.c
View File

@ -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
}
}

View File

@ -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)
{

View File

@ -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, "<?xml version='1.0' encoding='UTF-8' ?>\r\n"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
//"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNACAP>av-upload,image-upload,create-child-container,audio-upload</dlna:X_DLNACAP><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
"<serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service></serviceList></device></root>");
//"</iconList><serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service><service><serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType><serviceId>763f907c-8cfb-11dd-a382-c9c0ad9eae41</serviceId><SCPDURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</SCPDURL><controlURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</controlURL><eventSubURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</eventSubURL></service></serviceList></device></root>\r\n");
* len = strlen(ret);
return ret;
#endif

View File

@ -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

View File

@ -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

View File

@ -24,6 +24,12 @@
#include "upnpglobalvars.h"
#include "upnpdescgen.h"
#define HAVE_UUID 1
#ifdef HAVE_UUID
#include <uuid/uuid.h>
#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) {
}*/

View File

@ -8,11 +8,9 @@
#define __UPNPEVENTS_H__
#ifdef ENABLE_EVENTS
enum subscriber_service_enum {
EWanCFG = 1,
EContentDirectory,
EContentDirectory = 1,
EConnectionManager,
EWanIPC,
EL3F
EMSMediaReceiverRegistrar
};
void

View File

@ -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];

View File

@ -13,8 +13,8 @@
#include <sqlite3.h>
/* 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

View File

@ -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[] = "<?xml version=\"1.0\"?>\r\n"
"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
" <specVersion>"
" <major>1</major>"
" <minor>0</minor>"
" </specVersion>"
" <actionList />"
" <serviceStateTable />"
"</scpd>\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
}

View File

@ -64,28 +64,6 @@ BuildSendAndCloseSoapResp(struct upnphttp * h,
CloseSocket_upnphttp(h);
}
static void
GetStatusInfo(struct upnphttp * h, const char * action)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewConnectionStatus>Connected</NewConnectionStatus>"
"<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>"
"<NewUptime>%ld</NewUptime>"
"</u:%sResponse>";
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)
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<Source>"
/*"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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<ConnectionIDs>-1</ConnectionIDs>"
"<ConnectionIDs>0</ConnectionIDs>"
"</u:%sResponse>";
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, "&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
if( !passed_args->filter || strstr(passed_args->filter, "upnp:searchClass") )
{
strcat(passed_args->resp, "&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
}
}
sprintf(str_buf, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;"
@ -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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewExternalIPAddress>%s</NewExternalIPAddress>"
"</u:%sResponse>";
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; i<n_lan_addr; i++)
{
if( (h->clientaddr.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},