* @brief
*
* asf/wma file parser.
+ *
+ * Reference:
+ * http://www.microsoft.com/en-us/download/details.aspx?id=14995
*/
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#define _XOPEN_SOURCE 600
-#define _BSD_SOURCE
#include <lightmediascanner_plugin.h>
#include <lightmediascanner_db.h>
+#include <shared/util.h>
+
#include <endian.h>
+#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
-#define NSEC100_PER_SEC 10000000ULL
-#define MSEC_PER_SEC 1000ULL
+#define DECL_STR(cname, str) \
+ static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
-enum StreamTypes {
- STREAM_TYPE_UNKNOWN = 0,
- STREAM_TYPE_AUDIO,
- STREAM_TYPE_VIDEO
-};
+DECL_STR(_codec_audio_wmav1, "wmav1");
+DECL_STR(_codec_audio_wmav2, "wmav2");
+DECL_STR(_codec_audio_wmavpro, "wmavpro");
+DECL_STR(_codec_audio_wmavlossless, "wmavlossless");
+DECL_STR(_codec_audio_aac, "aac");
+DECL_STR(_codec_audio_flac, "flac");
+DECL_STR(_codec_audio_mp3, "mp3");
+
+DECL_STR(_codec_video_wmv1, "wmv1");
+DECL_STR(_codec_video_wmv2, "wmv2");
+DECL_STR(_codec_video_wmv3, "wmv3");
+
+DECL_STR(_dlna_wma_base, "WMABASE");
+DECL_STR(_dlna_wma_full, "WMAFULL");
+DECL_STR(_dlna_wma_pro, "WMAPRO");
+DECL_STR(_dlna_wma_mime, "audio/x-ms-wma");
+
+DECL_STR(_str_unknown, "<UNKNOWN>");
+#undef DECL_STR
+
+static void
+_fill_audio_dlna_profile(struct lms_audio_info *info)
+{
+ if ((info->codec.str == _codec_audio_wmav1.str ||
+ info->codec.str == _codec_audio_wmav2.str) &&
+ (info->sampling_rate <= 48000)) {
+ info->dlna_mime = _dlna_wma_mime;
+ if (info->bitrate <= 192999)
+ info->dlna_profile = _dlna_wma_base;
+ else
+ info->dlna_profile = _dlna_wma_full;
+ } else if (
+ info->codec.str == _codec_audio_wmavpro.str &&
+ info->sampling_rate <= 96000 &&
+ info->channels <= 8 &&
+ info->bitrate <= 1500000) {
+ info->dlna_mime = _dlna_wma_mime;
+ info->dlna_profile = _dlna_wma_pro;
+ }
+}
enum AttributeTypes {
ATTR_TYPE_UNICODE = 0,
ATTR_TYPE_GUID
};
+struct stream {
+ struct lms_stream base;
+ struct {
+ unsigned int sampling_rate;
+ unsigned int bitrate;
+ double framerate;
+ } priv;
+};
+
struct asf_info {
struct lms_string_size title;
struct lms_string_size artist;
struct lms_string_size album;
struct lms_string_size genre;
+ enum lms_stream_type type;
+ unsigned int length;
unsigned char trackno;
+
+ struct stream *streams;
};
struct plugin {
};
static const char _name[] = "asf";
+static const struct lms_string_size _container = LMS_STATIC_STRING_SIZE("asf");
static const struct lms_string_size _exts[] = {
LMS_STATIC_STRING_SIZE(".wma"),
LMS_STATIC_STRING_SIZE(".wmv"),
NULL
};
+/* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
+static const struct {
+ uint16_t id;
+ const struct lms_string_size *name;
+} _audio_codecs[] = {
+ /* id == 0 is special, check callers if it's needed */
+ { 0x0160, &_codec_audio_wmav1 },
+ { 0x0161, &_codec_audio_wmav2 },
+ { 0x0162, &_codec_audio_wmavpro },
+ { 0x0163, &_codec_audio_wmavlossless },
+ { 0x1600, &_codec_audio_aac },
+ { 0x706d, &_codec_audio_aac },
+ { 0x4143, &_codec_audio_aac },
+ { 0xA106, &_codec_audio_aac },
+ { 0xF1AC, &_codec_audio_flac },
+ { 0x0055, &_codec_audio_mp3 },
+ { 0x0, &_str_unknown }
+};
+
+/* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
+static const struct {
+ uint8_t id[4];
+ const struct lms_string_size *name;
+} _video_codecs[] = {
+ /* id == 0 is special, check callers if it's needed */
+ { "WMV1", &_codec_video_wmv1 },
+ { "WMV2", &_codec_video_wmv2 },
+ { "WMV3", &_codec_video_wmv3 },
+ { "XXXX", &_str_unknown }
+};
+
+
/* ASF GUIDs
*
* Microsoft defines these 16-byte (128-bit) GUIDs as:
* See http://www.microsoft.com/windows/windowsmedia/forpros/format/asfspec.aspx
*/
static const char header_guid[16] = "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
+static const char header_extension_guid[16] = "\xB5\x03\xBF\x5F\x2E\xA9\xCF\x11\x8E\xE3\x00\xC0\x0C\x20\x53\x65";
+static const char extended_stream_properties_guid[16] = "\xCB\xA5\xE6\x14\x72\xC6\x32\x43\x83\x99\xA9\x69\x52\x06\x5B\x5A";
+static const char language_list_guid[16] = "\xA9\x46\x43\x7C\xE0\xEF\xFC\x4B\xB2\x29\x39\x3E\xDE\x41\x5C\x85";
static const char file_properties_guid[16] = "\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65";
static const char stream_properties_guid[16] = "\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65";
static const char stream_type_audio_guid[16] = "\x40\x9E\x69\xF8\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
static const char stream_type_video_guid[16] = "\xC0\xEF\x19\xBC\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
static const char content_description_guid[16] = "\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
static const char extended_content_description_guid[16] = "\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50";
-static const char header_extension_guid[16] = "\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se";
static const char metadata_guid[16] = "\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA";
static const char metadata_library_guid[16] = "\224\034#D\230\224\321I\241A\x1d\x13NEpT";
static const char content_encryption_object_guid[16] = "\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E";
char *data;
ssize_t data_size, size;
- if (!str) {
- lseek(fd, count, SEEK_CUR);
- return 0;
- }
-
data = malloc(sizeof(char) * count);
data_size = read(fd, data, count);
if (data_size == -1) {
}
static int
-_parse_file_properties(lms_charset_conv_t *cs_conv, int fd,
- struct lms_audio_info *info)
+_parse_file_properties(int fd, struct asf_info *info)
{
struct {
char fileid[16];
/* ASF spec 01.20.06 sec. 3.2: we need to subtract the preroll value from
* the duration in order to obtain the real duration */
- info->length = (unsigned int)((le64toh(props.play_duration) / NSEC100_PER_SEC)
- - le64toh(props.preroll) / MSEC_PER_SEC);
+ info->length = (unsigned int)(
+ (le64toh(props.play_duration) / NSEC100_PER_SEC) -
+ le64toh(props.preroll) / MSEC_PER_SEC);
return r;
}
-static void
-_parse_content_description(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
+static const struct lms_string_size *
+_audio_codec_id_to_str(uint16_t id)
+{
+ unsigned int i;
+
+ for (i = 0; _audio_codecs[i].name != &_str_unknown; i++)
+ if (_audio_codecs[i].id == id)
+ return _audio_codecs[i].name;
+
+ return _audio_codecs[i].name;
+}
+
+static const struct lms_string_size *
+_video_codec_id_to_str(uint8_t id[4])
+{
+ unsigned int i;
+
+ for (i = 0; _video_codecs[i].name != &_str_unknown; i++)
+ if (memcmp(id, _video_codecs[i].id, 4) == 0)
+ return _video_codecs[i].name;
+
+ return _video_codecs[i].name;
+}
+
+static struct stream * _stream_get_or_create(struct asf_info *info,
+ unsigned int stream_id)
+{
+ struct stream *s;
+
+ for (s = info->streams; s; s = (struct stream *) s->base.next) {
+ if (s->base.stream_id == stream_id)
+ return s;
+ }
+
+ s = calloc(1, sizeof(*s));
+ if (!s)
+ return NULL;
+
+ /* The Stream Properties Object can be anywhere inside the Header Object:
+ * before the Header Extension Object, after it or embedded into the
+ * Extended Stream Properties, inside the Header Extension Object.
+ *
+ * When parsing we either create a new stream and prepend it to the list or
+ * we return the one already created by a previous object (see the loop
+ * above).
+ *
+ * Note that the stream type is only available in the Stream Properties
+ * Object. A file with an Extended Stream Properties Object referring to a
+ * stream that doesn't have a corresponding Stream Properties is invalid. We
+ * let it into the list, but it won't have the stream_type set. In this case
+ * LMS will end up ignoring the stream when we try to add the file in the
+ * database -- this is why we set type to -1 here */
+ s->base.stream_id = stream_id;
+ s->base.type = -1;
+ s->base.next = (struct lms_stream *) info->streams;
+ info->streams = s;
+
+ return s;
+}
+
+static void _stream_copy_extension_properties(struct stream *s)
+{
+ switch (s->base.type) {
+ case LMS_STREAM_TYPE_AUDIO:
+ s->base.audio.bitrate = s->priv.bitrate;
+ break;
+ case LMS_STREAM_TYPE_VIDEO:
+ s->base.video.bitrate = s->priv.bitrate;
+ s->base.video.framerate = s->priv.framerate;
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+_parse_stream_properties(int fd, struct asf_info *info)
+{
+ struct {
+ char stream_type[16];
+ char error_correction_type[16];
+ uint64_t time_offset;
+ uint32_t type_specific_len;
+ uint32_t error_correction_data_len;
+ uint16_t flags;
+ uint32_t reserved; /* don't use, unaligned */
+ } __attribute__((packed)) props;
+ unsigned int stream_id;
+ struct stream *s;
+ int r, type;
+
+ r = read(fd, &props, sizeof(props));
+ if (r != sizeof(props))
+ return r;
+
+ stream_id = le16toh(props.flags) & 0x7F;
+
+ /* Not a valid stream */
+ if (!stream_id)
+ return r;
+
+ if (memcmp(props.stream_type, stream_type_audio_guid, 16) == 0)
+ type = LMS_STREAM_TYPE_AUDIO;
+ else if (memcmp(props.stream_type, stream_type_video_guid, 16) == 0)
+ type = LMS_STREAM_TYPE_VIDEO;
+ else
+ /* ignore stream */
+ return r;
+
+ s = _stream_get_or_create(info, stream_id);
+ if (!s)
+ return -ENOMEM;
+
+ s->base.type = type;
+
+ if (s->base.type == LMS_STREAM_TYPE_AUDIO) {
+ if (le32toh(props.type_specific_len) < 18)
+ goto done;
+
+ s->base.codec = *_audio_codec_id_to_str(_read_word(fd));
+ s->base.audio.channels = _read_word(fd);
+ s->priv.sampling_rate = _read_dword(fd);
+ s->base.audio.sampling_rate = s->priv.sampling_rate;
+ s->base.audio.bitrate = _read_dword(fd) * 8;
+ } else {
+ struct {
+ uint32_t width_unused;
+ uint32_t height_unused;
+ uint8_t reserved;
+ uint16_t data_size_unused;
+ /* data */
+ uint32_t size;
+ uint32_t width;
+ uint32_t height;
+ uint16_t reseved2;
+ uint16_t bits_per_pixel;
+ uint8_t compression_id[4];
+ uint32_t image_size;
+
+ /* other fields are ignored */
+ } __attribute__((packed)) video;
+ unsigned int num, den;
+
+ r = read(fd, &video, sizeof(video));
+ if (r != sizeof(video))
+ goto done;
+
+ if ((unsigned int) r < get_le32(&video.size) -
+ (sizeof(video) - offsetof(typeof(video), width)))
+ goto done;
+
+ s->base.codec = *_video_codec_id_to_str(video.compression_id);
+ s->base.video.width = get_le32(&video.width);
+ s->base.video.height = get_le32(&video.height);
+
+ reduce_gcd(s->base.video.width, s->base.video.height, &num, &den);
+ asprintf(&s->base.video.aspect_ratio.str, "%u:%u", num, den);
+ s->base.video.aspect_ratio.len = s->base.video.aspect_ratio.str ?
+ strlen(s->base.video.aspect_ratio.str) : 0;
+ }
+
+ _stream_copy_extension_properties(s);
+
+done:
+ /* If there's any video stream, consider the file as video */
+ if (info->type != LMS_STREAM_TYPE_VIDEO)
+ info->type = s->base.type;
+
+ return r;
+}
+
+static int _parse_extended_stream_properties(lms_charset_conv_t *cs_conv,
+ int fd, struct asf_info *info)
+{
+ struct {
+ uint64_t start_time;
+ uint64_t end_time;
+ uint32_t data_bitrate;
+ uint32_t buffer_size;
+ uint32_t init_buffer_fullness;
+ uint32_t alt_data_bitrate;
+ uint32_t alt_buffer_size;
+ uint32_t alt_init_buffer_fullness;
+ uint32_t max_obj_size;
+ uint32_t flags;
+ uint16_t stream_id;
+ uint16_t lang_id;
+ uint64_t avg_time_per_frame;
+ uint16_t stream_name_count;
+ uint16_t payload_extension_system_count;
+ } __attribute__((packed)) props;
+ struct stream *s;
+ unsigned int stream_id;
+ uint32_t bitrate;
+ uint16_t n;
+ int r;
+
+ r = read(fd, &props, sizeof(props));
+ if (r != sizeof(props))
+ return r;
+
+ stream_id = get_le16(&props.stream_id);
+ s = _stream_get_or_create(info, stream_id);
+
+ bitrate = get_le32(&props.alt_data_bitrate); /* for vbr */
+ if (!bitrate)
+ bitrate = get_le32(&props.data_bitrate);
+ s->priv.bitrate = bitrate;
+ s->priv.framerate = (NSEC100_PER_SEC /
+ (double) get_le64(&props.avg_time_per_frame));
+ for (n = get_le16(&props.stream_name_count); n; n--) {
+ uint16_t j;
+ lseek(fd, 2, SEEK_CUR);
+ j = _read_word(fd);
+ lseek(fd, j, SEEK_CUR);
+ }
+ for (n = get_le16(&props.payload_extension_system_count); n; n--) {
+ uint32_t j;
+ lseek(fd, 18, SEEK_CUR);
+ j = _read_dword(fd);
+ lseek(fd, j, SEEK_CUR);
+ }
+
+ return 0;
+}
+
+/* Lazy implementation, let the parsing of subframes to the caller. Techically
+ * this is wrong, since it might parse objects in the extension header that
+ * should be in the header object, however this should parse ok all good files
+ * and eventually the bad ones. */
+static int _parse_header_extension(lms_charset_conv_t *cs_conv, int fd,
+ struct asf_info *info)
+{
+ lseek(fd, 22, SEEK_CUR);
+ return 0;
+}
+
+static int
+_parse_content_description(lms_charset_conv_t *cs_conv, int fd,
+ struct asf_info *info)
{
int title_length = _read_word(fd);
int artist_length = _read_word(fd);
- int copyright_length = _read_word(fd);
- int comment_length = _read_word(fd);
- int rating_length = _read_word(fd);
+
+ lseek(fd, 6, SEEK_CUR);
_read_string(fd, title_length, &info->title.str, &info->title.len);
lms_charset_conv_force(cs_conv, &info->title.str, &info->title.len);
_read_string(fd, artist_length, &info->artist.str, &info->artist.len);
lms_charset_conv_force(cs_conv, &info->artist.str, &info->artist.len);
+
/* ignore copyright, comment and rating */
- lseek(fd, copyright_length + comment_length + rating_length, SEEK_CUR);
+ return 1;
}
static void
_parse_attribute_name(int fd,
- int kind,
char **attr_name,
unsigned int *attr_name_len,
int *attr_type,
{
int attr_name_length;
- /* extended content descriptor */
- if (kind == 0) {
- attr_name_length = _read_word(fd);
- _read_string(fd, attr_name_length, attr_name, attr_name_len);
- *attr_type = _read_word(fd);
- *attr_size = _read_word(fd);
- }
- /* metadata & metadata library */
- else {
- lseek(fd, 2 + 2, SEEK_CUR); /* language and stream */
- attr_name_length = _read_word(fd);
- *attr_type = _read_word(fd);
- *attr_size = _read_dword(fd);
- _read_string(fd, attr_name_length, attr_name, attr_name_len);
- }
+ attr_name_length = _read_word(fd);
+ _read_string(fd, attr_name_length, attr_name, attr_name_len);
+ *attr_type = _read_word(fd);
+ *attr_size = _read_word(fd);
}
static void
}
}
-static void
-_skip_attribute(int fd, int kind)
-{
- int attr_type, attr_size;
- _parse_attribute_name(fd, kind, NULL, NULL, &attr_type, &attr_size);
- _skip_attribute_data(fd, kind, attr_type, attr_size);
-}
-
-static void
-_parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
+static int
+_parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd,
+ struct asf_info *info)
{
int count = _read_word(fd);
char *attr_name;
unsigned int attr_name_len;
int attr_type, attr_size;
+
while (count--) {
attr_name = NULL;
- _parse_attribute_name(fd, 0,
+ _parse_attribute_name(fd,
&attr_name, &attr_name_len,
&attr_type, &attr_size);
if (attr_type == ATTR_TYPE_UNICODE) {
}
else
_skip_attribute_data(fd, 0, attr_type, attr_size);
- if (attr_name)
- free(attr_name);
+ free(attr_name);
}
-}
-static void
-_skip_metadata(int fd)
-{
- int count = _read_word(fd);
- while (count--)
- _skip_attribute(fd, 1);
-}
-
-static void
-_skip_metadata_library(int fd)
-{
- int count = _read_word(fd);
- while (count--)
- _skip_attribute(fd, 2);
-}
-
-static void
-_skip_header_extension(int fd)
-{
- char guid[16];
- long long size, data_size, data_pos;
-
- lseek(fd, 18, SEEK_CUR);
- data_size = _read_dword(fd);
- data_pos = 0;
- while (data_pos < data_size) {
- read(fd, &guid, 16);
- size = _read_qword(fd);
- if (size == 0)
- break;
- if (memcmp(guid, metadata_guid, 16) == 0)
- _skip_metadata(fd);
- else if (memcmp(guid, metadata_library_guid, 16) == 0)
- _skip_metadata_library(fd);
- else
- lseek(fd, size - 24, SEEK_CUR);
- data_pos += size;
- }
+ return 1;
}
static void *
return (void*)(i + 1);
}
+static void streams_free(struct stream *streams)
+{
+ while (streams) {
+ struct stream *s = streams;
+ streams = (struct stream *) s->base.next;
+
+ switch (s->base.type) {
+ case LMS_STREAM_TYPE_VIDEO:
+ free(s->base.video.aspect_ratio.str);
+ break;
+ default:
+ break;
+ }
+
+ free(s);
+ }
+}
+
+/* TODO: Parse "Language List Object" (sec 4.6) which contains an array with all
+ * the languages used (they are in UTF-16, so they need to be properly
+ * converted). */
static int
_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
{
- struct asf_info info = { };
- struct lms_audio_info audio_info = { };
- struct lms_video_info video_info = { };
- int r, fd, num_objects, i;
+ struct asf_info info = { .type = LMS_STREAM_TYPE_UNKNOWN };
+ int r, fd;
char guid[16];
unsigned int size;
- int stream_type = STREAM_TYPE_UNKNOWN;
+ unsigned long long hdrsize;
+ off_t pos_end, pos = 0;
fd = open(finfo->path, O_RDONLY);
if (fd < 0) {
r = -2;
goto done;
}
+
if (memcmp(guid, header_guid, 16) != 0) {
fprintf(stderr, "ERROR: invalid header (%s).\n", finfo->path);
r = -3;
goto done;
}
- size = _read_qword(fd);
- num_objects = _read_dword(fd);
+ hdrsize = _read_qword(fd);
+ pos_end = lseek(fd, 6, SEEK_CUR) - 24 + hdrsize;
- lseek(fd, 2, SEEK_CUR);
+ while (1) {
+ if (!pos)
+ pos = lseek(fd, 0, SEEK_CUR);
+ if (pos > pos_end - 24)
+ break;
- for (i = 0; i < num_objects; ++i) {
read(fd, &guid, 16);
size = _read_qword(fd);
- if (memcmp(guid, file_properties_guid, 16) == 0) {
- r = _parse_file_properties(plugin->cs_conv, fd, &audio_info);
- if (r < 0)
- goto done;
- lseek(fd, size - (24 + r), SEEK_CUR);
- } else if (memcmp(guid, stream_properties_guid, 16) == 0) {
- read(fd, &guid, 16);
- if (memcmp(guid, stream_type_audio_guid, 16) == 0)
- stream_type = STREAM_TYPE_AUDIO;
- else if (memcmp(guid, stream_type_video_guid, 16) == 0)
- stream_type = STREAM_TYPE_VIDEO;
- lseek(fd, size - 40, SEEK_CUR);
- }
+ if (memcmp(guid, header_extension_guid, 16) == 0)
+ r = _parse_header_extension(plugin->cs_conv, fd, &info);
+ else if (memcmp(guid, extended_stream_properties_guid, 16) == 0)
+ r = _parse_extended_stream_properties(plugin->cs_conv, fd, &info);
+ else if (memcmp(guid, file_properties_guid, 16) == 0)
+ r = _parse_file_properties(fd, &info);
+ else if (memcmp(guid, stream_properties_guid, 16) == 0)
+ r = _parse_stream_properties(fd, &info);
+ else if (memcmp(guid, language_list_guid, 16) == 0)
+ r = 1;
else if (memcmp(guid, content_description_guid, 16) == 0)
- _parse_content_description(plugin->cs_conv, fd, &info);
+ r = _parse_content_description(plugin->cs_conv, fd, &info);
else if (memcmp(guid, extended_content_description_guid, 16) == 0)
- _parse_extended_content_description_object(plugin->cs_conv, fd, &info);
- else if (memcmp(guid, header_extension_guid, 16) == 0)
- _skip_header_extension(fd);
+ r = _parse_extended_content_description_object(plugin->cs_conv, fd,
+ &info);
else if (memcmp(guid, content_encryption_object_guid, 16) == 0 ||
- memcmp(guid, extended_content_encryption_object_guid, 16) == 0) {
+ memcmp(guid, extended_content_encryption_object_guid, 16) == 0)
/* ignore DRM'd files */
- fprintf(stderr, "ERROR: ignoring DRM'd file %s\n", finfo->path);
r = -4;
+ else
+ r = 1;
+
+ if (r < 0)
goto done;
- }
+
+ if (r > 0)
+ pos = lseek(fd, pos + size, SEEK_SET);
else
- lseek(fd, size - 24, SEEK_CUR);
+ pos = 0;
}
/* try to define stream type by extension */
- if (stream_type == STREAM_TYPE_UNKNOWN) {
+ if (info.type == LMS_STREAM_TYPE_UNKNOWN) {
long ext_idx = ((long)match) - 1;
if (strcmp(_exts[ext_idx].str, ".wma") == 0)
- stream_type = STREAM_TYPE_AUDIO;
+ info.type = LMS_STREAM_TYPE_AUDIO;
/* consider wmv and asf as video */
else
- stream_type = STREAM_TYPE_VIDEO;
+ info.type = LMS_STREAM_TYPE_VIDEO;
}
lms_string_size_strip_and_free(&info.title);
lms_string_size_strip_and_free(&info.album);
lms_string_size_strip_and_free(&info.genre);
- if (!info.title.str) {
- long ext_idx;
- ext_idx = ((long)match) - 1;
- info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
- info.title.str = malloc((info.title.len + 1) * sizeof(char));
- memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
- info.title.str[info.title.len] = '\0';
- lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
- }
+ if (!info.title.str)
+ info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
+ finfo->base,
+ &_exts[((long) match) - 1],
+ ctxt->cs_conv);
-#if 0
- fprintf(stderr, "file %s info\n", finfo->path);
- fprintf(stderr, "\ttitle='%s'\n", info.title.str);
- fprintf(stderr, "\tartist='%s'\n", info.artist.str);
- fprintf(stderr, "\talbum='%s'\n", info.album.str);
- fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
- fprintf(stderr, "\ttrackno=%d\n", info.trackno);
-#endif
+ if (info.type == LMS_STREAM_TYPE_AUDIO) {
+ struct lms_audio_info audio_info = { };
- if (stream_type == STREAM_TYPE_AUDIO) {
audio_info.id = finfo->id;
audio_info.title = info.title;
audio_info.artist = info.artist;
audio_info.album = info.album;
audio_info.genre = info.genre;
audio_info.trackno = info.trackno;
+ audio_info.length = info.length;
+ audio_info.container = _container;
+
+ /* ignore additional streams, use only the first one */
+ if (info.streams) {
+ struct stream *s = info.streams;
+ audio_info.channels = s->base.audio.channels;
+ audio_info.bitrate = s->base.audio.bitrate;
+ audio_info.sampling_rate = s->priv.sampling_rate;
+ audio_info.codec = s->base.codec;
+ }
+
+ _fill_audio_dlna_profile(&audio_info);
+
r = lms_db_audio_add(plugin->audio_db, &audio_info);
- }
- else {
+ } else {
+ struct lms_video_info video_info = { };
+
video_info.id = finfo->id;
video_info.title = info.title;
video_info.artist = info.artist;
+ video_info.length = info.length;
+ video_info.container = _container;
+ video_info.streams = (struct lms_stream *) info.streams;
r = lms_db_video_add(plugin->video_db, &video_info);
}
- done:
- if (info.title.str)
- free(info.title.str);
- if (info.artist.str)
- free(info.artist.str);
- if (info.album.str)
- free(info.album.str);
- if (info.genre.str)
- free(info.genre.str);
+done:
+ streams_free(info.streams);
+
+ free(info.title.str);
+ free(info.artist.str);
+ free(info.album.str);
+ free(info.genre.str);
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
close(fd);