Merge branch 'master' into ffmpeg

This commit is contained in:
Justin Maggard 2014-07-11 18:17:33 -07:00
commit 24ec6d24fb
20 changed files with 653 additions and 283 deletions

View File

@ -28,7 +28,7 @@ minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \
sql.c utils.c metadata.c scanner.c inotify.c \
tivo_utils.c tivo_beacon.c tivo_commands.c \
playlist.c image_utils.c albumart.c log.c \
tagutils/tagutils.c
containers.c tagutils/tagutils.c
#if NEED_VORBIS
vorbisflag = -lvorbis

18
NEWS
View File

@ -1,3 +1,21 @@
1.1.3 - Released 05-June-2014
--------------------------------
- Enhance log level settings.
- Fix Samsung browsing when root_container is set.
- Add Clang compiling support.
- Fix compiling on systems without iconv.
- Add merge_media_dirs option, to revert to the old behavior.
- Add Asus O!Play client support.
- Fix Broken SSDP multicast membership addition.
- Fix crash bug with an emtpy filter argument.
- Accept SMI subtitles in addition to SRT.
- Add BubbleUPnP detection and enable subtitle support.
- Allow the user to specify an arbitrary root container.
- Add libavcodec > 54 / libav 10 compatibility.
- Get embedded cover art from video files with recent libavformat versions.
- Disable Samsung DCM10 capability, as it breaks compatibility with new models.
- Add subtitle support for NetFront™ Living Connect middleware-based clients.
1.1.2 - Released 06-Mar-2014
--------------------------------
- Show client status on our basic presentation page.

View File

@ -58,7 +58,7 @@ struct client_type_s client_types[] =
/* User-Agent: DLNADOC/1.50 SEC_HHP_BD-D5100/1.0 */
{ ESamsungSeriesCDEBDP,
FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE,
"Samsung Series [CDE] BDP",
"Samsung Series [CDEF] BDP",
"SEC_HHP_BD",
EUserAgent
},
@ -66,8 +66,8 @@ struct client_type_s client_types[] =
/* User-Agent: DLNADOC/1.50 SEC_HHP_[TV]UE40D7000/1.0 */
/* User-Agent: DLNADOC/1.50 SEC_HHP_ Family TV/1.0 */
{ ESamsungSeriesCDE,
FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE | FLAG_SAMSUNG_DCM10,
"Samsung Series [CDE]",
FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE,
"Samsung Series [CDEF]",
"SEC_HHP_",
EUserAgent
},
@ -94,6 +94,14 @@ struct client_type_s client_types[] =
EUserAgent
},
/* User-Agent: IPI/1.0 UPnP/1.0 DLNADOC/1.50 */
{ ENetFrontLivingConnect,
FLAG_DLNA | FLAG_FORCE_SORT | FLAG_CAPTION_RES,
"NetFront Living Connect",
"IPI/1",
EUserAgent
},
{ EDenonReceiver,
FLAG_DLNA,
"Denon Receiver",
@ -232,7 +240,7 @@ struct client_type_s client_types[] =
struct client_cache_s clients[CLIENT_CACHE_SLOTS];
int
struct client_cache_s *
SearchClientCache(struct in_addr addr, int quiet)
{
int i;
@ -255,20 +263,20 @@ SearchClientCache(struct in_addr addr, int quiet)
else
{
memset(&clients[i], 0, sizeof(struct client_cache_s));
return -1;
return NULL;
}
}
if (!quiet)
DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [%s/entry %d]\n",
client_types[clients[i].type].name, i);
return i;
clients[i].type->name, i);
return &clients[i];
}
}
return -1;
return NULL;
}
int
struct client_cache_s *
AddClientCache(struct in_addr addr, int type)
{
int i;
@ -279,15 +287,15 @@ AddClientCache(struct in_addr addr, int type)
continue;
get_remote_mac(addr, clients[i].mac);
clients[i].addr = addr;
clients[i].type = type;
clients[i].type = &client_types[type];
clients[i].age = time(NULL);
DPRINTF(E_DEBUG, L_HTTP, "Added client [%s/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
client_types[type].name, inet_ntoa(clients[i].addr),
clients[i].mac[0], clients[i].mac[1], clients[i].mac[2],
clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], i);
return 0;
return &clients[i];
}
return -1;
return NULL;
}

View File

@ -38,9 +38,7 @@
#define FLAG_FORCE_SORT 0x00000800
#define FLAG_CAPTION_RES 0x00001000
/* Response-related flags */
#define FLAG_HAS_CAPTIONS 0x20000000
#define FLAG_FREE_OBJECT_ID 0x40000000
#define FLAG_ROOT_CONTAINER 0x80000000
#define FLAG_HAS_CAPTIONS 0x80000000
enum match_types {
EMatchNone,
@ -75,6 +73,7 @@ enum client_types {
EToshibaTV,
EAsusOPlay,
EBubbleUPnP,
ENetFrontLivingConnect,
EStandardDLNA150,
EStandardUPnP
};
@ -90,14 +89,15 @@ struct client_type_s {
struct client_cache_s {
struct in_addr addr;
unsigned char mac[6];
enum client_types type;
struct client_type_s *type;
time_t age;
int connections;
};
extern struct client_type_s client_types[];
extern struct client_cache_s clients[CLIENT_CACHE_SLOTS];
int SearchClientCache(struct in_addr addr, int quiet);
int AddClientCache(struct in_addr addr, int type);
struct client_cache_s *SearchClientCache(struct in_addr addr, int quiet);
struct client_cache_s *AddClientCache(struct in_addr addr, int type);
#endif

170
containers.c Normal file
View File

@ -0,0 +1,170 @@
/* MiniDLNA media server
* Copyright (C) 2014 NETGEAR
*
* This file is part of MiniDLNA.
*
* MiniDLNA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* MiniDLNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "clients.h"
#include "minidlnatypes.h"
#include "scanner.h"
#include "upnpglobalvars.h"
#include "containers.h"
#include "log.h"
#define NINETY_DAYS "7776000"
const char *music_id = MUSIC_ID;
const char *music_all_id = MUSIC_ALL_ID;
const char *music_genre_id = MUSIC_GENRE_ID;
const char *music_artist_id = MUSIC_ARTIST_ID;
const char *music_album_id = MUSIC_ALBUM_ID;
const char *music_plist_id = MUSIC_PLIST_ID;
const char *music_dir_id = MUSIC_DIR_ID;
const char *video_all_id = VIDEO_ALL_ID;
const char *video_dir_id = VIDEO_DIR_ID;
const char *image_all_id = IMAGE_ALL_ID;
const char *image_date_id = IMAGE_DATE_ID;
const char *image_camera_id = IMAGE_CAMERA_ID;
const char *image_dir_id = IMAGE_DIR_ID;
struct magic_container_s magic_containers[] =
{
/* Alternate root container */
{ NULL,
"0",
&runtime_vars.root_container,
NULL,
"0",
NULL,
NULL,
NULL,
NULL,
-1,
0,
},
/* Recent 50 audio items */
{ "Recently Added",
"1$FF0",
NULL,
"\"1$FF0$\" || OBJECT_ID",
"\"1$FF0\"",
"o.OBJECT_ID",
"(select null from DETAILS where MIME glob 'a*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)",
"MIME glob 'a*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")",
"order by TIMESTAMP DESC",
50,
0,
},
/* Recent 50 video items */
{ "Recently Added",
"2$FF0",
NULL,
"\"2$FF0$\" || OBJECT_ID",
"\"2$FF0\"",
"o.OBJECT_ID",
"(select null from DETAILS where MIME glob 'v*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)",
"MIME glob 'v*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")",
"order by TIMESTAMP DESC",
50,
0,
},
/* Recent 50 image items */
{ "Recently Added",
"3$FF0",
NULL,
"\"3$FF0$\" || OBJECT_ID",
"\"3$FF0\"",
"o.OBJECT_ID",
"(select null from DETAILS where MIME glob 'i*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)",
"MIME glob 'i*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")",
"order by TIMESTAMP DESC",
50,
0,
},
/* Microsoft PlaysForSure containers */
{ NULL, "4", &music_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "5", &music_genre_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "6", &music_artist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "7", &music_album_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "8", &video_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "B", &image_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "C", &image_date_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "F", &music_plist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "14", &music_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "15", &video_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "16", &image_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
{ NULL, "D2", &image_camera_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS },
/* Jump straight to Music on audio-only devices */
{ NULL, "0", &music_id, NULL, "0", NULL, NULL, NULL, NULL, -1, FLAG_AUDIO_ONLY },
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 }
};
struct magic_container_s *
in_magic_container(const char *id, int flags, const char **real_id)
{
size_t len;
int i;
for (i = 0; magic_containers[i].objectid_match; i++)
{
if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags))
continue;
if (magic_containers[i].objectid && !(*magic_containers[i].objectid))
continue;
DPRINTF(E_DEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match);
len = strlen(magic_containers[i].objectid_match);
if (strncmp(id, magic_containers[i].objectid_match, len) == 0)
{
if (*(id+len) == '$')
*real_id = id+len+1;
else if (*(id+len) == '\0')
*real_id = id;
else
continue;
DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match);
return &magic_containers[i];
}
}
return NULL;
}
struct magic_container_s *
check_magic_container(const char *id, int flags)
{
int i;
for (i = 0; magic_containers[i].objectid_match; i++)
{
if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags))
continue;
if (magic_containers[i].objectid && !(*magic_containers[i].objectid))
continue;
DPRINTF(E_DEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match);
if (strcmp(id, magic_containers[i].objectid_match) == 0)
{
DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match);
return &magic_containers[i];
}
}
return NULL;
}

36
containers.h Normal file
View File

@ -0,0 +1,36 @@
/* MiniDLNA media server
* Copyright (C) 2014 NETGEAR
*
* This file is part of MiniDLNA.
*
* MiniDLNA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* MiniDLNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
*/
struct magic_container_s {
const char *name;
const char *objectid_match;
const char **objectid;
const char *objectid_sql;
const char *parentid_sql;
const char *refid_sql;
const char *child_count;
const char *where;
const char *orderby;
int max_count;
int required_flags;
};
extern struct magic_container_s magic_containers[];
struct magic_container_s *in_magic_container(const char *id, int flags, const char **real_id);
struct magic_container_s *check_magic_container(const char *id, int flags);

77
libav.h
View File

@ -78,6 +78,10 @@
#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
#endif
#if LIBAVCODEC_VERSION_INT <= ((51<<16)+(50<<8)+1)
#define CODEC_ID_WMAPRO CODEC_ID_NONE
#endif
#if LIBAVCODEC_VERSION_MAJOR < 55
#define AV_CODEC_ID_AAC CODEC_ID_AAC
#define AV_CODEC_ID_AC3 CODEC_ID_AC3
@ -103,3 +107,76 @@
#define av_strerror(x, y, z) snprintf(y, z, "%d", x)
#endif
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
# if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2)
#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX
#define av_dict_get av_metadata_get
typedef AVMetadataTag AVDictionaryEntry;
# endif
#endif
static inline int
lav_open(AVFormatContext **ctx, const char *filename)
{
int ret;
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
ret = avformat_open_input(ctx, filename, NULL, NULL);
if (ret == 0)
avformat_find_stream_info(*ctx, NULL);
#else
ret = av_open_input_file(ctx, filename, NULL, 0, NULL);
if (ret == 0)
av_find_stream_info(*ctx);
#endif
return ret;
}
static inline void
lav_close(AVFormatContext *ctx)
{
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
avformat_close_input(&ctx);
#else
av_close_input_file(ctx);
#endif
}
static inline int
lav_get_fps(AVStream *s)
{
#if LIBAVCODEC_VERSION_MAJOR < 54
if (s->r_frame_rate.den)
return s->r_frame_rate.num / s->r_frame_rate.den;
#else
if (s->avg_frame_rate.den)
return s->avg_frame_rate.num / s->avg_frame_rate.den;
#endif
return 0;
}
static inline int
lav_get_interlaced(AVCodecContext *vc, AVStream *s)
{
#if LIBAVCODEC_VERSION_MAJOR < 54
return (vc->time_base.den ? (s->r_frame_rate.num / vc->time_base.den) : 0);
#else
return (vc->time_base.den ? (s->avg_frame_rate.num / vc->time_base.den) : 0);
#endif
}
static inline int
lav_is_thumbnail_stream(AVStream *s, uint8_t **data, int *size)
{
#if LIBAVFORMAT_VERSION_INT >= ((54<<16)+(6<<8))
if (s->disposition & AV_DISPOSITION_ATTACHED_PIC &&
s->codec->codec_id == AV_CODEC_ID_MJPEG)
{
if (data)
*data = s->attached_pic.data;
if (size)
*size = s->attached_pic.size;
return 1;
}
#endif
return 0;
}

View File

@ -55,11 +55,6 @@
#define FLAG_MIME 0x00000100
#define FLAG_DURATION 0x00000200
#define FLAG_RESOLUTION 0x00000400
#define FLAG_BITRATE 0x00000800
#define FLAG_FREQUENCY 0x00001000
#define FLAG_BPS 0x00002000
#define FLAG_CHANNELS 0x00004000
#define FLAG_ROTATION 0x00008000
/* Audio profile flags */
enum audio_profiles {
@ -76,40 +71,6 @@ enum audio_profiles {
PROFILE_AUDIO_AMR
};
static inline int
lav_open(AVFormatContext **ctx, const char *filename)
{
int ret;
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
ret = avformat_open_input(ctx, filename, NULL, NULL);
if (ret == 0)
avformat_find_stream_info(*ctx, NULL);
#else
ret = av_open_input_file(ctx, filename, NULL, 0, NULL);
if (ret == 0)
av_find_stream_info(*ctx);
#endif
return ret;
}
static inline void
lav_close(AVFormatContext *ctx)
{
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
avformat_close_input(&ctx);
#else
av_close_input_file(ctx);
#endif
}
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
# if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2)
#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX
#define av_dict_get av_metadata_get
typedef AVMetadataTag AVDictionaryEntry;
# endif
#endif
/* This function shamelessly copied from libdlna */
#define MPEG_TS_SYNC_CODE 0x47
#define MPEG_TS_PACKET_LENGTH 188
@ -296,16 +257,6 @@ free_metadata(metadata_t *m, uint32_t flags)
free(m->duration);
if( flags & FLAG_RESOLUTION )
free(m->resolution);
if( flags & FLAG_BITRATE )
free(m->bitrate);
if( flags & FLAG_FREQUENCY )
free(m->frequency);
if( flags & FLAG_BPS )
free(m->bps);
if( flags & FLAG_CHANNELS )
free(m->channels);
if( flags & FLAG_ROTATION )
free(m->rotation);
}
int64_t
@ -502,10 +453,10 @@ GetAudioMetadata(const char *path, char *name)
" (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
" TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) "
"VALUES"
" (%Q, %lld, %ld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);",
path, (long long)file.st_size, file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date,
m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track,
m.dlna_pn, song.mime?song.mime:m.mime, album_art);
" (%Q, %lld, %lld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);",
path, (long long)file.st_size, (long long)file.st_mtime, m.duration, song.channels, song.bitrate,
song.samplerate, m.date, m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc,
song.track, m.dlna_pn, song.mime?song.mime:m.mime, album_art);
if( ret != SQLITE_OK )
{
DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path);
@ -605,24 +556,21 @@ GetImageMetadata(const char *path, char *name)
e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
if( e )
{
int rotate;
switch( exif_get_short(e->data, exif_data_get_byte_order(ed)) )
{
case 3:
rotate = 180;
m.rotation = 180;
break;
case 6:
rotate = 90;
m.rotation = 90;
break;
case 8:
rotate = 270;
m.rotation = 270;
break;
default:
rotate = 0;
m.rotation = 0;
break;
}
if( rotate )
xasprintf(&m.rotation, "%d", rotate);
}
if( ed->size )
@ -686,9 +634,9 @@ no_exifdata:
" (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION,"
" ROTATION, THUMBNAIL, CREATOR, DLNA_PN, MIME) "
"VALUES"
" (%Q, '%q', %lld, %ld, %Q, %Q, %Q, %d, %Q, %Q, %Q);",
path, name, (long long)file.st_size, file.st_mtime, m.date, m.resolution,
m.rotation, thumb, m.creator, m.dlna_pn, m.mime);
" (%Q, '%q', %lld, %lld, %Q, %Q, %u, %d, %Q, %Q, %Q);",
path, name, (long long)file.st_size, (long long)file.st_mtime, m.date,
m.resolution, m.rotation, thumb, m.creator, m.dlna_pn, m.mime);
if( ret != SQLITE_OK )
{
DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path);
@ -749,6 +697,7 @@ GetVideoMetadata(const char *path, char *name)
continue;
}
else if( video_stream == -1 &&
!lav_is_thumbnail_stream(ctx->streams[i], &m.thumb_data, &m.thumb_size) &&
ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
{
video_stream = i;
@ -830,11 +779,9 @@ GetVideoMetadata(const char *path, char *name)
else if ( ac->bit_rate <= 385000 )
audio_profile = PROFILE_AUDIO_WMA_FULL;
break;
#if LIBAVCODEC_VERSION_INT > ((51<<16)+(50<<8)+1)
case AV_CODEC_ID_WMAPRO:
audio_profile = PROFILE_AUDIO_WMA_PRO;
break;
#endif
case AV_CODEC_ID_MP2:
audio_profile = PROFILE_AUDIO_MP2;
break;
@ -849,13 +796,8 @@ GetVideoMetadata(const char *path, char *name)
DPRINTF(E_DEBUG, L_METADATA, "Unhandled audio codec [0x%X]\n", ac->codec_id);
break;
}
xasprintf(&m.frequency, "%u", ac->sample_rate);
#if LIBAVCODEC_VERSION_INT < (52<<16)
xasprintf(&m.bps, "%u", ac->bits_per_sample);
#else
xasprintf(&m.bps, "%u", ac->bits_per_coded_sample);
#endif
xasprintf(&m.channels, "%u", ac->channels);
m.frequency = ac->sample_rate;
m.channels = ac->channels;
}
if( vc )
{
@ -865,7 +807,7 @@ GetVideoMetadata(const char *path, char *name)
DPRINTF(E_DEBUG, L_METADATA, "Container: '%s' [%s]\n", ctx->iformat->name, basepath);
xasprintf(&m.resolution, "%dx%d", vc->width, vc->height);
if( ctx->bit_rate > 8 )
xasprintf(&m.bitrate, "%u", ctx->bit_rate / 8);
m.bitrate = ctx->bit_rate / 8;
if( ctx->duration > 0 ) {
duration = (int)(ctx->duration / AV_TIME_BASE);
hours = (int)(duration / 3600);
@ -1006,11 +948,8 @@ GetVideoMetadata(const char *path, char *name)
vc->height * vc->sample_aspect_ratio.den,
1024*1024);
}
if (ctx->streams[video_stream]->r_frame_rate.den)
fps = ctx->streams[video_stream]->r_frame_rate.num / ctx->streams[video_stream]->r_frame_rate.den;
else
fps = 0;
interlaced = vc->time_base.den ? (ctx->streams[video_stream]->r_frame_rate.num / vc->time_base.den) : 0;
fps = lav_get_fps(ctx->streams[video_stream]);
interlaced = lav_get_interlaced(vc, ctx->streams[video_stream]);
if( ((((vc->width == 1920 || vc->width == 1440) && vc->height == 1080) ||
(vc->width == 720 && vc->height == 480)) && fps == 59 && interlaced) ||
((vc->width == 1280 && vc->height == 720) && fps == 59 && !interlaced) )
@ -1504,6 +1443,11 @@ GetVideoMetadata(const char *path, char *name)
m.creator = m.artist;
free_flags &= ~FLAG_CREATOR;
}
if (!m.thumb_data)
{
m.thumb_data = video.image;
m.thumb_size = video.image_size;
}
}
}
#ifndef NETGEAR
@ -1577,7 +1521,6 @@ video_no_dlna:
else
DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name);
}
lav_close(ctx);
if( !m.date )
{
@ -1589,15 +1532,16 @@ video_no_dlna:
if( !m.title )
m.title = strdup(name);
album_art = find_album_art(path, video.image, video.image_size);
album_art = find_album_art(path, m.thumb_data, m.thumb_size);
freetags(&video);
lav_close(ctx);
ret = sql_exec(db, "INSERT into DETAILS"
" (PATH, SIZE, TIMESTAMP, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION,"
" TITLE, CREATOR, ARTIST, GENRE, COMMENT, DLNA_PN, MIME, ALBUM_ART) "
"VALUES"
" (%Q, %lld, %ld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);",
path, (long long)file.st_size, file.st_mtime, m.duration,
" (%Q, %lld, %lld, %Q, %Q, %u, %u, %u, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);",
path, (long long)file.st_size, (long long)file.st_mtime, m.duration,
m.date, m.channels, m.bitrate, m.frequency, m.resolution,
m.title, m.creator, m.artist, m.genre, m.comment, m.dlna_pn,
m.mime, album_art);

View File

@ -25,22 +25,25 @@
#define __METADATA_H__
typedef struct metadata_s {
char *title;
char *artist;
char *creator;
char *album;
char *genre;
char *comment;
char *channels;
char *bitrate;
char *frequency;
char *bps;
char *resolution;
char *rotation;
char *duration;
char *date;
char *mime;
char *dlna_pn;
char * title;
char * artist;
char * creator;
char * album;
char * genre;
char * comment;
unsigned int disc;
unsigned int track;
unsigned int channels;
unsigned int bitrate;
unsigned int frequency;
unsigned int rotation;
char * resolution;
char * duration;
char * date;
char * mime;
char * dlna_pn;
int thumb_size;
uint8_t * thumb_data;
} metadata_t;
typedef enum {

View File

@ -372,7 +372,7 @@ rescan:
#if USE_FORK
scanning = 1;
sqlite3_close(db);
*scanner_pid = process_fork();
*scanner_pid = fork();
open_db(&db);
if (*scanner_pid == 0) /* child (scanner) process */
{
@ -464,6 +464,16 @@ static int strtobool(const char *str)
(atoi(str) == 1));
}
static void init_nls(void)
{
#ifdef ENABLE_NLS
setlocale(LC_MESSAGES, "");
setlocale(LC_CTYPE, "en_US.utf8");
DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR")));
textdomain("minidlna");
#endif
}
/* init phase :
* 1) read configuration file
* 2) read command line arguments
@ -969,6 +979,13 @@ init(int argc, char **argv)
DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to uid '%d'. [%s] EXITING.\n",
uid, strerror(errno));
children = calloc(runtime_vars.max_connections, sizeof(struct child));
if (!children)
{
DPRINTF(E_ERROR, L_GENERAL, "Allocation failed\n");
return 1;
}
return 0;
}
@ -1000,12 +1017,7 @@ main(int argc, char **argv)
for (i = 0; i < L_MAX; i++)
log_level[i] = E_WARN;
#ifdef ENABLE_NLS
setlocale(LC_MESSAGES, "");
setlocale(LC_CTYPE, "en_US.utf8");
DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR")));
textdomain("minidlna");
#endif
init_nls();
ret = init(argc, argv);
if (ret != 0)
@ -1182,11 +1194,6 @@ main(int argc, char **argv)
i++;
}
}
#ifdef DEBUG
/* for debug */
if (i > 1)
DPRINTF(E_DEBUG, L_GENERAL, "%d active incoming HTTP connections\n", i);
#endif
FD_ZERO(&writeset);
upnpevents_selectfds(&readset, &writeset, &max_fd);
@ -1285,7 +1292,11 @@ main(int argc, char **argv)
shutdown:
/* kill the scanner */
if (scanning && scanner_pid)
kill(scanner_pid, 9);
kill(scanner_pid, SIGKILL);
/* kill other child processes */
process_reap_children();
free(children);
/* close out open sockets */
while (upnphttphead.lh_first != NULL)
@ -1298,10 +1309,10 @@ shutdown:
close(sssdp);
if (shttpl >= 0)
close(shttpl);
#ifdef TIVO_SUPPORT
#ifdef TIVO_SUPPORT
if (sbeacon >= 0)
close(sbeacon);
#endif
#endif
for (i = 0; i < n_lan_addr; i++)
{

View File

@ -49,8 +49,8 @@ struct runtime_vars_s {
int port; /* HTTP Port */
int notify_interval; /* seconds between SSDP announces */
int max_connections; /* max number of simultaneous conenctions */
char *root_container; /* root ObjectID (instead of "0") */
char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */
const char *root_container; /* root ObjectID (instead of "0") */
const char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */
};
struct string_s {

View File

@ -319,7 +319,7 @@ ParseUPnPClient(char *location)
char *off = NULL, *p;
int content_len = sizeof(buf);
struct NameValueParserData xml;
int client;
struct client_cache_s *client;
int type = 0;
char *model, *serial, *name;
@ -458,14 +458,14 @@ close:
return;
/* Add this client to the cache if it's not there already. */
client = SearchClientCache(dest.sin_addr, 1);
if (client < 0)
if (!client)
{
AddClientCache(dest.sin_addr, type);
}
else
{
clients[client].type = type;
clients[client].age = time(NULL);
client->type = &client_types[type];
client->age = time(NULL);
}
}
@ -546,13 +546,13 @@ ProcessSSDPRequest(int s, unsigned short port)
(strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */
{
/* Check if the client is already in cache */
i = SearchClientCache(sendername.sin_addr, 1);
if (i >= 0)
struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1);
if (client)
{
if (clients[i].type < EStandardDLNA150 &&
clients[i].type != ESamsungSeriesA)
if (client->type->type < EStandardDLNA150 &&
client->type->type != ESamsungSeriesA)
{
clients[i].age = time(NULL);
client->age = time(NULL);
return;
}
}

View File

@ -43,10 +43,47 @@
#include "config.h"
#include "log.h"
static int number_of_children = 0;
struct child *children = NULL;
int number_of_children = 0;
static void
add_process_info(pid_t pid, struct client_cache_s *client)
{
struct child *child;
int i;
for (i = 0; i < runtime_vars.max_connections; i++)
{
child = children+i;
if (child->pid)
continue;
child->pid = pid;
child->client = client;
child->age = time(NULL);
break;
}
}
static inline void
remove_process_info(pid_t pid)
{
struct child *child;
int i;
for (i = 0; i < runtime_vars.max_connections; i++)
{
child = children+i;
if (child->pid != pid)
continue;
child->pid = 0;
if (child->client)
child->client->connections--;
break;
}
}
pid_t
process_fork(void)
process_fork(struct client_cache_s *client)
{
if (number_of_children >= runtime_vars.max_connections)
{
@ -58,7 +95,13 @@ process_fork(void)
pid_t pid = fork();
if (pid > 0)
++number_of_children;
{
number_of_children++;
if (client)
client->connections++;
add_process_info(pid, client);
}
return pid;
}
@ -76,7 +119,8 @@ process_handle_child_termination(int signal)
else
break;
}
--number_of_children;
number_of_children--;
remove_process_info(pid);
}
}
@ -87,7 +131,7 @@ process_daemonize(void)
#ifndef USE_DAEMON
int i;
switch(process_fork())
switch(fork())
{
/* fork error */
case -1:
@ -157,3 +201,17 @@ process_check_if_running(const char *fname)
return 0;
}
void
process_reap_children(void)
{
struct child *child;
int i;
for (i = 0; i < runtime_vars.max_connections; i++)
{
child = children+i;
if (child->pid)
kill(child->pid, SIGKILL);
}
}

View File

@ -30,6 +30,16 @@
#define __PROCESS_H__
#include <unistd.h>
#include "clients.h"
struct child {
pid_t pid;
time_t age;
struct client_cache_s *client;
};
extern struct child *children;
extern int number_of_children;
/**
* Fork a new child (just like fork()) but keep track of how many childs are
@ -37,7 +47,7 @@
* @return -1 if it couldn't fork, 0 in the child process, the pid of the
* child process in the parent process.
*/
pid_t process_fork(void);
pid_t process_fork(struct client_cache_s *client);
/**
* Handler to be called upon receiving SIGCHLD. This signal is received by the
@ -63,4 +73,9 @@ int process_daemonize(void);
*/
int process_check_if_running(const char *fname);
/**
* Kill all child processes
*/
void process_reap_children(void);
#endif // __PROCESS_H__

View File

@ -44,6 +44,7 @@
#include "sql.h"
#include "scanner.h"
#include "albumart.h"
#include "containers.h"
#include "log.h"
#if SCANDIR_CONST
@ -580,6 +581,25 @@ CreateDatabase(void)
if( ret != SQLITE_OK )
goto sql_failed;
}
for( i=0; magic_containers[i].objectid_match; i++ )
{
struct magic_container_s *magic = &magic_containers[i];
if (!magic->name)
continue;
if( sql_get_int_field(db, "SELECT 1 from OBJECTS where OBJECT_ID = '%s'", magic->objectid_match) == 0 )
{
char *parent = strdup(magic->objectid_match);
if (strrchr(parent, '$'))
*strrchr(parent, '$') = '\0';
ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)"
" values "
"('%s', '%s', %lld, 'container.storageFolder', '%q')",
magic->objectid_match, parent, GetFolderMetadata(magic->name, NULL, NULL, NULL, 0), magic->name);
free(parent);
if( ret != SQLITE_OK )
goto sql_failed;
}
}
sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);");
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);");

View File

@ -107,7 +107,7 @@ _get_aactags(char *file, struct song_metadata *psong)
if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
break;
current_data[len-1] = '\0';
current_data[current_size - 8] = '\0';
if(!memcmp(current_atom, "\xA9" "nam", 4))
psong->title = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||

View File

@ -57,7 +57,7 @@
#include <sqlite3.h>
#define MINIDLNA_VERSION "1.1.2"
#define MINIDLNA_VERSION "1.1.3"
#ifdef NETGEAR
# define SERVER_NAME "ReadyDLNA"

View File

@ -83,8 +83,7 @@
#include "sendfile.h"
//#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much?
#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much?
#define MAX_BUFFER_SIZE 2147483647
#define MIN_BUFFER_SIZE 65536
#include "icons.c"
@ -137,6 +136,7 @@ Delete_upnphttp(struct upnphttp * h)
static void
ParseHttpHeaders(struct upnphttp * h)
{
int client = 0;
char * line;
char * colon;
char * p;
@ -274,7 +274,7 @@ ParseHttpHeaders(struct upnphttp * h)
{
int i;
/* Skip client detection if we already detected it. */
if( h->req_client )
if( client )
goto next_header;
p = colon + 1;
while(isspace(*p))
@ -285,7 +285,7 @@ ParseHttpHeaders(struct upnphttp * h)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
h->req_client = i;
client = i;
break;
}
}
@ -294,7 +294,7 @@ ParseHttpHeaders(struct upnphttp * h)
{
int i;
/* Skip client detection if we already detected it. */
if( h->req_client && client_types[h->req_client].type < EStandardDLNA150 )
if( client && client_types[client].type < EStandardDLNA150 )
goto next_header;
p = colon + 1;
while(isspace(*p))
@ -305,7 +305,7 @@ ParseHttpHeaders(struct upnphttp * h)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
h->req_client = i;
client = i;
break;
}
}
@ -386,7 +386,7 @@ ParseHttpHeaders(struct upnphttp * h)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
h->req_client = i;
client = i;
break;
}
}
@ -432,29 +432,24 @@ next_header:
/* If the client type wasn't found, search the cache.
* This is done because a lot of clients like to send a
* different User-Agent with different types of requests. */
n = SearchClientCache(h->clientaddr, 0);
h->req_client = SearchClientCache(h->clientaddr, 0);
/* Add this client to the cache if it's not there already. */
if( n < 0 )
if (!h->req_client)
{
AddClientCache(h->clientaddr, h->req_client);
h->req_client = AddClientCache(h->clientaddr, client);
}
else if (h->req_client)
else if (client)
{
enum client_types type = client_types[h->req_client].type;
enum client_types ctype = client_types[clients[n].type].type;
enum client_types type = client_types[client].type;
enum client_types ctype = h->req_client->type->type;
/* If we know the client and our new detection is generic, use our cached info */
/* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
if ((ctype && ctype < EStandardDLNA150 && type >= EStandardDLNA150) ||
(ctype == ESamsungSeriesB && type == ESamsungSeriesA))
{
h->req_client = clients[n].type;
return;
}
clients[n].type = h->req_client;
clients[n].age = time(NULL);
h->req_client->type = &client_types[client];
h->req_client->age = time(NULL);
}
else
h->req_client = clients[n].type;
}
/* very minimalistic 400 error message */
@ -619,17 +614,20 @@ SendResp_presentation(struct upnphttp * h)
strcatf(&str,
"<h3>Connected clients</h3>"
"<table border=1 cellpadding=10>"
"<tr><td>ID</td><td>Type</td><td>IP Address</td><td>HW Address</td></tr>");
"<tr><td>ID</td><td>Type</td><td>IP Address</td><td>HW Address</td><td>Connections</td></tr>");
for (i = 0; i < CLIENT_CACHE_SLOTS; i++)
{
if (!clients[i].addr.s_addr)
continue;
strcatf(&str, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%02X:%02X:%02X:%02X:%02X:%02X</td></tr>",
i, client_types[clients[i].type].name, inet_ntoa(clients[i].addr),
strcatf(&str, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%02X:%02X:%02X:%02X:%02X:%02X</td><td>%d</td></tr>",
i, clients[i].type->name, inet_ntoa(clients[i].addr),
clients[i].mac[0], clients[i].mac[1], clients[i].mac[2],
clients[i].mac[3], clients[i].mac[4], clients[i].mac[5]);
clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], clients[i].connections);
}
strcatf(&str, "</table></BODY></HTML>\r\n");
strcatf(&str, "</table>");
strcatf(&str, "<br>%d connection%s currently open<br>", number_of_children, (number_of_children == 1 ? "" : "s"));
strcatf(&str, "</BODY></HTML>\r\n");
BuildResp_upnphttp(h, str.data, str.off);
SendResp_upnphttp(h);
@ -907,7 +905,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
{
/* If it's a Xbox360, we might need a special friendly_name to be recognized */
if( client_types[h->req_client].type == EXbox )
if( h->req_client && h->req_client->type->type == EXbox )
{
char model_sav[2];
i = 0;
@ -923,7 +921,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
friendly_name[i] = '\0';
memcpy(modelnumber, model_sav, 2);
}
else if( client_types[h->req_client].flags & FLAG_SAMSUNG_DCM10 )
else if( h->req_client && h->req_client->type->flags & FLAG_SAMSUNG_DCM10 )
{
sendXMLdesc(h, genRootDescSamsung);
}
@ -1637,7 +1635,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
#if USE_FORK
pid_t newpid = 0;
newpid = process_fork();
newpid = process_fork(h->req_client);
if( newpid > 0 )
{
CloseSocket_upnphttp(h);
@ -1800,7 +1798,8 @@ SendResp_dlnafile(struct upnphttp *h, char *object)
int64_t id;
int sendfh;
uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
uint32_t cflags = client_types[h->req_client].flags;
uint32_t cflags = h->req_client ? h->req_client->type->flags : 0;
enum client_types ctype = h->req_client ? h->req_client->type->type : 0;
static struct { int64_t id;
enum client_types client;
char path[PATH_MAX];
@ -1828,7 +1827,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object)
return;
}
}
if( id != last_file.id || h->req_client != last_file.client )
if( id != last_file.id || ctype != last_file.client )
{
snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id);
ret = sql_get_table(db, buf, &result, &rows, NULL);
@ -1847,7 +1846,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object)
}
/* Cache the result */
last_file.id = id;
last_file.client = h->req_client;
last_file.client = ctype;
strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
if( result[4] )
{
@ -1860,12 +1859,11 @@ SendResp_dlnafile(struct upnphttp *h, char *object)
/* Samsung TV's such as the A750 can natively support many
Xvid/DivX AVI's however, the DLNA server needs the
mime type to say video/mpeg */
else if( h->req_client == ESamsungSeriesA &&
strcmp(last_file.mime+6, "x-msvideo") == 0 )
else if( ctype == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 )
strcpy(last_file.mime+6, "mpeg");
}
/* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
else if( h->req_client == ESonyBDP )
else if( ctype == ESonyBDP )
{
if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
strcmp(last_file.mime+6, "mpeg") == 0 )
@ -1879,7 +1877,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object)
sqlite3_free_table(result);
}
#if USE_FORK
newpid = process_fork();
newpid = process_fork(h->req_client);
if( newpid > 0 )
{
CloseSocket_upnphttp(h);

View File

@ -86,7 +86,7 @@ struct upnphttp {
int req_contentlen;
int req_contentoff; /* header length */
enum httpCommands req_command;
enum client_types req_client;
struct client_cache_s * req_client;
const char * req_soapAction;
int req_soapActionLen;
const char * req_Callback; /* For SUBSCRIBE */

View File

@ -65,12 +65,19 @@
#include "utils.h"
#include "upnphttp.h"
#include "upnpsoap.h"
#include "containers.h"
#include "upnpreplyparse.h"
#include "getifaddr.h"
#include "scanner.h"
#include "sql.h"
#include "log.h"
#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
# define __SORT_LIMIT if( totalMatches < 10000 )
#else
# define __SORT_LIMIT
#endif
static void
BuildSendAndCloseSoapResp(struct upnphttp * h,
const char * body, int bodylen)
@ -349,7 +356,7 @@ set_filter_flags(char *filter, struct upnphttp *h)
{
char *item, *saveptr = NULL;
uint32_t flags = 0;
int samsung = client_types[h->req_client].flags & FLAG_SAMSUNG;
int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG);
if( !filter || (strlen(filter) <= 1) ) {
/* Not the full 32 bits. Skip vendor-specific stuff by default. */
@ -681,11 +688,35 @@ add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
runtime_vars.port, detailID, ext);
}
#define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \
static int
get_child_count(const char *object, struct magic_container_s *magic)
{
int ret;
if (magic && magic->child_count)
ret = sql_get_int_field(db, "SELECT count(*) from %s", magic->child_count);
else if (magic && magic->objectid && *(magic->objectid))
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", *(magic->objectid));
else
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object);
return (ret > 0) ? ret : 0;
}
static int
object_exists(const char *object)
{
int ret;
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
strcmp(object, "*") == 0 ? "0" : object);
return (ret > 0);
}
#define COLUMNS "o.DETAIL_ID, o.CLASS," \
" d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
" d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
" d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS
#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS
static int
callback(void *args, int argc, char **argv, char **azColName)
@ -730,9 +761,6 @@ callback(void *args, int argc, char **argv, char **azColName)
}
passed_args->returned++;
if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 )
parent = "0";
if( strncmp(class, "item", 4) == 0 )
{
uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
@ -1034,13 +1062,10 @@ callback(void *args, int argc, char **argv, char **azColName)
{
ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
if( passed_args->filter & FILTER_SEARCHABLE ) {
ret = strcatf(str, "searchable=\"1\" ");
ret = strcatf(str, "searchable=\"%d\" ", check_magic_container(id, passed_args->flags) ? 0 : 1);
}
if( passed_args->filter & FILTER_CHILDCOUNT ) {
int children;
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
children = (ret > 0) ? ret : 0;
ret = strcatf(str, "childCount=\"%d\"", children);
ret = strcatf(str, "childCount=\"%d\"", get_child_count(id, check_magic_container(id, passed_args->flags)));
}
/* 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 && (passed_args->filter & FILTER_UPNP_SEARCHCLASS) ) {
@ -1103,13 +1128,19 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
"<Result>"
"&lt;DIDL-Lite"
CONTENT_DIRECTORY_SCHEMAS;
struct magic_container_s *magic;
char *zErrMsg = NULL;
char *sql, *ptr;
struct Response args;
struct string_s str;
int totalMatches;
int totalMatches = 0;
int ret;
char *ObjectID, *Filter, *BrowseFlag, *SortCriteria;
const char *ObjectID, *BrowseFlag;
char *Filter, *SortCriteria;
const char *objectid_sql = "o.OBJECT_ID";
const char *parentid_sql = "o.PARENT_ID";
const char *refid_sql = "o.REF_ID";
char where[256] = "";
char *orderBy = NULL;
struct NameValueParserData data;
int RequestedCount = 0;
@ -1166,24 +1197,9 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
args.returned = 0;
args.requested = RequestedCount;
args.client = client_types[h->req_client].type;
args.flags = client_types[h->req_client].flags;
args.client = h->req_client ? h->req_client->type->type : 0;
args.flags = h->req_client ? h->req_client->type->flags : 0;
args.str = &str;
if( args.flags & FLAG_MS_PFS )
{
if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) )
{
ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
" where OBJECT_ID in "
"('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')",
ObjectID, ObjectID, ObjectID);
if( ptr )
{
ObjectID = ptr;
args.flags |= FLAG_FREE_OBJECT_ID;
}
}
}
DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
" * ObjectID: %s\n"
" * Count: %d\n"
@ -1194,47 +1210,62 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
ObjectID, RequestedCount, StartingIndex,
BrowseFlag, Filter, SortCriteria);
if( strcmp(ObjectID, "0") == 0 )
{
args.flags |= FLAG_ROOT_CONTAINER;
if( runtime_vars.root_container )
{
if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) )
ObjectID = MUSIC_DIR_ID;
else
ObjectID = runtime_vars.root_container;
}
else
{
if( args.flags & FLAG_AUDIO_ONLY )
ObjectID = MUSIC_ID;
}
}
if( strcmp(BrowseFlag+6, "Metadata") == 0 )
{
const char *id = ObjectID;
args.requested = 1;
sql = sqlite3_mprintf("SELECT %s, " COLUMNS
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where OBJECT_ID = '%q';",
(args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID",
ObjectID);
magic = in_magic_container(ObjectID, args.flags, &id);
if (magic)
{
if (magic->objectid_sql && strcmp(id, ObjectID) != 0)
objectid_sql = magic->objectid_sql;
if (magic->parentid_sql && strcmp(id, ObjectID) != 0)
parentid_sql = magic->parentid_sql;
if (magic->refid_sql)
refid_sql = magic->refid_sql;
}
sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where OBJECT_ID = '%q';",
objectid_sql, parentid_sql, refid_sql, id);
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
totalMatches = args.returned;
}
else
{
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%q'", ObjectID);
totalMatches = (ret > 0) ? ret : 0;
magic = check_magic_container(ObjectID, args.flags);
if (magic)
{
if (magic->objectid && *(magic->objectid))
ObjectID = *(magic->objectid);
if (magic->objectid_sql)
objectid_sql = magic->objectid_sql;
if (magic->parentid_sql)
parentid_sql = magic->parentid_sql;
if (magic->refid_sql)
refid_sql = magic->refid_sql;
if (magic->where)
strncpyt(where, magic->where, sizeof(where));
if (magic->max_count > 0)
{
ret = get_child_count(ObjectID, magic);
totalMatches = ret > magic->max_count ? magic->max_count : ret;
if (RequestedCount > magic->max_count || RequestedCount < 0)
RequestedCount = magic->max_count;
}
}
if (!where[0])
sqlite3_snprintf(sizeof(where), where, "PARENT_ID = '%q'", ObjectID);
if (!totalMatches)
totalMatches = get_child_count(ObjectID, magic);
ret = 0;
if( SortCriteria )
{
#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
if( totalMatches < 10000 )
#endif
__SORT_LIMIT
orderBy = parse_sort_criteria(SortCriteria, &ret);
}
else
else if (!orderBy)
{
if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
{
@ -1245,9 +1276,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
}
else if( args.flags & FLAG_FORCE_SORT )
{
#ifdef __sparc__
if( totalMatches < 10000 )
#endif
__SORT_LIMIT
ret = xasprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
}
else
@ -1266,10 +1295,11 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
goto browse_error;
}
sql = sqlite3_mprintf( SELECT_COLUMNS
sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where PARENT_ID = '%q' %s limit %d, %d;",
ObjectID, THISORNUL(orderBy), StartingIndex, RequestedCount);
" where %s %s limit %d, %d;",
objectid_sql, parentid_sql, refid_sql,
where, THISORNUL(orderBy), StartingIndex, RequestedCount);
DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
}
@ -1284,8 +1314,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
/* Does the object even exist? */
if( !totalMatches )
{
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", ObjectID);
if( ret <= 0 )
if( !object_exists(ObjectID) )
{
SoapError(h, 701, "No such object error");
goto browse_error;
@ -1300,8 +1329,6 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
BuildSendAndCloseSoapResp(h, str.data, str.off);
browse_error:
ClearNameValueList(&data);
if( args.flags & FLAG_FREE_OBJECT_ID )
sqlite3_free(ObjectID);
free(orderBy);
free(str.data);
}
@ -1586,13 +1613,15 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
"<Result>"
"&lt;DIDL-Lite"
CONTENT_DIRECTORY_SCHEMAS;
struct magic_container_s *magic;
char *zErrMsg = NULL;
char *sql, *ptr;
struct Response args;
struct string_s str;
int totalMatches;
int ret;
char *ContainerID, *Filter, *SearchCriteria, *SortCriteria;
const char *ContainerID;
char *Filter, *SearchCriteria, *SortCriteria;
char *orderBy = NULL, *where = NULL, sep[] = "$*";
char groupBy[] = "group by DETAIL_ID";
struct NameValueParserData data;
@ -1638,24 +1667,9 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
args.returned = 0;
args.requested = RequestedCount;
args.client = client_types[h->req_client].type;
args.flags = client_types[h->req_client].flags;
args.client = h->req_client ? h->req_client->type->type : 0;
args.flags = h->req_client ? h->req_client->type->flags : 0;
args.str = &str;
if( args.flags & FLAG_MS_PFS )
{
if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) )
{
ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
" where OBJECT_ID in "
"('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')",
ContainerID, ContainerID, ContainerID);
if( ptr )
{
ContainerID = ptr;
args.flags |= FLAG_FREE_OBJECT_ID;
}
}
}
DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
" * ObjectID: %s\n"
" * Count: %d\n"
@ -1666,8 +1680,12 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
ContainerID, RequestedCount, StartingIndex,
SearchCriteria, Filter, SortCriteria);
magic = check_magic_container(ContainerID, args.flags);
if (magic && magic->objectid && *(magic->objectid))
ContainerID = *(magic->objectid);
if( strcmp(ContainerID, "0") == 0 )
ContainerID[0] = '*';
ContainerID = "*";
if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 ||
GETFLAG(DLNA_STRICT_MASK) )
@ -1692,19 +1710,15 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
/* Does the object even exist? */
if( !totalMatches )
{
ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
!strcmp(ContainerID, "*")?"0":ContainerID);
if( ret <= 0 )
if( !object_exists(ContainerID) )
{
SoapError(h, 710, "No such container");
goto search_error;
}
}
#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
ret = 0;
if( totalMatches < 10000 )
#endif
orderBy = parse_sort_criteria(SortCriteria, &ret);
__SORT_LIMIT
orderBy = parse_sort_criteria(SortCriteria, &ret);
/* If it's a DLNA client, return an error for bad sort criteria */
if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) )
{
@ -1740,8 +1754,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
BuildSendAndCloseSoapResp(h, str.data, str.off);
search_error:
ClearNameValueList(&data);
if( args.flags & FLAG_FREE_OBJECT_ID )
sqlite3_free(ContainerID);
free(orderBy);
free(where);
free(str.data);