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
e4e02f91
authored
Sep 28, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Further improve WebVTT parser according to WebVTT spec
parent
71f542f7
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
399 additions
and
324 deletions
library/src/androidTest/assets/webvtt/live_typical
library/src/androidTest/assets/webvtt/typical_with_comments
library/src/androidTest/assets/webvtt/typical_with_metadata → library/src/androidTest/assets/webvtt/with_positioning
library/src/androidTest/assets/webvtt/typical_with_tags → library/src/androidTest/assets/webvtt/with_tags
library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java
library/src/main/java/com/google/android/exoplayer/text/Cue.java
library/src/main/java/com/google/android/exoplayer/text/CuePainter.java
library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCue.java
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
library/src/androidTest/assets/webvtt/live_typical
deleted
100644 → 0
View file @
71f542f7
WEBVTT
00:00.000 --> 00:01.234
This is the first subtitle.
00:02.345 --> 00:03.456
This is the second subtitle.
library/src/androidTest/assets/webvtt/typical_with_comments
View file @
e4e02f91
...
@@ -8,7 +8,9 @@ with multiple lines
...
@@ -8,7 +8,9 @@ with multiple lines
00:00.000 --> 00:01.234
00:00.000 --> 00:01.234
This is the first subtitle.
This is the first subtitle.
NOTE Single line comment
NOTE Single line comment with a space
NOTE Single line comment with a tab
2
2
00:02.345 --> 00:03.456
00:02.345 --> 00:03.456
...
...
library/src/androidTest/assets/webvtt/
typical_with_metadata
→
library/src/androidTest/assets/webvtt/
with_positioning
View file @
e4e02f91
...
@@ -16,7 +16,13 @@ NOTE Line as percentage and line alignment
...
@@ -16,7 +16,13 @@ NOTE Line as percentage and line alignment
00:04.000 --> 00:05.000 line:45%,end align:middle size:35%
00:04.000 --> 00:05.000 line:45%,end align:middle size:35%
This is the third subtitle.
This is the third subtitle.
NOTE Line as absolute negative number and without line alignment
NOTE Line as absolute negative number and without line alignment.
00:06.000 --> 00:07.000 line:-10 align:middle
This is the fourth subtitle.
NOTE The positioning alignment should be inherited from align.
00:07.000 --> 00:08.000 position:10% align:end size:10%
This is the fifth subtitle.
00:06.000 --> 00:07.000 line:-10 align:middle size:35%
This is the forth subtitle.
library/src/androidTest/assets/webvtt/
typical_
with_tags
→
library/src/androidTest/assets/webvtt/with_tags
View file @
e4e02f91
File moved
library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java
View file @
e4e02f91
...
@@ -15,32 +15,31 @@
...
@@ -15,32 +15,31 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
text
.
webvtt
;
package
com
.
google
.
android
.
exoplayer
.
text
.
webvtt
;
import
android.test.InstrumentationTestCase
;
import
android.text.Layout
;
import
com.google.android.exoplayer.text.Cue
;
import
com.google.android.exoplayer.text.Cue
;
import
android.test.InstrumentationTestCase
;
import
android.text.Layout.Alignment
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStream
;
import
java.util.List
;
/**
/**
* Unit test for {@link WebvttParser}.
* Unit test for {@link WebvttParser}.
*/
*/
public
class
WebvttParserTest
extends
InstrumentationTestCase
{
public
class
WebvttParserTest
extends
InstrumentationTestCase
{
private
static
final
String
TYPICAL_WEBVTT_FILE
=
"webvtt/typical"
;
private
static
final
String
TYPICAL_FILE
=
"webvtt/typical"
;
private
static
final
String
TYPICAL_WITH_IDS_WEBVTT_FILE
=
"webvtt/typical_with_identifiers"
;
private
static
final
String
TYPICAL_WITH_IDS_FILE
=
"webvtt/typical_with_identifiers"
;
private
static
final
String
TYPICAL_WITH_TAGS_WEBVTT_FILE
=
"webvtt/typical_with_tags"
;
private
static
final
String
TYPICAL_WITH_COMMENTS_FILE
=
"webvtt/typical_with_comments"
;
private
static
final
String
TYPICAL_WITH_COMMENTS_WEBVTT_FILE
=
"webvtt/typical_with_comments"
;
private
static
final
String
WITH_POSITIONING_FILE
=
"webvtt/with_positioning"
;
private
static
final
String
TYPICAL_WITH_METADATA_WEBVTT_FILE
=
"webvtt/typical_with_metadata"
;
private
static
final
String
WITH_TAGS_FILE
=
"webvtt/with_tags"
;
private
static
final
String
LIVE_TYPICAL_WEBVTT_FILE
=
"webvtt/live_typical"
;
private
static
final
String
EMPTY_FILE
=
"webvtt/empty"
;
private
static
final
String
EMPTY_WEBVTT_FILE
=
"webvtt/empty"
;
public
void
testParse
NullWebvttFile
()
throws
IOException
{
public
void
testParse
Empty
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
getInstrumentation
().
getContext
().
getResources
().
getAssets
().
open
(
EMPTY_WEBVTT_FILE
);
.
open
(
EMPTY_FILE
);
try
{
try
{
parser
.
parse
(
inputStream
);
parser
.
parse
(
inputStream
);
fail
(
"Expected IOException"
);
fail
(
"Expected IOException"
);
...
@@ -49,189 +48,113 @@ public class WebvttParserTest extends InstrumentationTestCase {
...
@@ -49,189 +48,113 @@ public class WebvttParserTest extends InstrumentationTestCase {
}
}
}
}
public
void
testParseTypical
WebvttFile
()
throws
IOException
{
public
void
testParseTypical
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
().
open
(
TYPICAL_
WEBVTT_
FILE
);
getInstrumentation
().
getContext
().
getResources
().
getAssets
().
open
(
TYPICAL_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
// test event count
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
// test first cue
// test cues
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertCue
(
subtitle
,
0
,
0
,
1234000
,
"This is the first subtitle."
);
assertEquals
(
"This is the first subtitle."
,
assertCue
(
subtitle
,
2
,
2345000
,
3456000
,
"This is the second subtitle."
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
1234000
,
subtitle
.
getEventTime
(
1
));
// test second cue
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the second subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
3
));
}
}
public
void
testParseTypicalWithIds
WebvttFile
()
throws
IOException
{
public
void
testParseTypicalWithIds
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
.
open
(
TYPICAL_WITH_IDS_FILE
);
.
open
(
TYPICAL_WITH_IDS_WEBVTT_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
// test event count
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
// test first cue
// test cues
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertCue
(
subtitle
,
0
,
0
,
1234000
,
"This is the first subtitle."
);
assertEquals
(
"This is the first subtitle."
,
assertCue
(
subtitle
,
2
,
2345000
,
3456000
,
"This is the second subtitle."
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
1234000
,
subtitle
.
getEventTime
(
1
));
// test second cue
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the second subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
3
));
}
}
public
void
testParseTypicalWith
TagsWebvttFile
()
throws
IOException
{
public
void
testParseTypicalWith
Comments
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
.
open
(
TYPICAL_WITH_COMMENTS_FILE
);
.
open
(
TYPICAL_WITH_TAGS_WEBVTT_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
assertEquals
(
8
,
subtitle
.
getEventTimeCount
());
// test first cue
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertEquals
(
"This is the first subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
1234000
,
subtitle
.
getEventTime
(
1
));
// test second cue
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the second subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
3
));
// test third cue
assertEquals
(
4000000
,
subtitle
.
getEventTime
(
4
));
assertEquals
(
"This is the third subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)).
get
(
0
).
text
.
toString
());
assertEquals
(
5000000
,
subtitle
.
getEventTime
(
5
));
// test fourth cue
assertEquals
(
6000000
,
subtitle
.
getEventTime
(
6
));
assertEquals
(
"This is the <fourth> &subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)).
get
(
0
).
text
.
toString
());
assertEquals
(
7000000
,
subtitle
.
getEventTime
(
7
));
}
public
void
testParseTypicalWithCommentsWebvttFile
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
.
open
(
TYPICAL_WITH_COMMENTS_WEBVTT_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
// test event count
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
// test first cue
// test cues
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertCue
(
subtitle
,
0
,
0
,
1234000
,
"This is the first subtitle."
);
assertEquals
(
"This is the first subtitle."
,
assertCue
(
subtitle
,
2
,
2345000
,
3456000
,
"This is the second subtitle."
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
1234000
,
subtitle
.
getEventTime
(
1
));
// test second cue
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the second subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
3
));
}
}
public
void
testParse
TypicalWithMetadataWebvttFile
()
throws
IOException
{
public
void
testParse
WithTags
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
.
open
(
WITH_TAGS_FILE
);
.
open
(
TYPICAL_WITH_METADATA_WEBVTT_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
// test event count
assertEquals
(
8
,
subtitle
.
getEventTimeCount
());
assertEquals
(
8
,
subtitle
.
getEventTimeCount
());
// test first cue
// test cues
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertCue
(
subtitle
,
0
,
0
,
1234000
,
"This is the first subtitle."
);
assertEquals
(
"This is the first subtitle."
,
assertCue
(
subtitle
,
2
,
2345000
,
3456000
,
"This is the second subtitle."
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertCue
(
subtitle
,
4
,
4000000
,
5000000
,
"This is the third subtitle."
);
assertEquals
(
10
,
assertCue
(
subtitle
,
6
,
6000000
,
7000000
,
"This is the <fourth> &subtitle."
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
position
);
assertEquals
(
Layout
.
Alignment
.
ALIGN_NORMAL
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
alignment
);
assertEquals
(
35
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
size
);
assertEquals
(
1234000
,
subtitle
.
getEventTime
(
1
));
// test second cue
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the second subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
Cue
.
UNSET_VALUE
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
position
);
assertEquals
(
Layout
.
Alignment
.
ALIGN_OPPOSITE
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
alignment
);
assertEquals
(
35
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
size
);
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
3
));
// test third cue
assertEquals
(
4000000
,
subtitle
.
getEventTime
(
4
));
assertEquals
(
"This is the third subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)).
get
(
0
).
text
.
toString
());
assertEquals
(
45
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)).
get
(
0
).
line
);
assertEquals
(
Layout
.
Alignment
.
ALIGN_CENTER
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)).
get
(
0
).
alignment
);
assertEquals
(
35
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)).
get
(
0
).
size
);
assertEquals
(
5000000
,
subtitle
.
getEventTime
(
5
));
// test forth cue
assertEquals
(
6000000
,
subtitle
.
getEventTime
(
6
));
assertEquals
(
"This is the forth subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)).
get
(
0
).
text
.
toString
());
assertEquals
(-
10
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)).
get
(
0
).
line
);
assertEquals
(
Layout
.
Alignment
.
ALIGN_CENTER
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)).
get
(
0
).
alignment
);
assertEquals
(
35
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)).
get
(
0
).
size
);
assertEquals
(
7000000
,
subtitle
.
getEventTime
(
7
));
}
}
public
void
testParse
LiveTypicalWebvttFile
()
throws
IOException
{
public
void
testParse
WithPositioning
()
throws
IOException
{
WebvttParser
parser
=
new
WebvttParser
();
WebvttParser
parser
=
new
WebvttParser
();
InputStream
inputStream
=
InputStream
inputStream
=
getInstrumentation
().
getContext
().
getResources
().
getAssets
()
getInstrumentation
().
getContext
().
getResources
().
getAssets
().
open
(
LIVE_TYPICAL_WEBVTT
_FILE
);
.
open
(
WITH_POSITIONING
_FILE
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
WebvttSubtitle
subtitle
=
parser
.
parse
(
inputStream
);
// test event count
// test event count
long
startTimeUs
=
0
;
assertEquals
(
10
,
subtitle
.
getEventTimeCount
());
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
// test cues
assertCue
(
subtitle
,
0
,
0
,
1234000
,
"This is the first subtitle."
,
Alignment
.
ALIGN_NORMAL
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
TYPE_UNSET
,
0.1f
,
Cue
.
ANCHOR_TYPE_START
,
0.35f
);
assertCue
(
subtitle
,
2
,
2345000
,
3456000
,
"This is the second subtitle."
,
Alignment
.
ALIGN_OPPOSITE
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
0.35f
);
assertCue
(
subtitle
,
4
,
4000000
,
5000000
,
"This is the third subtitle."
,
Alignment
.
ALIGN_CENTER
,
0.45f
,
Cue
.
LINE_TYPE_FRACTION
,
Cue
.
ANCHOR_TYPE_END
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
0.35f
);
assertCue
(
subtitle
,
6
,
6000000
,
7000000
,
"This is the fourth subtitle."
,
Alignment
.
ALIGN_CENTER
,
-
10
f
,
Cue
.
LINE_TYPE_NUMBER
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
);
assertCue
(
subtitle
,
8
,
7000000
,
8000000
,
"This is the fifth subtitle."
,
Alignment
.
ALIGN_OPPOSITE
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
TYPE_UNSET
,
0.1f
,
Cue
.
ANCHOR_TYPE_END
,
0.1f
);
}
private
static
void
assertCue
(
WebvttSubtitle
subtitle
,
int
eventTimeIndex
,
long
startTimeUs
,
int
endTimeUs
,
String
text
)
{
assertCue
(
subtitle
,
eventTimeIndex
,
startTimeUs
,
endTimeUs
,
text
,
null
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
);
}
// test first cue
private
static
void
assertCue
(
WebvttSubtitle
subtitle
,
int
eventTimeIndex
,
long
startTimeUs
,
assertEquals
(
startTimeUs
,
subtitle
.
getEventTime
(
0
));
int
endTimeUs
,
String
text
,
Alignment
textAlignment
,
float
line
,
int
lineType
,
int
lineAnchor
,
assertEquals
(
"This is the first subtitle."
,
float
position
,
int
positionAnchor
,
float
size
)
{
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
startTimeUs
,
subtitle
.
getEventTime
(
eventTimeIndex
));
assertEquals
(
startTimeUs
+
1234000
,
subtitle
.
getEventTime
(
1
));
assertEquals
(
endTimeUs
,
subtitle
.
getEventTime
(
eventTimeIndex
+
1
));
List
<
Cue
>
cues
=
subtitle
.
getCues
(
subtitle
.
getEventTime
(
eventTimeIndex
));
// test second cue
assertEquals
(
1
,
cues
.
size
());
assertEquals
(
startTimeUs
+
2345000
,
subtitle
.
getEventTime
(
2
));
// Assert cue properties
assertEquals
(
"This is the second subtitle."
,
Cue
cue
=
cues
.
get
(
0
);
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
assertEquals
(
text
,
cue
.
text
.
toString
());
assertEquals
(
startTimeUs
+
3456000
,
subtitle
.
getEventTime
(
3
));
assertEquals
(
textAlignment
,
cue
.
textAlignment
);
assertEquals
(
line
,
cue
.
line
);
assertEquals
(
lineType
,
cue
.
lineType
);
assertEquals
(
lineAnchor
,
cue
.
lineAnchor
);
assertEquals
(
position
,
cue
.
position
);
assertEquals
(
positionAnchor
,
cue
.
positionAnchor
);
assertEquals
(
size
,
cue
.
size
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/text/Cue.java
View file @
e4e02f91
...
@@ -23,30 +23,117 @@ import android.text.Layout.Alignment;
...
@@ -23,30 +23,117 @@ import android.text.Layout.Alignment;
public
class
Cue
{
public
class
Cue
{
/**
/**
*
Used by some methods to indicate that no value is set
.
*
An unset position or width
.
*/
*/
public
static
final
int
UNSET_VALUE
=
-
1
;
public
static
final
float
DIMEN_UNSET
=
Float
.
MIN_VALUE
;
/**
* An unset anchor or line type value.
*/
public
static
final
int
TYPE_UNSET
=
Integer
.
MIN_VALUE
;
/**
* Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue
* box.
*/
public
static
final
int
ANCHOR_TYPE_START
=
0
;
/**
* Anchors the middle of the cue box.
*/
public
static
final
int
ANCHOR_TYPE_MIDDLE
=
1
;
/**
* Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue
* box.
*/
public
static
final
int
ANCHOR_TYPE_END
=
2
;
/**
* Value for {@link #lineType} when {@link #line} is a fractional position.
*/
public
static
final
int
LINE_TYPE_FRACTION
=
0
;
/**
* Value for {@link #lineType} when {@link #line} is a line number.
*/
public
static
final
int
LINE_TYPE_NUMBER
=
1
;
/**
* The cue text. Note the {@link CharSequence} may be decorated with styling spans.
*/
public
final
CharSequence
text
;
public
final
CharSequence
text
;
/**
public
final
int
line
;
* The alignment of the cue text within the cue box.
public
final
int
position
;
*/
public
final
Alignment
alignment
;
public
final
Alignment
textAlignment
;
public
final
int
size
;
/**
* The position of the {@link #lineAnchor} of the cue box within the viewport in the direction
* orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of
* the value depends on the value of {@link #lineType}.
* <p>
* For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the
* fractional vertical position relative to the top of the viewport.
*/
public
final
float
line
;
/**
* The type of the {@link #line} value.
* <p>
* {@link #LINE_TYPE_FRACTION} indicates that {@link #line} is a fractional position within the
* viewport.
* <p>
* {@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size of each
* line is taken to be the size of the first line of the cue. When {@link #line} is greater than
* or equal to 0, lines count from the start of the viewport (the first line is numbered 0). When
* {@link #line} is negative, lines count from the end of the viewport (the last line is numbered
* -1). For horizontal text the size of the first line of the cue is its height, and the start
* and end of the viewport are the top and bottom respectively.
*/
public
final
int
lineType
;
/**
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
* <p>
* For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE}
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
* respectively.
*/
public
final
int
lineAnchor
;
/**
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
* the direction orthogonal to {@link #line}, or {@link #DIMEN_UNSET}.
* <p>
* For horizontal text, this is the horizontal position relative to the left of the viewport. Note
* that positioning is relative to the left of the viewport even in the case of right-to-left
* text.
*/
public
final
float
position
;
/**
* The cue box anchor positioned by {@link #position}. One of {@link #ANCHOR_TYPE_START},
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
* <p>
* For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE}
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
* respectively.
*/
public
final
int
positionAnchor
;
/**
* The size of the cue box in the writing direction specified as a fraction of the viewport size
* in that direction, or {@link #DIMEN_UNSET}.
*/
public
final
float
size
;
public
Cue
()
{
public
Cue
()
{
this
(
null
);
this
(
null
);
}
}
public
Cue
(
CharSequence
text
)
{
public
Cue
(
CharSequence
text
)
{
this
(
text
,
UNSET_VALUE
,
UNSET_VALUE
,
null
,
UNSET_VALUE
);
this
(
text
,
null
,
DIMEN_UNSET
,
TYPE_UNSET
,
TYPE_UNSET
,
DIMEN_UNSET
,
TYPE_UNSET
,
DIMEN_UNSET
);
}
}
public
Cue
(
CharSequence
text
,
int
line
,
int
position
,
Alignment
alignment
,
int
size
)
{
public
Cue
(
CharSequence
text
,
Alignment
textAlignment
,
float
line
,
int
lineType
,
int
lineAnchor
,
float
position
,
int
positionAnchor
,
float
size
)
{
this
.
text
=
text
;
this
.
text
=
text
;
this
.
textAlignment
=
textAlignment
;
this
.
line
=
line
;
this
.
line
=
line
;
this
.
lineType
=
lineType
;
this
.
lineAnchor
=
lineAnchor
;
this
.
position
=
position
;
this
.
position
=
position
;
this
.
alignment
=
alignment
;
this
.
positionAnchor
=
positionAnchor
;
this
.
size
=
size
;
this
.
size
=
size
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/text/CuePainter.java
View file @
e4e02f91
...
@@ -63,8 +63,13 @@ import android.util.Log;
...
@@ -63,8 +63,13 @@ import android.util.Log;
// Previous input variables.
// Previous input variables.
private
CharSequence
cueText
;
private
CharSequence
cueText
;
private
int
cuePosition
;
private
Alignment
cueTextAlignment
;
private
Alignment
cueAlignment
;
private
float
cueLine
;
private
int
cueLineType
;
private
int
cueLineAnchor
;
private
float
cuePosition
;
private
int
cuePositionAnchor
;
private
float
cueSize
;
private
boolean
applyEmbeddedStyles
;
private
boolean
applyEmbeddedStyles
;
private
int
foregroundColor
;
private
int
foregroundColor
;
private
int
backgroundColor
;
private
int
backgroundColor
;
...
@@ -120,7 +125,7 @@ import android.util.Log;
...
@@ -120,7 +125,7 @@ import android.util.Log;
* @param style The style to use when drawing the cue text.
* @param style The style to use when drawing the cue text.
* @param textSizePx The text size to use when drawing the cue text, in pixels.
* @param textSizePx The text size to use when drawing the cue text, in pixels.
* @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is
* @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is
* {@link Cue#
UNSET_VALUE
}, as a fraction of the viewport height
* {@link Cue#
DIMEN_UNSET
}, as a fraction of the viewport height
* @param canvas The canvas into which to draw.
* @param canvas The canvas into which to draw.
* @param cueBoxLeft The left position of the enclosing cue box.
* @param cueBoxLeft The left position of the enclosing cue box.
* @param cueBoxTop The top position of the enclosing cue box.
* @param cueBoxTop The top position of the enclosing cue box.
...
@@ -140,8 +145,13 @@ import android.util.Log;
...
@@ -140,8 +145,13 @@ import android.util.Log;
cueText
=
cueText
.
toString
();
cueText
=
cueText
.
toString
();
}
}
if
(
areCharSequencesEqual
(
this
.
cueText
,
cueText
)
if
(
areCharSequencesEqual
(
this
.
cueText
,
cueText
)
&&
Util
.
areEqual
(
this
.
cueTextAlignment
,
cue
.
textAlignment
)
&&
this
.
cueLine
==
cue
.
line
&&
this
.
cueLineType
==
cue
.
lineType
&&
Util
.
areEqual
(
this
.
cueLineAnchor
,
cue
.
lineAnchor
)
&&
this
.
cuePosition
==
cue
.
position
&&
this
.
cuePosition
==
cue
.
position
&&
Util
.
areEqual
(
this
.
cueAlignment
,
cue
.
alignment
)
&&
Util
.
areEqual
(
this
.
cuePositionAnchor
,
cue
.
positionAnchor
)
&&
this
.
cueSize
==
cue
.
size
&&
this
.
applyEmbeddedStyles
==
applyEmbeddedStyles
&&
this
.
applyEmbeddedStyles
==
applyEmbeddedStyles
&&
this
.
foregroundColor
==
style
.
foregroundColor
&&
this
.
foregroundColor
==
style
.
foregroundColor
&&
this
.
backgroundColor
==
style
.
backgroundColor
&&
this
.
backgroundColor
==
style
.
backgroundColor
...
@@ -161,8 +171,13 @@ import android.util.Log;
...
@@ -161,8 +171,13 @@ import android.util.Log;
}
}
this
.
cueText
=
cueText
;
this
.
cueText
=
cueText
;
this
.
cueTextAlignment
=
cue
.
textAlignment
;
this
.
cueLine
=
cue
.
line
;
this
.
cueLineType
=
cue
.
lineType
;
this
.
cueLineAnchor
=
cue
.
lineAnchor
;
this
.
cuePosition
=
cue
.
position
;
this
.
cuePosition
=
cue
.
position
;
this
.
cueAlignment
=
cue
.
alignment
;
this
.
cuePositionAnchor
=
cue
.
positionAnchor
;
this
.
cueSize
=
cue
.
size
;
this
.
applyEmbeddedStyles
=
applyEmbeddedStyles
;
this
.
applyEmbeddedStyles
=
applyEmbeddedStyles
;
this
.
foregroundColor
=
style
.
foregroundColor
;
this
.
foregroundColor
=
style
.
foregroundColor
;
this
.
backgroundColor
=
style
.
backgroundColor
;
this
.
backgroundColor
=
style
.
backgroundColor
;
...
@@ -182,16 +197,19 @@ import android.util.Log;
...
@@ -182,16 +197,19 @@ import android.util.Log;
textPaint
.
setTextSize
(
textSizePx
);
textPaint
.
setTextSize
(
textSizePx
);
int
textPaddingX
=
(
int
)
(
textSizePx
*
INNER_PADDING_RATIO
+
0.5f
);
int
textPaddingX
=
(
int
)
(
textSizePx
*
INNER_PADDING_RATIO
+
0.5f
);
int
availableWidth
=
parentWidth
-
textPaddingX
*
2
;
int
availableWidth
=
parentWidth
-
textPaddingX
*
2
;
if
(
cueSize
!=
Cue
.
DIMEN_UNSET
)
{
availableWidth
=
(
int
)
(
availableWidth
*
cueSize
);
}
if
(
availableWidth
<=
0
)
{
if
(
availableWidth
<=
0
)
{
Log
.
w
(
TAG
,
"Skipped drawing subtitle cue (insufficient space)"
);
Log
.
w
(
TAG
,
"Skipped drawing subtitle cue (insufficient space)"
);
return
;
return
;
}
}
Alignment
layoutAlignment
=
cueAlignment
==
null
?
Alignment
.
ALIGN_CENTER
:
cue
Alignment
;
Alignment
textAlignment
=
cueTextAlignment
==
null
?
Alignment
.
ALIGN_CENTER
:
cueText
Alignment
;
textLayout
=
new
StaticLayout
(
cueText
,
textPaint
,
availableWidth
,
layou
tAlignment
,
spacingMult
,
textLayout
=
new
StaticLayout
(
cueText
,
textPaint
,
availableWidth
,
tex
tAlignment
,
spacingMult
,
spacingAdd
,
true
);
spacingAdd
,
true
);
int
textHeight
=
textLayout
.
getHeight
();
int
textHeight
=
textLayout
.
getHeight
();
int
textWidth
=
0
;
int
textWidth
=
0
;
int
lineCount
=
textLayout
.
getLineCount
();
int
lineCount
=
textLayout
.
getLineCount
();
...
@@ -202,14 +220,13 @@ import android.util.Log;
...
@@ -202,14 +220,13 @@ import android.util.Log;
int
textLeft
;
int
textLeft
;
int
textRight
;
int
textRight
;
if
(
cue
.
position
!=
Cue
.
UNSET_VALUE
)
{
if
(
cue
Position
!=
Cue
.
DIMEN_UNSET
)
{
i
f
(
cue
.
alignment
==
Alignment
.
ALIGN_OPPOSITE
)
{
i
nt
anchorPosition
=
Math
.
round
(
parentWidth
*
cuePosition
)
+
parentLeft
;
textRight
=
(
parentWidth
*
cue
.
position
)
/
100
+
parentLeft
;
textLeft
=
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_END
?
anchorPosition
-
textWidth
textLeft
=
Math
.
max
(
textRight
-
textWidth
,
parentLeft
);
:
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorPosition
*
2
-
textWidth
)
/
2
}
else
{
:
anchorPosition
;
textLeft
=
(
parentWidth
*
cue
.
position
)
/
100
+
parentLeft
;
textLeft
=
Math
.
max
(
textLeft
,
parentLeft
)
;
textRight
=
Math
.
min
(
textLeft
+
textWidth
,
parentRight
);
textRight
=
Math
.
min
(
textLeft
+
textWidth
,
parentRight
);
}
}
else
{
}
else
{
textLeft
=
(
parentWidth
-
textWidth
)
/
2
;
textLeft
=
(
parentWidth
-
textWidth
)
/
2
;
textRight
=
textLeft
+
textWidth
;
textRight
=
textLeft
+
textWidth
;
...
@@ -217,12 +234,29 @@ import android.util.Log;
...
@@ -217,12 +234,29 @@ import android.util.Log;
int
textTop
;
int
textTop
;
int
textBottom
;
int
textBottom
;
if
(
cue
.
line
!=
Cue
.
UNSET_VALUE
)
{
if
(
cueLine
!=
Cue
.
DIMEN_UNSET
)
{
textTop
=
(
parentHeight
*
cue
.
line
)
/
100
+
parentTop
;
int
anchorPosition
;
if
(
cueLineType
==
Cue
.
LINE_TYPE_FRACTION
)
{
anchorPosition
=
Math
.
round
(
parentHeight
*
cueLine
)
+
parentTop
;
}
else
{
// cueLineType == Cue.LINE_TYPE_NUMBER
int
firstLineHeight
=
textLayout
.
getLineBottom
(
0
)
-
textLayout
.
getLineTop
(
0
);
if
(
cueLine
>=
0
)
{
anchorPosition
=
Math
.
round
(
cueLine
*
firstLineHeight
)
+
parentTop
;
}
else
{
anchorPosition
=
Math
.
round
(
cueLine
*
firstLineHeight
)
+
parentBottom
;
}
}
textTop
=
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_END
?
anchorPosition
-
textHeight
:
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorPosition
*
2
-
textHeight
)
/
2
:
anchorPosition
;
textBottom
=
textTop
+
textHeight
;
textBottom
=
textTop
+
textHeight
;
if
(
textBottom
>
parentBottom
)
{
if
(
textBottom
>
parentBottom
)
{
textTop
=
parentBottom
-
textHeight
;
textTop
=
parentBottom
-
textHeight
;
textBottom
=
parentBottom
;
textBottom
=
parentBottom
;
}
else
if
(
textTop
<
parentTop
)
{
textTop
=
parentTop
;
textBottom
=
parentTop
+
textHeight
;
}
}
}
else
{
}
else
{
textTop
=
parentBottom
-
textHeight
-
(
int
)
(
parentHeight
*
bottomPaddingFraction
);
textTop
=
parentBottom
-
textHeight
-
(
int
)
(
parentHeight
*
bottomPaddingFraction
);
...
@@ -232,7 +266,7 @@ import android.util.Log;
...
@@ -232,7 +266,7 @@ import android.util.Log;
textWidth
=
textRight
-
textLeft
;
textWidth
=
textRight
-
textLeft
;
// Update the derived drawing variables.
// Update the derived drawing variables.
this
.
textLayout
=
new
StaticLayout
(
cueText
,
textPaint
,
textWidth
,
layou
tAlignment
,
spacingMult
,
this
.
textLayout
=
new
StaticLayout
(
cueText
,
textPaint
,
textWidth
,
tex
tAlignment
,
spacingMult
,
spacingAdd
,
true
);
spacingAdd
,
true
);
this
.
textLeft
=
textLeft
;
this
.
textLeft
=
textLeft
;
this
.
textTop
=
textTop
;
this
.
textTop
=
textTop
;
...
...
library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java
View file @
e4e02f91
...
@@ -38,7 +38,7 @@ public final class SubtitleLayout extends View {
...
@@ -38,7 +38,7 @@ public final class SubtitleLayout extends View {
public
static
final
float
DEFAULT_TEXT_SIZE_FRACTION
=
0.0533f
;
public
static
final
float
DEFAULT_TEXT_SIZE_FRACTION
=
0.0533f
;
/**
/**
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#
UNSET_VALUE
}, as a
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#
DIMEN_UNSET
}, as a
* fraction of the viewport height.
* fraction of the viewport height.
*
*
* @see #setBottomPaddingFraction(float)
* @see #setBottomPaddingFraction(float)
...
@@ -174,7 +174,7 @@ public final class SubtitleLayout extends View {
...
@@ -174,7 +174,7 @@ public final class SubtitleLayout extends View {
}
}
/**
/**
* Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#
UNSET_VALUE
},
* Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#
DIMEN_UNSET
},
* as a fraction of the view's remaining height after its top and bottom padding have been
* as a fraction of the view's remaining height after its top and bottom padding have been
* subtracted.
* subtracted.
* <p>
* <p>
...
...
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCue.java
View file @
e4e02f91
...
@@ -28,16 +28,17 @@ import android.text.Layout.Alignment;
...
@@ -28,16 +28,17 @@ import android.text.Layout.Alignment;
public
final
long
endTime
;
public
final
long
endTime
;
public
WebvttCue
(
CharSequence
text
)
{
public
WebvttCue
(
CharSequence
text
)
{
this
(
Cue
.
UNSET_VALUE
,
Cue
.
UNSET_VALUE
,
text
);
this
(
0
,
0
,
text
);
}
}
public
WebvttCue
(
long
startTime
,
long
endTime
,
CharSequence
text
)
{
public
WebvttCue
(
long
startTime
,
long
endTime
,
CharSequence
text
)
{
this
(
startTime
,
endTime
,
text
,
Cue
.
UNSET_VALUE
,
Cue
.
UNSET_VALUE
,
null
,
Cue
.
UNSET_VALUE
);
this
(
startTime
,
endTime
,
text
,
null
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
,
Cue
.
TYPE_UNSET
,
Cue
.
DIMEN_UNSET
);
}
}
public
WebvttCue
(
long
startTime
,
long
endTime
,
CharSequence
text
,
int
line
,
int
position
,
public
WebvttCue
(
long
startTime
,
long
endTime
,
CharSequence
text
,
Alignment
textAlignment
,
Alignment
alignment
,
int
size
)
{
float
line
,
int
lineType
,
int
lineAnchor
,
float
position
,
int
positionAnchor
,
float
width
)
{
super
(
text
,
line
,
position
,
alignment
,
size
);
super
(
text
,
textAlignment
,
line
,
lineType
,
lineAnchor
,
position
,
positionAnchor
,
width
);
this
.
startTime
=
startTime
;
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
endTime
=
endTime
;
}
}
...
@@ -49,7 +50,7 @@ import android.text.Layout.Alignment;
...
@@ -49,7 +50,7 @@ import android.text.Layout.Alignment;
* @return True if this cue should be placed in the default position; false otherwise.
* @return True if this cue should be placed in the default position; false otherwise.
*/
*/
public
boolean
isNormalCue
()
{
public
boolean
isNormalCue
()
{
return
(
line
==
UNSET_VALUE
&&
position
==
UNSET_VALUE
);
return
(
line
==
DIMEN_UNSET
&&
position
==
DIMEN_UNSET
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
View file @
e4e02f91
...
@@ -24,7 +24,6 @@ import com.google.android.exoplayer.util.MimeTypes;
...
@@ -24,7 +24,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import
android.text.Html
;
import
android.text.Html
;
import
android.text.Layout.Alignment
;
import
android.text.Layout.Alignment
;
import
android.util.Log
;
import
android.util.Log
;
import
android.util.Pair
;
import
java.io.BufferedReader
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.IOException
;
...
@@ -43,34 +42,15 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -43,34 +42,15 @@ public final class WebvttParser implements SubtitleParser {
private
static
final
String
TAG
=
"WebvttParser"
;
private
static
final
String
TAG
=
"WebvttParser"
;
private
static
final
String
WEBVTT_FILE_HEADER_STRING
=
"^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"
;
private
static
final
Pattern
HEADER
=
Pattern
.
compile
(
"^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"
);
private
static
final
Pattern
WEBVTT_FILE_HEADER
=
private
static
final
Pattern
COMMENT_BLOCK
=
Pattern
.
compile
(
"^NOTE((\u0020|\u0009).*)?$"
);
Pattern
.
compile
(
WEBVTT_FILE_HEADER_STRING
);
private
static
final
Pattern
METADATA_HEADER
=
Pattern
.
compile
(
"\\S*[:=]\\S*"
);
private
static
final
Pattern
CUE_IDENTIFIER
=
Pattern
.
compile
(
"^(?!.*(-->)).*$"
);
private
static
final
String
WEBVTT_COMMENT_BLOCK_STRING
=
"^NOTE((\u0020|\u0009).*)?$"
;
private
static
final
Pattern
TIMESTAMP
=
Pattern
.
compile
(
"(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}"
);
private
static
final
Pattern
WEBVTT_COMMENT_BLOCK
=
private
static
final
Pattern
CUE_SETTING
=
Pattern
.
compile
(
"\\S*:\\S*"
);
Pattern
.
compile
(
WEBVTT_COMMENT_BLOCK_STRING
);
private
static
final
String
WEBVTT_METADATA_HEADER_STRING
=
"\\S*[:=]\\S*"
;
private
static
final
Pattern
WEBVTT_METADATA_HEADER
=
Pattern
.
compile
(
WEBVTT_METADATA_HEADER_STRING
);
private
static
final
String
WEBVTT_CUE_IDENTIFIER_STRING
=
"^(?!.*(-->)).*$"
;
private
static
final
Pattern
WEBVTT_CUE_IDENTIFIER
=
Pattern
.
compile
(
WEBVTT_CUE_IDENTIFIER_STRING
);
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
String
WEBVTT_CUE_SETTING_STRING
=
"\\S*:\\S*"
;
private
static
final
Pattern
WEBVTT_CUE_SETTING
=
Pattern
.
compile
(
WEBVTT_CUE_SETTING_STRING
);
private
static
final
String
WEBVTT_PERCENTAGE_NUMBER_STRING
=
"^([0-9]+|[0-9]+\\.[0-9]+)$"
;
private
static
final
String
NON_NUMERIC_STRING
=
".*[^0-9].*"
;
private
final
PositionHolder
positionHolder
;
private
final
StringBuilder
textBuilder
;
private
final
StringBuilder
textBuilder
;
private
final
boolean
strictParsing
;
private
final
boolean
strictParsing
;
/**
/**
...
@@ -88,6 +68,7 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -88,6 +68,7 @@ public final class WebvttParser implements SubtitleParser {
*/
*/
public
WebvttParser
(
boolean
strictParsing
)
{
public
WebvttParser
(
boolean
strictParsing
)
{
this
.
strictParsing
=
strictParsing
;
this
.
strictParsing
=
strictParsing
;
positionHolder
=
new
PositionHolder
();
textBuilder
=
new
StringBuilder
();
textBuilder
=
new
StringBuilder
();
}
}
...
@@ -100,7 +81,7 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -100,7 +81,7 @@ public final class WebvttParser implements SubtitleParser {
// file should start with "WEBVTT"
// file should start with "WEBVTT"
line
=
webvttData
.
readLine
();
line
=
webvttData
.
readLine
();
if
(
line
==
null
||
!
WEBVTT_FILE_
HEADER
.
matcher
(
line
).
matches
())
{
if
(
line
==
null
||
!
HEADER
.
matcher
(
line
).
matches
())
{
throw
new
ParserException
(
"Expected WEBVTT. Got "
+
line
);
throw
new
ParserException
(
"Expected WEBVTT. Got "
+
line
);
}
}
...
@@ -116,7 +97,7 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -116,7 +97,7 @@ public final class WebvttParser implements SubtitleParser {
}
}
if
(
strictParsing
)
{
if
(
strictParsing
)
{
Matcher
matcher
=
WEBVTT_
METADATA_HEADER
.
matcher
(
line
);
Matcher
matcher
=
METADATA_HEADER
.
matcher
(
line
);
if
(!
matcher
.
find
())
{
if
(!
matcher
.
find
())
{
throw
new
ParserException
(
"Unexpected line: "
+
line
);
throw
new
ParserException
(
"Unexpected line: "
+
line
);
}
}
...
@@ -126,38 +107,45 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -126,38 +107,45 @@ public final class WebvttParser implements SubtitleParser {
// process the cues and text
// process the cues and text
while
((
line
=
webvttData
.
readLine
())
!=
null
)
{
while
((
line
=
webvttData
.
readLine
())
!=
null
)
{
// parse webvtt comment block in case it is present
// parse webvtt comment block in case it is present
Matcher
matcher
=
WEBVTT_
COMMENT_BLOCK
.
matcher
(
line
);
Matcher
matcher
=
COMMENT_BLOCK
.
matcher
(
line
);
if
(
matcher
.
find
())
{
if
(
matcher
.
find
())
{
// read lines until finding an empty one (webvtt line terminator: CRLF, or LF or CR)
// read lines until finding an empty one (webvtt line terminator: CRLF, or LF or CR)
while
((
(
line
=
webvttData
.
readLine
())
!=
null
)
&&
(!
line
.
isEmpty
()
))
{
while
((
line
=
webvttData
.
readLine
())
!=
null
&&
!
line
.
isEmpty
(
))
{
//
just ignoring
comment text
//
ignore
comment text
}
}
continue
;
continue
;
}
}
// parse the cue identifier (if present) {
// parse the cue identifier (if present) {
matcher
=
WEBVTT_
CUE_IDENTIFIER
.
matcher
(
line
);
matcher
=
CUE_IDENTIFIER
.
matcher
(
line
);
if
(
matcher
.
find
())
{
if
(
matcher
.
find
())
{
// ignore the identifier (we currently don't use it) and read the next line
// ignore the identifier (we currently don't use it) and read the next line
line
=
webvttData
.
readLine
();
line
=
webvttData
.
readLine
();
if
(
line
==
null
)
{
// end of file
break
;
}
}
}
long
startTime
=
Cue
.
UNSET_VALUE
;
long
cueStartTime
;
long
endTime
=
Cue
.
UNSET_VALUE
;
long
cueEndTime
;
CharSequence
text
=
null
;
CharSequence
cueText
;
int
lineNum
=
Cue
.
UNSET_VALUE
;
Alignment
cueTextAlignment
=
null
;
int
position
=
Cue
.
UNSET_VALUE
;
float
cueLine
=
Cue
.
DIMEN_UNSET
;
Alignment
alignment
=
null
;
int
cueLineType
=
Cue
.
TYPE_UNSET
;
int
size
=
Cue
.
UNSET_VALUE
;
int
cueLineAnchor
=
Cue
.
TYPE_UNSET
;
float
cuePosition
=
Cue
.
DIMEN_UNSET
;
int
cuePositionAnchor
=
Cue
.
TYPE_UNSET
;
float
cueWidth
=
Cue
.
DIMEN_UNSET
;
// parse the cue timestamps
// parse the cue timestamps
matcher
=
WEBVTT_
TIMESTAMP
.
matcher
(
line
);
matcher
=
TIMESTAMP
.
matcher
(
line
);
// parse start timestamp
// parse start timestamp
if
(!
matcher
.
find
())
{
if
(!
matcher
.
find
())
{
throw
new
ParserException
(
"Expected cue start time: "
+
line
);
throw
new
ParserException
(
"Expected cue start time: "
+
line
);
}
else
{
}
else
{
s
tartTime
=
parseTimestampUs
(
matcher
.
group
());
cueS
tartTime
=
parseTimestampUs
(
matcher
.
group
());
}
}
// parse end timestamp
// parse end timestamp
...
@@ -166,12 +154,12 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -166,12 +154,12 @@ public final class WebvttParser implements SubtitleParser {
throw
new
ParserException
(
"Expected cue end time: "
+
line
);
throw
new
ParserException
(
"Expected cue end time: "
+
line
);
}
else
{
}
else
{
endTimeString
=
matcher
.
group
();
endTimeString
=
matcher
.
group
();
e
ndTime
=
parseTimestampUs
(
endTimeString
);
cueE
ndTime
=
parseTimestampUs
(
endTimeString
);
}
}
// parse the (optional) cue setting list
// parse the (optional) cue setting list
line
=
line
.
substring
(
line
.
indexOf
(
endTimeString
)
+
endTimeString
.
length
());
line
=
line
.
substring
(
line
.
indexOf
(
endTimeString
)
+
endTimeString
.
length
());
matcher
=
WEBVTT_
CUE_SETTING
.
matcher
(
line
);
matcher
=
CUE_SETTING
.
matcher
(
line
);
while
(
matcher
.
find
())
{
while
(
matcher
.
find
())
{
String
match
=
matcher
.
group
();
String
match
=
matcher
.
group
();
String
[]
parts
=
match
.
split
(
":"
,
2
);
String
[]
parts
=
match
.
split
(
":"
,
2
);
...
@@ -180,52 +168,44 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -180,52 +168,44 @@ public final class WebvttParser implements SubtitleParser {
try
{
try
{
if
(
"line"
.
equals
(
name
))
{
if
(
"line"
.
equals
(
name
))
{
Pair
<
String
,
Alignment
>
lineMetadata
=
parseLinePositionAttributes
(
value
);
parseLineAttribute
(
value
,
positionHolder
);
value
=
lineMetadata
.
first
;
cueLine
=
positionHolder
.
position
;
if
(
value
.
endsWith
(
"%"
))
{
cueLineType
=
positionHolder
.
lineType
;
lineNum
=
parseIntPercentage
(
value
);
cueLineAnchor
=
positionHolder
.
positionAnchor
;
}
else
{
// Following WebVTT spec, line number can be a negative number
int
sign
=
1
;
if
(
value
.
startsWith
(
"-"
)
&&
value
.
length
()
>
1
)
{
sign
=
-
1
;
value
=
value
.
substring
(
1
);
}
if
(
value
.
matches
(
NON_NUMERIC_STRING
))
{
Log
.
w
(
TAG
,
"Invalid line value: "
+
value
);
}
else
{
lineNum
=
sign
*
Integer
.
parseInt
(
value
);
}
}
}
else
if
(
"align"
.
equals
(
name
))
{
}
else
if
(
"align"
.
equals
(
name
))
{
// TODO: handle for RTL languages
cueTextAlignment
=
parseTextAlignment
(
value
);
alignment
=
parseAlignment
(
value
);
}
else
if
(
"position"
.
equals
(
name
))
{
}
else
if
(
"position"
.
equals
(
name
))
{
Pair
<
String
,
Alignment
>
lineMetadata
=
parseLinePositionAttributes
(
value
);
parsePositionAttribute
(
value
,
positionHolder
);
value
=
lineMetadata
.
first
;
cuePosition
=
positionHolder
.
position
;
position
=
parseIntPercentage
(
value
)
;
cuePositionAnchor
=
positionHolder
.
positionAnchor
;
}
else
if
(
"size"
.
equals
(
name
))
{
}
else
if
(
"size"
.
equals
(
name
))
{
size
=
parseInt
Percentage
(
value
);
cueWidth
=
parse
Percentage
(
value
);
}
else
{
}
else
{
Log
.
w
(
TAG
,
"Unknown cue setting "
+
name
+
":"
+
value
);
Log
.
w
(
TAG
,
"Unknown cue setting "
+
name
+
":"
+
value
);
}
}
}
catch
(
NumberFormatException
e
)
{
}
catch
(
NumberFormatException
e
)
{
Log
.
w
(
TAG
,
name
+
" contains an invalid value "
+
value
,
e
);
Log
.
w
(
TAG
,
e
.
getMessage
()
+
": "
+
match
);
}
}
}
}
if
(
cuePosition
!=
Cue
.
DIMEN_UNSET
&&
cuePositionAnchor
==
Cue
.
TYPE_UNSET
)
{
// Computed position alignment should be derived from the text alignment if it has not been
// set explicitly.
cuePositionAnchor
=
alignmentToAnchor
(
cueTextAlignment
);
}
// parse text
// parse text
textBuilder
.
setLength
(
0
);
textBuilder
.
setLength
(
0
);
while
((
(
line
=
webvttData
.
readLine
())
!=
null
)
&&
(!
line
.
isEmpty
()
))
{
while
((
line
=
webvttData
.
readLine
())
!=
null
&&
!
line
.
isEmpty
(
))
{
if
(
textBuilder
.
length
()
>
0
)
{
if
(
textBuilder
.
length
()
>
0
)
{
textBuilder
.
append
(
"<br>"
);
textBuilder
.
append
(
"<br>"
);
}
}
textBuilder
.
append
(
line
.
trim
());
textBuilder
.
append
(
line
.
trim
());
}
}
t
ext
=
Html
.
fromHtml
(
textBuilder
.
toString
());
cueT
ext
=
Html
.
fromHtml
(
textBuilder
.
toString
());
WebvttCue
cue
=
new
WebvttCue
(
startTime
,
endTime
,
text
,
lineNum
,
position
,
alignment
,
size
);
WebvttCue
cue
=
new
WebvttCue
(
cueStartTime
,
cueEndTime
,
cueText
,
cueTextAlignment
,
cueLine
,
cueLineType
,
cueLineAnchor
,
cuePosition
,
cuePositionAnchor
,
cueWidth
);
subtitles
.
add
(
cue
);
subtitles
.
add
(
cue
);
}
}
...
@@ -237,67 +217,116 @@ public final class WebvttParser implements SubtitleParser {
...
@@ -237,67 +217,116 @@ public final class WebvttParser implements SubtitleParser {
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
);
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
);
}
}
private
static
int
parseIntPercentage
(
String
s
)
throws
NumberFormatException
{
private
static
long
parseTimestampUs
(
String
s
)
throws
NumberFormatException
{
if
(!
s
.
endsWith
(
"%"
))
{
long
value
=
0
;
throw
new
NumberFormatException
(
s
+
" doesn't end with '%'"
);
String
[]
parts
=
s
.
split
(
"\\."
,
2
);
String
[]
subparts
=
parts
[
0
].
split
(
":"
);
for
(
int
i
=
0
;
i
<
subparts
.
length
;
i
++)
{
value
=
value
*
60
+
Long
.
parseLong
(
subparts
[
i
]);
}
}
return
(
value
*
1000
+
Long
.
parseLong
(
parts
[
1
]))
*
1000
;
s
=
s
.
substring
(
0
,
s
.
length
()
-
1
);
if
(!
s
.
matches
(
WEBVTT_PERCENTAGE_NUMBER_STRING
))
{
throw
new
NumberFormatException
(
s
+
" contains an invalid character"
);
}
}
int
value
=
Math
.
round
(
Float
.
parseFloat
(
s
));
private
static
void
parseLineAttribute
(
String
s
,
PositionHolder
out
)
if
(
value
<
0
||
value
>
100
)
{
throws
NumberFormatException
{
throw
new
NumberFormatException
(
value
+
" is out of range [0-100]"
);
int
lineAnchor
;
int
commaPosition
=
s
.
indexOf
(
","
);
if
(
commaPosition
!=
-
1
)
{
lineAnchor
=
parsePositionAnchor
(
s
.
substring
(
commaPosition
+
1
));
s
=
s
.
substring
(
0
,
commaPosition
);
}
else
{
lineAnchor
=
Cue
.
TYPE_UNSET
;
}
}
return
value
;
float
line
;
int
lineType
;
if
(
s
.
endsWith
(
"%"
))
{
line
=
parsePercentage
(
s
);
lineType
=
Cue
.
LINE_TYPE_FRACTION
;
}
else
{
line
=
Integer
.
parseInt
(
s
);
lineType
=
Cue
.
LINE_TYPE_NUMBER
;
}
}
out
.
position
=
line
;
private
static
long
parseTimestampUs
(
String
s
)
throws
NumberFormatException
{
out
.
positionAnchor
=
lineAnchor
;
if
(!
s
.
matches
(
WEBVTT_TIMESTAMP_STRING
))
{
out
.
lineType
=
lineType
;
throw
new
NumberFormatException
(
"has invalid format"
);
}
}
String
[]
parts
=
s
.
split
(
"\\."
,
2
);
private
static
void
parsePositionAttribute
(
String
s
,
PositionHolder
out
)
long
value
=
0
;
throws
NumberFormatException
{
for
(
String
group
:
parts
[
0
].
split
(
":"
))
{
int
positionAnchor
;
value
=
value
*
60
+
Long
.
parseLong
(
group
);
int
commaPosition
=
s
.
indexOf
(
","
);
if
(
commaPosition
!=
-
1
)
{
positionAnchor
=
parsePositionAnchor
(
s
.
substring
(
commaPosition
+
1
));
s
=
s
.
substring
(
0
,
commaPosition
);
}
else
{
positionAnchor
=
Cue
.
TYPE_UNSET
;
}
}
return
(
value
*
1000
+
Long
.
parseLong
(
parts
[
1
]))
*
1000
;
out
.
position
=
parsePercentage
(
s
);
out
.
positionAnchor
=
positionAnchor
;
out
.
lineType
=
Cue
.
TYPE_UNSET
;
}
}
private
static
Pair
<
String
,
Alignment
>
parseLinePositionAttributes
(
String
s
)
{
private
static
float
parsePercentage
(
String
s
)
throws
NumberFormatException
{
String
value
;
if
(!
s
.
endsWith
(
"%"
))
{
Alignment
alignment
=
null
;
throw
new
NumberFormatException
(
"Percentages must end with %"
);
}
s
=
s
.
substring
(
0
,
s
.
length
()
-
1
);
return
Float
.
parseFloat
(
s
)
/
100
;
}
int
commaPos
;
private
static
int
parsePositionAnchor
(
String
s
)
{
if
((
commaPos
=
s
.
indexOf
(
","
))
>
0
&&
commaPos
<
s
.
length
()
-
1
)
{
switch
(
s
)
{
alignment
=
parseAlignment
(
s
.
substring
(
commaPos
+
1
));
case
"start"
:
value
=
s
.
substring
(
0
,
commaPos
);
return
Cue
.
ANCHOR_TYPE_START
;
}
else
{
case
"middle"
:
value
=
s
;
return
Cue
.
ANCHOR_TYPE_MIDDLE
;
case
"end"
:
return
Cue
.
ANCHOR_TYPE_END
;
default
:
Log
.
w
(
TAG
,
"Invalid anchor value: "
+
s
);
return
Cue
.
TYPE_UNSET
;
}
}
}
return
new
Pair
<
String
,
Alignment
>(
value
,
alignment
);
private
static
Alignment
parseTextAlignment
(
String
s
)
{
switch
(
s
)
{
case
"start"
:
case
"left"
:
return
Alignment
.
ALIGN_NORMAL
;
case
"middle"
:
return
Alignment
.
ALIGN_CENTER
;
case
"end"
:
case
"right"
:
return
Alignment
.
ALIGN_OPPOSITE
;
default
:
Log
.
w
(
TAG
,
"Invalid alignment value: "
+
s
);
return
null
;
}
}
}
private
static
Alignment
parseAlignment
(
String
s
)
{
private
static
int
alignmentToAnchor
(
Alignment
alignment
)
{
Alignment
alignment
=
null
;
if
(
alignment
==
null
)
{
if
(
"start"
.
equals
(
s
))
{
return
Cue
.
TYPE_UNSET
;
alignment
=
Alignment
.
ALIGN_NORMAL
;
}
}
else
if
(
"middle"
.
equals
(
s
))
{
switch
(
alignment
)
{
alignment
=
Alignment
.
ALIGN_CENTER
;
case
ALIGN_NORMAL:
}
else
if
(
"end"
.
equals
(
s
))
{
return
Cue
.
ANCHOR_TYPE_START
;
alignment
=
Alignment
.
ALIGN_OPPOSITE
;
case
ALIGN_CENTER:
}
else
if
(
"left"
.
equals
(
s
))
{
return
Cue
.
ANCHOR_TYPE_MIDDLE
;
alignment
=
Alignment
.
ALIGN_NORMAL
;
case
ALIGN_OPPOSITE:
}
else
if
(
"right"
.
equals
(
s
))
{
return
Cue
.
ANCHOR_TYPE_END
;
alignment
=
Alignment
.
ALIGN_OPPOSITE
;
default
:
}
else
{
Log
.
w
(
TAG
,
"Unrecognized alignment: "
+
alignment
);
Log
.
w
(
TAG
,
"Invalid align value: "
+
s
);
return
Cue
.
ANCHOR_TYPE_START
;
}
}
}
return
alignment
;
private
static
final
class
PositionHolder
{
public
float
position
;
public
int
positionAnchor
;
public
int
lineType
;
}
}
}
}
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