From 534e2a3731dfde76e7d93160350badf1eeafac50 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Sep 2023 06:55:34 +0530 Subject: [PATCH] Obtain stream length as a Duration --- .../extractor/localization/TimeAgoParser.java | 63 ++++++++----------- .../BandcampRadioInfoItemExtractor.java | 19 +++--- ...campDiscographStreamInfoItemExtractor.java | 11 ++-- ...ndcampPlaylistStreamInfoItemExtractor.java | 10 ++- ...BandcampSearchStreamInfoItemExtractor.java | 10 +-- .../MediaCCCLiveStreamKioskExtractor.java | 13 ++-- .../extractors/MediaCCCRecentKiosk.java | 3 +- .../MediaCCCRecentKioskExtractor.java | 6 +- .../MediaCCCStreamInfoItemExtractor.java | 14 +++-- .../PeertubeStreamInfoItemExtractor.java | 16 +++-- .../SoundcloudStreamInfoItemExtractor.java | 17 ++--- .../youtube/YoutubeParsingHelper.java | 18 +++--- .../YoutubeFeedInfoItemExtractor.java | 11 +--- ...tubeMusicSongOrVideoInfoItemExtractor.java | 20 +++--- .../YoutubeReelInfoItemExtractor.java | 20 +++--- .../YoutubeStreamInfoItemExtractor.java | 16 ++--- .../extractor/stream/StreamInfoItem.java | 14 +++-- .../stream/StreamInfoItemExtractor.java | 13 ++-- .../localization/TimeAgoParserTest.java | 14 +++-- .../MediaCCCRecentListExtractorTest.java | 4 +- .../youtube/YoutubeParsingHelperTest.java | 18 +++--- 21 files changed, 176 insertions(+), 154 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java index a1ef801c6..246376483 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java @@ -4,15 +4,12 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.timeago.PatternsHolder; import org.schabi.newpipe.extractor.utils.Parser; +import java.time.Duration; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.regex.Pattern; -import java.util.regex.Matcher; -import java.util.regex.MatchResult; /** * A helper class that is meant to be used by services that need to parse durations such as @@ -68,45 +65,37 @@ public class TimeAgoParser { } /** - * Parses a textual duration into a duration computer number. + * Parses a textual duration into a {@link Duration} object. * * @param textualDuration the textual duration to parse - * @return the textual duration parsed, as a primitive {@code long} - * @throws ParsingException if the textual duration could not be parsed + * @return the textual duration parsed as a {@link Duration} */ - public long parseDuration(final String textualDuration) throws ParsingException { + public Duration parseDuration(final String textualDuration) throws ParsingException { // We can't use Matcher.results, as it is only available on Android 14 and above - final Matcher matcher = DURATION_PATTERN.matcher(textualDuration); - final List results = new ArrayList<>(); + final var matcher = DURATION_PATTERN.matcher(textualDuration); + + var duration = Duration.ZERO; while (matcher.find()) { - results.add(matcher.toMatchResult()); + final var match = matcher.toMatchResult(); + final String digits = match.group(1); + final String word = match.group(2); + + long amount; + try { + amount = Long.parseLong(digits); + } catch (final NumberFormatException ignored) { + amount = 1; + } + + try { + duration = duration.plus(amount, parseChronoUnit(word)); + } catch (final ParsingException ignored) { + } } - - return results.stream() - .map(match -> { - final String digits = match.group(1); - final String word = match.group(2); - - int amount; - try { - amount = Integer.parseInt(digits); - } catch (final NumberFormatException ignored) { - amount = 1; - } - - final ChronoUnit unit; - try { - unit = parseChronoUnit(word); - } catch (final ParsingException ignored) { - return 0L; - } - - return amount * unit.getDuration().getSeconds(); - }) - .filter(n -> n > 0) - .reduce(Long::sum) - .orElseThrow(() -> new ParsingException( - "Could not parse duration \"" + textualDuration + "\"")); + if (duration.isZero()) { + throw new ParsingException("Could not parse duration \"" + textualDuration + "\""); + } + return duration; } private int parseTimeAgoAmount(final String textualDate) { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java index 023234aa3..359999e0c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampRadioInfoItemExtractor.java @@ -2,21 +2,23 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate; + import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - +import java.time.Duration; import java.util.List; -import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL; -import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId; -import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor { @@ -26,13 +28,14 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor { show = radioShow; } + @Nonnull @Override - public long getDuration() { + public Duration getDuration() { /* Duration is only present in the more detailed information that has to be queried separately. Therefore, over 300 queries would be needed every time the kiosk is opened if we were to display the real value. */ //return query(show.getInt("id")).getLong("audio_duration"); - return 0; + return Duration.ZERO; } @Nullable diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java index cea350460..8a9c14089 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampDiscographStreamInfoItemExtractor.java @@ -1,14 +1,16 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId; + import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper; -import javax.annotation.Nonnull; import java.util.List; -import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId; +import javax.annotation.Nonnull; public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { @@ -43,9 +45,4 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf public List getThumbnails() throws ParsingException { return getImagesFromImageId(discograph.getLong("art_id"), true); } - - @Override - public long getDuration() { - return -1; - } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java index 5c37ec457..5c13b1467 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampPlaylistStreamInfoItemExtractor.java @@ -3,17 +3,20 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.stream.StreamExtractor; -import javax.annotation.Nonnull; import java.io.IOException; +import java.time.Duration; import java.util.Collections; import java.util.List; +import javax.annotation.Nonnull; + public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { private final JsonObject track; @@ -46,9 +49,10 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI return getUploaderUrl() + track.getString("title_link"); } + @Nonnull @Override - public long getDuration() { - return track.getLong("duration"); + public Duration getDuration() { + return Duration.ofSeconds(track.getLong("duration")); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java index 18c0c0dcc..f842d5672 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/streaminfoitem/BandcampSearchStreamInfoItemExtractor.java @@ -1,13 +1,14 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem; +import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult; + import org.jsoup.nodes.Element; import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import javax.annotation.Nonnull; import java.util.List; -import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult; +import javax.annotation.Nonnull; public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor { @@ -47,9 +48,4 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte public List getThumbnails() throws ParsingException { return getImagesFromSearchResult(searchResult); } - - @Override - public long getDuration() { - return -1; - } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamKioskExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamKioskExtractor.java index f6c5ac862..52a73aede 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamKioskExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamKioskExtractor.java @@ -1,17 +1,19 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors; +import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem; + import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; -import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor { @@ -60,11 +62,6 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor return false; } - @Override - public long getDuration() throws ParsingException { - return 0; - } - @Override public long getViewCount() throws ParsingException { return -1; diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java index d38d65fd5..893a06eed 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java @@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; import java.io.IOException; +import java.time.Duration; import java.util.Comparator; import javax.annotation.Nonnull; @@ -64,7 +65,7 @@ public class MediaCCCRecentKiosk extends KioskExtractor { .map(JsonObject.class::cast) .map(MediaCCCRecentKioskExtractor::new) // #813 / voc/voctoweb#609 -> returns faulty data -> filter it out - .filter(extractor -> extractor.getDuration() > 0) + .filter(extractor -> extractor.getDuration().compareTo(Duration.ZERO) > 0) .forEach(collector::commit); return new InfoItemsPage<>(collector, null); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java index df25b28d8..9ae222f74 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java @@ -9,6 +9,7 @@ import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConfe import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; +import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -52,11 +53,12 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor { return false; } + @Nonnull @Override - public long getDuration() { + public Duration getDuration() { // duration and length have the same value, see // https://github.com/voc/voctoweb/blob/master/app/views/public/shared/_event.json.jbuilder - return event.getInt("duration"); + return Duration.ofSeconds(event.getLong("duration")); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java index ec9d00f3a..d316ca118 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java @@ -1,6 +1,9 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems; +import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem; + import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; @@ -8,11 +11,11 @@ import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsin import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import java.time.Duration; import java.util.List; -import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor { private final JsonObject event; @@ -31,9 +34,10 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor return false; } + @Nonnull @Override - public long getDuration() { - return event.getInt("length"); + public Duration getDuration() { + return Duration.ofSeconds(event.getInt("length")); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java index 46aae43cc..86c87d6ea 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java @@ -1,6 +1,11 @@ package org.schabi.newpipe.extractor.services.peertube.extractors; +import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject; +import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem; +import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom; + import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.exceptions.ParsingException; @@ -9,12 +14,10 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.utils.JsonUtils; -import javax.annotation.Nonnull; +import java.time.Duration; import java.util.List; -import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject; -import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem; -import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom; +import javax.annotation.Nonnull; public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor { @@ -99,9 +102,10 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor return item.getBoolean("isLive") ? StreamType.LIVE_STREAM : StreamType.VIDEO_STREAM; } + @Nonnull @Override - public long getDuration() { - return item.getLong("duration"); + public Duration getDuration() { + return Duration.ofSeconds(item.getLong("duration")); } protected void setBaseUrl(final String baseUrl) { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java index 6fd6232e9..f44f114e5 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java @@ -1,5 +1,10 @@ package org.schabi.newpipe.extractor.services.soundcloud.extractors; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom; +import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; + import com.grack.nanojson.JsonObject; import org.schabi.newpipe.extractor.Image; @@ -8,13 +13,10 @@ import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; -import javax.annotation.Nonnull; +import java.time.Duration; import java.util.List; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom; -import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; +import javax.annotation.Nonnull; public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtractor { @@ -34,9 +36,10 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto return itemObject.getString("title"); } + @Nonnull @Override - public long getDuration() { - return itemObject.getLong("duration") / 1000L; + public Duration getDuration() { + return Duration.ofMillis(itemObject.getLong("duration")); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java index 1bce2c2f8..5b83d20e4 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java @@ -32,8 +32,8 @@ import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; import com.grack.nanojson.JsonWriter; -import org.jsoup.nodes.Entities; +import org.jsoup.nodes.Entities; import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.Image.ResolutionLevel; import org.schabi.newpipe.extractor.MetaInfo; @@ -58,10 +58,12 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -316,21 +318,21 @@ public final class YoutubeParsingHelper { * @return the duration in seconds * @throws ParsingException when more than 3 separators are found */ - public static int parseDurationString(@Nonnull final String input) + @Nonnull + public static Duration parseDurationString(@Nonnull final String input) throws ParsingException, NumberFormatException { // If time separator : is not detected, try . instead - final String[] splitInput = input.contains(":") - ? input.split(":") - : input.split("\\."); + final var splitInput = input.contains(":") ? input.split(":") : input.split("\\."); - final int[] units = {24, 60, 60, 1}; + final var units = new ChronoUnit[]{ChronoUnit.DAYS, ChronoUnit.HOURS, ChronoUnit.MINUTES, + ChronoUnit.SECONDS}; final int offset = units.length - splitInput.length; if (offset < 0) { throw new ParsingException("Error duration string with unknown format: " + input); } - int duration = 0; + Duration duration = Duration.ZERO; for (int i = 0; i < splitInput.length; i++) { - duration = units[i + offset] * (duration + convertDurationToInt(splitInput[i])); + duration = duration.plus(convertDurationToInt(splitInput[i]), units[i + offset]); } return duration; } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java index d917eb2d7..d4793a35c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java @@ -8,12 +8,13 @@ import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor { private final Element entryElement; @@ -33,12 +34,6 @@ public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor { return false; } - @Override - public long getDuration() { - // Not available when fetching through the feed endpoint. - return -1; - } - @Override public long getViewCount() { return Long.parseLong(entryElement.getElementsByTag("media:statistics").first() diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java index 11b220288..7eb27d464 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java @@ -1,7 +1,15 @@ package org.schabi.newpipe.extractor.services.youtube.extractors; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_SONGS; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_VIDEOS; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; + import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; @@ -11,15 +19,10 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.utils.Parser; import org.schabi.newpipe.extractor.utils.Utils; -import javax.annotation.Nonnull; +import java.time.Duration; import java.util.List; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; -import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_SONGS; -import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_VIDEOS; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; +import javax.annotation.Nonnull; public class YoutubeMusicSongOrVideoInfoItemExtractor implements StreamInfoItemExtractor { private final JsonObject songOrVideoInfoItem; @@ -66,8 +69,9 @@ public class YoutubeMusicSongOrVideoInfoItemExtractor implements StreamInfoItemE return false; } + @Nonnull @Override - public long getDuration() throws ParsingException { + public Duration getDuration() throws ParsingException { final String duration = descriptionElements.getObject(descriptionElements.size() - 1) .getString("text"); if (!isNullOrEmpty(duration)) { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeReelInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeReelInfoItemExtractor.java index 911cb4bed..e4e15727f 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeReelInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeReelInfoItemExtractor.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.extractor.services.youtube.extractors; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + import com.grack.nanojson.JsonObject; import org.schabi.newpipe.extractor.Image; @@ -11,15 +15,12 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.utils.Utils; +import java.time.Duration; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; - -import java.util.List; - /** * A {@link StreamInfoItemExtractor} for YouTube's {@code reelItemRenderers}. * @@ -68,15 +69,16 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor { return StreamType.VIDEO_STREAM; } + @Nonnull @Override - public long getDuration() throws ParsingException { + public Duration getDuration() throws ParsingException { // Duration of reelItems is only provided in the accessibility data // example: "VIDEO TITLE - 49 seconds - play video" // "VIDEO TITLE - 1 minute, 1 second - play video" final String accessibilityLabel = reelInfo.getObject("accessibility") .getObject("accessibilityData").getString("label"); if (accessibilityLabel == null || timeAgoParser == null) { - return 0; + return Duration.ZERO; } // This approach may be language dependent @@ -87,7 +89,7 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor { return timeAgoParser.parseDuration(textualDuration); } - return -1; + return Duration.ZERO; } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java index 178cc2bf6..082665c4c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java @@ -18,9 +18,9 @@ package org.schabi.newpipe.extractor.services.youtube.extractors; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -39,9 +39,7 @@ import org.schabi.newpipe.extractor.utils.JsonUtils; import org.schabi.newpipe.extractor.utils.Parser; import org.schabi.newpipe.extractor.utils.Utils; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - +import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -49,6 +47,9 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { private static final Pattern ACCESSIBILITY_DATA_VIEW_COUNT_REGEX = @@ -136,10 +137,11 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { throw new ParsingException("Could not get name"); } + @Nonnull @Override - public long getDuration() throws ParsingException { + public Duration getDuration() throws ParsingException { if (getStreamType() == StreamType.LIVE_STREAM) { - return -1; + return Duration.ZERO; } String duration = getTextFromObject(videoInfo.getObject("lengthText")); @@ -169,7 +171,7 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { if (isPremiere()) { // Premieres can be livestreams, so the duration is not available in this // case - return -1; + return Duration.ZERO; } throw new ParsingException("Could not get duration"); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItem.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItem.java index a478a6994..acca25bdd 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItem.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItem.java @@ -24,9 +24,11 @@ import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.localization.DateWrapper; +import java.time.Duration; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; /** * Info object for previews of unopened videos, e.g. search results, related videos. @@ -40,7 +42,7 @@ public class StreamInfoItem extends InfoItem { @Nullable private DateWrapper uploadDate; private long viewCount = -1; - private long duration = -1; + private Duration duration = Duration.ZERO; private String uploaderUrl = null; @Nonnull @@ -76,11 +78,15 @@ public class StreamInfoItem extends InfoItem { this.viewCount = viewCount; } - public long getDuration() { + public Duration getDuration() { return duration; } - public void setDuration(final long duration) { + public long getDurationInSeconds() { + return duration.toSeconds(); + } + + public void setDuration(final Duration duration) { this.duration = duration; } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItemExtractor.java index 62e69a433..09447e0f7 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfoItemExtractor.java @@ -25,9 +25,11 @@ import org.schabi.newpipe.extractor.InfoItemExtractor; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; +import java.time.Duration; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; public interface StreamInfoItemExtractor extends InfoItemExtractor { @@ -48,12 +50,15 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor { boolean isAd() throws ParsingException; /** - * Get the stream duration in seconds + * Get the stream duration. If it is not available, a zero-length duration is returned. * - * @return the stream duration in seconds + * @return the stream duration * @throws ParsingException if there is an error in the extraction */ - long getDuration() throws ParsingException; + @Nonnull + default Duration getDuration() throws ParsingException { + return Duration.ZERO; + } /** * Parses the number of views diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java index 4d12b3da9..f4d4d566d 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java @@ -1,11 +1,13 @@ package org.schabi.newpipe.extractor.localization; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import java.time.Duration; class TimeAgoParserTest { private static TimeAgoParser timeAgoParser; @@ -17,10 +19,10 @@ class TimeAgoParserTest { @Test void testGetDuration() throws ParsingException { - assertEquals(1, timeAgoParser.parseDuration("one second")); - assertEquals(1, timeAgoParser.parseDuration("second")); - assertEquals(49, timeAgoParser.parseDuration("49 seconds")); - assertEquals(61, timeAgoParser.parseDuration("1 minute, 1 second")); + assertEquals(Duration.ofSeconds(1), timeAgoParser.parseDuration("one second")); + assertEquals(Duration.ofSeconds(1), timeAgoParser.parseDuration("second")); + assertEquals(Duration.ofSeconds(49), timeAgoParser.parseDuration("49 seconds")); + assertEquals(Duration.ofSeconds(61), timeAgoParser.parseDuration("1 minute, 1 second")); } @Test diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCRecentListExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCRecentListExtractorTest.java index 6915859cd..93512dd2e 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCRecentListExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCRecentListExtractorTest.java @@ -42,8 +42,8 @@ public class MediaCCCRecentListExtractorTest { "Name=[" + item.getName() + "] of " + item + " is empty or null" ), () -> assertGreater(0, - item.getDuration(), - "Duration[=" + item.getDuration() + "] of " + item + " is <= 0" + item.getDurationInSeconds(), + "Duration[=" + item.getDurationInSeconds() + "] of " + item + " is <= 0" ) ); } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelperTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelperTest.java index 905b3a01e..3548a0c25 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelperTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelperTest.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.extractor.services.youtube; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.schabi.newpipe.downloader.DownloaderFactory; @@ -9,10 +13,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.stream.AudioTrackType; import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; public class YoutubeParsingHelperTest { @@ -38,9 +39,12 @@ public class YoutubeParsingHelperTest { @Test void testParseDurationString() throws ParsingException { - assertEquals(1162567, YoutubeParsingHelper.parseDurationString("12:34:56:07")); - assertEquals(4445767, YoutubeParsingHelper.parseDurationString("1,234:56:07")); - assertEquals(754, YoutubeParsingHelper.parseDurationString("12:34 ")); + assertEquals(Duration.ofDays(12).plusHours(34).plusMinutes(56).plusSeconds(7), + YoutubeParsingHelper.parseDurationString("12:34:56:07")); + assertEquals(Duration.ofHours(1234).plusMinutes(56).plusSeconds(7), + YoutubeParsingHelper.parseDurationString("1,234:56:07")); + assertEquals(Duration.ofMinutes(12).plusSeconds(34), + YoutubeParsingHelper.parseDurationString("12:34 ")); } @Test