From 5c3115bd6a66b28d978c42a067a63e0153fd7d0e 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 | 59 +++++++++---------- .../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, 177 insertions(+), 149 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 a1ef801c62..4661867aba 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,15 @@ 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; +import java.util.regex.Pattern; /** * A helper class that is meant to be used by services that need to parse durations such as @@ -68,45 +68,40 @@ public DateWrapper parse(final String textualDate) throws ParsingException { } /** - * 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 var matcher = DURATION_PATTERN.matcher(textualDuration); final List results = new ArrayList<>(); while (matcher.find()) { results.add(matcher.toMatchResult()); } - 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 + "\"")); + var duration = Duration.ZERO; + for (final var match : results) { + 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) { + } + } + 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 023234aa31..359999e0c9 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 BandcampRadioInfoItemExtractor(final JsonObject radioShow) { 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 cea3504608..8a9c140894 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 String getUrl() throws ParsingException { 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 5c37ec4573..5c13b14674 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 String getUrl() { 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 18c0c0dcce..f842d56722 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 String getUrl() throws ParsingException { 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 f6c5ac8627..52a73aede5 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 boolean isAd() throws ParsingException { 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 d38d65fd53..893a06eed9 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.StreamInfoItemsCollector; import java.io.IOException; +import java.time.Duration; import java.util.Comparator; import javax.annotation.Nonnull; @@ -64,7 +65,7 @@ public InfoItemsPage getInitialPage() throws IOException, Extrac .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 df25b28d89..9ae222f747 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.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 boolean isAd() { 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 ec9d00f3a0..d316ca1185 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.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 boolean isAd() { 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 46aae43ccf..86c87d6eaf 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.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 StreamType getStreamType() { 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 6fd6232e97..f44f114e5c 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.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 String getName() { 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 1bce2c2f82..5b83d20e47 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.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.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 static boolean isY2ubeURL(@Nonnull final URL url) { * @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 d917eb2d7b..d4793a35c5 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.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 boolean isAd() { 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 11b220288c..7eb27d4642 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.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 boolean isAd() { 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 911cb4bedf..e4e15727fb 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.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 StreamType getStreamType() throws ParsingException { 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 long getDuration() throws ParsingException { 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 178cc2bf67..082665c4c2 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.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.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 String getName() throws ParsingException { 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 long getDuration() throws ParsingException { 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 a478a6994b..acca25bddb 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.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 void setViewCount(final long viewCount) { 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 62e69a4338..09447e0f70 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.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 4d12b3da93..f4d4d566db 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 @@ static void setUp() { @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 6915859cd3..93512dd2e3 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 @@ private Stream getAllConditionsForItem(final StreamInfoItem item) { "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 905b3a01e1..3548a0c254 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.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 @@ void testAreHardcodedYoutubeMusicKeysValid() throws IOException, ExtractionExcep @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