Merge branch 'master' into ffmpeg
This commit is contained in:
commit
24ec6d24fb
@ -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
18
NEWS
@ -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.
|
||||
|
32
clients.c
32
clients.c
@ -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;
|
||||
}
|
||||
|
||||
|
12
clients.h
12
clients.h
@ -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
170
containers.c
Normal 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
36
containers.h
Normal 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
77
libav.h
@ -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;
|
||||
}
|
||||
|
108
metadata.c
108
metadata.c
@ -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);
|
||||
|
35
metadata.h
35
metadata.h
@ -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 {
|
||||
|
41
minidlna.c
41
minidlna.c
@ -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++)
|
||||
{
|
||||
|
@ -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 {
|
||||
|
18
minissdp.c
18
minissdp.c
@ -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;
|
||||
}
|
||||
}
|
||||
|
68
process.c
68
process.c
@ -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);
|
||||
}
|
||||
}
|
||||
|
17
process.h
17
process.h
@ -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__
|
||||
|
20
scanner.c
20
scanner.c
@ -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);");
|
||||
|
@ -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*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
|
||||
|
@ -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"
|
||||
|
68
upnphttp.c
68
upnphttp.c
@ -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);
|
||||
|
@ -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 */
|
||||
|
204
upnpsoap.c
204
upnpsoap.c
@ -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, "<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>"
|
||||
"<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>"
|
||||
"<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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user