From 87dca0f7ec6a22c8218489cb82454c4d6ed6beb6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 2 May 2023 21:36:11 +0200 Subject: [PATCH] Separate imageListToDbUrl from choosePreferredImage imageListToDbUrl should be used if the URL is going to be saved to the database, to avoid saving nothing in case at the moment of saving the user preference is to not show images. --- .../database/playlist/PlaylistStreamEntry.kt | 2 +- .../playlist/model/PlaylistRemoteEntity.java | 4 +- .../database/stream/StreamStatisticsEntry.kt | 2 +- .../database/stream/model/StreamEntity.kt | 10 +- .../subscription/SubscriptionEntity.java | 4 +- .../list/channel/ChannelFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 4 +- .../local/subscription/SubscriptionManager.kt | 4 +- .../newpipe/util/image/ImageStrategy.java | 104 ++++++++++++++---- 9 files changed, 96 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt index 47dc1d06a..1d74c6d31 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt @@ -29,7 +29,7 @@ data class PlaylistStreamEntry( item.duration = streamEntity.duration item.uploaderName = streamEntity.uploader item.uploaderUrl = streamEntity.uploaderUrl - item.thumbnails = ImageStrategy.urlToImageList(streamEntity.thumbnailUrl) + item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) return item } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index a64f2952c..7c6b4a8b0 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -71,7 +71,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { public PlaylistRemoteEntity(final PlaylistInfo info) { this(info.getServiceId(), info.getName(), info.getUrl(), // use uploader avatar when no thumbnail is available - ImageStrategy.choosePreferredImage(info.getThumbnails().isEmpty() + ImageStrategy.imageListToDbUrl(info.getThumbnails().isEmpty() ? info.getUploaderAvatars() : info.getThumbnails()), info.getUploaderName(), info.getStreamCount()); } @@ -89,7 +89,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { // we want to update the local playlist data even when either the remote thumbnail // URL changes, or the preferred image quality setting is changed by the user && TextUtils.equals(getThumbnailUrl(), - ImageStrategy.choosePreferredImage(info.getThumbnails())) + ImageStrategy.imageListToDbUrl(info.getThumbnails())) && TextUtils.equals(getUploader(), info.getUploaderName()); } diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt index 81e17b720..1f3654e7a 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt @@ -31,7 +31,7 @@ class StreamStatisticsEntry( item.duration = streamEntity.duration item.uploaderName = streamEntity.uploader item.uploaderUrl = streamEntity.uploaderUrl - item.thumbnails = ImageStrategy.urlToImageList(streamEntity.thumbnailUrl) + item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) return item } diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt index 4eecc9373..d9c160b89 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt @@ -68,7 +68,8 @@ data class StreamEntity( constructor(item: StreamInfoItem) : this( serviceId = item.serviceId, url = item.url, title = item.name, streamType = item.streamType, duration = item.duration, uploader = item.uploaderName, - uploaderUrl = item.uploaderUrl, thumbnailUrl = ImageStrategy.choosePreferredImage(item.thumbnails), viewCount = item.viewCount, + uploaderUrl = item.uploaderUrl, + thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails), viewCount = item.viewCount, textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(), isUploadDateApproximation = item.uploadDate?.isApproximation ) @@ -77,7 +78,8 @@ data class StreamEntity( constructor(info: StreamInfo) : this( serviceId = info.serviceId, url = info.url, title = info.name, streamType = info.streamType, duration = info.duration, uploader = info.uploaderName, - uploaderUrl = info.uploaderUrl, thumbnailUrl = ImageStrategy.choosePreferredImage(info.thumbnails), viewCount = info.viewCount, + uploaderUrl = info.uploaderUrl, + thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails), viewCount = info.viewCount, textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(), isUploadDateApproximation = info.uploadDate?.isApproximation ) @@ -87,7 +89,7 @@ data class StreamEntity( serviceId = item.serviceId, url = item.url, title = item.title, streamType = item.streamType, duration = item.duration, uploader = item.uploader, uploaderUrl = item.uploaderUrl, - thumbnailUrl = ImageStrategy.choosePreferredImage(item.thumbnails) + thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails) ) fun toStreamInfoItem(): StreamInfoItem { @@ -95,7 +97,7 @@ data class StreamEntity( item.duration = duration item.uploaderName = uploader item.uploaderUrl = uploaderUrl - item.thumbnails = ImageStrategy.urlToImageList(thumbnailUrl) + item.thumbnails = ImageStrategy.dbUrlToImageList(thumbnailUrl) if (viewCount != null) item.viewCount = viewCount as Long item.textualUploadDate = textualUploadDate diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java index 3fb474423..a61a22a84 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java @@ -58,7 +58,7 @@ public class SubscriptionEntity { final SubscriptionEntity result = new SubscriptionEntity(); result.setServiceId(info.getServiceId()); result.setUrl(info.getUrl()); - result.setData(info.getName(), ImageStrategy.choosePreferredImage(info.getAvatars()), + result.setData(info.getName(), ImageStrategy.imageListToDbUrl(info.getAvatars()), info.getDescription(), info.getSubscriberCount()); return result; } @@ -139,7 +139,7 @@ public class SubscriptionEntity { @Ignore public ChannelInfoItem toChannelInfoItem() { final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName()); - item.setThumbnails(ImageStrategy.urlToImageList(getAvatarUrl())); + item.setThumbnails(ImageStrategy.dbUrlToImageList(getAvatarUrl())); item.setSubscriberCount(getSubscriberCount()); item.setDescription(getDescription()); return item; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 3ece760ca..b16f40a4a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -355,7 +355,7 @@ public class ChannelFragment extends BaseStateFragment channel.setServiceId(info.getServiceId()); channel.setUrl(info.getUrl()); channel.setData(info.getName(), - ImageStrategy.choosePreferredImage(info.getAvatars()), + ImageStrategy.imageListToDbUrl(info.getAvatars()), info.getDescription(), info.getSubscriberCount()); channelSubscription = null; diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index ac82d5c91..fe2321059 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -61,7 +61,6 @@ import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.external_communication.ShareUtils -import org.schabi.newpipe.util.image.ImageStrategy import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -342,8 +341,7 @@ class SubscriptionFragment : BaseStateFragment() { val actions = DialogInterface.OnClickListener { _, i -> when (i) { 0 -> ShareUtils.shareText( - requireContext(), selectedItem.name, selectedItem.url, - ImageStrategy.choosePreferredImage(selectedItem.thumbnails) + requireContext(), selectedItem.name, selectedItem.url, selectedItem.thumbnails ) 1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url) 2 -> deleteChannel(selectedItem) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index cfd5196be..bd42bbe41 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -74,7 +74,7 @@ class SubscriptionManager(context: Context) { Completable.fromRunnable { it.setData( info.name, - ImageStrategy.choosePreferredImage(info.avatars), + ImageStrategy.imageListToDbUrl(info.avatars), info.description, info.subscriberCount ) @@ -105,7 +105,7 @@ class SubscriptionManager(context: Context) { } else if (info is ChannelInfo) { subscriptionEntity.setData( info.name, - ImageStrategy.choosePreferredImage(info.avatars), + ImageStrategy.imageListToDbUrl(info.avatars), info.description, info.subscriberCount ) diff --git a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.java b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.java index 1583c0b09..da97179b6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.java +++ b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.java @@ -49,30 +49,16 @@ public final class ImageStrategy { } /** - * Chooses an image amongst the provided list based on the user preference previously set with - * {@link #setPreferredImageQuality(PreferredImageQuality)}. {@code null} will be returned in - * case the list is empty or the user preference is to not show images. - *
- * These properties will be preferred, from most to least important: - *
    - *
  1. The image's {@link Image#getEstimatedResolutionLevel()} is not unknown and is close - * to {@link #preferredImageQuality}
  2. - *
  3. At least one of the image's width or height are known
  4. - *
  5. The highest resolution image is finally chosen if the user's preference is {@link - * PreferredImageQuality#HIGH}, otherwise the chosen image is the one that has the closest - * height to {@link #BEST_LOW_H} or {@link #BEST_MEDIUM_H}
  6. - *
+ * {@link #choosePreferredImage(List)} contains the description for this function's logic. * - * @param images the images from which to choose - * @return the chosen preferred image, or {@link null} if the list is empty or the user disabled - * images + * @param images the images from which to choose + * @param nonNoneQuality the preferred quality (must NOT be {@link PreferredImageQuality#NONE}) + * @return the chosen preferred image, or {@link null} if the list is empty + * @see #choosePreferredImage(List) */ @Nullable - public static String choosePreferredImage(@NonNull final List images) { - if (preferredImageQuality == PreferredImageQuality.NONE) { - return null; // do not load images - } - + static String choosePreferredImage(@NonNull final List images, + final PreferredImageQuality nonNoneQuality) { // this will be used to estimate the pixel count for images where only one of height or // width are known final double widthOverHeight = images.stream() @@ -82,7 +68,7 @@ public final class ImageStrategy { .findFirst() .orElse(1.0); - final Image.ResolutionLevel preferredLevel = preferredImageQuality.toResolutionLevel(); + final Image.ResolutionLevel preferredLevel = nonNoneQuality.toResolutionLevel(); final Comparator initialComparator = Comparator // the first step splits the images into groups of resolution levels .comparingInt(i -> { @@ -105,7 +91,7 @@ public final class ImageStrategy { // on how close its size is to BEST_LOW_H or BEST_MEDIUM_H (with proper units). Subgroups // without known image size will be left untouched since estimatePixelCount always returns // the same number for those. - final Comparator finalComparator = switch (preferredImageQuality) { + final Comparator finalComparator = switch (nonNoneQuality) { case NONE -> initialComparator; // unreachable case LOW -> initialComparator.thenComparingDouble(image -> { final double pixelCount = estimatePixelCount(image, widthOverHeight); @@ -128,8 +114,78 @@ public final class ImageStrategy { .orElse(null); } + /** + * Chooses an image amongst the provided list based on the user preference previously set with + * {@link #setPreferredImageQuality(PreferredImageQuality)}. {@code null} will be returned in + * case the list is empty or the user preference is to not show images. + *
+ * These properties will be preferred, from most to least important: + *
    + *
  1. The image's {@link Image#getEstimatedResolutionLevel()} is not unknown and is close + * to {@link #preferredImageQuality}
  2. + *
  3. At least one of the image's width or height are known
  4. + *
  5. The highest resolution image is finally chosen if the user's preference is {@link + * PreferredImageQuality#HIGH}, otherwise the chosen image is the one that has the height + * closest to {@link #BEST_LOW_H} or {@link #BEST_MEDIUM_H}
  6. + *
+ *
+ * Use {@link #imageListToDbUrl(List)} if the URL is going to be saved to the database, to avoid + * saving nothing in case at the moment of saving the user preference is to not show images. + * + * @param images the images from which to choose + * @return the chosen preferred image, or {@link null} if the list is empty or the user disabled + * images + * @see #imageListToDbUrl(List) + */ + @Nullable + public static String choosePreferredImage(@NonNull final List images) { + if (preferredImageQuality == PreferredImageQuality.NONE) { + return null; // do not load images + } + + return choosePreferredImage(images, preferredImageQuality); + } + + /** + * Like {@link #choosePreferredImage(List)}, except that if {@link #preferredImageQuality} is + * {@link PreferredImageQuality#NONE} an image will be chosen anyway (with preferred quality + * {@link PreferredImageQuality#MEDIUM}. + *
+ * To go back to a list of images (obviously with just the one chosen image) from a URL saved in + * the database use {@link #dbUrlToImageList(String)}. + * + * @param images the images from which to choose + * @return the chosen preferred image, or {@link null} if the list is empty + * @see #choosePreferredImage(List) + * @see #dbUrlToImageList(String) + */ + @Nullable + public static String imageListToDbUrl(@NonNull final List images) { + final PreferredImageQuality quality; + if (preferredImageQuality == PreferredImageQuality.NONE) { + quality = PreferredImageQuality.MEDIUM; + } else { + quality = preferredImageQuality; + } + + return choosePreferredImage(images, quality); + } + + /** + * Wraps the URL (coming from the database) in a {@code List} so that it is usable + * seamlessly in all of the places where the extractor would return a list of images, including + * allowing to build info objects based on database objects. + *
+ * To obtain a url to save to the database from a list of images use {@link + * #imageListToDbUrl(List)}. + * + * @param url the URL to wrap coming from the database, or {@code null} to get an empty list + * @return a list containing just one {@link Image} wrapping the provided URL, with unknown + * image size fields, or an empty list if the URL is {@code null} + * @see #imageListToDbUrl(List) + */ @NonNull - public static List urlToImageList(@Nullable final String url) { + public static List dbUrlToImageList(@Nullable final String url) { if (url == null) { return List.of(); } else {