summaryrefslogtreecommitdiff
path: root/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ChapterReader.java
blob: 81bfa67b6639c7b4708a3f3184d89d75385c816d (plain)
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;
    }

}