Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
43446750
authored
Sep 19, 2014
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add WebVTT parser.
parent
525b3097
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
295 additions
and
0 deletions
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
0 → 100644
View file @
43446750
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
text
.
webvtt
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.text.SubtitleParser
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.util.ArrayList
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* A simple WebVTT parser.
* <p>
* @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>
* <p>
*/
public
class
WebvttParser
implements
SubtitleParser
{
/**
* This parser allows a custom header to be prepended to the WebVTT data, in the form of a text
* line starting with this string.
*
* @hide
*/
public
static
final
String
EXO_HEADER
=
"EXO-HEADER"
;
/**
* A {@code OFFSET + value} element can be added to the custom header to specify an offset time
* (in microseconds) that should be subtracted from the embedded MPEGTS value.
*
* @hide
*/
public
static
final
String
OFFSET
=
"OFFSET:"
;
private
static
final
long
SAMPLING_RATE
=
90
;
private
static
final
String
WEBVTT_TIMESTAMP_STRING
=
"(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}"
;
private
static
final
Pattern
WEBVTT_TIMESTAMP
=
Pattern
.
compile
(
WEBVTT_TIMESTAMP_STRING
);
private
static
final
Pattern
MEDIA_TIMESTAMP_OFFSET
=
Pattern
.
compile
(
OFFSET
+
"\\d+"
);
private
static
final
Pattern
MEDIA_TIMESTAMP
=
Pattern
.
compile
(
"MPEGTS:\\d+"
);
@Override
public
WebvttSubtitle
parse
(
InputStream
inputStream
,
String
inputEncoding
,
long
startTimeUs
)
throws
IOException
{
ArrayList
<
WebvttCue
>
subtitles
=
new
ArrayList
<
WebvttCue
>();
long
mediaTimestampUs
=
startTimeUs
;
long
mediaTimestampOffsetUs
=
0
;
BufferedReader
webvttData
=
new
BufferedReader
(
new
InputStreamReader
(
inputStream
,
C
.
UTF8_NAME
));
String
line
;
// file should start with "WEBVTT" on the first line or "EXO-HEADER"
line
=
webvttData
.
readLine
();
if
(
line
==
null
)
{
throw
new
ParserException
(
"Expected WEBVTT or EXO-HEADER. Got null"
);
}
if
(
line
.
startsWith
(
EXO_HEADER
))
{
// parse the timestamp offset, if present
Matcher
matcher
=
MEDIA_TIMESTAMP_OFFSET
.
matcher
(
line
);
if
(
matcher
.
find
())
{
mediaTimestampOffsetUs
=
Long
.
parseLong
(
matcher
.
group
().
substring
(
7
));
}
// read the next line, which should now be WEBVTT
line
=
webvttData
.
readLine
();
if
(
line
==
null
)
{
throw
new
ParserException
(
"Expected WEBVTT. Got null"
);
}
}
if
(!
line
.
equals
(
"WEBVTT"
))
{
throw
new
ParserException
(
"Expected WEBVTT. Got "
+
line
);
}
// after "WEBVTT" there should be either an empty line or an "X-TIMESTAMP-MAP" line and then
// and empty line
line
=
webvttData
.
readLine
();
if
(!
line
.
isEmpty
())
{
if
(!
line
.
startsWith
(
"X-TIMESTAMP-MAP"
))
{
throw
new
ParserException
(
"Expected an empty line or X-TIMESTAMP-MAP. Got "
+
line
);
}
// parse the media timestamp
Matcher
matcher
=
MEDIA_TIMESTAMP
.
matcher
(
line
);
if
(!
matcher
.
find
())
{
throw
new
ParserException
(
"X-TIMESTAMP-MAP doesn't contain media timestmap: "
+
line
);
}
else
{
mediaTimestampUs
=
(
Long
.
parseLong
(
matcher
.
group
().
substring
(
7
))
*
1000
)
/
SAMPLING_RATE
-
mediaTimestampOffsetUs
;
}
mediaTimestampUs
=
getAdjustedStartTime
(
mediaTimestampUs
);
// read in the next line (which should be an empty line)
line
=
webvttData
.
readLine
();
}
if
(!
line
.
isEmpty
())
{
throw
new
ParserException
(
"Expected an empty line after WEBVTT or X-TIMESTAMP-MAP. Got "
+
line
);
}
// process the cues and text
while
((
line
=
webvttData
.
readLine
())
!=
null
)
{
// parse the cue timestamps
Matcher
matcher
=
WEBVTT_TIMESTAMP
.
matcher
(
line
);
long
startTime
;
long
endTime
;
String
text
=
""
;
// parse start timestamp
if
(!
matcher
.
find
())
{
throw
new
ParserException
(
"Expected cue start time: "
+
line
);
}
else
{
startTime
=
parseTimestampUs
(
matcher
.
group
())
+
mediaTimestampUs
;
}
// parse end timestamp
if
(!
matcher
.
find
())
{
throw
new
ParserException
(
"Expected cue end time: "
+
line
);
}
else
{
endTime
=
parseTimestampUs
(
matcher
.
group
())
+
mediaTimestampUs
;
}
// parse text
while
(((
line
=
webvttData
.
readLine
())
!=
null
)
&&
(!
line
.
isEmpty
()))
{
text
+=
line
.
trim
()
+
"\n"
;
}
WebvttCue
cue
=
new
WebvttCue
(
startTime
,
endTime
,
text
);
subtitles
.
add
(
cue
);
}
webvttData
.
close
();
inputStream
.
close
();
// copy WebvttCue data into arrays for WebvttSubtitle constructor
String
[]
cueText
=
new
String
[
subtitles
.
size
()];
long
[]
cueTimesUs
=
new
long
[
2
*
subtitles
.
size
()];
for
(
int
subtitleIndex
=
0
;
subtitleIndex
<
subtitles
.
size
();
subtitleIndex
++)
{
int
arrayIndex
=
subtitleIndex
*
2
;
WebvttCue
cue
=
subtitles
.
get
(
subtitleIndex
);
cueTimesUs
[
arrayIndex
]
=
cue
.
startTime
;
cueTimesUs
[
arrayIndex
+
1
]
=
cue
.
endTime
;
cueText
[
subtitleIndex
]
=
cue
.
text
;
}
WebvttSubtitle
subtitle
=
new
WebvttSubtitle
(
cueText
,
mediaTimestampUs
,
cueTimesUs
);
return
subtitle
;
}
@Override
public
boolean
canParse
(
String
mimeType
)
{
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
);
}
protected
long
getAdjustedStartTime
(
long
startTimeUs
)
{
return
startTimeUs
;
}
private
static
long
parseTimestampUs
(
String
s
)
throws
NumberFormatException
{
if
(!
s
.
matches
(
WEBVTT_TIMESTAMP_STRING
))
{
throw
new
NumberFormatException
(
"has invalid format"
);
}
String
[]
parts
=
s
.
split
(
"\\."
,
2
);
long
value
=
0
;
for
(
String
group
:
parts
[
0
].
split
(
":"
))
{
value
=
value
*
60
+
Long
.
parseLong
(
group
);
}
return
(
value
*
1000
+
Long
.
parseLong
(
parts
[
1
]))
*
1000
;
}
private
static
class
WebvttCue
{
public
final
long
startTime
;
public
final
long
endTime
;
public
final
String
text
;
public
WebvttCue
(
long
startTime
,
long
endTime
,
String
text
)
{
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
text
=
text
;
}
}
}
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java
0 → 100644
View file @
43446750
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
text
.
webvtt
;
import
com.google.android.exoplayer.text.Subtitle
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
import
java.util.Arrays
;
/**
* A representation of a WebVTT subtitle.
*/
public
class
WebvttSubtitle
implements
Subtitle
{
private
final
String
[]
cueText
;
private
final
long
startTimeUs
;
private
final
long
[]
cueTimesUs
;
private
final
long
[]
sortedCueTimesUs
;
/**
* @param cueText Text to be displayed during each cue.
* @param startTimeUs The start time of the subtitle.
* @param cueTimesUs Cue event times, where cueTimesUs[2 * i] and cueTimesUs[(2 * i) + 1] are
* the start and end times, respectively, corresponding to cueText[i].
*/
public
WebvttSubtitle
(
String
[]
cueText
,
long
startTimeUs
,
long
[]
cueTimesUs
)
{
this
.
cueText
=
cueText
;
this
.
startTimeUs
=
startTimeUs
;
this
.
cueTimesUs
=
cueTimesUs
;
this
.
sortedCueTimesUs
=
Arrays
.
copyOf
(
cueTimesUs
,
cueTimesUs
.
length
);
Arrays
.
sort
(
sortedCueTimesUs
);
}
@Override
public
long
getStartTime
()
{
return
startTimeUs
;
}
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
Assertions
.
checkArgument
(
timeUs
>=
0
);
int
index
=
Util
.
binarySearchCeil
(
sortedCueTimesUs
,
timeUs
,
false
,
false
);
return
index
<
sortedCueTimesUs
.
length
?
index
:
-
1
;
}
@Override
public
int
getEventTimeCount
()
{
return
sortedCueTimesUs
.
length
;
}
@Override
public
long
getEventTime
(
int
index
)
{
Assertions
.
checkArgument
(
index
>=
0
);
Assertions
.
checkArgument
(
index
<
sortedCueTimesUs
.
length
);
return
sortedCueTimesUs
[
index
];
}
@Override
public
long
getLastEventTime
()
{
if
(
getEventTimeCount
()
==
0
)
{
return
-
1
;
}
return
sortedCueTimesUs
[
sortedCueTimesUs
.
length
-
1
];
}
@Override
public
String
getText
(
long
timeUs
)
{
StringBuilder
subtitleStringBuilder
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
cueTimesUs
.
length
;
i
+=
2
)
{
if
((
cueTimesUs
[
i
]
<=
timeUs
)
&&
(
timeUs
<
cueTimesUs
[
i
+
1
]))
{
subtitleStringBuilder
.
append
(
cueText
[
i
/
2
]);
}
}
return
(
subtitleStringBuilder
.
length
()
>
0
)
?
subtitleStringBuilder
.
toString
()
:
null
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment