All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mp4parser.muxer.tracks.webvtt.WebVttTrack Maven / Gradle / Ivy

Go to download

This package has a focus on file operation. It can read A/V data from Random Access Datasources

There is a newer version: 1.9.56
Show newest version
package org.mp4parser.muxer.tracks.webvtt;

import org.mp4parser.Box;
import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox;
import org.mp4parser.boxes.iso14496.part30.WebVTTConfigurationBox;
import org.mp4parser.boxes.iso14496.part30.WebVTTSampleEntry;
import org.mp4parser.boxes.iso14496.part30.WebVTTSourceLabelBox;
import org.mp4parser.boxes.sampleentry.SampleEntry;
import org.mp4parser.muxer.AbstractTrack;
import org.mp4parser.muxer.Sample;
import org.mp4parser.muxer.TrackMetaData;
import org.mp4parser.muxer.tracks.webvtt.sampleboxes.CuePayloadBox;
import org.mp4parser.muxer.tracks.webvtt.sampleboxes.CueSettingsBox;
import org.mp4parser.muxer.tracks.webvtt.sampleboxes.VTTCueBox;
import org.mp4parser.muxer.tracks.webvtt.sampleboxes.VTTEmptyCueBox;
import org.mp4parser.tools.ByteBufferByteChannel;
import org.mp4parser.tools.Mp4Arrays;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.mp4parser.tools.CastUtils.l2i;


public class WebVttTrack extends AbstractTrack {
    private static final String WEBVTT_FILE_HEADER_STRING = "^\uFEFF?WEBVTT((\\u0020|\u0009).*)?$";
    private static final Pattern WEBVTT_FILE_HEADER =
            Pattern.compile(WEBVTT_FILE_HEADER_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 final Sample EMPTY_SAMPLE = new Sample() {
        ByteBuffer vtte;

        {
            VTTEmptyCueBox vttEmptyCueBox = new VTTEmptyCueBox();
            vtte = ByteBuffer.allocate(l2i(vttEmptyCueBox.getSize()));
            try {
                vttEmptyCueBox.getBox(new ByteBufferByteChannel(vtte));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            vtte.rewind();
        }


        public void writeTo(WritableByteChannel channel) throws java.io.IOException {
            channel.write(vtte.duplicate());
        }

        public long getSize() {
            return vtte.remaining();
        }

        public ByteBuffer asByteBuffer() {
            return vtte.duplicate();
        }

        @Override
        public SampleEntry getSampleEntry() {
            return sampleEntry;
        }
    };
    TrackMetaData trackMetaData = new TrackMetaData();
    List samples = new ArrayList();
    long[] sampleDurations = new long[0];
    WebVTTSampleEntry sampleEntry;

    public WebVttTrack(InputStream is, String trackName, Locale locale) throws IOException {
        super(trackName);
        trackMetaData.setTimescale(1000);
        trackMetaData.setLanguage(locale.getISO3Language());
        long mediaTimestampUs = 0;


        sampleEntry = new WebVTTSampleEntry();
        WebVTTConfigurationBox webVttConf = new WebVTTConfigurationBox();
        sampleEntry.addBox(webVttConf);
        sampleEntry.addBox(new WebVTTSourceLabelBox());

        BufferedReader webvttData = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        String line;

        // file should start with "WEBVTT"
        line = webvttData.readLine();
        if (line == null || !WEBVTT_FILE_HEADER.matcher(line).matches()) {
            throw new IOException("Expected WEBVTT. Got " + line);
        }
        webVttConf.setConfig(webVttConf.getConfig() + "\n" + line);
        while (true) {
            line = webvttData.readLine();
            if (line == null) {
                // we reached EOF before finishing the header
                throw new IOException("Expected an empty line after webvtt header");
            } else if (line.isEmpty()) {
                // we've read the newline that separates the header from the body
                break;
            }

            Matcher matcher = WEBVTT_METADATA_HEADER.matcher(line);
            if (!matcher.find()) {
                throw new IOException("Expected WebVTT metadata header. Got " + line);
            }
            webVttConf.setConfig(webVttConf.getConfig() + "\n" + line);
        }


        // process the cues and text
        while ((line = webvttData.readLine()) != null) {
            if ("".equals(line.trim())) {
                continue;
            }
            // parse the cue identifier (if present) {
            Matcher matcher = WEBVTT_CUE_IDENTIFIER.matcher(line);
            if (matcher.find()) {
                // ignore the identifier (we currently don't use it) and read the next line
                line = webvttData.readLine();
            }

            long startTime;
            long endTime;

            // parse the cue timestamps
            matcher = WEBVTT_TIMESTAMP.matcher(line);

            // parse start timestamp
            if (!matcher.find()) {
                throw new IOException("Expected cue start time: " + line);
            } else {
                startTime = parseTimestampUs(matcher.group());
            }

            // parse end timestamp
            String endTimeString;
            if (!matcher.find()) {
                throw new IOException("Expected cue end time: " + line);
            } else {
                endTimeString = matcher.group();
                endTime = parseTimestampUs(endTimeString);
            }

            // parse the (optional) cue setting list
            line = line.substring(line.indexOf(endTimeString) + endTimeString.length());
            matcher = WEBVTT_CUE_SETTING.matcher(line);
            String settings = null;
            while (matcher.find()) {
                settings = matcher.group();
            }
            StringBuilder payload = new StringBuilder();
            while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
                if (payload.length() > 0) {
                    payload.append("\n");
                }
                payload.append(line.trim());
            }

            if (startTime != mediaTimestampUs) {
                //System.err.println("" + mediaTimestampUs + " - " + startTime + " Add empty sample");
                sampleDurations = Mp4Arrays.copyOfAndAppend(sampleDurations, startTime - mediaTimestampUs);
                samples.add(EMPTY_SAMPLE);
            }
            sampleDurations = Mp4Arrays.copyOfAndAppend(sampleDurations, endTime - startTime);
            VTTCueBox vttCueBox = new VTTCueBox();
            if (settings != null) {
                CueSettingsBox csb = new CueSettingsBox();
                csb.setContent(settings);
                vttCueBox.setCueSettingsBox(csb);
            }
            CuePayloadBox cuePayloadBox = new CuePayloadBox();
            cuePayloadBox.setContent(payload.toString());
            vttCueBox.setCuePayloadBox(cuePayloadBox);

            samples.add(new BoxBearingSample(Collections.singletonList(vttCueBox)));


            mediaTimestampUs = endTime;
            // samples.add();
        }


    }

    private static long parseTimestampUs(String s) throws NumberFormatException {
        if (!s.matches(WEBVTT_TIMESTAMP_STRING)) {
            throw new NumberFormatException("has invalid format");
        }

        String[] parts = s.split("\\.", 2);
        long value = 0;
        for (String group : parts[0].split(":")) {
            value = value * 60 + Long.parseLong(group);
        }
        return (value * 1000 + Long.parseLong(parts[1]));
    }

    public List getSampleEntries() {
        return Collections.singletonList(sampleEntry);
    }

    public long[] getSampleDurations() {
        long[] adoptedSampleDuration = new long[sampleDurations.length];
        for (int i = 0; i < adoptedSampleDuration.length; i++) {
            adoptedSampleDuration[i] = sampleDurations[i] * trackMetaData.getTimescale() / 1000;
        }
        return adoptedSampleDuration;

    }

    public TrackMetaData getTrackMetaData() {
        return trackMetaData;
    }

    public String getHandler() {
        return "text";
    }

    public List getSamples() {
        return samples;
    }

    public void close() throws java.io.IOException {

    }

    private class BoxBearingSample implements Sample {
        List boxes;

        public BoxBearingSample(List boxes) {
            this.boxes = boxes;
        }

        public void writeTo(WritableByteChannel channel) throws java.io.IOException {
            for (Box box : boxes) {
                box.getBox(channel);
            }
        }

        public long getSize() {
            long l = 0;
            for (Box box : boxes) {
                l += box.getSize();
            }
            return l;
        }

        public ByteBuffer asByteBuffer() {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                writeTo(Channels.newChannel(baos));
            } catch (java.io.IOException e) {
                throw new RuntimeException(e);
            }

            return ByteBuffer.wrap(baos.toByteArray());
        }

        @Override
        public SampleEntry getSampleEntry() {
            return WebVttTrack.this.sampleEntry;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy