Extract stream duration as a Java 8 Duration

pull/1106/head
Isira Seneviratne 2024-07-12 08:24:03 +05:30
rodzic 0b99100dbd
commit ed02dd8bc0
21 zmienionych plików z 94 dodań i 80 usunięć

Wyświetl plik

@ -11,7 +11,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List; 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.BASE_URL;
@ -26,13 +26,14 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
show = radioShow; show = radioShow;
} }
@Nonnull
@Override @Override
public long getDuration() { public Duration getDurationObject() {
/* Duration is only present in the more detailed information that has to be queried /* 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 separately. Therefore, over 300 queries would be needed every time the kiosk is opened if we
were to display the real value. */ were to display the real value. */
//return query(show.getInt("id")).getLong("audio_duration"); //return Duration.ofSeconds(query(show.getInt("id")).getLong("audio_duration"));
return 0; return Duration.ZERO;
} }
@Nullable @Nullable

Wyświetl plik

@ -43,9 +43,4 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
public List<Image> getThumbnails() throws ParsingException { public List<Image> getThumbnails() throws ParsingException {
return getImagesFromImageId(discograph.getLong("art_id"), true); return getImagesFromImageId(discograph.getLong("art_id"), true);
} }
@Override
public long getDuration() {
return -1;
}
} }

Wyświetl plik

@ -11,6 +11,7 @@ import org.schabi.newpipe.extractor.stream.StreamExtractor;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -51,9 +52,10 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
} }
} }
@Nonnull
@Override @Override
public long getDuration() { public Duration getDurationObject() {
return track.getLong("duration"); return Duration.ofSeconds(track.getLong("duration"));
} }
@Override @Override

Wyświetl plik

@ -47,9 +47,4 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
public List<Image> getThumbnails() throws ParsingException { public List<Image> getThumbnails() throws ParsingException {
return getImagesFromSearchResult(searchResult); return getImagesFromSearchResult(searchResult);
} }
@Override
public long getDuration() {
return -1;
}
} }

Wyświetl plik

@ -60,11 +60,6 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
return false; return false;
} }
@Override
public long getDuration() throws ParsingException {
return 0;
}
@Override @Override
public long getViewCount() throws ParsingException { public long getViewCount() throws ParsingException {
return -1; return -1;

Wyświetl plik

@ -64,7 +64,7 @@ public class MediaCCCRecentKiosk extends KioskExtractor<StreamInfoItem> {
.map(JsonObject.class::cast) .map(JsonObject.class::cast)
.map(MediaCCCRecentKioskExtractor::new) .map(MediaCCCRecentKioskExtractor::new)
// #813 / voc/voctoweb#609 -> returns faulty data -> filter it out // #813 / voc/voctoweb#609 -> returns faulty data -> filter it out
.filter(extractor -> extractor.getDuration() > 0) .filter(extractor -> !extractor.getDurationObject().isZero())
.forEach(collector::commit); .forEach(collector::commit);
return new InfoItemsPage<>(collector, null); return new InfoItemsPage<>(collector, null);

Wyświetl plik

@ -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.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import java.time.Duration;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
@ -53,10 +54,11 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
} }
@Override @Override
public long getDuration() { @Nonnull
public Duration getDurationObject() {
// duration and length have the same value, see // duration and length have the same value, see
// https://github.com/voc/voctoweb/blob/master/app/views/public/shared/_event.json.jbuilder // 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 @Override

Wyświetl plik

@ -10,6 +10,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List; import java.util.List;
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem; import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
@ -32,8 +33,9 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
} }
@Override @Override
public long getDuration() { @Nonnull
return event.getInt("length"); public Duration getDurationObject() {
return Duration.ofSeconds(event.getLong("length"));
} }
@Override @Override

Wyświetl plik

@ -10,6 +10,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.utils.JsonUtils; import org.schabi.newpipe.extractor.utils.JsonUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.List; import java.util.List;
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject; import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
@ -100,8 +101,9 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor
} }
@Override @Override
public long getDuration() { @Nonnull
return item.getLong("duration"); public Duration getDurationObject() {
return Duration.ofSeconds(item.getLong("duration"));
} }
protected void setBaseUrl(final String baseUrl) { protected void setBaseUrl(final String baseUrl) {

Wyświetl plik

@ -9,6 +9,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.List; import java.util.List;
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
@ -35,8 +36,9 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
} }
@Override @Override
public long getDuration() { @Nonnull
return itemObject.getLong("duration") / 1000L; public Duration getDurationObject() {
return Duration.ofMillis(itemObject.getLong("duration"));
} }
@Override @Override

Wyświetl plik

@ -44,8 +44,8 @@ import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException; import com.grack.nanojson.JsonParserException;
import com.grack.nanojson.JsonWriter; 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;
import org.schabi.newpipe.extractor.Image.ResolutionLevel; import org.schabi.newpipe.extractor.Image.ResolutionLevel;
import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.downloader.Response;
@ -67,10 +67,12 @@ import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -240,21 +242,22 @@ public final class YoutubeParsingHelper {
* @return the duration in seconds * @return the duration in seconds
* @throws ParsingException when more than 3 separators are found * @throws ParsingException when more than 3 separators are found
*/ */
public static int parseDurationString(@Nonnull final String input) public static Duration parseDurationString(@Nonnull final String input)
throws ParsingException, NumberFormatException { throws ParsingException {
// If time separator : is not detected, try . instead // If time separator : is not detected, try . instead
final String[] splitInput = input.contains(":") final String[] splitInput = input.contains(":")
? input.split(":") ? input.split(":")
: input.split("\\."); : input.split("\\.");
final int[] units = {24, 60, 60, 1}; final var units = List.of(ChronoUnit.DAYS, ChronoUnit.HOURS, ChronoUnit.MINUTES,
final int offset = units.length - splitInput.length; ChronoUnit.SECONDS);
final int offset = units.size() - splitInput.length;
if (offset < 0) { if (offset < 0) {
throw new ParsingException("Error duration string with unknown format: " + input); 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++) { for (int i = 0; i < splitInput.length; i++) {
duration = units[i + offset] * (duration + convertDurationToInt(splitInput[i])); duration = duration.plus(convertDurationToInt(splitInput[i]), units.get(i + offset));
} }
return duration; return duration;
} }
@ -271,11 +274,7 @@ public final class YoutubeParsingHelper {
* @return The converted integer or 0 if the conversion failed. * @return The converted integer or 0 if the conversion failed.
*/ */
private static int convertDurationToInt(final String input) { private static int convertDurationToInt(final String input) {
if (input == null || input.isEmpty()) { final String clearedInput = input != null ? Utils.removeNonDigitCharacters(input) : "";
return 0;
}
final String clearedInput = Utils.removeNonDigitCharacters(input);
try { try {
return Integer.parseInt(clearedInput); return Integer.parseInt(clearedInput);
} catch (final NumberFormatException ex) { } catch (final NumberFormatException ex) {

Wyświetl plik

@ -33,12 +33,6 @@ public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
return false; return false;
} }
@Override
public long getDuration() {
// Not available when fetching through the feed endpoint.
return -1;
}
@Override @Override
public long getViewCount() { public long getViewCount() {
return Long.parseLong(entryElement.getElementsByTag("media:statistics").first() return Long.parseLong(entryElement.getElementsByTag("media:statistics").first()

Wyświetl plik

@ -12,6 +12,7 @@ import org.schabi.newpipe.extractor.utils.Parser;
import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.extractor.utils.Utils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.List; import java.util.List;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
@ -67,7 +68,8 @@ public class YoutubeMusicSongOrVideoInfoItemExtractor implements StreamInfoItemE
} }
@Override @Override
public long getDuration() throws ParsingException { @Nonnull
public Duration getDurationObject() throws ParsingException {
final String duration = descriptionElements.getObject(descriptionElements.size() - 1) final String duration = descriptionElements.getObject(descriptionElements.size() - 1)
.getString("text"); .getString("text");
if (!isNullOrEmpty(duration)) { if (!isNullOrEmpty(duration)) {

Wyświetl plik

@ -5,7 +5,6 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonObject;
import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.localization.DateWrapper;
@ -96,11 +95,6 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor {
return false; return false;
} }
@Override
public long getDuration() throws ParsingException {
return -1;
}
@Override @Override
public String getUploaderName() throws ParsingException { public String getUploaderName() throws ParsingException {
return null; return null;

Wyświetl plik

@ -41,7 +41,7 @@ import org.schabi.newpipe.extractor.utils.Utils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
@ -136,10 +136,11 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
throw new ParsingException("Could not get name"); throw new ParsingException("Could not get name");
} }
@Nonnull
@Override @Override
public long getDuration() throws ParsingException { public Duration getDurationObject() throws ParsingException {
if (getStreamType() == StreamType.LIVE_STREAM) { if (getStreamType() == StreamType.LIVE_STREAM) {
return -1; return Duration.ZERO;
} }
String duration = getTextFromObject(videoInfo.getObject("lengthText")); String duration = getTextFromObject(videoInfo.getObject("lengthText"));
@ -169,7 +170,7 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
if (isPremiere()) { if (isPremiere()) {
// Premieres can be livestreams, so the duration is not available in this // Premieres can be livestreams, so the duration is not available in this
// case // case
return -1; return Duration.ZERO;
} }
throw new ParsingException("Could not get duration"); throw new ParsingException("Could not get duration");

Wyświetl plik

@ -26,6 +26,7 @@ import org.schabi.newpipe.extractor.localization.DateWrapper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List; import java.util.List;
/** /**
@ -40,7 +41,8 @@ public class StreamInfoItem extends InfoItem {
@Nullable @Nullable
private DateWrapper uploadDate; private DateWrapper uploadDate;
private long viewCount = -1; private long viewCount = -1;
private long duration = -1; @Nonnull
private Duration duration = Duration.ZERO;
private String uploaderUrl = null; private String uploaderUrl = null;
@Nonnull @Nonnull
@ -76,12 +78,21 @@ public class StreamInfoItem extends InfoItem {
this.viewCount = viewCount; this.viewCount = viewCount;
} }
public long getDuration() { @Nonnull
public Duration getDurationObject() {
return duration; return duration;
} }
public void setDurationObject(@Nonnull final Duration durationObject) {
this.duration = durationObject;
}
public long getDuration() {
return duration.toSeconds();
}
public void setDuration(final long duration) { public void setDuration(final long duration) {
this.duration = duration; this.duration = Duration.ofSeconds(duration);
} }
public String getUploaderUrl() { public String getUploaderUrl() {

Wyświetl plik

@ -27,6 +27,7 @@ import org.schabi.newpipe.extractor.localization.DateWrapper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List; import java.util.List;
public interface StreamInfoItemExtractor extends InfoItemExtractor { public interface StreamInfoItemExtractor extends InfoItemExtractor {
@ -48,12 +49,25 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
boolean isAd() throws ParsingException; boolean isAd() throws ParsingException;
/** /**
* Get the stream duration in seconds * Get the stream duration as a {@link Duration}.
* *
* @return the stream duration in seconds or -1 if no duration is available * @return the stream duration in seconds or {@link Duration#ZERO} if no duration is available
* @throws ParsingException if there is an error in the extraction * @throws ParsingException if there is an error in the extraction
*/ */
long getDuration() throws ParsingException; @Nonnull
default Duration getDurationObject() throws ParsingException {
return Duration.ZERO;
}
/**
* Get the stream duration in seconds.
*
* @return the stream duration in seconds or 0 if no duration is available
* @throws ParsingException if there is an error in the extraction
*/
default long getDuration() throws ParsingException {
return getDurationObject().toSeconds();
}
/** /**
* Parses the number of views * Parses the number of views

Wyświetl plik

@ -44,12 +44,12 @@ public class StreamInfoItemsCollector
throw new FoundAdException("Found ad"); throw new FoundAdException("Found ad");
} }
final StreamInfoItem resultItem = new StreamInfoItem( final var resultItem = new StreamInfoItem(getServiceId(), extractor.getUrl(),
getServiceId(), extractor.getUrl(), extractor.getName(), extractor.getStreamType()); extractor.getName(), extractor.getStreamType());
// optional information // optional information
try { try {
resultItem.setDuration(extractor.getDuration()); resultItem.setDurationObject(extractor.getDurationObject());
} catch (final Exception e) { } catch (final Exception e) {
addError(e); addError(e);
} }

Wyświetl plik

@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -99,12 +100,9 @@ public class ExtractorAsserts {
assertGreater(expected, actual, actual + " is not > " + expected); assertGreater(expected, actual, actual + " is not > " + expected);
} }
public static void assertGreater( public static <T extends Comparable<T>> void assertGreater(final T expected, final T actual,
final long expected, final String message) {
final long actual, assertTrue(actual.compareTo(expected) > 0, message);
final String message
) {
assertTrue(actual > expected, message);
} }
public static void assertGreaterOrEqual(final long expected, final long actual) { public static void assertGreaterOrEqual(final long expected, final long actual) {

Wyświetl plik

@ -14,6 +14,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.kiosk.KioskExtractor; import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -41,9 +42,9 @@ public class MediaCCCRecentListExtractorTest {
isNullOrEmpty(item.getName()), isNullOrEmpty(item.getName()),
"Name=[" + item.getName() + "] of " + item + " is empty or null" "Name=[" + item.getName() + "] of " + item + " is empty or null"
), ),
() -> assertGreater(0, () -> assertGreater(Duration.ZERO,
item.getDuration(), item.getDurationObject(),
"Duration[=" + item.getDuration() + "] of " + item + " is <= 0" "Duration[=" + item.getDurationObject() + "] of " + item + " is <= 0"
) )
); );
} }

Wyświetl plik

@ -9,6 +9,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.AudioTrackType; import org.schabi.newpipe.extractor.stream.AudioTrackType;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@ -38,9 +39,12 @@ public class YoutubeParsingHelperTest {
@Test @Test
void testParseDurationString() throws ParsingException { void testParseDurationString() throws ParsingException {
assertEquals(1162567, YoutubeParsingHelper.parseDurationString("12:34:56:07")); assertEquals(Duration.ofDays(12).plusHours(34).plusMinutes(56).plusSeconds(7),
assertEquals(4445767, YoutubeParsingHelper.parseDurationString("1,234:56:07")); YoutubeParsingHelper.parseDurationString("12:34:56:07"));
assertEquals(754, YoutubeParsingHelper.parseDurationString("12:34 ")); 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 @Test