[vlc-commits] codec: webvtt: add CSS support

Francois Cartegnie git at videolan.org
Tue Nov 7 12:37:33 CET 2017


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Tue Oct 17 14:05:50 2017 +0200| [506b43d0a3de0e2bfd75ae769d4f5483931ee740] | committer: Francois Cartegnie

codec: webvtt: add CSS support

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=506b43d0a3de0e2bfd75ae769d4f5483931ee740
---

 modules/codec/Makefile.am        |   2 +
 modules/codec/webvtt/css_style.c | 242 ++++++++++++++++
 modules/codec/webvtt/css_style.h |  22 ++
 modules/codec/webvtt/subsvtt.c   | 591 +++++++++++++++++++++++++++++++++++----
 4 files changed, 795 insertions(+), 62 deletions(-)

diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
index ca62716bdd..cefbf87330 100644
--- a/modules/codec/Makefile.am
+++ b/modules/codec/Makefile.am
@@ -238,6 +238,8 @@ libwebvtt_plugin_la_SOURCES += codec/webvtt/CSSGrammar.y \
                                codec/webvtt/CSSLexer.l \
                                codec/webvtt/css_parser.c \
                                codec/webvtt/css_parser.h \
+                               codec/webvtt/css_style.c \
+                               codec/webvtt/css_style.h \
                                codec/webvtt/css_bridge.h
 endif
 
diff --git a/modules/codec/webvtt/css_style.c b/modules/codec/webvtt/css_style.c
new file mode 100644
index 0000000000..0ba1449920
--- /dev/null
+++ b/modules/codec/webvtt/css_style.c
@@ -0,0 +1,242 @@
+/*****************************************************************************
+ * css_style.c : CSS styles conversions
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * 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_common.h>
+#include <vlc_text_style.h>
+
+#include "css_parser.h"
+#include "css_style.h"
+
+static void Color( vlc_css_term_t term,
+                   int *color, uint8_t *alpha,
+                   uint16_t *feat, int cflag, int aflag )
+{
+    if( term.type == TYPE_FUNCTION )
+    {
+        if( term.function ) /* func( expr ) */
+        {
+            if( ( !strcmp( term.psz, "rgb" ) && term.function->i_count == 3 ) ||
+                ( !strcmp( term.psz, "rgba" ) && term.function->i_count == 4 ) )
+            {
+                *color = (((int)term.function->seq[0].term.val) << 16) |
+                         (((int)term.function->seq[1].term.val) << 8) |
+                          ((int)term.function->seq[2].term.val);
+                *feat |= cflag;
+                if( term.psz[3] != 0 ) /* rgba */
+                {
+                    *alpha = term.function->seq[3].term.val * STYLE_ALPHA_OPAQUE;
+                    *feat |= aflag;
+                }
+            }
+        }
+    }
+    else if( term.type == TYPE_STRING ||
+             term.type == TYPE_HEXCOLOR ||
+             term.type == TYPE_IDENTIFIER )
+    {
+        bool b_valid = false;
+        unsigned i_color = vlc_html_color( term.psz, &b_valid );
+        if( b_valid )
+        {
+            *alpha = (i_color & 0xFF000000) >> 24;
+            *color = i_color & 0x00FFFFFF;
+            *feat |= cflag|aflag;
+        }
+    }
+}
+
+static void OutlineWidth( vlc_css_term_t term, text_style_t *p_style )
+{
+    if( term.type >= TYPE_PIXELS )
+    {
+        p_style->i_outline_width = term.val;
+        p_style->i_style_flags |= STYLE_OUTLINE;
+        p_style->i_features |= STYLE_HAS_FLAGS;
+    }
+}
+
+static void OutlineColor( vlc_css_term_t term, text_style_t *p_style )
+{
+    Color( term, &p_style->i_outline_color, &p_style->i_outline_alpha,
+           &p_style->i_features, STYLE_HAS_OUTLINE_COLOR, STYLE_HAS_OUTLINE_ALPHA );
+}
+
+static void ShadowDrop( vlc_css_term_t term, text_style_t *p_style )
+{
+    if( term.type >= TYPE_PIXELS )
+    {
+        p_style->i_shadow_width = term.val;
+        p_style->i_style_flags |= STYLE_SHADOW;
+        p_style->i_features |= STYLE_HAS_FLAGS;
+    }
+}
+
+static void ShadowColor( vlc_css_term_t term, text_style_t *p_style )
+{
+    Color( term, &p_style->i_shadow_color, &p_style->i_shadow_alpha,
+           &p_style->i_features, STYLE_HAS_SHADOW_COLOR, STYLE_HAS_SHADOW_ALPHA );
+}
+
+void webvtt_FillStyleFromCssDeclaration( const vlc_css_declaration_t *p_decl, text_style_t *p_style )
+{
+    if( !p_decl->psz_property || !p_style )
+        return;
+
+    /* Only support simple expressions for now */
+    if( p_decl->expr->i_count < 1 )
+        return;
+
+    vlc_css_term_t term0 = p_decl->expr->seq[0].term;
+
+    if( !strcmp( p_decl->psz_property, "color" ) )
+    {
+        Color( term0, &p_style->i_font_color, &p_style->i_font_alpha,
+               &p_style->i_features, STYLE_HAS_FONT_COLOR, STYLE_HAS_FONT_ALPHA );
+    }
+    else if( !strcmp( p_decl->psz_property, "text-decoration" ) )
+    {
+        if( term0.type == TYPE_STRING )
+        {
+            if( !strcmp( term0.psz, "none" ) )
+            {
+                p_style->i_style_flags &= ~(STYLE_STRIKEOUT|STYLE_UNDERLINE);
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+            else if( !strcmp( term0.psz, "line-through" ) )
+            {
+                p_style->i_style_flags |= STYLE_STRIKEOUT;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+            else if( !strcmp( term0.psz, "underline" ) )
+            {
+                p_style->i_style_flags |= STYLE_UNDERLINE;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+        }
+    }
+    else if( !strcmp( p_decl->psz_property, "text-shadow" ) )
+    {
+        ShadowDrop( term0, p_style );
+        if( p_decl->expr->i_count == 3 )
+            ShadowColor( p_decl->expr->seq[2].term, p_style );
+    }
+    else if( !strcmp( p_decl->psz_property, "background-color" ) )
+    {
+        Color( term0, &p_style->i_background_color, &p_style->i_background_alpha,
+               &p_style->i_features, STYLE_HAS_BACKGROUND_COLOR, STYLE_HAS_BACKGROUND_ALPHA );
+        p_style->i_style_flags |= STYLE_BACKGROUND;
+        p_style->i_features |= STYLE_HAS_FLAGS;
+    }
+    else if( !strcmp( p_decl->psz_property, "outline-color" ) )
+    {
+        OutlineColor( term0, p_style );
+    }
+    else if( !strcmp( p_decl->psz_property, "outline-width" ) )
+    {
+        OutlineWidth( term0, p_style );
+    }
+    else if( !strcmp( p_decl->psz_property, "outline" ) )
+    {
+        OutlineWidth( term0, p_style );
+        if( p_decl->expr->i_count == 3 )
+            OutlineColor( p_decl->expr->seq[2].term, p_style );
+    }
+    else if( !strcmp( p_decl->psz_property, "font-family" ) )
+    {
+        if( term0.type >= TYPE_STRING )
+        {
+            char *psz_font = NULL;
+            const char *psz = strchr( term0.psz, ',' );
+            if( psz )
+                psz_font = strndup( term0.psz, psz - term0.psz + 1 );
+            else
+                psz_font = strdup( term0.psz );
+            free( p_style->psz_fontname );
+            p_style->psz_fontname = vlc_css_unquoted( psz_font );
+            free( psz_font );
+        }
+    }
+    else if( !strcmp( p_decl->psz_property, "font-style" ) )
+    {
+        if( term0.type >= TYPE_STRING )
+        {
+            if( !strcmp(term0.psz, "normal") )
+            {
+                p_style->i_style_flags &= ~STYLE_ITALIC;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+            else if( !strcmp(term0.psz, "italic") )
+            {
+                p_style->i_style_flags |= STYLE_ITALIC;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+        }
+    }
+    else if( !strcmp( p_decl->psz_property, "font-weight" ) )
+    {
+        if( term0.type >= TYPE_STRING )
+        {
+            if( !strcmp(term0.psz, "normal") )
+            {
+                p_style->i_style_flags &= ~STYLE_BOLD;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+            if( !strcmp(term0.psz, "bold") )
+            {
+                p_style->i_style_flags |= STYLE_BOLD;
+                p_style->i_features |= STYLE_HAS_FLAGS;
+            }
+        }
+        else if( term0.type == TYPE_NONE )
+        {
+            if( term0.val >= 700.0 )
+                p_style->i_style_flags |= STYLE_BOLD;
+            else
+                p_style->i_style_flags &= ~STYLE_BOLD;
+            p_style->i_features |= STYLE_HAS_FLAGS;
+        }
+    }
+    else if( !strcmp( p_decl->psz_property, "font-size" ) )
+    {
+        if( term0.type == TYPE_PIXELS )
+            p_style->i_font_size = term0.val;
+        else if( term0.type == TYPE_EMS )
+            p_style->f_font_relsize = term0.val * 5.33 / 1.06;
+        else if( term0.type == TYPE_PERCENT )
+            p_style->f_font_relsize = term0.val * 5.33 / 100;
+    }
+    else if( !strcmp( p_decl->psz_property, "font" ) )
+    {
+        /* what to do ? */
+    }
+    else if( !strcmp( p_decl->psz_property, "white-space" ) )
+    {
+        if( term0.type >= TYPE_STRING )
+        {
+            if( !strcmp(term0.psz, "normal" ) )
+                p_style->e_wrapinfo = STYLE_WRAP_DEFAULT;
+            if( !strcmp(term0.psz, "nowrap" ) )
+                p_style->e_wrapinfo = STYLE_WRAP_NONE;
+        }
+    }
+}
diff --git a/modules/codec/webvtt/css_style.h b/modules/codec/webvtt/css_style.h
new file mode 100644
index 0000000000..0ed34fbb84
--- /dev/null
+++ b/modules/codec/webvtt/css_style.h
@@ -0,0 +1,22 @@
+/*****************************************************************************
+ * css_style.h : CSS styles conversions
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * 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.
+ *****************************************************************************/
+
+void webvtt_FillStyleFromCssDeclaration( const vlc_css_declaration_t *p_decl,
+                                         text_style_t *p_style );
diff --git a/modules/codec/webvtt/subsvtt.c b/modules/codec/webvtt/subsvtt.c
index 47138a1f10..ad1ff75257 100644
--- a/modules/codec/webvtt/subsvtt.c
+++ b/modules/codec/webvtt/subsvtt.c
@@ -38,6 +38,12 @@
 #include "../demux/mp4/minibox.h"
 #include "webvtt.h"
 
+
+#ifdef HAVE_CSS
+#  include "css_parser.h"
+#  include "css_style.h"
+#endif
+
 #include <ctype.h>
 
 //#define SUBSVTT_DEBUG
@@ -53,6 +59,7 @@ typedef struct webvtt_dom_cue_t webvtt_dom_cue_t;
 #define WEBVTT_REGION_LINES_COUNT          18
 #define WEBVTT_DEFAULT_LINE_HEIGHT_VH    5.33
 #define WEBVTT_LINE_TO_HEIGHT_RATIO      1.06
+#define WEBVTT_MAX_DEPTH                 20 /* recursion prevention for now */
 
 enum webvtt_align_e
 {
@@ -83,7 +90,6 @@ enum webvtt_node_type_e
     NODE_TEXT,
     NODE_CUE,
     NODE_REGION,
-    NODE_VIDEO,
 };
 
 #define WEBVTT_NODE_BASE_MEMBERS \
@@ -102,6 +108,7 @@ struct webvtt_region_t
     float viewport_anchor_x;
     float viewport_anchor_y;
     bool b_scroll_up;
+    text_style_t *p_cssstyle;
     webvtt_dom_node_t *p_child;
 };
 
@@ -113,6 +120,7 @@ struct webvtt_dom_cue_t
     mtime_t i_stop;
     webvtt_cue_settings_t settings;
     unsigned i_lines;
+    text_style_t *p_cssstyle;
     webvtt_dom_node_t *p_child;
 };
 
@@ -128,15 +136,10 @@ typedef struct
     mtime_t i_start;
     char *psz_tag;
     char *psz_attrs;
+    text_style_t *p_cssstyle;
     webvtt_dom_node_t *p_child;
 } webvtt_dom_tag_t;
 
-typedef struct
-{
-    WEBVTT_NODE_BASE_MEMBERS
-    webvtt_dom_node_t *p_child;
-} webvtt_dom_video_t;
-
 struct webvtt_dom_node_t
 {
     WEBVTT_NODE_BASE_MEMBERS
@@ -144,7 +147,11 @@ struct webvtt_dom_node_t
 
 struct decoder_sys_t
 {
-    webvtt_dom_video_t *p_root;
+    webvtt_dom_tag_t *p_root;
+#ifdef HAVE_CSS
+    /* CSS */
+    vlc_css_rule_t *p_css_rules;
+#endif
 };
 
 #define ATOM_iden VLC_FOURCC('i', 'd', 'e', 'n')
@@ -334,18 +341,13 @@ static void webvtt_domnode_Debug( webvtt_dom_node_t *p_node, int i_depth )
         }
     }
 }
+#define webvtt_domnode_Debug(a,b) webvtt_domnode_Debug((webvtt_dom_node_t *)a,b)
 #endif
 
 static void webvtt_domnode_ChainDelete( webvtt_dom_node_t *p_node );
 static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue );
 static void webvtt_region_Delete( webvtt_region_t *p_region );
 
-static void webvtt_dom_video_Delete( webvtt_dom_video_t *p_node )
-{
-    webvtt_domnode_ChainDelete( p_node->p_child );
-    free( p_node );
-}
-
 static void webvtt_dom_text_Delete( webvtt_dom_text_t *p_node )
 {
     free( p_node->psz_text );
@@ -354,6 +356,7 @@ static void webvtt_dom_text_Delete( webvtt_dom_text_t *p_node )
 
 static void webvtt_dom_tag_Delete( webvtt_dom_tag_t *p_node )
 {
+    text_style_Delete( p_node->p_cssstyle );
     free( p_node->psz_attrs );
     free( p_node->psz_tag );
     webvtt_domnode_ChainDelete( p_node->p_child );
@@ -385,21 +388,11 @@ static void webvtt_domnode_ChainDelete( webvtt_dom_node_t *p_node )
             webvtt_dom_cue_Delete( (webvtt_dom_cue_t *) p_node );
         else if( p_node->type == NODE_REGION )
             webvtt_region_Delete( (webvtt_region_t *) p_node );
-        else if( p_node->type == NODE_VIDEO )
-            webvtt_dom_video_Delete( (webvtt_dom_video_t *) p_node );
 
         p_node = p_next;
     }
 }
 
-static webvtt_dom_video_t * webvtt_dom_video_New( void )
-{
-    webvtt_dom_video_t *p_node = calloc( 1, sizeof(*p_node) );
-    if( p_node )
-        p_node->type = NODE_VIDEO;
-    return p_node;
-}
-
 static webvtt_dom_text_t * webvtt_dom_text_New( webvtt_dom_node_t *p_parent )
 {
     webvtt_dom_text_t *p_node = calloc( 1, sizeof(*p_node) );
@@ -438,7 +431,7 @@ static webvtt_dom_node_t * webvtt_domnode_getParentByTag( webvtt_dom_node_t *p_p
     return p_parent;
 }
 
-static const webvtt_dom_node_t * webvtt_domnode_getFirstChild( const webvtt_dom_node_t *p_node )
+static webvtt_dom_node_t * webvtt_domnode_getFirstChild( webvtt_dom_node_t *p_node )
 {
     webvtt_dom_node_t *p_child = NULL;
     switch( p_node->type )
@@ -457,6 +450,336 @@ static const webvtt_dom_node_t * webvtt_domnode_getFirstChild( const webvtt_dom_
     }
     return p_child;
 }
+#define webvtt_domnode_getFirstChild(a) webvtt_domnode_getFirstChild((webvtt_dom_node_t *)a)
+
+static mtime_t webvtt_domnode_GetPlaybackTime( const webvtt_dom_node_t *p_node, bool b_end )
+{
+    for( ; p_node; p_node = p_node->p_parent )
+    {
+        if( p_node->type == NODE_TAG )
+        {
+            mtime_t i_start = ((const webvtt_dom_tag_t *) p_node)->i_start;
+            if( i_start > -1 && !b_end )
+                return i_start;
+        }
+        else if( p_node->type == NODE_CUE )
+        {
+            break;
+        }
+    }
+    if( p_node )
+        return b_end ? ((const webvtt_dom_cue_t *) p_node)->i_stop:
+                       ((const webvtt_dom_cue_t *) p_node)->i_start;
+    return VLC_TS_INVALID;
+}
+
+#ifdef HAVE_CSS
+static bool webvtt_domnode_Match_Class( const webvtt_dom_node_t *p_node, const char *psz )
+{
+    const size_t i_len = strlen( psz );
+    if( p_node->type == NODE_TAG )
+    {
+        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
+        while( p_tagnode->psz_attrs && psz )
+        {
+            const char *p = strstr( p_tagnode->psz_attrs, psz );
+            if( !p )
+                return false;
+            if( p > psz && p[-1] == '.' && !isalnum(p[i_len]) )
+                return true;
+            psz = p + 1;
+        }
+    }
+    return false;
+}
+
+static bool webvtt_domnode_Match_Id( const webvtt_dom_node_t *p_node, const char *psz_id )
+{
+    if( !psz_id )
+        return false;
+    if( *psz_id == '#' )
+        psz_id++;
+    if( p_node->type == NODE_REGION )
+        return ((webvtt_region_t *)p_node)->psz_id &&
+                !strcmp( ((webvtt_region_t *)p_node)->psz_id, psz_id );
+    else if( p_node->type == NODE_CUE )
+        return ((webvtt_dom_cue_t *)p_node)->psz_id &&
+                !strcmp( ((webvtt_dom_cue_t *)p_node)->psz_id, psz_id );
+    return false;
+}
+
+static bool webvtt_domnode_Match_Tag( const webvtt_dom_node_t *p_node, const char *psz_tag )
+{
+    if( p_node->type == NODE_TAG && psz_tag )
+    {
+        /* special case, not allowed to match anywhere but root */
+        if( !strcmp(psz_tag, "video") && p_node->p_parent )
+            return false;
+        return ((webvtt_dom_tag_t *)p_node)->psz_tag &&
+                !strcmp( ((webvtt_dom_tag_t *)p_node)->psz_tag, psz_tag );
+    }
+    else return false;
+}
+
+static bool webvtt_domnode_Match_PseudoClass( const webvtt_dom_node_t *p_node, const char *psz,
+                                              mtime_t i_playbacktime )
+{
+    if( !strcmp(psz, "past") || !strcmp(psz, "future") )
+    {
+        mtime_t i_start = webvtt_domnode_GetPlaybackTime( p_node, false );
+        return ( *psz == 'p' ) ? i_start < i_playbacktime : i_start > i_playbacktime;
+    }
+    return false;
+}
+
+static bool webvtt_domnode_Match_PseudoElement( const webvtt_dom_node_t *p_node, const char *psz )
+{
+    if( !strcmp(psz, "cue") )
+        return p_node->type == NODE_CUE;
+    else if( !strcmp(psz, "cue-region") )
+        return p_node->type == NODE_REGION;
+    return false;
+}
+
+static bool MatchAttribute( const char *psz_attr, const char *psz_lookup, enum vlc_css_match_e match )
+{
+    switch( match )
+    {
+        case MATCH_EQUALS:
+            return !strcmp( psz_attr, psz_lookup );
+        case MATCH_INCLUDES:
+        {
+            const char *p = strstr( psz_attr, psz_lookup );
+            if( p && ( p == psz_attr || isspace(p[-1]) ) )
+            {
+                const char *end = p + strlen( psz_lookup );
+                return (*end == 0 || isspace(*end));
+            }
+            break;
+        }
+        case MATCH_DASHMATCH:
+        {
+            size_t i_len = strlen(psz_lookup);
+            if( !strncmp( psz_attr, psz_lookup, i_len ) )
+            {
+                const char *end = psz_attr + i_len;
+                return (*end == 0 || !isalnum(*end) );
+            }
+            break;
+        }
+        case MATCH_BEGINSWITH:
+            return !strncmp( psz_attr, psz_lookup, strlen(psz_lookup) );
+        case MATCH_ENDSWITH:
+        {
+            const char *p = strstr( psz_attr, psz_lookup );
+            return (p && *p && p[1] == 0);
+        }
+        case MATCH_CONTAINS:
+            return !!strstr( psz_attr, psz_lookup );
+        default:
+            break;
+    }
+    return false;
+}
+
+static bool webvtt_domnode_Match_Attribute( const webvtt_dom_node_t *p_node,
+                                            const char *psz, const vlc_css_selector_t *p_matchsel )
+{
+    if( p_node->type == NODE_TAG && p_matchsel )
+    {
+        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
+
+        if( ( !strcmp( p_tagnode->psz_tag, "v" ) && !strcmp( psz, "voice" ) ) || /* v = only voice */
+            ( !strcmp( p_tagnode->psz_tag, "lang" ) && !strcmp( psz, "lang" ) ) )
+        {
+            const char *psz_start = NULL;
+            /* skip classes decl */
+            for( const char *p = p_tagnode->psz_attrs; *p; p++ )
+            {
+                if( isspace(*p) )
+                {
+                    psz_start = p + 1;
+                }
+                else if( psz_start != NULL )
+                {
+                    break;
+                }
+            }
+
+            if( psz_start == NULL || *psz_start == 0 )
+                psz_start = p_tagnode->psz_attrs;
+
+            if( !p_matchsel ) /* attribute check only */
+                return strlen( psz_start ) > 0;
+
+            return MatchAttribute( psz_start, p_matchsel->psz_name, p_matchsel->match );
+        }
+    }
+    return false;
+}
+
+static bool webvtt_domnode_MatchType( decoder_t *p_dec, const webvtt_dom_node_t *p_node,
+                                      const vlc_css_selector_t *p_sel, mtime_t i_playbacktime )
+{
+    VLC_UNUSED(p_dec);
+    switch( p_sel->type )
+    {
+        case SELECTOR_SIMPLE:
+            return webvtt_domnode_Match_Tag( p_node, p_sel->psz_name );
+        case SELECTOR_PSEUDOCLASS:
+            return webvtt_domnode_Match_PseudoClass( p_node, p_sel->psz_name,
+                                                     i_playbacktime );
+        case SELECTOR_PSEUDOELEMENT:
+            return webvtt_domnode_Match_PseudoElement( p_node, p_sel->psz_name );
+        case SPECIFIER_ID:
+            return webvtt_domnode_Match_Id( p_node, p_sel->psz_name );
+        case SPECIFIER_CLASS:
+            return webvtt_domnode_Match_Class( p_node, p_sel->psz_name );
+        case SPECIFIER_ATTRIB:
+            return webvtt_domnode_Match_Attribute( p_node, p_sel->psz_name, p_sel->p_matchsel );
+    }
+    return false;
+}
+#endif
+
+static text_style_t ** get_ppCSSStyle( webvtt_dom_node_t *p_node )
+{
+    switch( p_node->type )
+    {
+        case NODE_CUE:
+            return &((webvtt_dom_cue_t *)p_node)->p_cssstyle;
+        case NODE_REGION:
+            return &((webvtt_region_t *)p_node)->p_cssstyle;
+        case NODE_TAG:
+            return &((webvtt_dom_tag_t *)p_node)->p_cssstyle;
+        default:
+            return NULL;
+    }
+}
+
+static text_style_t * webvtt_domnode_getCSSStyle( webvtt_dom_node_t *p_node )
+{
+    text_style_t **pp_style = get_ppCSSStyle( p_node );
+    if( pp_style )
+        return *pp_style;
+    return NULL;
+}
+#define webvtt_domnode_getCSSStyle(a) webvtt_domnode_getCSSStyle((webvtt_dom_node_t *)a)
+
+static bool webvtt_domnode_supportsCSSStyle( webvtt_dom_node_t *p_node )
+{
+    return get_ppCSSStyle( p_node ) != NULL;
+}
+
+static void webvtt_domnode_setCSSStyle( webvtt_dom_node_t *p_node, text_style_t *p_style )
+{
+    text_style_t **pp_style = get_ppCSSStyle( p_node );
+    if( !pp_style )
+    {
+        assert( pp_style );
+        if( p_style )
+            text_style_Delete( p_style );
+        return;
+    }
+    if( *pp_style )
+        text_style_Delete( *pp_style );
+    *pp_style = p_style;
+}
+
+#ifdef HAVE_CSS
+static void webvtt_domnode_SelectNodesInTree( decoder_t *p_dec, const vlc_css_selector_t *p_sel,
+                                              const webvtt_dom_node_t *p_tree, int i_max_depth,
+                                              mtime_t i_playbacktime, vlc_array_t *p_results );
+
+static void webvtt_domnode_SelectChildNodesInTree( decoder_t *p_dec, const vlc_css_selector_t *p_sel,
+                                                   const webvtt_dom_node_t *p_root, int i_max_depth,
+                                                   mtime_t i_playbacktime, vlc_array_t *p_results )
+{
+    const webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_root );
+    if( i_max_depth > 0 )
+    {
+        for( ; p_child; p_child = p_child->p_next )
+            webvtt_domnode_SelectNodesInTree( p_dec, p_sel, p_child, i_max_depth - 1,
+                                              i_playbacktime, p_results );
+    }
+}
+
+static void webvtt_domnode_SelectNodesBySpeficier( decoder_t *p_dec, const vlc_css_selector_t *p_spec,
+                                                   const webvtt_dom_node_t *p_node,
+                                                   mtime_t i_playbacktime, vlc_array_t *p_results )
+{
+    if( p_spec == NULL )
+        return;
+
+    switch( p_spec->combinator )
+    {
+        case RELATION_DESCENDENT:
+            webvtt_domnode_SelectChildNodesInTree( p_dec, p_spec, p_node, WEBVTT_MAX_DEPTH,
+                                                   i_playbacktime, p_results );
+            break;
+        case RELATION_DIRECTADJACENT:
+            for( const webvtt_dom_node_t *p_adj = p_node->p_next; p_adj; p_adj = p_adj->p_next )
+                webvtt_domnode_SelectChildNodesInTree( p_dec, p_spec, p_adj, 1,
+                                                       i_playbacktime, p_results );
+            break;
+        case RELATION_INDIRECTADJACENT:
+            for( const webvtt_dom_node_t *p_adj = webvtt_domnode_getFirstChild( p_node->p_parent );
+                                          p_adj && p_adj != p_node; p_adj = p_adj->p_next )
+                webvtt_domnode_SelectChildNodesInTree( p_dec, p_spec, p_adj, 1,
+                                                       i_playbacktime, p_results );
+            break;
+        case RELATION_CHILD:
+            webvtt_domnode_SelectChildNodesInTree( p_dec, p_spec, p_node, 1,
+                                                   i_playbacktime, p_results );
+            break;
+        case RELATION_SELF:
+            webvtt_domnode_SelectNodesInTree( p_dec, p_spec, p_node, WEBVTT_MAX_DEPTH,
+                                              i_playbacktime, p_results );
+    }
+}
+
+static void webvtt_domnode_SelectNodesInTree( decoder_t *p_dec, const vlc_css_selector_t *p_sel,
+                                              const webvtt_dom_node_t *p_root, int i_max_depth,
+                                              mtime_t i_playbacktime, vlc_array_t *p_results )
+{
+    if( p_root == NULL )
+        return;
+
+    if( webvtt_domnode_MatchType( p_dec, p_root, p_sel, i_playbacktime ) )
+    {
+        if( p_sel->specifiers.p_first == NULL )
+        {
+            /* End of matching, this node is part of results */
+            (void) vlc_array_append( p_results, (void *) p_root );
+        }
+        else webvtt_domnode_SelectNodesBySpeficier( p_dec, p_sel->specifiers.p_first, p_root,
+                                                    i_playbacktime, p_results );
+    }
+
+    /* lookup other subnodes */
+    webvtt_domnode_SelectChildNodesInTree( p_dec, p_sel, p_root, i_max_depth - 1,
+                                           i_playbacktime, p_results );
+}
+
+static void webvtt_domnode_SelectRuleNodes( decoder_t *p_dec, const vlc_css_rule_t *p_rule,
+                                            mtime_t i_playbacktime, vlc_array_t *p_results )
+{
+    const webvtt_dom_node_t *p_cues = p_dec->p_sys->p_root->p_child;
+    for( const vlc_css_selector_t *p_sel = p_rule->p_selectors; p_sel; p_sel = p_sel->p_next )
+    {
+        vlc_array_t tempresults;
+        vlc_array_init( &tempresults );
+        for( const webvtt_dom_node_t *p_node = p_cues; p_node; p_node = p_node->p_next )
+        {
+            webvtt_domnode_SelectNodesInTree( p_dec, p_sel, p_node, WEBVTT_MAX_DEPTH,
+                                              i_playbacktime, &tempresults );
+        }
+        for( size_t i=0; i<vlc_array_count(&tempresults); i++ )
+            (void) vlc_array_append( p_results, vlc_array_item_at_index( &tempresults, i ) );
+        vlc_array_clear( &tempresults );
+    }
+}
+#endif
 
 static inline bool IsEndTag( const char *psz )
 {
@@ -517,6 +840,7 @@ static webvtt_dom_cue_t * webvtt_dom_cue_New( mtime_t i_start, mtime_t i_end )
         p_cue->i_stop = i_end;
         p_cue->p_child = NULL;
         p_cue->i_lines = 0;
+        p_cue->p_cssstyle = NULL;
         webvtt_cue_settings_Init( &p_cue->settings );
     }
     return p_cue;
@@ -531,6 +855,7 @@ static void webvtt_dom_cue_ClearText( webvtt_dom_cue_t *p_cue )
 
 static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue )
 {
+    text_style_Delete( p_cue->p_cssstyle );
     webvtt_dom_cue_ClearText( p_cue );
     webvtt_cue_settings_Clean( &p_cue->settings );
     free( p_cue->psz_id );
@@ -725,6 +1050,7 @@ static void webvtt_region_AddCue( webvtt_region_t *p_region,
 
 static void webvtt_region_Delete( webvtt_region_t *p_region )
 {
+    text_style_Delete( p_region->p_cssstyle );
     webvtt_region_ClearCues( p_region );
     free( p_region->psz_id );
     free( p_region );
@@ -745,6 +1071,7 @@ static webvtt_region_t * webvtt_region_New( void )
         p_region->viewport_anchor_x = 0;
         p_region->viewport_anchor_y = 1.0; /* 100% */
         p_region->b_scroll_up = false;
+        p_region->p_cssstyle = NULL;
         p_region->p_child = NULL;
     }
     return p_region;
@@ -878,57 +1205,92 @@ static void ProcessCue( decoder_t *p_dec, const char *psz, webvtt_dom_cue_t *p_c
 #endif
 }
 
-static text_style_t * InheritStyles( decoder_t *p_dec, const webvtt_dom_node_t *p_node )
+static text_style_t * ComputeStyle( decoder_t *p_dec, const webvtt_dom_node_t *p_leaf )
 {
     VLC_UNUSED(p_dec);
-
     text_style_t *p_style = NULL;
-    for( ; p_node; p_node = p_node->p_parent )
+    mtime_t i_tagtime = -1;
+
+    for( const webvtt_dom_node_t *p_node = p_leaf ; p_node; p_node = p_node->p_parent )
     {
-        if( p_node->type == NODE_TAG )
+        bool b_nooverride = false;
+        if( p_node->type == NODE_CUE )
+        {
+            const webvtt_dom_cue_t *p_cue = (const webvtt_dom_cue_t *)p_node;
+            if( p_cue )
+            {
+                if( i_tagtime > -1 ) /* don't override timed stylings */
+                    b_nooverride = true;
+            }
+        }
+        else if( p_node->type == NODE_TAG )
         {
             const webvtt_dom_tag_t *p_tagnode = (const webvtt_dom_tag_t *)p_node;
-            if ( p_tagnode->psz_tag == NULL )
+
+            if( p_tagnode->i_start > -1 )
             {
-                continue;
+                /* Ignore other timed stylings */
+                if( i_tagtime == -1 )
+                    i_tagtime = p_tagnode->i_start;
+                else
+                    continue;
             }
-            else if ( !strcmp( p_tagnode->psz_tag, "b" ) )
+
+            if ( p_tagnode->psz_tag )
             {
-                if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                if ( !strcmp( p_tagnode->psz_tag, "b" ) )
                 {
-                    p_style->i_style_flags |= STYLE_BOLD;
-                    p_style->i_features |= STYLE_HAS_FLAGS;
+                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                    {
+                        p_style->i_style_flags |= STYLE_BOLD;
+                        p_style->i_features |= STYLE_HAS_FLAGS;
+                    }
                 }
-            }
-            else if ( !strcmp( p_tagnode->psz_tag, "i" ) )
-            {
-                if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                else if ( !strcmp( p_tagnode->psz_tag, "i" ) )
                 {
-                    p_style->i_style_flags |= STYLE_ITALIC;
-                    p_style->i_features |= STYLE_HAS_FLAGS;
+                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                    {
+                        p_style->i_style_flags |= STYLE_ITALIC;
+                        p_style->i_features |= STYLE_HAS_FLAGS;
+                    }
                 }
-            }
-            else if ( !strcmp( p_tagnode->psz_tag, "u" ) )
-            {
-                if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                else if ( !strcmp( p_tagnode->psz_tag, "u" ) )
                 {
-                    p_style->i_style_flags |= STYLE_UNDERLINE;
-                    p_style->i_features |= STYLE_HAS_FLAGS;
+                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                    {
+                        p_style->i_style_flags |= STYLE_UNDERLINE;
+                        p_style->i_features |= STYLE_HAS_FLAGS;
+                    }
                 }
-            }
-            else if ( !strcmp( p_tagnode->psz_tag, "v" ) && p_tagnode->psz_attrs )
-            {
-                if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                else if ( !strcmp( p_tagnode->psz_tag, "v" ) && p_tagnode->psz_attrs )
                 {
-                    unsigned a = 0;
-                    for( char *p = p_tagnode->psz_attrs; *p; p++ )
-                        a = (a << 3) ^ *p;
-                    p_style->i_font_color = (0x7F7F7F | a) & 0xFFFFFF;
-                    p_style->i_features |= STYLE_HAS_FONT_COLOR;
+#ifdef HAVE_CSS
+                    if( p_dec->p_sys->p_css_rules == NULL ) /* Only auto style when no CSS sheet */
+#endif
+                    {
+                        if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
+                        {
+                            unsigned a = 0;
+                            for( char *p = p_tagnode->psz_attrs; *p; p++ )
+                                a = (a << 3) ^ *p;
+                            p_style->i_font_color = (0x7F7F7F | a) & 0xFFFFFF;
+                            p_style->i_features |= STYLE_HAS_FONT_COLOR;
+                        }
+                    }
                 }
             }
         }
+
+        const text_style_t *p_nodestyle = webvtt_domnode_getCSSStyle( p_node );
+        if( p_nodestyle )
+        {
+            if( p_style )
+                text_style_Merge( p_style, p_nodestyle, false );
+            else if( !b_nooverride )
+                p_style = text_style_Duplicate( p_nodestyle );
+        }
     }
+
     return p_style;
 }
 
@@ -984,7 +1346,7 @@ static text_segment_t *ConvertNodesToSegments( decoder_t *p_dec,
             {
                 if( (*pp_append)->psz_text )
                     vlc_xml_decode( (*pp_append)->psz_text );
-                (*pp_append)->style = InheritStyles( p_dec, p_node );
+                (*pp_append)->style = ComputeStyle( p_dec, p_node );
             }
         }
         else if( p_node->type == NODE_TAG )
@@ -1072,7 +1434,6 @@ static void GetTimedTags( const webvtt_dom_node_t *p_node,
             } break;
             case NODE_REGION:
             case NODE_CUE:
-            case NODE_VIDEO:
                 GetTimedTags( webvtt_domnode_getFirstChild( p_node ),
                               i_start, i_stop, p_times );
                 break;
@@ -1104,11 +1465,62 @@ static void CreateSpuOrNewUpdaterRegion( decoder_t *p_dec,
     }
 }
 
+static void ClearCSSStyles( webvtt_dom_node_t *p_node )
+{
+    if( webvtt_domnode_supportsCSSStyle( p_node ) )
+        webvtt_domnode_setCSSStyle( p_node, NULL );
+    webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_node );
+    for ( ; p_child ; p_child = p_child->p_next )
+        ClearCSSStyles( p_child );
+}
+
+#ifdef HAVE_CSS
+static void ApplyCSSRules( decoder_t *p_dec, const vlc_css_rule_t *p_rule,
+                           mtime_t i_playbacktime )
+{
+    for ( ;  p_rule ; p_rule = p_rule->p_next )
+    {
+        vlc_array_t results;
+        vlc_array_init( &results );
+
+        webvtt_domnode_SelectRuleNodes( p_dec, p_rule, i_playbacktime, &results );
+
+        for( const vlc_css_declaration_t *p_decl = p_rule->p_declarations;
+                                          p_decl; p_decl = p_decl->p_next )
+        {
+            for( size_t i=0; i<vlc_array_count(&results); i++ )
+            {
+                webvtt_dom_node_t *p_node = vlc_array_item_at_index( &results, i );
+                if( !webvtt_domnode_supportsCSSStyle( p_node ) )
+                    continue;
+
+                text_style_t *p_style = webvtt_domnode_getCSSStyle( p_node );
+                if( !p_style )
+                {
+                    p_style = text_style_Create( STYLE_NO_DEFAULTS );
+                    webvtt_domnode_setCSSStyle( p_node, p_style );
+                }
+
+                if( !p_style )
+                    continue;
+
+                webvtt_FillStyleFromCssDeclaration( p_decl, p_style );
+            }
+        }
+        vlc_array_clear( &results );
+    }
+}
+#endif
+
 static void RenderRegions( decoder_t *p_dec, mtime_t i_start, mtime_t i_stop )
 {
     subpicture_t *p_spu = NULL;
     subpicture_updater_sys_region_t *p_updtregion = NULL;
 
+#ifdef HAVE_CSS
+    ApplyCSSRules( p_dec, p_dec->p_sys->p_css_rules, i_start );
+#endif
+
     for( const webvtt_dom_node_t *p_node = p_dec->p_sys->p_root->p_child;
                                   p_node; p_node = p_node->p_next )
     {
@@ -1217,12 +1629,18 @@ static void Render( decoder_t *p_dec, mtime_t i_start, mtime_t i_stop )
                  (const webvtt_dom_tag_t *) vlc_array_item_at_index( &timedtags, i );
          if( p_tag->i_start != i_substart ) /* might be duplicates */
          {
+             if( i > 0 )
+                 ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
              RenderRegions( p_dec, i_substart, p_tag->i_start );
              i_substart = p_tag->i_start;
          }
     }
     if( i_substart != i_stop )
+    {
+        if( i_substart != i_start )
+            ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
         RenderRegions( p_dec, i_substart, i_stop );
+    }
 
     vlc_array_clear( &timedtags );
 }
@@ -1288,6 +1706,9 @@ static int ProcessISOBMFF( decoder_t *p_dec,
 struct parser_ctx
 {
     webvtt_region_t *p_region;
+#ifdef HAVE_CSS
+    struct vlc_memstream css;
+#endif
     decoder_t *p_dec;
 };
 
@@ -1312,6 +1733,30 @@ static void ParserHeaderHandler( void *priv, enum webvtt_header_line_e s,
             else webvtt_region_Delete( ctx->p_region );
             ctx->p_region = NULL;
         }
+#ifdef HAVE_CSS
+        else if( ctx->css.stream )
+        {
+            if( vlc_memstream_close( &ctx->css ) == VLC_SUCCESS )
+            {
+                vlc_css_parser_t p;
+                vlc_css_parser_Init(&p);
+                vlc_css_parser_ParseBytes( &p,
+                                          (const uint8_t *) ctx->css.ptr,
+                                           ctx->css.length );
+#  ifdef CSS_PARSER_DEBUG
+                vlc_css_parser_Debug( &p );
+#  endif
+                vlc_css_rule_t **pp_append = &p_sys->p_css_rules;
+                while( *pp_append )
+                    pp_append = &((*pp_append)->p_next);
+                *pp_append = p.rules.p_first;
+                p.rules.p_first = NULL;
+
+                vlc_css_parser_Clean(&p);
+                free( ctx->css.ptr );
+            }
+        }
+#endif
 
         if( !psz_line )
             return;
@@ -1320,12 +1765,23 @@ static void ParserHeaderHandler( void *priv, enum webvtt_header_line_e s,
         {
             if( s == WEBVTT_HEADER_REGION )
                 ctx->p_region = webvtt_region_New();
+#ifdef HAVE_CSS
+            else if( s == WEBVTT_HEADER_STYLE )
+                (void) vlc_memstream_open( &ctx->css );
+#endif
             return;
         }
     }
 
     if( s == WEBVTT_HEADER_REGION && ctx->p_region )
         webvtt_region_Parse( ctx->p_region, (char*) psz_line );
+#ifdef HAVE_CSS
+    else if( s == WEBVTT_HEADER_STYLE && ctx->css.stream )
+    {
+        vlc_memstream_puts( &ctx->css, psz_line );
+        vlc_memstream_putc( &ctx->css, '\n' );
+    }
+#endif
 }
 
 static void LoadExtradata( decoder_t *p_dec )
@@ -1338,6 +1794,9 @@ static void LoadExtradata( decoder_t *p_dec )
         return;
 
    struct parser_ctx ctx;
+#ifdef HAVE_CSS
+   ctx.css.stream = NULL;
+#endif
    ctx.p_region = NULL;
    ctx.p_dec = p_dec;
    webvtt_text_parser_t *p_parser =
@@ -1363,15 +1822,18 @@ static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
     if( p_block == NULL ) /* No Drain */
         return VLCDEC_SUCCESS;
 
+    mtime_t i_start = p_block->i_pts - VLC_TS_0;
+    mtime_t i_stop = i_start + p_block->i_length;
+
     if( p_block->i_flags & BLOCK_FLAG_DISCONTINUITY )
         ClearCuesByTime( &p_dec->p_sys->p_root->p_child, INT64_MAX );
     else
-        ClearCuesByTime( &p_dec->p_sys->p_root->p_child, p_block->i_dts );
+        ClearCuesByTime( &p_dec->p_sys->p_root->p_child, i_start );
 
     ProcessISOBMFF( p_dec, p_block->p_buffer, p_block->i_buffer,
-                    p_block->i_pts, p_block->i_pts + p_block->i_length );
+                    i_start, i_stop );
 
-    Render( p_dec, p_block->i_pts, p_block->i_pts + p_block->i_length );
+    Render( p_dec, i_start, i_stop );
 
     block_Release( p_block );
     return VLCDEC_SUCCESS;
@@ -1387,6 +1849,10 @@ void CloseDecoder( vlc_object_t *p_this )
 
     webvtt_domnode_ChainDelete( (webvtt_dom_node_t *) p_sys->p_root );
 
+#ifdef HAVE_CSS
+    vlc_css_rules_Delete( p_sys->p_css_rules );
+#endif
+
     free( p_sys );
 }
 
@@ -1406,12 +1872,13 @@ int OpenDecoder( vlc_object_t *p_this )
     if( unlikely( p_sys == NULL ) )
         return VLC_ENOMEM;
 
-    p_sys->p_root = webvtt_dom_video_New();
+    p_sys->p_root = webvtt_dom_tag_New( NULL );
     if( !p_sys->p_root )
     {
         free( p_sys );
         return VLC_ENOMEM;
     }
+    p_sys->p_root->psz_tag = strdup( "video" );
 
     p_dec->pf_decode = DecodeBlock;
 




More information about the vlc-commits mailing list