diff --git a/Makefile.am b/Makefile.am index 9b55a8a..9273aa5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/NEWS b/NEWS index 565321d..72a3093 100644 --- a/NEWS +++ b/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. diff --git a/clients.c b/clients.c index 78d6453..8d82ba2 100644 --- a/clients.c +++ b/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; } diff --git a/clients.h b/clients.h index a6966f4..d30f4ed 100644 --- a/clients.h +++ b/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 diff --git a/containers.c b/containers.c new file mode 100644 index 0000000..8e95ee3 --- /dev/null +++ b/containers.c @@ -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 . + */ +#include +#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; +} diff --git a/containers.h b/containers.h new file mode 100644 index 0000000..aef77c5 --- /dev/null +++ b/containers.h @@ -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 . + */ + +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); diff --git a/libav.h b/libav.h index 4ef7f18..01da752 100644 --- a/libav.h +++ b/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; +} diff --git a/metadata.c b/metadata.c index 2ed36f7..f3987c6 100644 --- a/metadata.c +++ b/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); diff --git a/metadata.h b/metadata.h index 96364dc..a7ceee6 100644 --- a/metadata.h +++ b/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 { diff --git a/minidlna.c b/minidlna.c index c8eed5b..be34fe3 100644 --- a/minidlna.c +++ b/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++) { diff --git a/minidlnatypes.h b/minidlnatypes.h index bc4e195..6879b70 100644 --- a/minidlnatypes.h +++ b/minidlnatypes.h @@ -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 { diff --git a/minissdp.c b/minissdp.c index 0ab7ab5..ba9a861 100644 --- a/minissdp.c +++ b/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; } } diff --git a/process.c b/process.c index 00b802b..96ec1c3 100644 --- a/process.c +++ b/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); + } +} diff --git a/process.h b/process.h index 7fa7682..713c92c 100644 --- a/process.h +++ b/process.h @@ -30,6 +30,16 @@ #define __PROCESS_H__ #include +#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__ diff --git a/scanner.c b/scanner.c index 5ae5612..ee3af48 100644 --- a/scanner.c +++ b/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);"); diff --git a/tagutils/tagutils-aac.c b/tagutils/tagutils-aac.c index 16b1301..ed6fb55 100644 --- a/tagutils/tagutils-aac.c +++ b/tagutils/tagutils-aac.c @@ -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) || diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 0ff9f2c..75d55e2 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -57,7 +57,7 @@ #include -#define MINIDLNA_VERSION "1.1.2" +#define MINIDLNA_VERSION "1.1.3" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA" diff --git a/upnphttp.c b/upnphttp.c index 5834b03..b6dd887 100644 --- a/upnphttp.c +++ b/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, "Connected clients" "" - "IDTypeIP AddressHW Address"); + "IDTypeIP AddressHW AddressConnections"); for (i = 0; i < CLIENT_CACHE_SLOTS; i++) { if (!clients[i].addr.s_addr) continue; - strcatf(&str, "%d%s%s%02X:%02X:%02X:%02X:%02X:%02X", - i, client_types[clients[i].type].name, inet_ntoa(clients[i].addr), + strcatf(&str, "%d%s%s%02X:%02X:%02X:%02X:%02X:%02X%d", + 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, "