From 9180be85ba216e501b6ccfac9f9eba20d40d0021 Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Sun, 10 Feb 2019 17:59:40 -0500 Subject: [PATCH 1/9] Update tests to handle minute timestamps The definition of 'short timestamp' now means minutes and seconds, while 'long timestamp' means hours, minutes, and seconds. The first part of a timestamp may have one or two digits. Tests updated for this new definition. --- .../test/antennapod/util/ConverterTest.java | 4 +-- .../util/playback/TimelineTest.java | 30 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java b/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java index 47fca41ba..c7ed7d812 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java @@ -17,7 +17,7 @@ public class ConverterTest extends AndroidTestCase { public void testGetDurationStringShort() throws Exception { String expected = "13:05"; - int input = 47110000; + int input = 785000; assertEquals(expected, Converter.getDurationStringShort(input)); } @@ -29,7 +29,7 @@ public class ConverterTest extends AndroidTestCase { public void testDurationStringShortToMs() throws Exception { String input = "8:30"; - long expected = 30600000; + long expected = 510000; assertEquals(expected, Converter.durationStringShortToMs(input)); } } diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index 7e535e12c..40e6605d7 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -50,9 +50,29 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } - public void testProcessShownotesAddTimecodeHHMMNoChapters() throws Exception { + 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, "

Some test text with a timecode " + timeStr + " here.

"); + 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, "

Some test text with a timecode " + timeStr + " here.

"); + 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 = 3600 * 1000 * 10 + 60 * 1000 * 11; + final long time = 60 * 1000 * 10 + 1000 * 11; Playable p = newTestPlayable(null, "

Some test text with a timecode " + timeStr + " here.

"); Timeline t = new Timeline(context, p); @@ -61,7 +81,7 @@ public class TimelineTest extends InstrumentationTestCase { } public void testProcessShownotesAddTimecodeParentheses() throws Exception { - final String timeStr = "10:11"; + final String timeStr = "10:11:00"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; Playable p = newTestPlayable(null, "

Some test text with a timecode (" + timeStr + ") here.

"); @@ -71,7 +91,7 @@ public class TimelineTest extends InstrumentationTestCase { } public void testProcessShownotesAddTimecodeBrackets() throws Exception { - final String timeStr = "10:11"; + final String timeStr = "10:11:00"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; Playable p = newTestPlayable(null, "

Some test text with a timecode [" + timeStr + "] here.

"); @@ -81,7 +101,7 @@ public class TimelineTest extends InstrumentationTestCase { } public void testProcessShownotesAddTimecodeAngleBrackets() throws Exception { - final String timeStr = "10:11"; + final String timeStr = "10:11:00"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; Playable p = newTestPlayable(null, "

Some test text with a timecode <" + timeStr + "> here.

"); From 96b0336b2cbaa2166964757c089957bf4f1a420b Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Sun, 10 Feb 2019 18:01:00 -0500 Subject: [PATCH 2/9] Update Timeline regex Regex now looks for the new types of short and long timestamps. --- .../de/danoeh/antennapod/core/util/Converter.java | 12 ++++++------ .../antennapod/core/util/playback/Timeline.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java index 6966667bf..1f9418c98 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java @@ -76,11 +76,11 @@ public final class Converter { /** 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; + int minutes = duration / MINUTES_MIL; + int rest = duration - minutes * MINUTES_MIL; + int seconds = rest / SECONDS_MIL; - return String.format(Locale.getDefault(), "%02d:%02d", h, m); + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds); } /** Converts long duration string (HH:MM:SS) to milliseconds. */ @@ -100,8 +100,8 @@ public final class Converter { if (parts.length != 2) { return 0; } - return Integer.parseInt(parts[0]) * 3600 * 1000 + - Integer.parseInt(parts[1]) * 1000 * 60; + return Integer.parseInt(parts[0]) * 60 * 1000 + + Integer.parseInt(parts[1]) * 1000; } /** Converts milliseconds to a localized string containing hours and minutes */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 34cfe6d05..22349b029 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -68,7 +68,7 @@ public class Timeline { private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); private static final String TIMECODE_LINK = "%s"; - 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(?:(?:([01]?\\d|2[0-3]):)?([0-5]?\\d):)?([0-5]?\\d)\\b"); private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); From d0f617880cda8bacfc70610d5ef04a6c8805a74b Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Tue, 12 Feb 2019 17:32:58 -0500 Subject: [PATCH 3/9] Converter handles HH:MM and MM:SS --- .../test/antennapod/util/ConverterTest.java | 8 ++--- .../antennapod/core/util/Converter.java | 29 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java b/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java index c7ed7d812..cd1a489d8 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/ConverterTest.java @@ -17,8 +17,8 @@ public class ConverterTest extends AndroidTestCase { public void testGetDurationStringShort() throws Exception { String expected = "13:05"; - int input = 785000; - 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 = 510000; - assertEquals(expected, Converter.durationStringShortToMs(input)); + assertEquals(30600000, Converter.durationStringShortToMs(input, true)); + assertEquals(510000, Converter.durationStringShortToMs(input, false)); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java index 1f9418c98..6ecca941a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java @@ -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 minutes = duration / MINUTES_MIL; - int rest = duration - minutes * MINUTES_MIL; - int seconds = rest / SECONDS_MIL; - - return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds); + /** 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", 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]) * 60 * 1000 + - Integer.parseInt(parts[1]) * 1000; + + 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 */ From 39b9df50642dae9ac4129f22721093a6ce43148b Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Tue, 12 Feb 2019 19:43:57 -0500 Subject: [PATCH 4/9] 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. --- .../util/playback/TimelineTest.java | 60 +++++++++--- .../core/util/playback/Timeline.java | 98 ++++++++++++++----- 2 files changed, 118 insertions(+), 40 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index 40e6605d7..dcec1c61f 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -30,12 +30,12 @@ public class TimelineTest extends InstrumentationTestCase { context = getInstrumentation().getTargetContext(); } - private Playable newTestPlayable(List chapters, String shownotes) { + private Playable newTestPlayable(List 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, "

Some test text with a timecode " + timeStr + " here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode " + timeStr + " here.

", 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, "

Some test text with a timecode " + timeStr + " here.

", 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, "

Some test text with a timecode " + timeStr + " here.

", 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, "

Some test text with a timecode " + timeStr + " here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode " + timeStr + " here.

", 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, "

Some test text with a timecode " + timeStr + " here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode " + timeStr + " here.

", 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, "

Some test text with a timecode " + timeStr + " here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!

", 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, "

Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!

", 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, "

Some test text with a timecode (" + timeStr + ") here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode (" + timeStr + ") here.

", 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, "

Some test text with a timecode [" + timeStr + "] here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode [" + timeStr + "] here.

", 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, "

Some test text with a timecode <" + timeStr + "> here.

"); + Playable p = newTestPlayable(null, "

Some test text with a timecode <" + timeStr + "> here.

", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); String res = t.processShownotes(true); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 22349b029..5b58596f7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -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> 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 replacements = new ArrayList<>(); + for (Pair 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); + } } From c49e98b5462c9511dffe250a94b17be637884c49 Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Tue, 12 Feb 2019 20:53:17 -0500 Subject: [PATCH 5/9] Handle more then 23 hours --- .../de/test/antennapod/util/playback/TimelineTest.java | 10 ++++++++++ .../danoeh/antennapod/core/util/playback/Timeline.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index dcec1c61f..157773595 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -50,6 +50,16 @@ public class TimelineTest extends InstrumentationTestCase { 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, "

Some test text with a timecode " + timeStr + " here.

", 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; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 5b58596f7..34f00fe79 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -70,7 +70,7 @@ public class Timeline { private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:([01]?\\d|2[0-3]):)?([0-5]?\\d):)?([0-5]?\\d)\\b"); + private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d):)?([0-5]?\\d)\\b"); private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); From e94e4bc3d03d2e6d5122700fd3161bfa63dc4371 Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Wed, 13 Feb 2019 21:06:19 -0500 Subject: [PATCH 6/9] 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. --- .../util/playback/TimelineTest.java | 4 +- .../core/util/playback/Timeline.java | 90 +++++++++---------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index 157773595..e6b11a4b0 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -110,12 +110,14 @@ public class TimelineTest extends InstrumentationTestCase { } 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, "

Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!

", 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); + checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings); } public void testProcessShownotesAddTimecodeParentheses() throws Exception { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 34f00fe79..057de06dd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -170,70 +170,64 @@ public class Timeline { 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 + if (elementsWithTimeCodes.size() == 0) { + // No elements with timecodes + return; + } + + int playableDuration = playable == null ? Integer.MAX_VALUE : playable.getDuration(); 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> matches = new ArrayList<>(); - while (matcherLong.find()) { - matches.add(0, new Pair<>(matcherLong.group(1) != null, matcherLong.group(0))); - } + if (playableDuration != Integer.MAX_VALUE) { - // Now loop through the reversed matches and get the replacements. Store them in - // non-reversed order. - ArrayList replacements = new ArrayList<>(); - for (Pair matchPair : matches) { - boolean isLongFormat = matchPair.first; - String group = matchPair.second; - int time = isLongFormat - ? Converter.durationStringLongToMs(group) - : Converter.durationStringShortToMs(group, useHourFormat); + // 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()) { - String rep = group; - if (playable == null) { - rep = createTimeLink(time, group); - } else { - int duration = playable.getDuration(); + // We only want short timecodes right now. + if (matcherForElement.group(1) == null) { + int time = Converter.durationStringShortToMs(matcherForElement.group(0), true); - 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); + // 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; } } } - 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(); - int index = 0; - matcherLong.reset(); - while (matcherLong.find()) { - matcherLong.appendReplacement(buffer, replacements.get(index)); - index++; + + 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); } - matcherLong.appendTail(buffer); + matcherForElement.appendTail(buffer); element.html(buffer.toString()); } } - - private String createTimeLink(int time, String group) { - return String.format(Locale.getDefault(), TIMECODE_LINK, time, group); - } } From 6f69b4b1403f2ca4a7fd1125d145bf13252c3732 Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Sat, 2 Mar 2019 08:47:52 -0500 Subject: [PATCH 7/9] Adjust regext to ignore X:Y timecodes --- .../de/test/antennapod/util/playback/TimelineTest.java | 9 +++++++++ .../danoeh/antennapod/core/util/playback/Timeline.java | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index e6b11a4b0..c88c06a95 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -150,6 +150,15 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + public void testProcessShownotesAndInvalidTimecode() throws Exception { + final String timeStr = "2:1"; + + Playable p = newTestPlayable(null, "

Some test text with a timecode <" + timeStr + "> here.

", 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); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 057de06dd..7b9a49fa8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -70,7 +70,7 @@ public class Timeline { private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d):)?([0-5]?\\d)\\b"); + private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d):)?([0-5][0-9]?\\d)\\b"); private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); From 7e3ccd97da53108b8c5cec06cc0ae42454cdf42e Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Sat, 2 Mar 2019 12:12:26 -0500 Subject: [PATCH 8/9] Fix regex --- .../de/test/antennapod/util/playback/TimelineTest.java | 10 ++++++++-- .../danoeh/antennapod/core/util/playback/Timeline.java | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index c88c06a95..4bef14cd9 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -151,9 +151,15 @@ public class TimelineTest extends InstrumentationTestCase { } public void testProcessShownotesAndInvalidTimecode() throws Exception { - final String timeStr = "2:1"; + final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"}; - Playable p = newTestPlayable(null, "

Some test text with a timecode <" + timeStr + "> here.

", Integer.MAX_VALUE); + StringBuilder shownotes = new StringBuilder("

Some test text with timecodes "); + for (String timeStr : timeStrs) { + shownotes.append(timeStr).append(" "); + } + shownotes.append("here.

"); + + 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]); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 7b9a49fa8..07dd5b3a7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -70,7 +70,7 @@ public class Timeline { private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d):)?([0-5][0-9]?\\d)\\b"); + private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d))?:([0-5]\\d)\\b"); private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); From 1d0e701525a33c638822a59241d99b96e026649a Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Sun, 3 Mar 2019 13:36:28 -0500 Subject: [PATCH 9/9] Another atempt at fixing the regex --- .../java/de/danoeh/antennapod/core/util/playback/Timeline.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 07dd5b3a7..56550bb06 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -70,7 +70,7 @@ public class Timeline { private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(\\d*):)?([0-5]?\\d))?:([0-5]\\d)\\b"); + private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b"); private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
");