Timecode parsing logic now handles two short formats

We now handle both HH:MM and MM:SS when paring timecodes. We will move
in reverse order (assuming that the timecodes will increase over the
course of the document) and parse short codes as HH:MM. When we get a
result that does not fit into the duration we will change to parse as
MM:SS and use that for the rest of the document.
This commit is contained in:
Nathan Mascitelli 2019-02-12 19:43:57 -05:00
parent d0f617880c
commit 39b9df5064
2 changed files with 118 additions and 40 deletions

View File

@ -30,12 +30,12 @@ public class TimelineTest extends InstrumentationTestCase {
context = getInstrumentation().getTargetContext();
}
private Playable newTestPlayable(List<Chapter> chapters, String shownotes) {
private Playable newTestPlayable(List<Chapter> chapters, String shownotes, int duration) {
FeedItem item = new FeedItem(0, "Item", "item-id", "http://example.com/item", new Date(), FeedItem.PLAYED, null);
item.setChapters(chapters);
item.setContentEncoded(shownotes);
FeedMedia media = new FeedMedia(item, "http://example.com/episode", 100, "audio/mp3");
media.setDuration(Integer.MAX_VALUE);
media.setDuration(duration);
item.setMedia(media);
return media;
}
@ -44,7 +44,27 @@ public class TimelineTest extends InstrumentationTestCase {
final String timeStr = "10:11:12";
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
public void testProcessShownotesAddTimecodeHHMMNoChapters() throws Exception {
final String timeStr = "10:11";
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
public void testProcessShownotesAddTimecodeMMSSNoChapters() throws Exception {
final String timeStr = "10:11";
final long time = 10 * 60 * 1000 + 11 * 1000;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 11 * 60 * 1000);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
@ -54,7 +74,7 @@ public class TimelineTest extends InstrumentationTestCase {
final String timeStr = "2:11:12";
final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
@ -64,47 +84,55 @@ public class TimelineTest extends InstrumentationTestCase {
final String timeStr = "1:12";
final long time = 60 * 1000 + 12 * 1000;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 2 * 60 * 1000);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
public void testProcessShownotesAddTimecodeMMSSNoChapters() throws Exception {
final String timeStr = "10:11";
final long time = 60 * 1000 * 10 + 1000 * 11;
public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() throws Exception {
final String[] timeStrings = new String[]{ "10:12", "1:10:12" };
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 60 * 60 * 1000);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings);
}
public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() throws Exception {
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);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 60 * 1000 + 12 * 60 * 1000 }, timeStrings);
}
public void testProcessShownotesAddTimecodeParentheses() throws Exception {
final String timeStr = "10:11:00";
final String timeStr = "10:11";
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode (" + timeStr + ") here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode (" + timeStr + ") here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
public void testProcessShownotesAddTimecodeBrackets() throws Exception {
final String timeStr = "10:11:00";
final String timeStr = "10:11";
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode [" + timeStr + "] here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode [" + timeStr + "] here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
public void testProcessShownotesAddTimecodeAngleBrackets() throws Exception {
final String timeStr = "10:11:00";
final String timeStr = "10:11";
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
Playable p = newTestPlayable(null, "<p> Some test text with a timecode <" + timeStr + "> here.</p>");
Playable p = newTestPlayable(null, "<p> Some test text with a timecode <" + timeStr + "> here.</p>", Integer.MAX_VALUE);
Timeline t = new Timeline(context, p);
String res = t.processShownotes(true);
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});

View File

@ -7,6 +7,7 @@ import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import org.jsoup.Jsoup;
@ -14,6 +15,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -127,35 +129,12 @@ public class Timeline {
// apply timecode links
if (addTimecodes) {
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
for (Element element : elementsWithTimeCodes) {
Matcher matcherLong = TIMECODE_REGEX.matcher(element.html());
StringBuffer buffer = new StringBuffer();
while (matcherLong.find()) {
String h = matcherLong.group(1);
String group = matcherLong.group(0);
int time = (h != null) ? Converter.durationStringLongToMs(group) :
Converter.durationStringShortToMs(group);
String rep;
if (playable == null || playable.getDuration() > time) {
rep = String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
} else {
rep = group;
}
matcherLong.appendReplacement(buffer, rep);
}
matcherLong.appendTail(buffer);
element.html(buffer.toString());
}
addTimecodes(document, playable);
}
return document.toString();
}
/**
* Returns true if the given link is a timecode link.
*/
@ -186,4 +165,75 @@ public class Timeline {
public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
this.shownotesProvider = shownotesProvider;
}
private void addTimecodes(Document document, final Playable playable) {
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
// Assuming the timecodes are going to increase through the document loop through the
// elements backwards so we can determine when/if we need to shift from HH:MM to MM:SS
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
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
// non-reversed order.
ArrayList<String> replacements = new ArrayList<>();
for (Pair<Boolean, String> matchPair : matches) {
boolean isLongFormat = matchPair.first;
String group = matchPair.second;
int time = isLongFormat
? Converter.durationStringLongToMs(group)
: Converter.durationStringShortToMs(group, useHourFormat);
String rep = group;
if (playable == null) {
rep = createTimeLink(time, group);
} else {
int duration = playable.getDuration();
if (duration > time) {
rep = createTimeLink(time, group);
} else if (!isLongFormat && useHourFormat) {
// 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;
}
}
}
replacements.add(0, rep);
}
// Now that we have all the replacements, replace.
StringBuffer buffer = new StringBuffer();
int index = 0;
matcherLong.reset();
while (matcherLong.find()) {
matcherLong.appendReplacement(buffer, replacements.get(index));
index++;
}
matcherLong.appendTail(buffer);
element.html(buffer.toString());
}
}
private String createTimeLink(int time, String group) {
return String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
}
}