package com.googlecode.mp4parser; import org.mp4parser.Container; import org.mp4parser.muxer.Movie; import org.mp4parser.muxer.Track; import org.mp4parser.muxer.builder.DefaultFragmenterImpl; import org.mp4parser.muxer.builder.DefaultMp4Builder; import org.mp4parser.muxer.container.mp4.MovieCreator; import org.mp4parser.muxer.tracks.TextTrackImpl; import java.io.*; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Adds subtitles. */ public class SubTitleExample { public static void main(String[] args) throws IOException { Movie m1 = MovieCreator.build(("C:\\dev\\DRMTODAY-872\\Tears_Of_Steel_128000_eng.mp4")); Movie m2 = MovieCreator.build(("C:\\dev\\DRMTODAY-872\\Tears_Of_Steel_600000.mp4")); //WebVttTrack webVttTrack = new WebVttTrack(new , "subs", Locale.ENGLISH); TextTrackImpl textTrack = new TextTrackImpl(); textTrack.getSubs().addAll( WebVttParser.parse(new FileInputStream("C:\\dev\\DRMTODAY-872\\Tears_Of_Steel_eng.vtt"))); Movie m = new Movie(); for (Track track : m2.getTracks()) { m.addTrack(track); } for (Track track : m1.getTracks()) { m.addTrack(track); } m.addTrack(textTrack); DefaultMp4Builder builder = new DefaultMp4Builder(); builder.setFragmenter(new DefaultFragmenterImpl(2)); Container c = builder.build(m); WritableByteChannel wbc = new FileOutputStream("output.mp4").getChannel(); c.writeContainer(wbc); } public static class WebVttParser { 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); static List<TextTrackImpl.Line> parse(InputStream in) throws IOException { BufferedReader webvttData = new BufferedReader(new InputStreamReader(in, "UTF-8")); String line; List<TextTrackImpl.Line> samples = new ArrayList<TextTrackImpl.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); } 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); } } // 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()); } samples.add(new TextTrackImpl.Line(startTime, endTime, payload.toString())); } return samples; } 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])); } } }