1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
package de.danoeh.antennapod.parser.media.id3;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
import org.apache.commons.io.input.CountingInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
/**
* Reads ID3 chapters.
* See https://id3.org/id3v2-chapters-1.0
*/
public class ChapterReader extends ID3Reader {
private static final String TAG = "ID3ChapterReader";
public static final String FRAME_ID_CHAPTER = "CHAP";
public static final String FRAME_ID_TITLE = "TIT2";
public static final String FRAME_ID_LINK = "WXXX";
public static final String FRAME_ID_PICTURE = "APIC";
public static final String MIME_IMAGE_URL = "-->";
public static final int IMAGE_TYPE_COVER = 3;
private final List<Chapter> chapters = new ArrayList<>();
public ChapterReader(CountingInputStream input) {
super(input);
}
@Override
protected void readFrame(@NonNull FrameHeader frameHeader) throws IOException, ID3ReaderException {
if (FRAME_ID_CHAPTER.equals(frameHeader.getId())) {
Log.d(TAG, "Handling frame: " + frameHeader.toString());
Chapter chapter = readChapter(frameHeader);
Log.d(TAG, "Chapter done: " + chapter);
chapters.add(chapter);
} else {
super.readFrame(frameHeader);
}
}
public Chapter readChapter(@NonNull FrameHeader frameHeader) throws IOException, ID3ReaderException {
int chapterStartedPosition = getPosition();
String elementId = readIsoStringNullTerminated(100);
long startTime = readInt();
skipBytes(12); // Ignore end time, start offset, end offset
ID3Chapter chapter = new ID3Chapter(elementId, startTime);
// Read sub-frames
while (getPosition() < chapterStartedPosition + frameHeader.getSize()) {
FrameHeader subFrameHeader = readFrameHeader();
readChapterSubFrame(subFrameHeader, chapter);
}
return chapter;
}
public void readChapterSubFrame(@NonNull FrameHeader frameHeader, @NonNull Chapter chapter)
throws IOException, ID3ReaderException {
Log.d(TAG, "Handling subframe: " + frameHeader.toString());
int frameStartPosition = getPosition();
switch (frameHeader.getId()) {
case FRAME_ID_TITLE:
chapter.setTitle(readEncodingAndString(frameHeader.getSize()));
Log.d(TAG, "Found title: " + chapter.getTitle());
break;
case FRAME_ID_LINK:
readEncodingAndString(frameHeader.getSize()); // skip description
String url = readIsoStringNullTerminated(frameStartPosition + frameHeader.getSize() - getPosition());
try {
String decodedLink = URLDecoder.decode(url, "ISO-8859-1");
chapter.setLink(decodedLink);
Log.d(TAG, "Found link: " + chapter.getLink());
} catch (IllegalArgumentException iae) {
Log.w(TAG, "Bad URL found in ID3 data");
}
break;
case FRAME_ID_PICTURE:
byte encoding = readByte();
String mime = readEncodedString(encoding, frameHeader.getSize());
byte type = readByte();
String description = readEncodedString(encoding, frameHeader.getSize());
Log.d(TAG, "Found apic: " + mime + "," + description);
if (MIME_IMAGE_URL.equals(mime)) {
String link = readIsoStringNullTerminated(frameHeader.getSize());
Log.d(TAG, "Link: " + link);
if (TextUtils.isEmpty(chapter.getImageUrl()) || type == IMAGE_TYPE_COVER) {
chapter.setImageUrl(link);
}
} else {
int alreadyConsumed = getPosition() - frameStartPosition;
int rawImageDataLength = frameHeader.getSize() - alreadyConsumed;
if (TextUtils.isEmpty(chapter.getImageUrl()) || type == IMAGE_TYPE_COVER) {
chapter.setImageUrl(EmbeddedChapterImage.makeUrl(getPosition(), rawImageDataLength));
}
}
break;
default:
Log.d(TAG, "Unknown chapter sub-frame.");
break;
}
// Skip garbage to fill frame completely
// This also asserts that we are not reading too many bytes from this frame.
int alreadyConsumed = getPosition() - frameStartPosition;
skipBytes(frameHeader.getSize() - alreadyConsumed);
}
public List<Chapter> getChapters() {
return chapters;
}
}
|