|
1 | 1 | package org.schabi.newpipe.extractor.services.soundcloud; |
2 | 2 |
|
| 3 | +import static org.schabi.newpipe.extractor.Image.ResolutionLevel.LOW; |
| 4 | +import static org.schabi.newpipe.extractor.Image.ResolutionLevel.MEDIUM; |
| 5 | +import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; |
| 6 | +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; |
| 7 | +import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; |
| 8 | + |
3 | 9 | import com.grack.nanojson.JsonArray; |
4 | 10 | import com.grack.nanojson.JsonObject; |
5 | 11 | import com.grack.nanojson.JsonParser; |
|
9 | 15 | import org.jsoup.nodes.Element; |
10 | 16 | import org.jsoup.select.Elements; |
11 | 17 | import org.schabi.newpipe.extractor.MultiInfoItemsCollector; |
| 18 | +import org.schabi.newpipe.extractor.Image; |
12 | 19 | import org.schabi.newpipe.extractor.NewPipe; |
13 | 20 | import org.schabi.newpipe.extractor.channel.ChannelInfoItemsCollector; |
14 | 21 | import org.schabi.newpipe.extractor.downloader.Downloader; |
|
20 | 27 | import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistInfoItemExtractor; |
21 | 28 | import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor; |
22 | 29 | import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector; |
| 30 | +import org.schabi.newpipe.extractor.utils.ImageSuffix; |
23 | 31 | import org.schabi.newpipe.extractor.utils.JsonUtils; |
24 | 32 | import org.schabi.newpipe.extractor.utils.Parser; |
25 | 33 | import org.schabi.newpipe.extractor.utils.Parser.RegexException; |
26 | 34 | import org.schabi.newpipe.extractor.utils.Utils; |
27 | 35 |
|
28 | 36 | import javax.annotation.Nonnull; |
| 37 | +import javax.annotation.Nullable; |
29 | 38 | import java.io.IOException; |
30 | 39 | import java.net.MalformedURLException; |
31 | 40 | import java.net.URL; |
|
35 | 44 | import java.util.Collections; |
36 | 45 | import java.util.List; |
37 | 46 | import java.util.Map; |
38 | | - |
39 | | -import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; |
40 | | -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; |
41 | | -import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; |
| 47 | +import java.util.stream.Collectors; |
42 | 48 |
|
43 | 49 | public final class SoundcloudParsingHelper { |
| 50 | + // CHECKSTYLE:OFF |
| 51 | + // From https://web.archive.org/web/20210214185000/https://developers.soundcloud.com/docs/api/reference#tracks |
| 52 | + // and researches on images used by the websites |
| 53 | + // CHECKSTYLE:ON |
| 54 | + /* |
| 55 | + SoundCloud avatars and artworks are almost squares |
| 56 | +
|
| 57 | + When we get non-square pictures, all these images variants are still squares, except the |
| 58 | + original and the crop versions provides images which are respecting aspect ratios. |
| 59 | + The websites only use the square variants. |
| 60 | +
|
| 61 | + t2400x2400 and t3000x3000 variants also exists, but are not returned as several images are |
| 62 | + uploaded with a lower size than these variants: in this case, these variants return an upscaled |
| 63 | + version of the original image. |
| 64 | + */ |
| 65 | + private static final List<ImageSuffix> ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS = |
| 66 | + List.of(new ImageSuffix("mini.jpg", 16, 16, LOW), |
| 67 | + new ImageSuffix("t20x20.jpg", 20, 20, LOW), |
| 68 | + new ImageSuffix("small.jpg", 32, 32, LOW), |
| 69 | + new ImageSuffix("badge.jpg", 47, 47, LOW), |
| 70 | + new ImageSuffix("t50x50.jpg", 50, 50, LOW), |
| 71 | + new ImageSuffix("t60x60.jpg", 60, 60, LOW), |
| 72 | + // Seems to work also on avatars, even if it is written to be not the case in |
| 73 | + // the old API docs |
| 74 | + new ImageSuffix("t67x67.jpg", 67, 67, LOW), |
| 75 | + new ImageSuffix("t80x80.jpg", 80, 80, LOW), |
| 76 | + new ImageSuffix("large.jpg", 100, 100, LOW), |
| 77 | + new ImageSuffix("t120x120.jpg", 120, 120, LOW), |
| 78 | + new ImageSuffix("t200x200.jpg", 200, 200, MEDIUM), |
| 79 | + new ImageSuffix("t240x240.jpg", 240, 240, MEDIUM), |
| 80 | + new ImageSuffix("t250x250.jpg", 250, 250, MEDIUM), |
| 81 | + new ImageSuffix("t300x300.jpg", 300, 300, MEDIUM), |
| 82 | + new ImageSuffix("t500x500.jpg", 500, 500, MEDIUM)); |
| 83 | + |
| 84 | + private static final List<ImageSuffix> VISUALS_URL_SUFFIXES_AND_RESOLUTIONS = |
| 85 | + List.of(new ImageSuffix("t1240x260.jpg", 1240, 260, MEDIUM), |
| 86 | + new ImageSuffix("t2480x520.jpg", 2480, 520, MEDIUM)); |
| 87 | + |
44 | 88 | private static String clientId; |
45 | 89 | public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/"; |
46 | 90 |
|
@@ -366,4 +410,57 @@ public static String getAvatarUrl(final JsonObject object) { |
366 | 410 | public static String getUploaderName(final JsonObject object) { |
367 | 411 | return object.getObject("user").getString("username", ""); |
368 | 412 | } |
| 413 | + |
| 414 | + @Nonnull |
| 415 | + public static List<Image> getAllImagesFromTrackObject(@Nonnull final JsonObject trackObject) |
| 416 | + throws ParsingException { |
| 417 | + final String artworkUrl = trackObject.getString("artwork_url"); |
| 418 | + if (artworkUrl != null) { |
| 419 | + return getAllImagesFromArtworkOrAvatarUrl(artworkUrl); |
| 420 | + } |
| 421 | + final String avatarUrl = trackObject.getObject("user").getString("avatar_url"); |
| 422 | + if (avatarUrl != null) { |
| 423 | + return getAllImagesFromArtworkOrAvatarUrl(avatarUrl); |
| 424 | + } |
| 425 | + |
| 426 | + throw new ParsingException("Could not get track or track user's thumbnails"); |
| 427 | + } |
| 428 | + |
| 429 | + @Nonnull |
| 430 | + public static List<Image> getAllImagesFromArtworkOrAvatarUrl( |
| 431 | + @Nullable final String originalArtworkOrAvatarUrl) { |
| 432 | + if (isNullOrEmpty(originalArtworkOrAvatarUrl)) { |
| 433 | + return List.of(); |
| 434 | + } |
| 435 | + |
| 436 | + return getAllImagesFromImageUrlReturned( |
| 437 | + // Artwork and avatars are originally returned with the "large" resolution, which |
| 438 | + // is 100x100 |
| 439 | + originalArtworkOrAvatarUrl.replace("large.jpg", ""), |
| 440 | + ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS); |
| 441 | + } |
| 442 | + |
| 443 | + @Nonnull |
| 444 | + public static List<Image> getAllImagesFromVisualUrl( |
| 445 | + @Nullable final String originalVisualUrl) { |
| 446 | + if (isNullOrEmpty(originalVisualUrl)) { |
| 447 | + return List.of(); |
| 448 | + } |
| 449 | + |
| 450 | + return getAllImagesFromImageUrlReturned( |
| 451 | + // Images are originally returned with the "original" resolution, which may be |
| 452 | + // huge so don't include it for size purposes |
| 453 | + originalVisualUrl.replace("original.jpg", ""), |
| 454 | + VISUALS_URL_SUFFIXES_AND_RESOLUTIONS); |
| 455 | + } |
| 456 | + |
| 457 | + private static List<Image> getAllImagesFromImageUrlReturned( |
| 458 | + @Nonnull final String baseImageUrl, |
| 459 | + @Nonnull final List<ImageSuffix> imageSuffixes) { |
| 460 | + return imageSuffixes.stream() |
| 461 | + .map(imageSuffix -> new Image(baseImageUrl + imageSuffix.getSuffix(), |
| 462 | + imageSuffix.getHeight(), imageSuffix.getWidth(), |
| 463 | + imageSuffix.getResolutionLevel())) |
| 464 | + .collect(Collectors.toUnmodifiableList()); |
| 465 | + } |
369 | 466 | } |
0 commit comments