[vlc-commits] [Git][videolan/vlc][master] 7 commits: playlist: m3u: export channel as publisher
Hugo Beauzée-Luyssen (@chouquette)
gitlab at videolan.org
Wed Nov 24 21:01:40 UTC 2021
Hugo Beauzée-Luyssen pushed to branch master at VideoLAN / VLC
Commits:
d195dc61 by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: export channel as publisher
- - - - -
2e9148ce by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: handle IPTV EXTGRP tag
- - - - -
85d2158a by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: fix parsing IPTV syntax
- - - - -
77ab598b by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: fix bogus token matching
- - - - -
da6cf697 by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: parse EXTINF time as non integer
- - - - -
058936a9 by Francois Cartegnie at 2021-11-24T20:32:00+00:00
playlist: m3u: update current node item name
- - - - -
16d6b231 by Francois Cartegnie at 2021-11-24T20:32:00+00:00
tests: playlist: add m3u
- - - - -
3 changed files:
- modules/demux/playlist/m3u.c
- test/Makefile.am
- + test/modules/demux/playlist/m3u.c
Changes:
=====================================
modules/demux/playlist/m3u.c
=====================================
@@ -241,7 +241,7 @@ static int CreateEntry( input_item_node_t *p_node, const struct entry_meta_s *me
if( meta->psz_tvgid )
input_item_AddInfo( p_input, "XMLTV", "tvg-id", "%s", meta->psz_tvgid );
if( meta->psz_grouptitle )
- input_item_SetAlbum( p_input, meta->psz_grouptitle );
+ input_item_SetPublisher( p_input, meta->psz_grouptitle );
input_item_node_AppendItem( p_node, p_input );
input_item_Release( p_input );
@@ -252,6 +252,7 @@ static int CreateEntry( input_item_node_t *p_node, const struct entry_meta_s *me
static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
{
char *psz_line;
+ char *psz_group = NULL; /* group is toggling tag */
struct entry_meta_s meta;
entry_meta_Init( &meta );
char * (*pf_dup) (const char *) = p_demux->p_sys;
@@ -283,6 +284,15 @@ static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
meta.i_duration = INPUT_DURATION_INDEFINITE;
parseEXTINF( psz_parse, pf_dup, &meta );
}
+ else if( !strncasecmp( psz_parse, "EXTGRP:", sizeof("EXTGRP:") -1 ) )
+ {
+ psz_parse += sizeof("EXTGRP:") - 1;
+ if( *psz_parse )
+ {
+ free( psz_group );
+ psz_group = pf_dup( psz_parse );
+ }
+ }
else if( !strncasecmp( psz_parse, "EXTVLCOPT:",
sizeof("EXTVLCOPT:") -1 ) )
{
@@ -310,7 +320,7 @@ static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
sizeof( "PLAYLIST:" ) - 1 ) )
{
psz_parse += sizeof( "PLAYLIST:" ) - 1;
- input_item_SetTitle( p_demux->p_input_item, psz_parse );
+ input_item_SetTitle( p_subitems->p_item, psz_parse );
}
}
else if( !strncasecmp( psz_parse, "RTSPtext", sizeof("RTSPtext") -1 ) )
@@ -323,6 +333,8 @@ static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
if( !meta.psz_name && psz_parse )
/* Use filename as name for relative entries */
meta.psz_name = strdup( psz_parse );
+ if( psz_group && !meta.psz_grouptitle )
+ meta.psz_grouptitle = strdup( psz_group );
meta.psz_mrl = ProcessMRL( psz_parse, p_demux->psz_url );
free( psz_parse );
@@ -343,6 +355,7 @@ static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
/* Cleanup state */
entry_meta_Clean( &meta );
entry_meta_Init( &meta );
+ free( psz_group );
}
}
return VLC_SUCCESS; /* Needed for correct operation of go back */
@@ -399,47 +412,50 @@ static void parseEXTINFIptvDiots( char *psz_string,
char *(*pf_dup)(const char *),
struct entry_meta_s *meta )
{
- char **ppsz_meta = NULL;
- if( strncmp( psz_string, "tvg-", 4 ) &&
- strncmp( psz_string, "group-", 6 ) )
- return;
- char *psz_sep = strchr( psz_string + 4, '=' );
- if( unlikely(!psz_sep) )
+ char *psz = strchr( psz_string, '=' );
+ if( unlikely(!psz) )
return;
- size_t i_keylen = psz_sep - psz_string;
-
- if( !strncmp( psz_string + 4, "logo", i_keylen - 4 ) )
- ppsz_meta = &meta->psz_album_art;
- else if( !strncmp( psz_string + 4, "name", i_keylen - 4 ) )
- ppsz_meta = &meta->psz_name;
- else if( !strncmp( psz_string + 4, "language", i_keylen - 4 ) )
- ppsz_meta = &meta->psz_language;
- else if( !strncmp( psz_string + 4, "id", i_keylen - 4 ) )
- ppsz_meta = &meta->psz_tvgid;
- else if( !strncmp( psz_string + 6, "title", i_keylen - 4 ) )
+
+ char **ppsz_meta = NULL;
+ *psz = 0;
+ if( !strncasecmp( psz_string, "tvg-", 4 ) )
+ {
+ if( !strcasecmp( psz_string + 4, "logo" ) )
+ ppsz_meta = &meta->psz_album_art;
+ else if( !strcasecmp( psz_string + 4, "name" ) )
+ ppsz_meta = &meta->psz_name;
+ else if( !strcasecmp( psz_string + 4, "language" ) )
+ ppsz_meta = &meta->psz_language;
+ else if( !strcasecmp( psz_string + 4, "id" ) )
+ ppsz_meta = &meta->psz_tvgid;
+ }
+ else if( !strcasecmp( psz_string, "group-title" ) )
+ {
ppsz_meta = &meta->psz_grouptitle;
+ }
+ *psz = '=';
if( !ppsz_meta || *ppsz_meta /* no overwrite */ )
return;
- char *psz_value = psz_sep + 1;
- size_t i_valuelen = strlen( psz_value );
+ size_t i_valuelen = strlen( ++psz );
if( unlikely(i_valuelen == 0) )
return;
- bool b_escaped = (*psz_value == '"');
+ bool b_escaped = (*psz == '"');
if( i_valuelen > 2 && b_escaped )
{
- psz_value[ i_valuelen - 1 ] = 0;
- *ppsz_meta = pf_dup( psz_value + 1 );
+ psz[ i_valuelen - 1 ] = 0;
+ *ppsz_meta = pf_dup( psz + 1 );
}
else
- *ppsz_meta = pf_dup( psz_value );
+ *ppsz_meta = pf_dup( psz );
}
static void parseEXTINFIptvDiotsInDuration( char *psz_string,
char *(*pf_dup)(const char *),
- struct entry_meta_s *meta )
+ struct entry_meta_s *meta,
+ char **ppsz_end )
{
for( ;; )
{
@@ -456,6 +472,18 @@ static void parseEXTINFIptvDiotsInDuration( char *psz_string,
{
switch( *psz_string )
{
+ case ',': /* Last comma for title */
+ if(!b_escaped)
+ {
+ if(b_value)
+ {
+ *psz_string = '\0';
+ parseEXTINFIptvDiots( psz_start, pf_dup, meta );
+ }
+ *ppsz_end = psz_string + 1;
+ return;
+ }
+ break;
case '"':
if(!b_escaped && b_value)
return;
@@ -502,24 +530,41 @@ static void parseEXTINF( char *psz_string,
while( psz_string < end && ( *psz_string == '\t' || *psz_string == ' ' ) )
psz_string++;
- /* duration: read to next comma */
- char *psz_comma = strchr( psz_string, ',' );
- if( psz_comma )
- {
- *psz_comma = '\0'; /* Split strings */
- if( ++psz_comma < end )
- parseEXTINFTitle( psz_comma, pf_dup, meta );
- }
-
/* Parse duration */
char *psz_end = NULL;
- long i_parsed_duration = strtol( psz_string, &psz_end, 10 );
+ float i_parsed_duration = us_strtof( psz_string, &psz_end );
if( i_parsed_duration > 0 )
- meta->i_duration = vlc_tick_from_sec( i_parsed_duration );
+ meta->i_duration = vlc_tick_from_sec( (double)i_parsed_duration );
+
+ /* skip to first unmatched */
+ if( psz_end )
+ psz_string = psz_end;
+
+ /* skip whitespaces */
+ while( psz_string < end && ( *psz_string == '\t' || *psz_string == ' ' ) )
+ psz_string++;
+
+ if( psz_string == end )
+ return;
- if( psz_end && psz_end != psz_string && ( *psz_end == '\t' || *psz_end == ' ' ) )
+ /* EXTINF:1,title*/
+ /* EXTINF: -123.12 ,title*/
+ if( *psz_string == ',' )
{
- parseEXTINFIptvDiotsInDuration( psz_end, pf_dup, meta );
+ if( ++psz_string < end )
+ parseEXTINFTitle( psz_string, pf_dup, meta );
+ }
+ /* EXTINF: -1 tvg-foo="val" tvg-foo2="val",title */
+ /* EXTINF: -1 tvg-foo="val,val2" ,title */
+ else if( *psz_string >= 'A' && *psz_string <= 'z' )
+ {
+ psz_end = NULL;
+ parseEXTINFIptvDiotsInDuration( psz_string, pf_dup, meta, &psz_end );
+ if( psz_end && *psz_end ) /* returns past last comma */
+ {
+ free(meta->psz_name);
+ meta->psz_name = pf_dup( psz_end );
+ }
}
}
=====================================
test/Makefile.am
=====================================
@@ -41,6 +41,7 @@ check_PROGRAMS = \
test_modules_keystore \
test_modules_demux_timestamps_filter \
test_modules_demux_ts_pes \
+ test_modules_playlist_m3u \
$(NULL)
if ENABLE_SOUT
@@ -151,6 +152,8 @@ test_modules_demux_ts_pes_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_modules_demux_ts_pes_SOURCES = modules/demux/ts_pes.c \
../modules/demux/mpeg/ts_pes.c \
../modules/demux/mpeg/ts_pes.h
+test_modules_playlist_m3u_SOURCES = modules/demux/playlist/m3u.c
+test_modules_playlist_m3u_LDADD = $(LIBVLCCORE) $(LIBVLC)
checkall:
=====================================
test/modules/demux/playlist/m3u.c
=====================================
@@ -0,0 +1,223 @@
+/*****************************************************************************
+ * m3u.c: M3U unit testing
+ *****************************************************************************
+ * Copyright (C) 2021 VideoLabs, VideoLAN and VLC Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc/vlc.h>
+#include "../../../../lib/libvlc_internal.h"
+#include "../../../libvlc/test.h"
+
+#include <vlc_common.h>
+#include <vlc_modules.h>
+#include <vlc_demux.h>
+#include <vlc_meta.h>
+#include <vlc_input_item.h>
+
+#define BAILOUT(run) { fprintf(stderr, "failed %s line %d\n", run, __LINE__); \
+ return 1; }
+#define EXPECT(foo) if(!(foo)) BAILOUT(run)
+#define NOPFIL(n) "bar"#n
+#define NOPURI(n) INPUT_ITEM_URI_NOP "/" NOPFIL(n)
+
+static int runtest(const char *run,
+ libvlc_instance_t *vlc,
+ const char *data, size_t datasz,
+ int(*checkfunc)(const char *, const input_item_node_t *))
+{
+ stream_t *s = vlc_stream_MemoryNew(vlc->p_libvlc_int, (uint8_t *)data, datasz, true);
+ if(!s)
+ BAILOUT(run);
+
+ demux_t *pl = demux_New(VLC_OBJECT(vlc->p_libvlc_int), "m3u", INPUT_ITEM_URI_NOP, s, NULL);
+ if(!pl || !pl->pf_readdir)
+ {
+ vlc_stream_Delete(s);
+ BAILOUT(run);
+ }
+
+ int ret = 0;
+ input_item_t *p_item = input_item_New(NULL, NULL);
+ if(p_item)
+ {
+ input_item_node_t *p_node = input_item_node_Create(p_item);
+ if(p_node)
+ {
+ pl->pf_readdir(pl, p_node);
+ ret = checkfunc(run, p_node);
+ input_item_node_Delete(p_node);
+ }
+ else
+ {
+ ret = 1;
+ }
+ input_item_Release(p_item);
+ }
+ else
+ {
+ ret = 1;
+ }
+
+ demux_Delete(pl);
+
+ return ret;
+}
+
+const char m3uplaylist0[] =
+"#EXTM3U\n"
+"#JUNK\n"
+NOPFIL(0) "\n"
+NOPURI(1) "\n";
+
+static int check0(const char *run, const input_item_node_t *p_node)
+{
+ EXPECT(p_node->i_children == 2);
+
+ const input_item_t *p_item = p_node->pp_children[0]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(0), p_item->psz_uri));
+ EXPECT(!strcmp(NOPFIL(0), p_item->psz_name));
+ EXPECT(p_item->i_duration == INPUT_DURATION_INDEFINITE);
+
+ p_item = p_node->pp_children[1]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(p_item->psz_name, p_item->psz_uri));
+ EXPECT(!strcmp(NOPURI(1), p_item->psz_name));
+
+ return 0;
+}
+
+const char m3uplaylist1[] =
+"#EXTM3U\n"
+"#EXTINF: 1\n"
+NOPFIL(0) "\n"
+"#EXTINF: 1.11\n"
+NOPURI(1) "\n"
+"#EXTINF: -2,\n"
+"#JUNK:foo\n"
+NOPURI(2) "\n"
+"#EXTINF: 3,artist3 - name3\n"
+NOPURI(3) "\n"
+"#EXTINF: 4,,name4\n"
+NOPURI(4) "\n"
+"#EXTINF: 5,artist5,name5\n"
+NOPURI(5) "\n";
+
+static int check1(const char *run, const input_item_node_t *p_node)
+{
+ EXPECT(p_node->i_children == 6);
+
+ const input_item_t *p_item = p_node->pp_children[0]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPFIL(0), p_item->psz_name));
+ EXPECT(p_item->i_duration == vlc_tick_from_sec(1));
+
+ p_item = p_node->pp_children[1]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(1), p_item->psz_name));
+ EXPECT(p_item->i_duration == vlc_tick_from_sec(1.11));
+
+ p_item = p_node->pp_children[2]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(2), p_item->psz_uri));
+ EXPECT(!strcmp(p_item->psz_name, p_item->psz_uri));
+ EXPECT(p_item->i_duration == INPUT_DURATION_INDEFINITE);
+
+ p_item = p_node->pp_children[3]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(3), p_item->psz_uri));
+ EXPECT(!strcmp("name3", p_item->psz_name));
+ const char *p = vlc_meta_Get(p_item->p_meta, vlc_meta_Artist);
+ EXPECT(p && !strcmp("artist3", p));
+
+ p_item = p_node->pp_children[4]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp("name4", p_item->psz_name));
+ EXPECT(vlc_meta_Get(p_item->p_meta, vlc_meta_Artist) == NULL);
+
+ p_item = p_node->pp_children[5]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp("name5", p_item->psz_name));
+ p = vlc_meta_Get(p_item->p_meta, vlc_meta_Artist);
+ EXPECT(p && !strcmp("artist5", p));
+
+ return 0;
+}
+
+const char m3uplaylist2[] =
+"#EXTM3U\n"
+"#PLAYLIST:playlist0\n"
+"#EXTINF:-1 tvg-id=\"id0\" tvg-logo=\"logo0\" group-title=\"group0\",name0\n"
+NOPURI(0)"\n"
+"#EXTGRP:group1\n"
+"#EXTINF:-1,name1\n"
+NOPURI(1)"\n"
+"#EXTINF:-1,name2\n"
+NOPURI(2)"\n";
+
+static int check2(const char *run, const input_item_node_t *p_node)
+{
+ EXPECT(p_node->i_children == 3);
+ const char *p = vlc_meta_Get(p_node->p_item->p_meta, vlc_meta_Title);
+ EXPECT(p && !strcmp(p, "playlist0"));
+
+ const input_item_t *p_item = p_node->pp_children[0]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(0), p_item->psz_uri));
+ EXPECT(!strcmp("name0", p_item->psz_name));
+ p = vlc_meta_Get(p_item->p_meta, vlc_meta_Publisher);
+ EXPECT(p && !strcmp("group0", p));
+ p = vlc_meta_Get(p_item->p_meta, vlc_meta_ArtworkURL);
+
+ p_item = p_node->pp_children[1]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(1), p_item->psz_uri));
+ EXPECT(!strcmp("name1", p_item->psz_name));
+ p = vlc_meta_Get(p_item->p_meta, vlc_meta_Publisher);
+ EXPECT(p && !strcmp("group1", p));
+
+ p_item = p_node->pp_children[2]->p_item;
+ EXPECT(p_item->psz_name && p_item->psz_uri);
+ EXPECT(!strcmp(NOPURI(2), p_item->psz_uri));
+ EXPECT(!strcmp("name2", p_item->psz_name));
+ p = vlc_meta_Get(p_item->p_meta, vlc_meta_Publisher);
+ EXPECT(p && !strcmp("group1", p));
+
+ return 0;
+}
+
+
+int main(void)
+{
+ test_init();
+
+ libvlc_instance_t *vlc = libvlc_new(0, NULL);
+ if(!vlc)
+ return 1;
+
+ int ret = runtest("run0", vlc, m3uplaylist0, sizeof(m3uplaylist0), check0);
+ if(!ret)
+ ret = runtest("run1", vlc, m3uplaylist1, sizeof(m3uplaylist1), check1);
+ if(!ret)
+ ret = runtest("run2", vlc, m3uplaylist2, sizeof(m3uplaylist2), check2);
+
+ libvlc_release(vlc);
+ return ret;
+}
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b35560babc5f31042d0ecd8eb02a7668d41c48e2...16d6b2311d7780bedfe938be64051a7587ce81cd
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b35560babc5f31042d0ecd8eb02a7668d41c48e2...16d6b2311d7780bedfe938be64051a7587ce81cd
You're receiving this email because of your account on code.videolan.org.
More information about the vlc-commits
mailing list