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:
parent
d0f617880c
commit
39b9df5064
@ -30,12 +30,12 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||||||
context = getInstrumentation().getTargetContext();
|
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);
|
FeedItem item = new FeedItem(0, "Item", "item-id", "http://example.com/item", new Date(), FeedItem.PLAYED, null);
|
||||||
item.setChapters(chapters);
|
item.setChapters(chapters);
|
||||||
item.setContentEncoded(shownotes);
|
item.setContentEncoded(shownotes);
|
||||||
FeedMedia media = new FeedMedia(item, "http://example.com/episode", 100, "audio/mp3");
|
FeedMedia media = new FeedMedia(item, "http://example.com/episode", 100, "audio/mp3");
|
||||||
media.setDuration(Integer.MAX_VALUE);
|
media.setDuration(duration);
|
||||||
item.setMedia(media);
|
item.setMedia(media);
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
@ -44,7 +44,27 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||||||
final String timeStr = "10:11:12";
|
final String timeStr = "10:11:12";
|
||||||
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
@ -54,7 +74,7 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||||||
final String timeStr = "2:11:12";
|
final String timeStr = "2:11:12";
|
||||||
final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
@ -64,47 +84,55 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||||||
final String timeStr = "1:12";
|
final String timeStr = "1:12";
|
||||||
final long time = 60 * 1000 + 12 * 1000;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testProcessShownotesAddTimecodeMMSSNoChapters() throws Exception {
|
public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() throws Exception {
|
||||||
final String timeStr = "10:11";
|
final String[] timeStrings = new String[]{ "10:12", "1:10:12" };
|
||||||
final long time = 60 * 1000 * 10 + 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 " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 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[]{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 {
|
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;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testProcessShownotesAddTimecodeBrackets() throws Exception {
|
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;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testProcessShownotesAddTimecodeAngleBrackets() throws Exception {
|
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;
|
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);
|
Timeline t = new Timeline(context, p);
|
||||||
String res = t.processShownotes(true);
|
String res = t.processShownotes(true);
|
||||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||||
|
@ -7,6 +7,7 @@ import android.support.annotation.ColorInt;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@ -14,6 +15,7 @@ import org.jsoup.nodes.Document;
|
|||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -127,35 +129,12 @@ public class Timeline {
|
|||||||
|
|
||||||
// apply timecode links
|
// apply timecode links
|
||||||
if (addTimecodes) {
|
if (addTimecodes) {
|
||||||
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
|
addTimecodes(document, playable);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return document.toString();
|
return document.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given link is a timecode link.
|
* Returns true if the given link is a timecode link.
|
||||||
*/
|
*/
|
||||||
@ -186,4 +165,75 @@ public class Timeline {
|
|||||||
public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
|
public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
|
||||||
this.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user