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 {