From 6163126d81baaac024bc55ddfc34941bc6d2778d Mon Sep 17 00:00:00 2001 From: Claudio Ciccani Date: Sat, 10 Nov 2007 17:40:55 +0100 Subject: Added new playlist demuxer, supporting M3U, RAM, PLS, QTL, ASX, SMIL, XSPF, and RSS playlists. --- src/demuxers/Makefile.am | 6 +- src/demuxers/demux_playlist.c | 746 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 751 insertions(+), 1 deletion(-) create mode 100644 src/demuxers/demux_playlist.c diff --git a/src/demuxers/Makefile.am b/src/demuxers/Makefile.am index ac132463b..ba3041d5d 100644 --- a/src/demuxers/Makefile.am +++ b/src/demuxers/Makefile.am @@ -52,7 +52,8 @@ xineplug_LTLIBRARIES = \ xineplug_dmx_nsv.la \ xineplug_dmx_matroska.la \ xineplug_dmx_iff.la \ - xineplug_dmx_flv.la + xineplug_dmx_flv.la \ + xineplug_dmx_playlist.la xineplug_dmx_avi_la_SOURCES = demux_avi.c xineplug_dmx_avi_la_LIBADD = $(XINE_LIB) $(LTLIBINTL) @@ -141,3 +142,6 @@ xineplug_dmx_iff_la_LIBADD = $(XINE_LIB) $(LTLIBINTL) xineplug_dmx_flv_la_SOURCES = demux_flv.c xineplug_dmx_flv_la_LIBADD = $(XINE_LIB) $(LTLIBINTL) + +xineplug_dmx_playlist_la_SOURCES = demux_playlist.c +xineplug_dmx_playlist_la_LIBADD = $(XINE_LIB) $(LTLIBINTL) diff --git a/src/demuxers/demux_playlist.c b/src/demuxers/demux_playlist.c new file mode 100644 index 000000000..bbe6998e6 --- /dev/null +++ b/src/demuxers/demux_playlist.c @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2007 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * + * Playlist parser/demuxer by + * Claudio Ciccani (klan@users.sourceforge.net) + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "demux_playlist" +#define LOG_VERBOSE +/* +#define LOG +*/ + +#include "xine_internal.h" +#include "xineutils.h" +#include "bswap.h" +#include "demux.h" + +typedef enum { + XINE_PLT_NONE = 0, + XINE_PLT_REF = ME_FOURCC('R','E','F',0), + XINE_PLT_M3U = ME_FOURCC('M','3','U',0), + XINE_PLT_RAM = ME_FOURCC('R','A','M',0), + XINE_PLT_PLS = ME_FOURCC('P','L','S',0), + XINE_PLT_ASX = ME_FOURCC('A','S','X',0), + XINE_PLT_SMI = ME_FOURCC('S','M','I',0), + XINE_PLT_QTL = ME_FOURCC('Q','T','L',0), + XINE_PLT_XSPF = ME_FOURCC('X','S','P',0), + XINE_PLT_RSS = ME_FOURCC('R','S','S',0) +} playlist_t; + +typedef struct { + demux_plugin_t demux_plugin; + + xine_t *xine; + xine_stream_t *stream; + input_plugin_t *input; + + playlist_t playlist; + + int status; +} demux_playlist_t; + +typedef struct { + demux_class_t demux_class; +} demux_playlist_class_t; + + +static playlist_t detect_by_extension (input_plugin_t *input) { + char *ext; + + ext = strrchr (input->get_mrl (input), '.'); + if (!ext) + return XINE_PLT_NONE; + + if (!strcasecmp (ext, ".m3u")) + return XINE_PLT_M3U; + if (!strcasecmp (ext, ".ram")) + return XINE_PLT_RAM; + if (!strcasecmp (ext, ".pls")) + return XINE_PLT_PLS; + if (!strcasecmp (ext, ".wax") || + !strcasecmp (ext, ".wvx") || + !strcasecmp (ext, ".asx")) + return XINE_PLT_ASX; + if (!strcasecmp (ext, ".smi") || + !strcasecmp (ext, ".smil")) + return XINE_PLT_SMI; + if (!strcasecmp (ext, ".qtl")) + return XINE_PLT_QTL; + if (!strcasecmp (ext, ".xspf")) + return XINE_PLT_XSPF; + if (!strcasecmp (ext, ".rss")) + return XINE_PLT_RSS; + + return XINE_PLT_NONE; +} + +static playlist_t detect_by_content (input_plugin_t *input) { + char buf[256], *tmp; + int len; + + len = _x_demux_read_header (input, buf, sizeof(buf)-1); + if (len <= 0) + return XINE_PLT_NONE; + buf[len] = '\0'; + + tmp = buf; + while (*tmp && isspace(*tmp)) + tmp++; + + if (!strncmp (tmp, "[Reference]", 11) || + !strncmp (tmp, "Ref1=", 5)) + return XINE_PLT_REF; + if (!strncmp (tmp, "#EXTM3U", 7)) + return XINE_PLT_M3U; + if (!strncmp (tmp, "file://", 7) || + !strncmp (tmp, "http://", 7) || + !strncmp (tmp, "rtsp://", 7) || + !strncmp (tmp, "pnm://", 6)) + return XINE_PLT_RAM; + if (!strncmp (tmp, "[Playlist]", 10 )) + return XINE_PLT_PLS; + if (!strncasecmp (tmp, " s && isspace(*e)) + *e-- = '\0'; + + return s; +} + +static int parse_time (const char *s) { + int t = 0; + int i; + + if (!s) + return 0; + + if (!strncmp (s, "npt=", 4)) + s += 4; + else if (!strncmp (s, "smpte=", 6)) + s += 6; + + for (i = 0; i < 3; i++) { + t *= 60; + t += atoi(s); + s = strchr (s, ':'); + if (!s) + break; + s++; + } + + return t*1000; +} + +static void parse_ref (demux_playlist_t *this, char *data, int length) { + char *src = data; + char *end; + int alt = 0; + + while (src && *src) { + end = strchr (src, '\n'); + if (end) + *end = '\0'; + + src = trim (src); + if (!strncmp (src, "Ref", 3)) { + src = strchr (src, '='); + if (src && *(src+1)) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, alt++, src+1, NULL, 0, 0); + } + } + + src = end; + if (src) + src++; + } +} + +static void parse_m3u (demux_playlist_t *this, char *data, int length) { + char *src = data; + char *end; + char *title = NULL; + + while (src && *src) { + end = strchr (src, '\n'); + if (end) + *end = '\0'; + + src = trim (src); + if (*src == '#') { + if (!strncmp (src+1, "EXTINF:", 7)) { + title = strchr (src+8, ','); + if (title) + title++; + } + } + else if (*src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, 0, 0); + } + + src = end; + if (src) + src++; + } +} + +static void parse_ram (demux_playlist_t *this, char *data, int length) { + char *src = data; + char *end; + + while (src && *src) { + end = strchr (src, '\n'); + if (end) + *end = '\0'; + + src = trim (src); + if (!strcmp (src, "--stop--")) + break; + + if (*src && *src != '#') { + char *title = NULL; + + if (!strncmp (src, "rtsp://", 7) || !strncmp (src, "pnm://", 7)) { + char *tmp = strrchr (src, '?'); + if (tmp) { + *tmp = '\0'; + title = strstr (tmp+1, "title="); + if (title) { + title += 6; + tmp = strchr (title, '&'); + if (tmp) + *tmp = '\0'; + } + } + } + + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, 0, 0); + } + + src = end; + if (src) + src++; + } +} + +static void parse_pls (demux_playlist_t *this, char *data, int length) { + char *src = data; + char *end; + + while (src && *src) { + end = strchr (src, '\n'); + if (end) + *end = '\0'; + + src = trim (src); + if (!strncmp (src, "File", 4)) { + src = strchr (src+4, '='); + if (src && *(src+1)) { + lprintf ("mrl:'%s'\n", src+1); + _x_demux_send_mrl_reference (this->stream, 0, src+1, NULL, 0, 0); + } + } + + src = end; + if (src) + src++; + } +} + +static void parse_asx (demux_playlist_t *this, char *data, int length) { + xml_node_t *root, *node, *tmp; + int is_asx = 0; + + xml_parser_init (data, length, XML_PARSER_CASE_INSENSITIVE); + + if (xml_parser_build_tree (&root) >= 0) { + if (!strcasecmp (root->name, "asx")) { + is_asx = 1; + + for (node = root->child; node; node = node->next) { + if (!strcasecmp (node->name, "entry")) { + const char *title = NULL; + const char *src = NULL; + const char *start = NULL; + const char *duration = NULL; + + for (tmp = node->child; tmp; tmp = tmp->next) { + if (!strcasecmp (tmp->name, "title")) { + title = tmp->data; + } + else if (!strcasecmp (tmp->name, "ref")) { + src = xml_parser_get_property (tmp, "href"); + } + else if (!strcasecmp (tmp->name, "starttime")) { + start = xml_parser_get_property (tmp, "value"); + } + else if (!strcasecmp (tmp->name, "duration")) { + duration = xml_parser_get_property (tmp, "value"); + } + } + + if (src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, + parse_time(start), parse_time(duration)); + } + } + } + } + + xml_parser_free_tree (root); + } + + if (!is_asx) { + /* No tags found? Might be a references list. */ + parse_ref (this, data, length); + } +} + +static void parse_smi (demux_playlist_t *this, char *data, int length) { + xml_node_t *root, *node, *tmp; + int is_smi = 0; + + xml_parser_init (data, length, XML_PARSER_CASE_SENSITIVE); + + if (xml_parser_build_tree (&root) >= 0) { + for (node = root; node; node = node->next) { + if (!strcmp (node->name, "smil")) + break; + } + + if (node) { + is_smi = 1; + + for (node = node->child; node; node = node->next) { + if (!strcmp (node->name, "body")) { + for (tmp = node->child; tmp; tmp = tmp->next) { + if (!strcmp (tmp->name, "audio") || !strcmp (tmp->name, "video")) { + const char *src, *title; + int start, end; + + src = xml_parser_get_property (tmp, "src"); + title = xml_parser_get_property (tmp, "title"); + start = parse_time (xml_parser_get_property (tmp, "clipBegin") ? : + xml_parser_get_property (tmp, "clip-begin")); + end = parse_time (xml_parser_get_property (tmp, "clipEnd") ? : + xml_parser_get_property (tmp, "clip-end")); + + if (src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, + start, end ? (end-start) : 0); + } + } + } + } + } + } + + xml_parser_free_tree (root); + } + + if (!is_smi) { + /* No tags found? Might be a RAM playlist. */ + parse_ram (this, data, length); + } +} + +static void parse_qtl (demux_playlist_t *this, char *data, int length) { + xml_node_t *root, *node; + + xml_parser_init (data, length, XML_PARSER_CASE_SENSITIVE); + + if (xml_parser_build_tree (&root) >= 0) { + for (node = root; node; node = node->next) { + if (!strcmp (node->name, "embed")) { + const char *src; + + src = xml_parser_get_property (node, "src"); + if (src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, NULL, 0, 0); + } + } + } + + xml_parser_free_tree (root); + } +} + +static void parse_xspf (demux_playlist_t *this, char *data, int length) { + xml_node_t *root, *node, *tmp; + + xml_parser_init (data, length, XML_PARSER_CASE_SENSITIVE); + + if (xml_parser_build_tree (&root) >= 0) { + for (node = root; node; node = node->next) { + if (!strcmp (node->name, "playlist")) + break; + } + if (node) { + for (node = node->child; node; node = node->next) { + if (!strcmp (node->name, "trackList")) + break; + } + } + if (node) { + for (node = node->child; node; node = node->next) { + if (!strcmp (node->name, "track")) { + char *src = NULL; + char *title = NULL; + + for (tmp = node->child; tmp; tmp = tmp->next) { + if (!strcmp (tmp->name, "location")) { + src = trim((char*)tmp->data); + } + else if (!strcmp (tmp->name, "title")) { + title = trim((char*)tmp->data); + } + } + + if (src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, 0, 0); + } + } + } + } + + xml_parser_free_tree (root); + } +} + +static void parse_rss (demux_playlist_t *this, char *data, int length) { + xml_node_t *root, *node, *item, *tmp; + + xml_parser_init (data, length, XML_PARSER_CASE_SENSITIVE); + + if (xml_parser_build_tree (&root) >= 0) { + for (node = root; node; node = node->next) { + if (!strcmp (node->name, "rss")) + break; + } + + if (node) { + for (node = node->child; node; node = node->next) { + if (strcmp (node->name, "channel")) + continue; + + for (item = node->child; item; item = item->next) { + if (!strcmp (item->name, "item")) { + const char *title = NULL; + const char *src = NULL; + + for (tmp = item->child; tmp; tmp = tmp->next) { + if (!strcmp (tmp->name, "title")) { + title = tmp->data; + } + else if (!strcmp (tmp->name, "enclosure")) { + src = xml_parser_get_property (tmp, "url"); + } + } + + if (src) { + lprintf ("mrl:'%s'\n", src); + _x_demux_send_mrl_reference (this->stream, 0, src, title, 0, 0); + } + } + } + } + } + + xml_parser_free_tree (root); + } +} + + +static void demux_playlist_send_headers (demux_plugin_t *this_gen) { + demux_playlist_t *this = (demux_playlist_t *) this_gen; + + this->status = DEMUX_OK; + + _x_demux_control_start (this->stream); + + this->input->seek (this->input, 0, SEEK_SET); +} + + +static int demux_playlist_send_chunk (demux_plugin_t *this_gen) { + demux_playlist_t *this = (demux_playlist_t *) this_gen; + char *data = NULL; + int length; + + length = this->input->get_length (this->input); + if (length > 0) { + data = xine_xmalloc (length+1); + if (data) + this->input->read (this->input, data, length); + } + else { + char buf[1024]; + int len; + + length = 0; + while ((len = this->input->read (this->input, buf, sizeof(buf))) > 0) { + data = realloc (data, length+len+1); + if (!data) + break; + + memcpy (data+length, buf, len); + length += len; + data[length] = '\0'; + } + } + + lprintf ("data:%p length:%d\n", data, length); + + if (data) { + switch (this->playlist) { + case XINE_PLT_REF: + parse_ref (this, data, length); + break; + case XINE_PLT_M3U: + parse_m3u (this, data, length); + break; + case XINE_PLT_RAM: + parse_ram (this, data, length); + break; + case XINE_PLT_PLS: + parse_pls (this, data, length); + break; + case XINE_PLT_ASX: + parse_asx (this, data, length); + break; + case XINE_PLT_SMI: + parse_smi (this, data, length); + break; + case XINE_PLT_QTL: + parse_qtl (this, data, length); + break; + case XINE_PLT_XSPF: + parse_xspf (this, data, length); + break; + case XINE_PLT_RSS: + parse_rss (this, data, length); + break; + default: + lprintf ("unexpected playlist type 0x%08x\n", this->playlist); + break; + } + + free (data); + } + + this->status = DEMUX_FINISHED; + + return DEMUX_FINISHED; +} + +static int demux_playlist_seek (demux_plugin_t *this_gen, + off_t start_pos, int start_time, int playing) { + return DEMUX_OK; +} + +static void demux_playlist_dispose (demux_plugin_t *this_gen) { + demux_playlist_t *this = (demux_playlist_t *) this_gen; + + free (this); +} + +static int demux_playlist_get_status (demux_plugin_t *this_gen) { + demux_playlist_t *this = (demux_playlist_t *) this_gen; + + return this->status; +} + +static int demux_playlist_get_stream_length (demux_plugin_t *this_gen) { + return 0; +} + +static uint32_t demux_playlist_get_capabilities (demux_plugin_t *this_gen) { + return DEMUX_CAP_NOCAP; +} + +static int demux_playlist_get_optional_data (demux_plugin_t *this_gen, + void *data, int data_type) { + return DEMUX_OPTIONAL_UNSUPPORTED; +} + + +static demux_plugin_t *open_plugin (demux_class_t *class_gen, + xine_stream_t *stream, input_plugin_t *input) { + demux_playlist_t *this; + + this = xine_xmalloc (sizeof (demux_playlist_t)); + this->xine = stream->xine; + this->stream = stream; + this->input = input; + + this->demux_plugin.send_headers = demux_playlist_send_headers; + this->demux_plugin.send_chunk = demux_playlist_send_chunk; + this->demux_plugin.seek = demux_playlist_seek; + this->demux_plugin.dispose = demux_playlist_dispose; + this->demux_plugin.get_status = demux_playlist_get_status; + this->demux_plugin.get_stream_length = demux_playlist_get_stream_length; + this->demux_plugin.get_capabilities = demux_playlist_get_capabilities; + this->demux_plugin.get_optional_data = demux_playlist_get_optional_data; + this->demux_plugin.demux_class = class_gen; + + switch (stream->content_detection_method) { + case METHOD_BY_EXTENSION: + lprintf ("detect by extension\n"); + this->playlist = detect_by_extension (input); + if (!this->playlist) { + free (this); + return NULL; + } + break; + + case METHOD_BY_CONTENT: + case METHOD_EXPLICIT: + lprintf ("detect by content\n"); + this->playlist = detect_by_content (input); + if (!this->playlist) { + free (this); + return NULL; + } + break; + + default: + free (this); + return NULL; + } + + lprintf ("playlist:0x%08x (%s)\n", this->playlist, (char*)&this->playlist); + + return &this->demux_plugin; +} + +static const char *get_description (demux_class_t *this_gen) { + return "Playlist demux plugin"; +} + +static const char *get_identifier (demux_class_t *this_gen) { + return "playlist"; +} + +static const char *get_extensions (demux_class_t *this_gen) { + return "m3u ram pls asx wax wvx smi smil qtl xspf rss"; +} + +static const char *get_mimetypes (demux_class_t *this_gen) { + return "audio/mpegurl: m3u: M3U playlist;" + "audio/x-mpegurl: m3u: M3U playlist;" + //"audio/x-pn-realaudio: ram: RAM playlist;" + //"audio/vnd.rn-realaudio: ram: RAM playlist;" + "audio/x-scpls: pls: Winamp playlist;" + "audio/x-ms-wax: wax, asx: WAX playlist;" + "audio/x-ms-wvx: wvx, asx: WVX playlist;" + "application/smil: smi, smil: SMIL playlist;" + "application/x-quicktimeplayer: qtl: Quicktime playlist;" + "application/xspf+xml: xspf: XSPF playlist;"; +} + +static void class_dispose (demux_class_t *this_gen) { + demux_playlist_class_t *this = (demux_playlist_class_t *) this_gen; + + free (this); +} + +static void *init_plugin (xine_t *xine, void *data) { + demux_playlist_class_t *this; + + this = xine_xmalloc (sizeof(demux_playlist_class_t)); + + this->demux_class.open_plugin = open_plugin; + this->demux_class.get_description = get_description; + this->demux_class.get_identifier = get_identifier; + this->demux_class.get_mimetypes = get_mimetypes; + this->demux_class.get_extensions = get_extensions; + this->demux_class.dispose = class_dispose; + + return this; +} + +/* + * exported plugin catalog entry + */ +static const demuxer_info_t demux_info_flv = { + 10 /* priority */ +}; + +const plugin_info_t xine_plugin_info[] EXPORTED = { + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_DEMUX, 26, "playlist", XINE_VERSION_CODE, &demux_info_flv, init_plugin }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +}; -- cgit v1.2.3