From 7f818217d28f3066174bd9cf97088c821938df68 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Mon, 25 Jul 2022 18:40:02 +0200 Subject: [PATCH] [SoundCloud] Add utility methods to get images from track JSON objects and image URLs These new public and static methods, added in SoundcloudParsingHelper, getAllImagesFromArtworkOrAvatarUrl(String) and getAllImagesFromVisualUrl(String) (which call a common private method, getAllImagesFromImageUrlReturned(String, List, List)), return an unmodifiable list of JPEG images containing almost every image resolution provided by SoundCloud except the original size and the tiny resolution (for artworks and avatars, as the image size is 20x20 for artworks and 18x18 for avatars, so very close to or equal to the t20x20 resolution): - for artworks and avatars: - mini: 16x16; - t20x20: 20x20; - small: 32x32; - badge: 47x47; - t50x50: 50x50; - t60x60: 60x60; - t67x67: 67x67; - large: 100x100; - t120x120: 120x120; - t200x200: 200x200; - t240x240: 240x240; - t250x250: 250x250; - t300x300: 300x300; - t500x500: 500x500. - for visuals/user banners: - t1240x260: 1240x260; - t2480x520: 2480x520. Duplicated code in two methods of SoundcloudParsingHelper (getUsersFromApi(ChannelInfoItemsCollector, String) and getStreamsFromApi(StreamInfoItemsCollector, String, boolean)) has been merged into one common private method, getNextPageUrlFromResponseObject(JsonObject). --- .../soundcloud/SoundcloudParsingHelper.java | 105 +++++++++++++++++- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java index a029d85da..bae7c4fe4 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java @@ -1,5 +1,11 @@ package org.schabi.newpipe.extractor.services.soundcloud; +import static org.schabi.newpipe.extractor.Image.ResolutionLevel.LOW; +import static org.schabi.newpipe.extractor.Image.ResolutionLevel.MEDIUM; +import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; +import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; + import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; @@ -9,6 +15,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.schabi.newpipe.extractor.MultiInfoItemsCollector; +import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfoItemsCollector; import org.schabi.newpipe.extractor.downloader.Downloader; @@ -20,12 +27,14 @@ import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudCha import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistInfoItemExtractor; import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; +import org.schabi.newpipe.extractor.utils.ImageSuffix; import org.schabi.newpipe.extractor.utils.JsonUtils; import org.schabi.newpipe.extractor.utils.Parser; import org.schabi.newpipe.extractor.utils.Parser.RegexException; import org.schabi.newpipe.extractor.utils.Utils; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -35,12 +44,47 @@ import java.time.format.DateTimeParseException; import java.util.Collections; import java.util.List; import java.util.Map; - -import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; +import java.util.stream.Collectors; public final class SoundcloudParsingHelper { + // CHECKSTYLE:OFF + // From https://web.archive.org/web/20210214185000/https://developers.soundcloud.com/docs/api/reference#tracks + // and researches on images used by the websites + // CHECKSTYLE:ON + /* + SoundCloud avatars and artworks are almost squares + + When we get non-square pictures, all these images variants are still squares, except the + original and the crop versions provides images which are respecting aspect ratios. + The websites only use the square variants. + + t2400x2400 and t3000x3000 variants also exists, but are not returned as several images are + uploaded with a lower size than these variants: in this case, these variants return an upscaled + version of the original image. + */ + private static final List ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS = + List.of(new ImageSuffix("mini.jpg", 16, 16, LOW), + new ImageSuffix("t20x20.jpg", 20, 20, LOW), + new ImageSuffix("small.jpg", 32, 32, LOW), + new ImageSuffix("badge.jpg", 47, 47, LOW), + new ImageSuffix("t50x50.jpg", 50, 50, LOW), + new ImageSuffix("t60x60.jpg", 60, 60, LOW), + // Seems to work also on avatars, even if it is written to be not the case in + // the old API docs + new ImageSuffix("t67x67.jpg", 67, 67, LOW), + new ImageSuffix("t80x80.jpg", 80, 80, LOW), + new ImageSuffix("large.jpg", 100, 100, LOW), + new ImageSuffix("t120x120.jpg", 120, 120, LOW), + new ImageSuffix("t200x200.jpg", 200, 200, MEDIUM), + new ImageSuffix("t240x240.jpg", 240, 240, MEDIUM), + new ImageSuffix("t250x250.jpg", 250, 250, MEDIUM), + new ImageSuffix("t300x300.jpg", 300, 300, MEDIUM), + new ImageSuffix("t500x500.jpg", 500, 500, MEDIUM)); + + private static final List VISUALS_URL_SUFFIXES_AND_RESOLUTIONS = + List.of(new ImageSuffix("t1240x260.jpg", 1240, 260, MEDIUM), + new ImageSuffix("t2480x520.jpg", 2480, 520, MEDIUM)); + private static String clientId; public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/"; @@ -366,4 +410,57 @@ public final class SoundcloudParsingHelper { public static String getUploaderName(final JsonObject object) { return object.getObject("user").getString("username", ""); } + + @Nonnull + public static List getAllImagesFromTrackObject(@Nonnull final JsonObject trackObject) + throws ParsingException { + final String artworkUrl = trackObject.getString("artwork_url"); + if (artworkUrl != null) { + return getAllImagesFromArtworkOrAvatarUrl(artworkUrl); + } + final String avatarUrl = trackObject.getObject("user").getString("avatar_url"); + if (avatarUrl != null) { + return getAllImagesFromArtworkOrAvatarUrl(avatarUrl); + } + + throw new ParsingException("Could not get track or track user's thumbnails"); + } + + @Nonnull + public static List getAllImagesFromArtworkOrAvatarUrl( + @Nullable final String originalArtworkOrAvatarUrl) { + if (isNullOrEmpty(originalArtworkOrAvatarUrl)) { + return List.of(); + } + + return getAllImagesFromImageUrlReturned( + // Artwork and avatars are originally returned with the "large" resolution, which + // is 100x100 + originalArtworkOrAvatarUrl.replace("large.jpg", ""), + ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS); + } + + @Nonnull + public static List getAllImagesFromVisualUrl( + @Nullable final String originalVisualUrl) { + if (isNullOrEmpty(originalVisualUrl)) { + return List.of(); + } + + return getAllImagesFromImageUrlReturned( + // Images are originally returned with the "original" resolution, which may be + // huge so don't include it for size purposes + originalVisualUrl.replace("original.jpg", ""), + VISUALS_URL_SUFFIXES_AND_RESOLUTIONS); + } + + private static List getAllImagesFromImageUrlReturned( + @Nonnull final String baseImageUrl, + @Nonnull final List imageSuffixes) { + return imageSuffixes.stream() + .map(imageSuffix -> new Image(baseImageUrl + imageSuffix.getSuffix(), + imageSuffix.getHeight(), imageSuffix.getWidth(), + imageSuffix.getResolutionLevel())) + .collect(Collectors.toUnmodifiableList()); + } }