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
b333169a
authored
Jul 10, 2017
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge branch 'dev-v2' of
git://github.com/jcable/ExoPlayer
into jcable-dev-v2
parents
78e05457
402c985a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
474 additions
and
5 deletions
library/core/src/androidTest/assets/ssa/empty
library/core/src/androidTest/assets/ssa/invalid_timecodes
library/core/src/androidTest/assets/ssa/no_end_timecodes
library/core/src/androidTest/assets/ssa/typical
library/core/src/androidTest/assets/ssa/typical_dialogue
library/core/src/androidTest/assets/ssa/typical_format
library/core/src/androidTest/assets/ssa/typical_header
library/core/src/androidTest/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java
library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaSubtitle.java
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/core/src/androidTest/assets/ssa/empty
0 → 100644
View file @
b333169a
File mode changed
library/core/src/androidTest/assets/ssa/invalid_timecodes
0 → 100644
View file @
b333169a
[Script Info]
Title: SomeTitle
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,Invalid,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
Dialogue: 0,0:00:02.34,Invalid,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
\ No newline at end of file
library/core/src/androidTest/assets/ssa/no_end_timecodes
0 → 100644
View file @
b333169a
[Script Info]
Title: SomeTitle
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00, ,Default,Olly,This is the first subtitle.
Dialogue: 0,0:00:02.34, ,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04.56, ,Default,Olly,This is the third subtitle, with a comma.
\ No newline at end of file
library/core/src/androidTest/assets/ssa/typical
0 → 100644
View file @
b333169a
[Script Info]
Title: SomeTitle
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
\ No newline at end of file
library/core/src/androidTest/assets/ssa/typical_dialogue
0 → 100644
View file @
b333169a
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
\ No newline at end of file
library/core/src/androidTest/assets/ssa/typical_format
0 → 100644
View file @
b333169a
Format: Layer, Start, End, Style, Name, Text
\ No newline at end of file
library/core/src/androidTest/assets/ssa/typical_header
0 → 100644
View file @
b333169a
[Script Info]
Title: SomeTitle
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
\ No newline at end of file
library/core/src/androidTest/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java
0 → 100644
View file @
b333169a
/*
* Copyright (C) 2016 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
.
exoplayer2
.
text
.
ssa
;
import
android.test.InstrumentationTestCase
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
java.io.IOException
;
import
java.util.ArrayList
;
/**
* Unit test for {@link SsaDecoder}.
*/
public
final
class
SsaDecoderTest
extends
InstrumentationTestCase
{
private
static
final
String
EMPTY
=
"ssa/empty"
;
private
static
final
String
TYPICAL
=
"ssa/typical"
;
private
static
final
String
TYPICAL_HEADER_ONLY
=
"ssa/typical_header"
;
private
static
final
String
TYPICAL_DIALOGUE_ONLY
=
"ssa/typical_dialogue"
;
private
static
final
String
TYPICAL_FORMAT_ONLY
=
"ssa/typical_format"
;
private
static
final
String
INVALID_TIMECODES
=
"ssa/invalid_timecodes"
;
private
static
final
String
NO_END_TIMECODES
=
"ssa/no_end_timecodes"
;
public
void
testDecodeEmpty
()
throws
IOException
{
SsaDecoder
decoder
=
new
SsaDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
EMPTY
);
SsaSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
0
,
subtitle
.
getEventTimeCount
());
assertTrue
(
subtitle
.
getCues
(
0
).
isEmpty
());
}
public
void
testDecodeTypical
()
throws
IOException
{
SsaDecoder
decoder
=
new
SsaDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL
);
SsaSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
6
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue2
(
subtitle
,
2
);
assertTypicalCue3
(
subtitle
,
4
);
}
public
void
testDecodeTypicalWithInitializationData
()
throws
IOException
{
byte
[]
headerBytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_HEADER_ONLY
);
byte
[]
formatBytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_FORMAT_ONLY
);
ArrayList
<
byte
[]>
initializationData
=
new
ArrayList
<>();
initializationData
.
add
(
formatBytes
);
initializationData
.
add
(
headerBytes
);
SsaDecoder
decoder
=
new
SsaDecoder
(
initializationData
);
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_DIALOGUE_ONLY
);
SsaSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
6
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue2
(
subtitle
,
2
);
assertTypicalCue3
(
subtitle
,
4
);
}
public
void
testDecodeInvalidTimecodes
()
throws
IOException
{
// Parsing should succeed, parsing the third cue only.
SsaDecoder
decoder
=
new
SsaDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
INVALID_TIMECODES
);
SsaSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
2
,
subtitle
.
getEventTimeCount
());
assertTypicalCue3
(
subtitle
,
0
);
}
public
void
testDecodeNoEndTimecodes
()
throws
IOException
{
SsaDecoder
decoder
=
new
SsaDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
NO_END_TIMECODES
);
SsaSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
3
,
subtitle
.
getEventTimeCount
());
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertEquals
(
"This is the first subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
assertEquals
(
2340000
,
subtitle
.
getEventTime
(
1
));
assertEquals
(
"This is the second subtitle \nwith a newline \nand another."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
1
)).
get
(
0
).
text
.
toString
());
assertEquals
(
4560000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"This is the third subtitle, with a comma."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
}
private
static
void
assertTypicalCue1
(
SsaSubtitle
subtitle
,
int
eventIndex
)
{
assertEquals
(
0
,
subtitle
.
getEventTime
(
eventIndex
));
assertEquals
(
"This is the first subtitle."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
eventIndex
)).
get
(
0
).
text
.
toString
());
assertEquals
(
1230000
,
subtitle
.
getEventTime
(
eventIndex
+
1
));
}
private
static
void
assertTypicalCue2
(
SsaSubtitle
subtitle
,
int
eventIndex
)
{
assertEquals
(
2340000
,
subtitle
.
getEventTime
(
eventIndex
));
assertEquals
(
"This is the second subtitle \nwith a newline \nand another."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
eventIndex
)).
get
(
0
).
text
.
toString
());
assertEquals
(
3450000
,
subtitle
.
getEventTime
(
eventIndex
+
1
));
}
private
static
void
assertTypicalCue3
(
SsaSubtitle
subtitle
,
int
eventIndex
)
{
assertEquals
(
4560000
,
subtitle
.
getEventTime
(
eventIndex
));
assertEquals
(
"This is the third subtitle, with a comma."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
eventIndex
)).
get
(
0
).
text
.
toString
());
assertEquals
(
8900000
,
subtitle
.
getEventTime
(
eventIndex
+
1
));
}
}
library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java
View file @
b333169a
...
...
@@ -37,7 +37,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
EMPTY_FILE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
// Assert that the subtitle is empty.
assertEquals
(
0
,
subtitle
.
getEventTimeCount
());
assertTrue
(
subtitle
.
getCues
(
0
).
isEmpty
());
}
...
...
@@ -46,6 +46,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_FILE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
6
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue2
(
subtitle
,
2
);
...
...
@@ -56,6 +57,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_WITH_BYTE_ORDER_MARK
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
6
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue2
(
subtitle
,
2
);
...
...
@@ -66,6 +68,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_EXTRA_BLANK_LINE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
6
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue2
(
subtitle
,
2
);
...
...
@@ -77,6 +80,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_MISSING_TIMECODE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue3
(
subtitle
,
2
);
...
...
@@ -87,6 +91,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_MISSING_SEQUENCE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
4
,
subtitle
.
getEventTimeCount
());
assertTypicalCue1
(
subtitle
,
0
);
assertTypicalCue3
(
subtitle
,
2
);
...
...
@@ -97,6 +102,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
SubripDecoder
decoder
=
new
SubripDecoder
();
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
TYPICAL_NEGATIVE_TIMESTAMPS
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
assertEquals
(
2
,
subtitle
.
getEventTimeCount
());
assertTypicalCue3
(
subtitle
,
0
);
}
...
...
@@ -106,20 +112,16 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
byte
[]
bytes
=
TestUtil
.
getByteArray
(
getInstrumentation
(),
NO_END_TIMECODES_FILE
);
SubripSubtitle
subtitle
=
decoder
.
decode
(
bytes
,
bytes
.
length
,
false
);
// Test event count.
assertEquals
(
3
,
subtitle
.
getEventTimeCount
());
// Test first cue.
assertEquals
(
0
,
subtitle
.
getEventTime
(
0
));
assertEquals
(
"SubRip doesn't technically allow missing end timecodes."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
0
)).
get
(
0
).
text
.
toString
());
// Test second cue.
assertEquals
(
2345000
,
subtitle
.
getEventTime
(
1
));
assertEquals
(
"We interpret it to mean that a subtitle extends to the start of the next one."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
1
)).
get
(
0
).
text
.
toString
());
// Test third cue.
assertEquals
(
3456000
,
subtitle
.
getEventTime
(
2
));
assertEquals
(
"Or to the end of the media."
,
subtitle
.
getCues
(
subtitle
.
getEventTime
(
2
)).
get
(
0
).
text
.
toString
());
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
View file @
b333169a
This diff is collapsed.
Click to expand it.
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
View file @
b333169a
...
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.text.cea.Cea608Decoder
;
import
com.google.android.exoplayer2.text.cea.Cea708Decoder
;
import
com.google.android.exoplayer2.text.dvb.DvbDecoder
;
import
com.google.android.exoplayer2.text.ssa.SsaDecoder
;
import
com.google.android.exoplayer2.text.subrip.SubripDecoder
;
import
com.google.android.exoplayer2.text.ttml.TtmlDecoder
;
import
com.google.android.exoplayer2.text.tx3g.Tx3gDecoder
;
...
...
@@ -58,6 +59,7 @@ public interface SubtitleDecoderFactory {
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})</li>
* <li>TTML ({@link TtmlDecoder})</li>
* <li>SubRip ({@link SubripDecoder})</li>
* <li>SSA/ASS ({@link SsaDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})</li>
* <li>Cea608 ({@link Cea608Decoder})</li>
* <li>Cea708 ({@link Cea708Decoder})</li>
...
...
@@ -70,6 +72,7 @@ public interface SubtitleDecoderFactory {
public
boolean
supportsFormat
(
Format
format
)
{
String
mimeType
=
format
.
sampleMimeType
;
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
)
||
MimeTypes
.
TEXT_SSA
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_TTML
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4VTT
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_SUBRIP
.
equals
(
mimeType
)
...
...
@@ -85,6 +88,8 @@ public interface SubtitleDecoderFactory {
switch
(
format
.
sampleMimeType
)
{
case
MimeTypes
.
TEXT_VTT
:
return
new
WebvttDecoder
();
case
MimeTypes
.
TEXT_SSA
:
return
new
SsaDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_MP4VTT
:
return
new
Mp4WebvttDecoder
();
case
MimeTypes
.
APPLICATION_TTML
:
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
0 → 100644
View file @
b333169a
/*
* Copyright (C) 2017 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
.
exoplayer2
.
text
.
ssa
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.SimpleSubtitleDecoder
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.LongArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* A {@link SimpleSubtitleDecoder} for SSA/ASS.
*/
public
final
class
SsaDecoder
extends
SimpleSubtitleDecoder
{
private
static
final
String
TAG
=
"SsaDecoder"
;
private
static
final
Pattern
SSA_TIMECODE_PATTERN
=
Pattern
.
compile
(
"(?:(\\d+):)?(\\d+):(\\d+)(?::|\\.)(\\d+)"
);
private
static
final
String
FORMAT_LINE_PREFIX
=
"Format: "
;
private
static
final
String
DIALOGUE_LINE_PREFIX
=
"Dialogue: "
;
private
final
boolean
haveInitializationData
;
private
int
formatKeyCount
;
private
int
formatStartIndex
;
private
int
formatEndIndex
;
private
int
formatTextIndex
;
public
SsaDecoder
()
{
this
(
null
);
}
/**
* @param initializationData Optional initialization data for the decoder. If not null, the
* initialization data must consist of two byte arrays. The first must contain an SSA format
* line. The second must contain an SSA header that will be assumed common to all samples.
*/
public
SsaDecoder
(
List
<
byte
[]>
initializationData
)
{
super
(
"SsaDecoder"
);
if
(
initializationData
!=
null
)
{
haveInitializationData
=
true
;
String
formatLine
=
new
String
(
initializationData
.
get
(
0
));
Assertions
.
checkArgument
(
formatLine
.
startsWith
(
FORMAT_LINE_PREFIX
));
parseFormatLine
(
formatLine
);
parseHeader
(
new
ParsableByteArray
(
initializationData
.
get
(
1
)));
}
else
{
haveInitializationData
=
false
;
}
}
@Override
protected
SsaSubtitle
decode
(
byte
[]
bytes
,
int
length
,
boolean
reset
)
{
ArrayList
<
Cue
>
cues
=
new
ArrayList
<>();
LongArray
cueTimesUs
=
new
LongArray
();
ParsableByteArray
data
=
new
ParsableByteArray
(
bytes
,
length
);
if
(!
haveInitializationData
)
{
parseHeader
(
data
);
}
parseEventBody
(
data
,
cues
,
cueTimesUs
);
Cue
[]
cuesArray
=
new
Cue
[
cues
.
size
()];
cues
.
toArray
(
cuesArray
);
long
[]
cueTimesUsArray
=
cueTimesUs
.
toArray
();
return
new
SsaSubtitle
(
cuesArray
,
cueTimesUsArray
);
}
/**
* Parses the header of the subtitle.
*
* @param data A {@link ParsableByteArray} from which the header should be read.
*/
private
void
parseHeader
(
ParsableByteArray
data
)
{
String
currentLine
;
while
((
currentLine
=
data
.
readLine
())
!=
null
)
{
// TODO: Parse useful data from the header.
if
(
currentLine
.
startsWith
(
"[Events]"
))
{
// We've reached the event body.
return
;
}
}
}
/**
* Parses the event body of the subtitle.
*
* @param data A {@link ParsableByteArray} from which the body should be read.
* @param cues A list to which parsed cues will be added.
* @param cueTimesUs An array to which parsed cue timestamps will be added.
*/
private
void
parseEventBody
(
ParsableByteArray
data
,
List
<
Cue
>
cues
,
LongArray
cueTimesUs
)
{
String
currentLine
;
while
((
currentLine
=
data
.
readLine
())
!=
null
)
{
if
(!
haveInitializationData
&&
currentLine
.
startsWith
(
FORMAT_LINE_PREFIX
))
{
parseFormatLine
(
currentLine
);
}
else
if
(
currentLine
.
startsWith
(
DIALOGUE_LINE_PREFIX
))
{
parseDialogueLine
(
currentLine
,
cues
,
cueTimesUs
);
}
}
}
/**
* Parses a format line.
*
* @param formatLine The line to parse.
*/
private
void
parseFormatLine
(
String
formatLine
)
{
String
[]
values
=
TextUtils
.
split
(
formatLine
.
substring
(
FORMAT_LINE_PREFIX
.
length
()),
","
);
formatKeyCount
=
values
.
length
;
formatStartIndex
=
C
.
INDEX_UNSET
;
formatEndIndex
=
C
.
INDEX_UNSET
;
formatTextIndex
=
C
.
INDEX_UNSET
;
for
(
int
i
=
0
;
i
<
formatKeyCount
;
i
++)
{
String
key
=
values
[
i
].
trim
().
toLowerCase
();
switch
(
key
)
{
case
"start"
:
formatStartIndex
=
i
;
break
;
case
"end"
:
formatEndIndex
=
i
;
break
;
case
"text"
:
formatTextIndex
=
i
;
break
;
default
:
// Do nothing.
break
;
}
}
}
/**
* Parses a dialogue line.
*
* @param dialogueLine The line to parse.
* @param cues A list to which parsed cues will be added.
* @param cueTimesUs An array to which parsed cue timestamps will be added.
*/
private
void
parseDialogueLine
(
String
dialogueLine
,
List
<
Cue
>
cues
,
LongArray
cueTimesUs
)
{
if
(
formatKeyCount
==
0
)
{
Log
.
w
(
TAG
,
"Skipping dialogue line before format: "
+
dialogueLine
);
return
;
}
String
[]
lineValues
=
dialogueLine
.
substring
(
DIALOGUE_LINE_PREFIX
.
length
())
.
split
(
","
,
formatKeyCount
);
long
startTimeUs
=
SsaDecoder
.
parseTimecodeUs
(
lineValues
[
formatStartIndex
]);
if
(
startTimeUs
==
C
.
TIME_UNSET
)
{
Log
.
w
(
TAG
,
"Skipping invalid timing: "
+
dialogueLine
);
return
;
}
long
endTimeUs
=
C
.
TIME_UNSET
;
String
endTimeString
=
lineValues
[
formatEndIndex
];
if
(!
endTimeString
.
trim
().
isEmpty
())
{
endTimeUs
=
SsaDecoder
.
parseTimecodeUs
(
endTimeString
);
if
(
endTimeUs
==
C
.
TIME_UNSET
)
{
Log
.
w
(
TAG
,
"Skipping invalid timing: "
+
dialogueLine
);
return
;
}
}
String
text
=
lineValues
[
formatTextIndex
]
.
replaceAll
(
"\\{.*?\\}"
,
""
)
.
replaceAll
(
"\\\\N"
,
"\n"
)
.
replaceAll
(
"\\\\n"
,
"\n"
);
cues
.
add
(
new
Cue
(
text
));
cueTimesUs
.
add
(
startTimeUs
);
if
(
endTimeUs
!=
C
.
TIME_UNSET
)
{
cues
.
add
(
null
);
cueTimesUs
.
add
(
endTimeUs
);
}
}
/**
* Parses an SSA timecode string.
*
* @param timeString The string to parse.
* @return The parsed timestamp in microseconds.
*/
public
static
long
parseTimecodeUs
(
String
timeString
)
{
Matcher
matcher
=
SSA_TIMECODE_PATTERN
.
matcher
(
timeString
);
if
(!
matcher
.
matches
())
{
return
C
.
TIME_UNSET
;
}
long
timestampUs
=
Long
.
parseLong
(
matcher
.
group
(
1
))
*
60
*
60
*
C
.
MICROS_PER_SECOND
;
timestampUs
+=
Long
.
parseLong
(
matcher
.
group
(
2
))
*
60
*
C
.
MICROS_PER_SECOND
;
timestampUs
+=
Long
.
parseLong
(
matcher
.
group
(
3
))
*
C
.
MICROS_PER_SECOND
;
timestampUs
+=
Long
.
parseLong
(
matcher
.
group
(
4
))
*
10000
;
// 100ths of a second.
return
timestampUs
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaSubtitle.java
0 → 100644
View file @
b333169a
/*
* Copyright (C) 2017 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
.
exoplayer2
.
text
.
ssa
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Collections
;
import
java.util.List
;
/**
* A representation of an SSA/ASS subtitle.
*/
/* package */
final
class
SsaSubtitle
implements
Subtitle
{
private
final
Cue
[]
cues
;
private
final
long
[]
cueTimesUs
;
/**
* @param cues The cues in the subtitle. Null entries may be used to represent empty cues.
* @param cueTimesUs The cue times, in microseconds.
*/
public
SsaSubtitle
(
Cue
[]
cues
,
long
[]
cueTimesUs
)
{
this
.
cues
=
cues
;
this
.
cueTimesUs
=
cueTimesUs
;
}
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchCeil
(
cueTimesUs
,
timeUs
,
false
,
false
);
return
index
<
cueTimesUs
.
length
?
index
:
C
.
INDEX_UNSET
;
}
@Override
public
int
getEventTimeCount
()
{
return
cueTimesUs
.
length
;
}
@Override
public
long
getEventTime
(
int
index
)
{
Assertions
.
checkArgument
(
index
>=
0
);
Assertions
.
checkArgument
(
index
<
cueTimesUs
.
length
);
return
cueTimesUs
[
index
];
}
@Override
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchFloor
(
cueTimesUs
,
timeUs
,
true
,
false
);
if
(
index
==
-
1
||
cues
[
index
]
==
null
)
{
// timeUs is earlier than the start of the first cue, or we have an empty cue.
return
Collections
.
emptyList
();
}
else
{
return
Collections
.
singletonList
(
cues
[
index
]);
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
b333169a
...
...
@@ -65,6 +65,7 @@ public final class MimeTypes {
public
static
final
String
AUDIO_UNKNOWN
=
BASE_TYPE_AUDIO
+
"/x-unknown"
;
public
static
final
String
TEXT_VTT
=
BASE_TYPE_TEXT
+
"/vtt"
;
public
static
final
String
TEXT_SSA
=
BASE_TYPE_TEXT
+
"/x-ssa"
;
public
static
final
String
APPLICATION_MP4
=
BASE_TYPE_APPLICATION
+
"/mp4"
;
public
static
final
String
APPLICATION_WEBM
=
BASE_TYPE_APPLICATION
+
"/webm"
;
...
...
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