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

nl.vpro.domain.subtitles.TT888 Maven / Gradle / Ivy

Go to download

Subtitles are related to media, but are implemented completely parallel. Classes to contain, parse, and assemble subtitle objects are here.

There is a newer version: 8.3.0
Show newest version
package nl.vpro.domain.subtitles;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.lang3.StringUtils;


/**
 * @author Michiel Meeuwissen
 * @since 4.8
 */
@Slf4j
public class TT888 {

    private TT888() {
    }

    public static final Charset CHARSET = SubtitlesFormat.TT888.getCharset();

    public static StringBuilder format(Cue cue, StringBuilder builder) {
        //001 0:01:00 0:02:00 ondertitels !

        builder.append(String.format("%04d ", cue.getSequence()));
        if (cue.getStart() != null) {
            builder.append(formatDuration(cue.getStart()));
        }
        builder.append(" ");
        if (cue.getEnd() != null) {
            builder.append(formatDuration(cue.getEnd()));
        }
        builder.append("\n");
        if (cue.getContent() != null) {
            builder.append(cue.getContent());
        }
        builder.append("\n\n");
        return builder;
    }

    static String formatDuration(Duration duration) {
        long millis = duration.toMillis();
        boolean negative = millis < 0;
        if (negative) {
            millis *= -1;
        }
        long centiseconds = millis / 10;
        /*long hours = centiseconds / 360000;
        centiseconds -= hours * 360000;
        */
        long minutes = centiseconds / 6000;
        centiseconds -= minutes * 6000;
        long seconds = centiseconds / 100;
        centiseconds -= seconds * 100;
        return String.format("%s%02d:%02d:%02d", negative ? "-": "", minutes, seconds, centiseconds);
    }



    public static Stream parse(String parent, Duration offset, Function offsetGuesser, InputStream inputStream) {
        return parse(parent, offset, offsetGuesser, inputStream, CHARSET);
    }

    public static Stream parseUTF8(String parent, Duration offset, Function offsetGuesser, InputStream inputStream) {
        return parse(parent, offset, offsetGuesser, inputStream, StandardCharsets.UTF_8);
    }


    public static Stream parse(String parent, Duration offset, Function offsetGuesser, InputStream inputStream, Charset charset) {
        return parse(parent, offset, offsetGuesser, new InputStreamReader(inputStream, charset));
    }

    static Stream parse(final String parent, final Duration offsetParameter, Function offsetGuesser, Reader reader) {
        final Iterator stream = new BufferedReader(reader)
            .lines().iterator();

        Iterator cues = new Iterator() {

            boolean needsFindNext = true;
            String timeLine = null;
            final StringBuilder content = new StringBuilder();
            Duration offset = offsetParameter;

            long count = 0;

            @Override
            public boolean hasNext() {
                findNext();
                return timeLine != null;
            }

            @Override
            public Cue next() {
                findNext();
                if (timeLine == null) {
                    throw new NoSuchElementException();
                }
                needsFindNext = true;
                try {
                    String contentString = content.toString();
                    TimeLine parsedTimeLine = parseTimeline(timeLine);
                    if (offset == null) {
                        offset = offsetGuesser.apply(parsedTimeLine);
                    }
                    return createCue(parent, parsedTimeLine, offset, contentString);
                } catch (IllegalArgumentException e) {
                    log.error(e.getMessage(), e);
                    return null;
                } finally {
                    count++;
                }

            }

            protected void findNext() {
                if (needsFindNext) {
                    timeLine = null;
                    content.setLength(0);
                    while (stream.hasNext()) {
                        String l = stream.next();
                        if (StringUtils.isNotBlank(l)) {
                            timeLine = l.trim();
                            break;
                        }
                    }
                    while (stream.hasNext()) {
                        String l = stream.next();
                        if (StringUtils.isBlank(l) && content.length() > 0) {
                            break;
                        }
                        if (content.length() > 0) {
                            content.append('\n');
                        }
                        content.append(l.trim());
                    }
                    needsFindNext = false;
                }
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(cues, Spliterator.ORDERED), false);

    }

    static Cue createCue(String parent, TimeLine timeLine, Duration offset, String content) {
        return Cue.builder()
            .mid(parent)
            .sequence(timeLine.sequence)
            .start(timeLine.start.minus(offset))
            .end(timeLine.end.minus(offset))
            .content(content)
            .build();
    }


    private static final Pattern SEQ = Pattern.compile("([-+]?\\d+):?");
    public static TimeLine parseTimeline(String timeLine) {
        String[] split = timeLine.split("\\s+");
        try {
            Matcher matcher = SEQ.matcher(split[0]);
            Integer integer = matcher.matches() ? Integer.parseInt(matcher.group(1)) : null;
            return new TimeLine(
                integer,
                parseTime(split[1]),
                parseTime(split[2])
            );
        } catch (NumberFormatException | ArrayIndexOutOfBoundsException nfe) {
            throw new IllegalArgumentException("Could not parse " + timeLine + " (" + Arrays.asList(split) + ").  Reason: " + nfe.getClass() + " " + nfe.getMessage(), nfe);
        }


    }

    private static Duration parseTime(String duration) {
        String[] split = duration.split(":", 4);
        int index = 0;
        long hours = Long.parseLong(split[0]);
        long minutes = Long.parseLong(split[1]);
        long seconds = Long.parseLong(split[2]);
        long hunderds = Long.parseLong(split[3]);
        return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds).plusMillis(hunderds * 10);
    }


    static void format(Iterator cueIterator, OutputStream out) throws IOException {
        Writer writer = new OutputStreamWriter(out, CHARSET);
        format(cueIterator, writer);
        writer.flush();
    }

    static void format(Iterator cueIterator, Writer writer) throws IOException {
        StringBuilder builder = new StringBuilder();
        while (cueIterator.hasNext()) {
            Cue cue = cueIterator.next();
            if (cue != null) {
                format(cue, builder);
                writer.write(builder.toString());
                builder.setLength(0);
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy