Use a single format for short timecodes

It is unlikely that multiple formats for short timecodes would be used
in one document. Therefor we will parse all the short timecodes to see
if they are all less then the duration as HH:MM. If they are we will use
that, otherwise we will parse them as MM:SS.
This commit is contained in:
Nathan Mascitelli 2019-02-13 21:06:19 -05:00
parent c49e98b546
commit e94e4bc3d0
2 changed files with 45 additions and 49 deletions

View File

@ -110,12 +110,14 @@ public class TimelineTest extends InstrumentationTestCase {
} }
public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() throws Exception { public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() throws Exception {
// One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS.
final String[] timeStrings = new String[]{ "10:12", "2:12" }; final String[] timeStrings = new String[]{ "10:12", "2:12" };
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000); Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000);
Timeline t = new Timeline(context, p); Timeline t = new Timeline(context, p);
String res = t.processShownotes(true); String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 60 * 1000 + 12 * 60 * 1000 }, timeStrings); checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings);
} }
public void testProcessShownotesAddTimecodeParentheses() throws Exception { public void testProcessShownotesAddTimecodeParentheses() throws Exception {

View File

@ -170,70 +170,64 @@ public class Timeline {
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX); Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes"); Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
// Assuming the timecodes are going to increase through the document loop through the if (elementsWithTimeCodes.size() == 0) {
// elements backwards so we can determine when/if we need to shift from HH:MM to MM:SS // No elements with timecodes
return;
}
int playableDuration = playable == null ? Integer.MAX_VALUE : playable.getDuration();
boolean useHourFormat = true; boolean useHourFormat = true;
for (int i = elementsWithTimeCodes.size() - 1; i >= 0 ; i--) {
Element element = elementsWithTimeCodes.get(i);
Matcher matcherLong = TIMECODE_REGEX.matcher(element.html());
// Get all matches and store in reverse order if (playableDuration != Integer.MAX_VALUE) {
ArrayList<Pair<Boolean, String>> matches = new ArrayList<>();
while (matcherLong.find()) {
matches.add(0, new Pair<>(matcherLong.group(1) != null, matcherLong.group(0)));
}
// Now loop through the reversed matches and get the replacements. Store them in // We need to decide if we are going to treat short timecodes as HH:MM or MM:SS. To do
// non-reversed order. // so we will parse all the short timecodes and see if they fit in the duration. If one
ArrayList<String> replacements = new ArrayList<>(); // does not we will use MM:SS, otherwise all will be parsed as HH:MM.
for (Pair<Boolean, String> matchPair : matches) { for (Element element : elementsWithTimeCodes) {
boolean isLongFormat = matchPair.first; Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
String group = matchPair.second; while (matcherForElement.find()) {
int time = isLongFormat
? Converter.durationStringLongToMs(group)
: Converter.durationStringShortToMs(group, useHourFormat);
String rep = group; // We only want short timecodes right now.
if (playable == null) { if (matcherForElement.group(1) == null) {
rep = createTimeLink(time, group); int time = Converter.durationStringShortToMs(matcherForElement.group(0), true);
} else {
int duration = playable.getDuration();
if (duration > time) { // If the parsed timecode is greater then the duration then we know we need to
rep = createTimeLink(time, group); // use the minute format so we are done.
} else if (!isLongFormat && useHourFormat) { if (time > playableDuration) {
// The duration calculated in hours is too long and the timecode format is
// short. So try and see if it will work when treated as minutes.
time = Converter.durationStringShortToMs(group, false);
if (duration > time) {
// We have found the treating a short timecode as minutes works, do that
// from now on.
rep = createTimeLink(time, group);
useHourFormat = false; useHourFormat = false;
break;
} }
} }
} }
replacements.add(0, rep); if (!useHourFormat) {
break;
}
} }
}
// Now that we have all the replacements, replace. for (Element element : elementsWithTimeCodes) {
Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
int index = 0;
matcherLong.reset(); while (matcherForElement.find()) {
while (matcherLong.find()) { String group = matcherForElement.group(0);
matcherLong.appendReplacement(buffer, replacements.get(index));
index++; int time = matcherForElement.group(1) != null
? Converter.durationStringLongToMs(group)
: Converter.durationStringShortToMs(group, useHourFormat);
String replacementText = group;
if (time < playableDuration) {
replacementText = String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
}
matcherForElement.appendReplacement(buffer, replacementText);
} }
matcherLong.appendTail(buffer); matcherForElement.appendTail(buffer);
element.html(buffer.toString()); element.html(buffer.toString());
} }
} }
private String createTimeLink(int time, String group) {
return String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
}
} }