Merge pull request #3032 from shortspider/3031-TimecodeRegex
Change Timecode Regex
This commit is contained in:
commit
1593a06077
|
@ -17,8 +17,8 @@ public class ConverterTest extends AndroidTestCase {
|
|||
|
||||
public void testGetDurationStringShort() throws Exception {
|
||||
String expected = "13:05";
|
||||
int input = 47110000;
|
||||
assertEquals(expected, Converter.getDurationStringShort(input));
|
||||
assertEquals(expected, Converter.getDurationStringShort(47110000, true));
|
||||
assertEquals(expected, Converter.getDurationStringShort(785000, false));
|
||||
}
|
||||
|
||||
public void testDurationStringLongToMs() throws Exception {
|
||||
|
@ -29,7 +29,7 @@ public class ConverterTest extends AndroidTestCase {
|
|||
|
||||
public void testDurationStringShortToMs() throws Exception {
|
||||
String input = "8:30";
|
||||
long expected = 30600000;
|
||||
assertEquals(expected, Converter.durationStringShortToMs(input));
|
||||
assertEquals(30600000, Converter.durationStringShortToMs(input, true));
|
||||
assertEquals(510000, Converter.durationStringShortToMs(input, false));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,17 @@ 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 testProcessShownotesAddTimecodeHHMMSSMoreThen24HoursNoChapters() throws Exception {
|
||||
final String timeStr = "25:00:00";
|
||||
final long time = 25 * 60 * 60 * 1000;
|
||||
|
||||
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});
|
||||
|
@ -54,17 +64,67 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||
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 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});
|
||||
}
|
||||
|
||||
public void testProcessShownotesAddTimecodeHMMSSNoChapters() throws Exception {
|
||||
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>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
public void testProcessShownotesAddTimecodeMSSNoChapters() throws Exception {
|
||||
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>", 2 * 60 * 1000);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
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 " + 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[]{ 10 * 60 * 1000 + 12 * 1000, 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings);
|
||||
}
|
||||
|
||||
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" };
|
||||
|
||||
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 * 1000 + 12 * 1000 }, timeStrings);
|
||||
}
|
||||
|
||||
public void testProcessShownotesAddTimecodeParentheses() 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>");
|
||||
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});
|
||||
|
@ -74,7 +134,7 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||
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});
|
||||
|
@ -84,12 +144,27 @@ public class TimelineTest extends InstrumentationTestCase {
|
|||
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 testProcessShownotesAndInvalidTimecode() throws Exception {
|
||||
final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"};
|
||||
|
||||
StringBuilder shownotes = new StringBuilder("<p> Some test text with timecodes ");
|
||||
for (String timeStr : timeStrs) {
|
||||
shownotes.append(timeStr).append(" ");
|
||||
}
|
||||
shownotes.append("here.</p>");
|
||||
|
||||
Playable p = newTestPlayable(null, shownotes.toString(), Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
checkLinkCorrect(res, new long[0], new String[0]);
|
||||
}
|
||||
|
||||
private void checkLinkCorrect(String res, long[] timecodes, String[] timecodeStr) {
|
||||
assertNotNull(res);
|
||||
Document d = Jsoup.parse(res);
|
||||
|
|
|
@ -74,13 +74,14 @@ public final class Converter {
|
|||
return String.format(Locale.getDefault(), "%02d:%02d:%02d", h, m, s);
|
||||
}
|
||||
|
||||
/** Converts milliseconds to a string containing hours and minutes */
|
||||
public static String getDurationStringShort(int duration) {
|
||||
int h = duration / HOURS_MIL;
|
||||
int rest = duration - h * HOURS_MIL;
|
||||
int m = rest / MINUTES_MIL;
|
||||
/** Converts milliseconds to a string containing hours and minutes or minutes and seconds*/
|
||||
public static String getDurationStringShort(int duration, boolean durationIsInHours) {
|
||||
int firstPartBase = durationIsInHours ? HOURS_MIL : MINUTES_MIL;
|
||||
int firstPart = duration / firstPartBase;
|
||||
int leftoverFromFirstPart = duration - firstPart * firstPartBase;
|
||||
int secondPart = leftoverFromFirstPart / (durationIsInHours ? MINUTES_MIL : SECONDS_MIL);
|
||||
|
||||
return String.format(Locale.getDefault(), "%02d:%02d", h, m);
|
||||
return String.format(Locale.getDefault(), "%02d:%02d", firstPart, secondPart);
|
||||
}
|
||||
|
||||
/** Converts long duration string (HH:MM:SS) to milliseconds. */
|
||||
|
@ -94,14 +95,20 @@ public final class Converter {
|
|||
Integer.parseInt(parts[2]) * 1000;
|
||||
}
|
||||
|
||||
/** Converts short duration string (HH:MM) to milliseconds. */
|
||||
public static int durationStringShortToMs(String input) {
|
||||
/**
|
||||
* Converts short duration string (XX:YY) to milliseconds. If durationIsInHours is true then the
|
||||
* format is HH:MM, otherwise it's MM:SS.
|
||||
* */
|
||||
public static int durationStringShortToMs(String input, boolean durationIsInHours) {
|
||||
String[] parts = input.split(":");
|
||||
if (parts.length != 2) {
|
||||
return 0;
|
||||
}
|
||||
return Integer.parseInt(parts[0]) * 3600 * 1000 +
|
||||
Integer.parseInt(parts[1]) * 1000 * 60;
|
||||
|
||||
int modifier = durationIsInHours ? 60 : 1;
|
||||
|
||||
return Integer.parseInt(parts[0]) * 60 * 1000 * modifier+
|
||||
Integer.parseInt(parts[1]) * 1000 * modifier;
|
||||
}
|
||||
|
||||
/** Converts milliseconds to a localized string containing hours and minutes */
|
||||
|
|
|
@ -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;
|
||||
|
@ -68,7 +70,7 @@ public class Timeline {
|
|||
|
||||
private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
|
||||
private static final String TIMECODE_LINK = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
|
||||
private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(([0-9][0-9])):))?(([0-9][0-9])):(([0-9][0-9]))\\b");
|
||||
private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b");
|
||||
private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
|
||||
|
||||
|
||||
|
@ -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,69 @@ 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");
|
||||
|
||||
if (elementsWithTimeCodes.size() == 0) {
|
||||
// No elements with timecodes
|
||||
return;
|
||||
}
|
||||
|
||||
int playableDuration = playable == null ? Integer.MAX_VALUE : playable.getDuration();
|
||||
boolean useHourFormat = true;
|
||||
|
||||
if (playableDuration != Integer.MAX_VALUE) {
|
||||
|
||||
// We need to decide if we are going to treat short timecodes as HH:MM or MM:SS. To do
|
||||
// so we will parse all the short timecodes and see if they fit in the duration. If one
|
||||
// does not we will use MM:SS, otherwise all will be parsed as HH:MM.
|
||||
for (Element element : elementsWithTimeCodes) {
|
||||
Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
|
||||
while (matcherForElement.find()) {
|
||||
|
||||
// We only want short timecodes right now.
|
||||
if (matcherForElement.group(1) == null) {
|
||||
int time = Converter.durationStringShortToMs(matcherForElement.group(0), true);
|
||||
|
||||
// If the parsed timecode is greater then the duration then we know we need to
|
||||
// use the minute format so we are done.
|
||||
if (time > playableDuration) {
|
||||
useHourFormat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!useHourFormat) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Element element : elementsWithTimeCodes) {
|
||||
|
||||
Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
while (matcherForElement.find()) {
|
||||
String group = matcherForElement.group(0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
matcherForElement.appendTail(buffer);
|
||||
element.html(buffer.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue