@Override protected SsaSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList<Cue> cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray data = new ParsableByteArray(bytes, length); if (!haveInitializationData) { parseHeader(data); } parseEventBody(data, cues, cueTimesUs); Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); long[] cueTimesUsArray = cueTimesUs.toArray(); return new SsaSubtitle(cuesArray, cueTimesUsArray); }
/** * Parses the event body of the subtitle. * * @param data A {@link ParsableByteArray} from which the body should be read. * @param cues A list to which parsed cues will be added. * @param cueTimesUs An array to which parsed cue timestamps will be added. */ private void parseEventBody(ParsableByteArray data, List<Cue> cues, LongArray cueTimesUs) { String currentLine; while ((currentLine = data.readLine()) != null) { if (!haveInitializationData && currentLine.startsWith(FORMAT_LINE_PREFIX)) { parseFormatLine(currentLine); } else if (currentLine.startsWith(DIALOGUE_LINE_PREFIX)) { parseDialogueLine(currentLine, cues, cueTimesUs); } } }
void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { case ID_SEGMENT: if (segmentContentPosition != C.POSITION_UNSET && segmentContentPosition != contentPosition) { throw new ParserException("Multiple Segment elements not supported"); } segmentContentPosition = contentPosition; segmentContentSize = contentSize; break; case ID_SEEK: seekEntryId = UNSET_ENTRY_ID; seekEntryPosition = C.POSITION_UNSET; break; case ID_CUES: cueTimesUs = new LongArray(); cueClusterPositions = new LongArray(); break; case ID_CUE_POINT: seenClusterPositionForCurrentCuePoint = false; break; case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. if (seekForCuesEnabled && cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { // We don't know where the Cues element is located. It's most likely omitted. Allow // playback, but disable seeking. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); sentSeekMap = true; } } break; case ID_BLOCK_GROUP: sampleSeenReferenceBlock = false; break; case ID_CONTENT_ENCODING: // TODO: check and fail if more than one content encoding is present. break; case ID_CONTENT_ENCRYPTION: currentTrack.hasContentEncryption = true; break; case ID_TRACK_ENTRY: currentTrack = new Track(); break; case ID_MASTERING_METADATA: currentTrack.hasColorInfo = true; break; default: break; } }
@Override protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList<Cue> cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); String currentLine; while ((currentLine = subripData.readLine()) != null) { if (currentLine.length() == 0) { // Skip blank lines. continue; } // Parse the index line as a sanity check. try { Integer.parseInt(currentLine); } catch (NumberFormatException e) { Log.w(TAG, "Skipping invalid index: " + currentLine); continue; } // Read and parse the timing line. boolean haveEndTimecode = false; currentLine = subripData.readLine(); Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); if (matcher.matches()) { cueTimesUs.add(parseTimecode(matcher, 1)); if (!TextUtils.isEmpty(matcher.group(6))) { haveEndTimecode = true; cueTimesUs.add(parseTimecode(matcher, 6)); } } else { Log.w(TAG, "Skipping invalid timing: " + currentLine); continue; } // Read and parse the text. textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("<br>"); } textBuilder.append(currentLine.trim()); } Spanned text = Html.fromHtml(textBuilder.toString()); cues.add(new Cue(text)); if (haveEndTimecode) { cues.add(null); } } Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); long[] cueTimesUsArray = cueTimesUs.toArray(); return new SubripSubtitle(cuesArray, cueTimesUsArray); }
void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { case ID_SEGMENT: if (segmentContentPosition != C.POSITION_UNSET && segmentContentPosition != contentPosition) { throw new ParserException("Multiple Segment elements not supported"); } segmentContentPosition = contentPosition; segmentContentSize = contentSize; break; case ID_SEEK: seekEntryId = UNSET_ENTRY_ID; seekEntryPosition = C.POSITION_UNSET; break; case ID_CUES: cueTimesUs = new LongArray(); cueClusterPositions = new LongArray(); break; case ID_CUE_POINT: seenClusterPositionForCurrentCuePoint = false; break; case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. if (cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { // We don't know where the Cues element is located. It's most likely omitted. Allow // playback, but disable seeking. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); sentSeekMap = true; } } break; case ID_BLOCK_GROUP: sampleSeenReferenceBlock = false; break; case ID_CONTENT_ENCODING: // TODO: check and fail if more than one content encoding is present. break; case ID_CONTENT_ENCRYPTION: currentTrack.hasContentEncryption = true; break; case ID_TRACK_ENTRY: currentTrack = new Track(); break; default: break; } }
@Override protected SubripSubtitle decode(byte[] bytes, int length) { ArrayList<Cue> cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); String currentLine; while ((currentLine = subripData.readLine()) != null) { if (currentLine.length() == 0) { // Skip blank lines. continue; } // Parse the index line as a sanity check. try { Integer.parseInt(currentLine); } catch (NumberFormatException e) { Log.w(TAG, "Skipping invalid index: " + currentLine); continue; } // Read and parse the timing line. boolean haveEndTimecode = false; currentLine = subripData.readLine(); Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); if (matcher.matches()) { cueTimesUs.add(parseTimecode(matcher, 1)); if (!TextUtils.isEmpty(matcher.group(6))) { haveEndTimecode = true; cueTimesUs.add(parseTimecode(matcher, 6)); } } else { Log.w(TAG, "Skipping invalid timing: " + currentLine); continue; } // Read and parse the text. textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("<br>"); } textBuilder.append(currentLine.trim()); } Spanned text = Html.fromHtml(textBuilder.toString()); cues.add(new Cue(text)); if (haveEndTimecode) { cues.add(null); } } Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); long[] cueTimesUsArray = cueTimesUs.toArray(); return new SubripSubtitle(cuesArray, cueTimesUsArray); }
void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { case ID_SEGMENT: if (segmentContentPosition != C.POSITION_UNSET && segmentContentPosition != contentPosition) { throw new ParserException("Multiple Segment elements not supported"); } segmentContentPosition = contentPosition; segmentContentSize = contentSize; return; case ID_SEEK: seekEntryId = UNSET_ENTRY_ID; seekEntryPosition = C.POSITION_UNSET; return; case ID_CUES: cueTimesUs = new LongArray(); cueClusterPositions = new LongArray(); return; case ID_CUE_POINT: seenClusterPositionForCurrentCuePoint = false; return; case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. if (cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { // We don't know where the Cues element is located. It's most likely omitted. Allow // playback, but disable seeking. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); sentSeekMap = true; } } return; case ID_BLOCK_GROUP: sampleSeenReferenceBlock = false; return; case ID_CONTENT_ENCODING: // TODO: check and fail if more than one content encoding is present. return; case ID_CONTENT_ENCRYPTION: currentTrack.hasContentEncryption = true; return; case ID_TRACK_ENTRY: currentTrack = new Track(); return; default: return; } }
@Override protected SubripSubtitle decode(byte[] bytes, int length) { ArrayList<Cue> cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); boolean haveEndTimecode; String currentLine; while ((currentLine = subripData.readLine()) != null) { if (currentLine.length() == 0) { // Skip blank lines. continue; } // Parse the index line as a sanity check. try { Integer.parseInt(currentLine); } catch (NumberFormatException e) { Log.w(TAG, "Skipping invalid index: " + currentLine); continue; } // Read and parse the timing line. haveEndTimecode = false; currentLine = subripData.readLine(); Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); if (matcher.find()) { cueTimesUs.add(parseTimecode(matcher.group(1))); String endTimecode = matcher.group(2); if (!TextUtils.isEmpty(endTimecode)) { haveEndTimecode = true; cueTimesUs.add(parseTimecode(matcher.group(2))); } } else { Log.w(TAG, "Skipping invalid timing: " + currentLine); continue; } // Read and parse the text. textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("<br>"); } textBuilder.append(currentLine.trim()); } Spanned text = Html.fromHtml(textBuilder.toString()); cues.add(new Cue(text)); if (haveEndTimecode) { cues.add(null); } } Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); long[] cueTimesUsArray = cueTimesUs.toArray(); return new SubripSubtitle(cuesArray, cueTimesUsArray); }
/** * Parses a dialogue line. * * @param dialogueLine The line to parse. * @param cues A list to which parsed cues will be added. * @param cueTimesUs An array to which parsed cue timestamps will be added. */ private void parseDialogueLine(String dialogueLine, List<Cue> cues, LongArray cueTimesUs) { if (formatKeyCount == 0) { Log.w(TAG, "Skipping dialogue line before complete format: " + dialogueLine); return; } String[] lineValues = dialogueLine.substring(DIALOGUE_LINE_PREFIX.length()) .split(",", formatKeyCount); if (lineValues.length != formatKeyCount) { Log.w(TAG, "Skipping dialogue line with fewer columns than format: " + dialogueLine); return; } long startTimeUs = SsaDecoder.parseTimecodeUs(lineValues[formatStartIndex]); if (startTimeUs == C.TIME_UNSET) { Log.w(TAG, "Skipping invalid timing: " + dialogueLine); return; } long endTimeUs = C.TIME_UNSET; String endTimeString = lineValues[formatEndIndex]; if (!endTimeString.trim().isEmpty()) { endTimeUs = SsaDecoder.parseTimecodeUs(endTimeString); if (endTimeUs == C.TIME_UNSET) { Log.w(TAG, "Skipping invalid timing: " + dialogueLine); return; } } String text = lineValues[formatTextIndex] .replaceAll("\\{.*?\\}", "") .replaceAll("\\\\N", "\n") .replaceAll("\\\\n", "\n"); cues.add(new Cue(text)); cueTimesUs.add(startTimeUs); if (endTimeUs != C.TIME_UNSET) { cues.add(null); cueTimesUs.add(endTimeUs); } }
@Override protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList<Cue> cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); String currentLine; while ((currentLine = subripData.readLine()) != null) { if (currentLine.length() == 0) { // Skip blank lines. continue; } // Parse the index line as a sanity check. try { Integer.parseInt(currentLine); } catch (NumberFormatException e) { Log.w(TAG, "Skipping invalid index: " + currentLine); continue; } // Read and parse the timing line. boolean haveEndTimecode = false; currentLine = subripData.readLine(); if (currentLine == null) { Log.w(TAG, "Unexpected end"); break; } Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); if (matcher.matches()) { cueTimesUs.add(parseTimecode(matcher, 1)); if (!TextUtils.isEmpty(matcher.group(6))) { haveEndTimecode = true; cueTimesUs.add(parseTimecode(matcher, 6)); } } else { Log.w(TAG, "Skipping invalid timing: " + currentLine); continue; } // Read and parse the text. textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("<br>"); } textBuilder.append(currentLine.trim()); } Spanned text = Html.fromHtml(textBuilder.toString()); cues.add(new Cue(text)); if (haveEndTimecode) { cues.add(null); } } Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); long[] cueTimesUsArray = cueTimesUs.toArray(); return new SubripSubtitle(cuesArray, cueTimesUsArray); }