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
7594f5b7
authored
Oct 18, 2016
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Further enhance ID3 decoder + support
parent
e2ff401e
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
175 additions
and
181 deletions
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
View file @
7594f5b7
...
@@ -16,6 +16,8 @@
...
@@ -16,6 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
java.util.regex.Matcher
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
java.util.regex.Pattern
;
...
@@ -66,6 +68,25 @@ public final class GaplessInfoHolder {
...
@@ -66,6 +68,25 @@ public final class GaplessInfoHolder {
}
}
/**
/**
* Populates the holder with data parsed from ID3 {@link Metadata}.
*
* @param metadata The metadata from which to parse the gapless information.
* @return Whether the holder was populated.
*/
public
boolean
setFromMetadata
(
Metadata
metadata
)
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
if
(
setFromComment
(
commentFrame
.
description
,
commentFrame
.
text
))
{
return
true
;
}
}
}
return
false
;
}
/**
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
* or MPEG 4 user data), if valid and non-zero.
*
*
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
deleted
100644 → 0
View file @
e2ff401e
/*
* 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
.
extractor
.
mp3
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
/**
* Utility for parsing ID3 version 2 metadata in MP3 files.
*/
/* package */
final
class
Id3Util
{
/**
* The maximum valid length for metadata in bytes.
*/
private
static
final
int
MAXIMUM_METADATA_SIZE
=
3
*
1024
*
1024
;
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
/**
* Peeks data from the input and parses ID3 metadata, including gapless playback information.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @return The metadata, if present, {@code null} otherwise.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
public
static
Metadata
parseId3
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
Metadata
result
=
null
;
ParsableByteArray
scratch
=
new
ParsableByteArray
(
10
);
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
10
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
ID3_TAG
)
{
break
;
}
int
majorVersion
=
scratch
.
readUnsignedByte
();
int
minorVersion
=
scratch
.
readUnsignedByte
();
int
flags
=
scratch
.
readUnsignedByte
();
int
length
=
scratch
.
readSynchSafeInt
();
int
frameLength
=
length
+
10
;
try
{
if
(
canParseMetadata
(
majorVersion
,
minorVersion
,
flags
,
length
))
{
input
.
resetPeekPosition
();
byte
[]
frame
=
new
byte
[
frameLength
];
input
.
peekFully
(
frame
,
0
,
frameLength
);
return
new
Id3Decoder
().
decode
(
frame
,
frameLength
);
}
else
{
input
.
advancePeekPosition
(
length
);
}
}
catch
(
MetadataDecoderException
e
)
{
e
.
printStackTrace
();
}
peekedId3Bytes
+=
frameLength
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
return
result
;
}
private
static
boolean
canParseMetadata
(
int
majorVersion
,
int
minorVersion
,
int
flags
,
int
length
)
{
return
minorVersion
!=
0xFF
&&
majorVersion
>=
2
&&
majorVersion
<=
4
&&
length
<=
MAXIMUM_METADATA_SIZE
&&
!(
majorVersion
==
2
&&
((
flags
&
0x3F
)
!=
0
||
(
flags
&
0x40
)
!=
0
))
&&
!(
majorVersion
==
3
&&
(
flags
&
0x1F
)
!=
0
)
&&
!(
majorVersion
==
4
&&
(
flags
&
0x0F
)
!=
0
);
}
private
Id3Util
()
{}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
View file @
7594f5b7
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
import
android.util.Log
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.ParserException
;
...
@@ -28,7 +29,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
...
@@ -28,7 +29,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.EOFException
;
import
java.io.EOFException
;
...
@@ -51,6 +53,8 @@ public final class Mp3Extractor implements Extractor {
...
@@ -51,6 +53,8 @@ public final class Mp3Extractor implements Extractor {
};
};
private
static
final
String
TAG
=
"Mp3Extractor"
;
/**
/**
* The maximum number of bytes to search when synchronizing, before giving up.
* The maximum number of bytes to search when synchronizing, before giving up.
*/
*/
...
@@ -59,6 +63,18 @@ public final class Mp3Extractor implements Extractor {
...
@@ -59,6 +63,18 @@ public final class Mp3Extractor implements Extractor {
* The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up.
* The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up.
*/
*/
private
static
final
int
MAX_SNIFF_BYTES
=
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
;
private
static
final
int
MAX_SNIFF_BYTES
=
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
;
/**
* First three bytes of a well formed ID3 tag header.
*/
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
/**
* Length of an ID3 tag header.
*/
private
static
final
int
ID3_HEADER_LENGTH
=
10
;
/**
* Maximum length of data read into {@link #scratch}.
*/
private
static
final
int
SCRATCH_LENGTH
=
10
;
/**
/**
* Mask that includes the audio header values that must match between frames.
* Mask that includes the audio header values that must match between frames.
...
@@ -100,7 +116,7 @@ public final class Mp3Extractor implements Extractor {
...
@@ -100,7 +116,7 @@ public final class Mp3Extractor implements Extractor {
*/
*/
public
Mp3Extractor
(
long
forcedFirstSampleTimestampUs
)
{
public
Mp3Extractor
(
long
forcedFirstSampleTimestampUs
)
{
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
scratch
=
new
ParsableByteArray
(
4
);
scratch
=
new
ParsableByteArray
(
SCRATCH_LENGTH
);
synchronizedHeader
=
new
MpegAudioHeader
();
synchronizedHeader
=
new
MpegAudioHeader
();
gaplessInfoHolder
=
new
GaplessInfoHolder
();
gaplessInfoHolder
=
new
GaplessInfoHolder
();
basisTimeUs
=
C
.
TIME_UNSET
;
basisTimeUs
=
C
.
TIME_UNSET
;
...
@@ -147,7 +163,7 @@ public final class Mp3Extractor implements Extractor {
...
@@ -147,7 +163,7 @@ public final class Mp3Extractor implements Extractor {
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfoHolder
.
encoderDelay
,
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
,
metadata
));
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
,
null
));
}
}
return
readSample
(
input
);
return
readSample
(
input
);
}
}
...
@@ -202,18 +218,7 @@ public final class Mp3Extractor implements Extractor {
...
@@ -202,18 +218,7 @@ public final class Mp3Extractor implements Extractor {
int
searchLimitBytes
=
sniffing
?
MAX_SNIFF_BYTES
:
MAX_SYNC_BYTES
;
int
searchLimitBytes
=
sniffing
?
MAX_SNIFF_BYTES
:
MAX_SYNC_BYTES
;
input
.
resetPeekPosition
();
input
.
resetPeekPosition
();
if
(
input
.
getPosition
()
==
0
)
{
if
(
input
.
getPosition
()
==
0
)
{
metadata
=
Id3Util
.
parseId3
(
input
);
peekId3Data
(
input
);
if
(!
gaplessInfoHolder
.
hasGaplessInfo
())
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
if
(
gaplessInfoHolder
.
setFromComment
(
commentFrame
.
description
,
commentFrame
.
text
))
{
break
;
}
}
}
}
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
if
(!
sniffing
)
{
if
(!
sniffing
)
{
input
.
skipFully
(
peekedId3Bytes
);
input
.
skipFully
(
peekedId3Bytes
);
...
@@ -268,6 +273,49 @@ public final class Mp3Extractor implements Extractor {
...
@@ -268,6 +273,49 @@ public final class Mp3Extractor implements Extractor {
}
}
/**
/**
* Peeks ID3 data from the input, including gapless playback information.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
private
void
peekId3Data
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
ID3_HEADER_LENGTH
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
ID3_TAG
)
{
// Not an ID3 tag.
break
;
}
scratch
.
skipBytes
(
3
);
// Skip major version, minor version and flags.
int
framesLength
=
scratch
.
readSynchSafeInt
();
int
tagLength
=
ID3_HEADER_LENGTH
+
framesLength
;
try
{
if
(
metadata
==
null
)
{
byte
[]
id3Data
=
new
byte
[
tagLength
];
System
.
arraycopy
(
scratch
.
data
,
0
,
id3Data
,
0
,
ID3_HEADER_LENGTH
);
input
.
peekFully
(
id3Data
,
ID3_HEADER_LENGTH
,
framesLength
);
metadata
=
new
Id3Decoder
().
decode
(
id3Data
,
tagLength
);
if
(
metadata
!=
null
)
{
gaplessInfoHolder
.
setFromMetadata
(
metadata
);
}
}
else
{
input
.
advancePeekPosition
(
framesLength
);
}
}
catch
(
MetadataDecoderException
e
)
{
Log
.
e
(
TAG
,
"Failed to decode ID3 tag"
,
e
);
}
peekedId3Bytes
+=
tagLength
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
}
/**
* Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide
* Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide
* data from the start of the first frame in the stream. On returning, the input's position will
* data from the start of the first frame in the stream. On returning, the input's position will
* be set to the start of the first frame of audio.
* be set to the start of the first frame of audio.
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
View file @
7594f5b7
...
@@ -63,7 +63,7 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -63,7 +63,7 @@ public final class Id3Decoder implements MetadataDecoder {
int
frameHeaderSize
=
id3Header
.
majorVersion
==
2
?
6
:
10
;
int
frameHeaderSize
=
id3Header
.
majorVersion
==
2
?
6
:
10
;
while
(
id3Data
.
bytesLeft
()
>=
frameHeaderSize
)
{
while
(
id3Data
.
bytesLeft
()
>=
frameHeaderSize
)
{
Id3Frame
frame
=
decodeFrame
(
id3Header
,
id3Data
);
Id3Frame
frame
=
decodeFrame
(
id3Header
.
majorVersion
,
id3Data
);
if
(
frame
!=
null
)
{
if
(
frame
!=
null
)
{
id3Frames
.
add
(
frame
);
id3Frames
.
add
(
frame
);
}
}
...
@@ -72,6 +72,40 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -72,6 +72,40 @@ public final class Id3Decoder implements MetadataDecoder {
return
new
Metadata
(
id3Frames
);
return
new
Metadata
(
id3Frames
);
}
}
// TODO: Move the following three methods nearer to the bottom of the file.
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
int
terminationPos
=
indexOfZeroByte
(
data
,
fromIndex
);
// For single byte encoding charsets, we're done.
if
(
encoding
==
ID3_TEXT_ENCODING_ISO_8859_1
||
encoding
==
ID3_TEXT_ENCODING_UTF_8
)
{
return
terminationPos
;
}
// Otherwise ensure an even index and look for a second zero byte.
while
(
terminationPos
<
data
.
length
-
1
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
return
terminationPos
;
}
terminationPos
=
indexOfZeroByte
(
data
,
terminationPos
+
1
);
}
return
data
.
length
;
}
private
static
int
indexOfZeroByte
(
byte
[]
data
,
int
fromIndex
)
{
for
(
int
i
=
fromIndex
;
i
<
data
.
length
;
i
++)
{
if
(
data
[
i
]
==
(
byte
)
0
)
{
return
i
;
}
}
return
data
.
length
;
}
private
static
int
delimiterLength
(
int
encodingByte
)
{
return
(
encodingByte
==
ID3_TEXT_ENCODING_ISO_8859_1
||
encodingByte
==
ID3_TEXT_ENCODING_UTF_8
)
?
1
:
2
;
}
/**
/**
* @param data A {@link ParsableByteArray} from which the header should be read.
* @param data A {@link ParsableByteArray} from which the header should be read.
* @return The parsed header, or null if the ID3 tag is unsupported.
* @return The parsed header, or null if the ID3 tag is unsupported.
...
@@ -126,15 +160,15 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -126,15 +160,15 @@ public final class Id3Decoder implements MetadataDecoder {
return
new
Id3Header
(
majorVersion
,
isUnsynchronized
,
framesSize
);
return
new
Id3Header
(
majorVersion
,
isUnsynchronized
,
framesSize
);
}
}
private
Id3Frame
decodeFrame
(
Id3Header
id3Header
,
ParsableByteArray
id3Data
)
private
Id3Frame
decodeFrame
(
int
majorVersion
,
ParsableByteArray
id3Data
)
throws
MetadataDecoderException
{
throws
MetadataDecoderException
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
id3Header
.
majorVersion
>=
3
?
id3Data
.
readUnsignedByte
()
:
0
;
int
frameId3
=
majorVersion
>=
3
?
id3Data
.
readUnsignedByte
()
:
0
;
int
frameSize
;
int
frameSize
;
if
(
id3Header
.
majorVersion
==
4
)
{
if
(
majorVersion
==
4
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
frameSize
=
id3Data
.
readUnsignedIntToInt
();
if
((
frameSize
&
0x808080
L
)
==
0
)
{
if
((
frameSize
&
0x808080
L
)
==
0
)
{
// Parse the frame size as a syncsafe integer, as per the spec.
// Parse the frame size as a syncsafe integer, as per the spec.
...
@@ -144,13 +178,13 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -144,13 +178,13 @@ public final class Id3Decoder implements MetadataDecoder {
// Proceed using the frame size read as an unsigned integer.
// Proceed using the frame size read as an unsigned integer.
Log
.
w
(
TAG
,
"Frame size not specified as syncsafe integer"
);
Log
.
w
(
TAG
,
"Frame size not specified as syncsafe integer"
);
}
}
}
else
if
(
id3Header
.
majorVersion
==
3
)
{
}
else
if
(
majorVersion
==
3
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
frameSize
=
id3Data
.
readUnsignedIntToInt
();
}
else
/* id3Header.majorVersion == 2 */
{
}
else
/* id3Header.majorVersion == 2 */
{
frameSize
=
id3Data
.
readUnsignedInt24
();
frameSize
=
id3Data
.
readUnsignedInt24
();
}
}
int
flags
=
id3Header
.
majorVersion
>=
2
?
id3Data
.
readShort
()
:
0
;
int
flags
=
majorVersion
>=
3
?
id3Data
.
readShort
()
:
0
;
if
(
frameId0
==
0
&&
frameId1
==
0
&&
frameId2
==
0
&&
frameId3
==
0
&&
frameSize
==
0
if
(
frameId0
==
0
&&
frameId1
==
0
&&
frameId2
==
0
&&
frameId3
==
0
&&
frameSize
==
0
&&
flags
==
0
)
{
&&
flags
==
0
)
{
// We must be reading zero padding at the end of the tag.
// We must be reading zero padding at the end of the tag.
...
@@ -159,6 +193,9 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -159,6 +193,9 @@ public final class Id3Decoder implements MetadataDecoder {
}
}
int
nextFramePosition
=
id3Data
.
getPosition
()
+
frameSize
;
int
nextFramePosition
=
id3Data
.
getPosition
()
+
frameSize
;
if
(
nextFramePosition
>
id3Data
.
limit
())
{
return
null
;
}
// Frame flags.
// Frame flags.
boolean
isCompressed
=
false
;
boolean
isCompressed
=
false
;
...
@@ -166,12 +203,12 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -166,12 +203,12 @@ public final class Id3Decoder implements MetadataDecoder {
boolean
isUnsynchronized
=
false
;
boolean
isUnsynchronized
=
false
;
boolean
hasDataLength
=
false
;
boolean
hasDataLength
=
false
;
boolean
hasGroupIdentifier
=
false
;
boolean
hasGroupIdentifier
=
false
;
if
(
id3Header
.
majorVersion
==
3
)
{
if
(
majorVersion
==
3
)
{
isCompressed
=
(
flags
&
0x0080
)
!=
0
;
isCompressed
=
(
flags
&
0x0080
)
!=
0
;
isEncrypted
=
(
flags
&
0x0040
)
!=
0
;
isEncrypted
=
(
flags
&
0x0040
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0020
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0020
)
!=
0
;
hasDataLength
=
isCompressed
;
hasDataLength
=
isCompressed
;
}
else
if
(
id3Header
.
majorVersion
==
4
)
{
}
else
if
(
majorVersion
==
4
)
{
hasGroupIdentifier
=
(
flags
&
0x0040
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0040
)
!=
0
;
isCompressed
=
(
flags
&
0x0008
)
!=
0
;
isCompressed
=
(
flags
&
0x0008
)
!=
0
;
isEncrypted
=
(
flags
&
0x0004
)
!=
0
;
isEncrypted
=
(
flags
&
0x0004
)
!=
0
;
...
@@ -199,26 +236,29 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -199,26 +236,29 @@ public final class Id3Decoder implements MetadataDecoder {
try
{
try
{
Id3Frame
frame
;
Id3Frame
frame
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
(
majorVersion
==
2
||
frameId3
==
'X'
))
{
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
frameId3
==
'B'
)
{
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
(
frameId3
==
'B'
||
majorVersion
==
2
))
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
)
{
}
else
if
(
majorVersion
==
2
?
(
frameId0
==
'P'
&&
frameId1
==
'I'
&&
frameId2
==
'C'
)
frame
=
decodeApicFrame
(
id3Data
,
frameSize
);
:
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
))
{
frame
=
decodeApicFrame
(
id3Data
,
frameSize
,
majorVersion
);
}
else
if
(
frameId0
==
'T'
)
{
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
frameId3
!=
0
?
String
id
=
majorVersion
==
2
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
)
:
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
);
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
}
else
if
(
frameId0
==
'C'
&&
frameId1
==
'O'
&&
frameId2
==
'M'
&&
}
else
if
(
frameId0
==
'C'
&&
frameId1
==
'O'
&&
frameId2
==
'M'
(
frameId3
==
'M'
||
frameId3
==
0
))
{
&&
(
frameId3
==
'M'
||
majorVersion
==
2
))
{
frame
=
decodeCommentFrame
(
id3Data
,
frameSize
);
frame
=
decodeCommentFrame
(
id3Data
,
frameSize
);
}
else
{
}
else
{
String
id
=
frameId3
!=
0
?
String
id
=
majorVersion
==
2
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
)
:
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
);
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
}
}
return
frame
;
return
frame
;
...
@@ -288,16 +328,29 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -288,16 +328,29 @@ public final class Id3Decoder implements MetadataDecoder {
return
new
GeobFrame
(
mimeType
,
filename
,
description
,
objectData
);
return
new
GeobFrame
(
mimeType
,
filename
,
description
,
objectData
);
}
}
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
throws
UnsupportedEncodingException
{
int
majorVersion
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
frameSize
-
1
];
byte
[]
data
=
new
byte
[
frameSize
-
1
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
int
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
String
mimeType
;
String
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
);
int
mimeTypeEndIndex
;
if
(
majorVersion
==
2
)
{
mimeTypeEndIndex
=
2
;
mimeType
=
"image/"
+
new
String
(
data
,
0
,
3
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
equals
(
"image/jpg"
))
{
mimeType
=
"image/jpeg"
;
}
}
else
{
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
indexOf
(
'/'
)
==
-
1
)
{
mimeType
=
"image/"
+
mimeType
;
}
}
int
pictureType
=
data
[
mimeTypeEndIndex
+
1
]
&
0xFF
;
int
pictureType
=
data
[
mimeTypeEndIndex
+
1
]
&
0xFF
;
...
@@ -312,20 +365,6 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -312,20 +365,6 @@ public final class Id3Decoder implements MetadataDecoder {
return
new
ApicFrame
(
mimeType
,
description
,
pictureType
,
pictureData
);
return
new
ApicFrame
(
mimeType
,
description
,
pictureType
,
pictureData
);
}
}
private
static
TextInformationFrame
decodeTextInformationFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
frameSize
-
1
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
int
descriptionEndIndex
=
indexOfEos
(
data
,
0
,
encoding
);
String
description
=
new
String
(
data
,
0
,
descriptionEndIndex
,
charset
);
return
new
TextInformationFrame
(
id
,
description
);
}
private
static
CommentFrame
decodeCommentFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
private
static
CommentFrame
decodeCommentFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
throws
UnsupportedEncodingException
{
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
int
encoding
=
id3Data
.
readUnsignedByte
();
...
@@ -348,6 +387,20 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -348,6 +387,20 @@ public final class Id3Decoder implements MetadataDecoder {
return
new
CommentFrame
(
language
,
description
,
text
);
return
new
CommentFrame
(
language
,
description
,
text
);
}
}
private
static
TextInformationFrame
decodeTextInformationFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
frameSize
-
1
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
int
descriptionEndIndex
=
indexOfEos
(
data
,
0
,
encoding
);
String
description
=
new
String
(
data
,
0
,
descriptionEndIndex
,
charset
);
return
new
TextInformationFrame
(
id
,
description
);
}
private
static
BinaryFrame
decodeBinaryFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
private
static
BinaryFrame
decodeBinaryFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
{
String
id
)
{
byte
[]
frame
=
new
byte
[
frameSize
];
byte
[]
frame
=
new
byte
[
frameSize
];
...
@@ -395,39 +448,6 @@ public final class Id3Decoder implements MetadataDecoder {
...
@@ -395,39 +448,6 @@ public final class Id3Decoder implements MetadataDecoder {
}
}
}
}
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
int
terminationPos
=
indexOfZeroByte
(
data
,
fromIndex
);
// For single byte encoding charsets, we're done.
if
(
encoding
==
ID3_TEXT_ENCODING_ISO_8859_1
||
encoding
==
ID3_TEXT_ENCODING_UTF_8
)
{
return
terminationPos
;
}
// Otherwise ensure an even index and look for a second zero byte.
while
(
terminationPos
<
data
.
length
-
1
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
return
terminationPos
;
}
terminationPos
=
indexOfZeroByte
(
data
,
terminationPos
+
1
);
}
return
data
.
length
;
}
private
static
int
indexOfZeroByte
(
byte
[]
data
,
int
fromIndex
)
{
for
(
int
i
=
fromIndex
;
i
<
data
.
length
;
i
++)
{
if
(
data
[
i
]
==
(
byte
)
0
)
{
return
i
;
}
}
return
data
.
length
;
}
private
static
int
delimiterLength
(
int
encodingByte
)
{
return
(
encodingByte
==
ID3_TEXT_ENCODING_ISO_8859_1
||
encodingByte
==
ID3_TEXT_ENCODING_UTF_8
)
?
1
:
2
;
}
private
static
final
class
Id3Header
{
private
static
final
class
Id3Header
{
private
final
int
majorVersion
;
private
final
int
majorVersion
;
...
...
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