Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
VttWriter
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 8
702
0.00% covered (danger)
0.00%
0 / 1
 write
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 formatCue
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 normalizeCueId
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 formatTimestamp
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
6
 fixNewlines
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatNodes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 formatNode
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
210
1<?php
2
3namespace MediaWiki\TimedMediaHandler\TimedText;
4
5class VttWriter extends Writer {
6    // https://www.w3.org/TR/webvtt1/
7    //
8    // note: webvtt ids may be alphanumeric
9    // note: position info may follow on the timestamp line
10    // todo: style blocks
11    // todo: note/comment blocks
12    // todo: region blocks
13    // todo: formatting
14    // todo: chapter markers -- is that the id?
15    // todo: metadata (json blobs)
16
17    /** @var true[] */
18    protected $usedIdentifiers = [];
19
20    /**
21     * @inheritDoc
22     */
23    public function write( $cues ) {
24        return "WEBVTT\n\n" . implode( "\n\n",
25            array_map(
26                [ $this, 'formatCue' ],
27                $cues
28            )
29        );
30    }
31
32    /**
33     * @param DOM\Cue $cue cue to output
34     * @return string
35     */
36    public function formatCue( DOM\Cue $cue ) {
37        return $this->normalizeCueId( $cue->id, $this->usedIdentifiers ) .
38            "\n" .
39            $this->formatTimestamp( $cue->start ) .
40            " --> " .
41            $this->formatTimestamp( $cue->end ) .
42            "\n" .
43            $this->fixNewlines( $this->formatNodes( $cue->nodes ) );
44    }
45
46    /**
47     * @param string $id
48     * @param array &$usedMap
49     *
50     * @return string
51     */
52    public function normalizeCueId( $id, &$usedMap ) {
53        // https://www.w3.org/TR/webvtt1/#webvtt-cue-identifier
54        $id = str_replace( "\n", "", $id );
55        $id = str_replace( "-->", "- ->", $id );
56
57        // Must be globally unique
58        if ( isset( $usedMap[$id] ) ) {
59            for ( $i = 2; ; $i++ ) {
60                $alt = "$id $i";
61                if ( !isset( $usedMap[$alt] ) ) {
62                    $id = $alt;
63                    break;
64                }
65            }
66        }
67        $usedMap[$id] = true;
68
69        return $id;
70    }
71
72    /**
73     * @param float $time
74     *
75     * @return string
76     */
77    public function formatTimestamp( $time ) {
78        $s = floor( $time );
79        $frac = $time - $s;
80        $millis = round( $frac * 1000.0 );
81
82        $seconds = $s % 60;
83        $s = ( $s - $seconds ) / 60;
84
85        $minutes = $s % 60;
86        $s = ( $s - $minutes ) / 60;
87
88        $hours = $s;
89
90        if ( $hours > 0 ) {
91            return sprintf( "%02d:%02d:%02d.%03d",
92                $hours,
93                $minutes,
94                $seconds,
95                $millis
96            );
97        }
98
99        return sprintf( "%02d:%02d.%03d",
100            $minutes,
101            $seconds,
102            $millis
103        );
104    }
105
106    /**
107     * @param string $text
108     *
109     * @return string
110     */
111    public function fixNewlines( $text ) {
112        // Cues must not contain blank lines, but may
113        // contain newlines as character references.
114        //
115        // @todo use &#10; instead of adding a space here;
116        // but that's not supported yet by VideoJS or Firefox.
117        return str_replace( "\n\n", "\n \n", $text );
118    }
119
120    /**
121     * @param string $text
122     *
123     * @return string
124     */
125    public function formatText( $text ) {
126        // < and > and & and friends are special, kinda like HTML
127        // but not exactly
128        return htmlspecialchars( $text, ENT_NOQUOTES | ENT_HTML5, 'utf-8' );
129    }
130
131    /**
132     * @param array $nodes
133     *
134     * @return string
135     */
136    public function formatNodes( $nodes ) {
137        $s = '';
138        foreach ( $nodes as $node ) {
139            $s .= $this->formatNode( $node );
140        }
141        return $s;
142    }
143
144    /**
145     * @param DOM\Node $node
146     *
147     * @return string
148     */
149    public function formatNode( DOM\Node $node ) {
150        if ( $node instanceof DOM\InternalNode ) {
151            if ( $node instanceof DOM\ClassNode ) {
152                $tag = 'c';
153            } elseif ( $node instanceof DOM\ItalicNode ) {
154                $tag = 'i';
155            } elseif ( $node instanceof DOM\BoldNode ) {
156                $tag = 'b';
157            } elseif ( $node instanceof DOM\UnderlineNode ) {
158                $tag = 'u';
159            } elseif ( $node instanceof DOM\RubyNode ) {
160                $tag = 'ruby';
161            } elseif ( $node instanceof DOM\RubyTextNode ) {
162                $tag = 'rt';
163            } elseif ( $node instanceof DOM\LanguageNode ) {
164                $tag = 'lang';
165            } else {
166                $tag = '';
167            }
168
169            $content = $this->formatNodes( $node->nodes );
170
171            if ( $tag ) {
172                $out = '<';
173                $out .= $tag;
174                foreach ( $node->classes as $class ) {
175                    $out .= '.';
176                    $out .= $class;
177                }
178                if ( $node->annotation !== '' ) {
179                    $out .= ' ';
180                    $out .= $node->annotation;
181                }
182                $out .= '>';
183                $out .= $content;
184                $out .= '</';
185                $out .= $tag;
186                $out .= '>';
187                return $out;
188            }
189
190            return $content;
191        }
192
193        if ( $node instanceof DOM\TextNode ) {
194            return $this->formatText( $node->text );
195        }
196
197        if ( $node instanceof DOM\TimestampNode ) {
198            return '<' . $this->formatTimestamp( $node->timestamp ) . '>';
199        }
200
201        return '';
202    }
203}