[YouTube] Add support for shortsLockupViewModels

This new UI data type is replacing the reelItemRenderer one.
This commit is contained in:
AudricV 2024-09-08 17:21:40 +02:00
parent 6e3a4a6d9d
commit f926fbcf35
No known key found for this signature in database
GPG Key ID: DA92EC7905614198
3 changed files with 185 additions and 2 deletions

View File

@ -299,6 +299,9 @@ public class YoutubeChannelTabExtractor extends ChannelTabExtractor {
} else if (richItem.has("reelItemRenderer")) { } else if (richItem.has("reelItemRenderer")) {
commitReel(collector, richItem.getObject("reelItemRenderer"), commitReel(collector, richItem.getObject("reelItemRenderer"),
channelVerifiedStatus, channelName, channelUrl); channelVerifiedStatus, channelName, channelUrl);
} else if (richItem.has("shortsLockupViewModel")) {
commitShortsLockup(collector, richItem.getObject("shortsLockupViewModel"),
channelVerifiedStatus, channelName, channelUrl);
} else if (richItem.has("playlistRenderer")) { } else if (richItem.has("playlistRenderer")) {
commitPlaylist(collector, richItem.getObject("playlistRenderer"), commitPlaylist(collector, richItem.getObject("playlistRenderer"),
channelVerifiedStatus, channelName, channelUrl); channelVerifiedStatus, channelName, channelUrl);
@ -356,6 +359,30 @@ public class YoutubeChannelTabExtractor extends ChannelTabExtractor {
}); });
} }
private static void commitShortsLockup(@Nonnull final MultiInfoItemsCollector collector,
@Nonnull final JsonObject shortsLockupViewModel,
@Nonnull final VerifiedStatus channelVerifiedStatus,
@Nullable final String channelName,
@Nullable final String channelUrl) {
collector.commit(
new YoutubeShortsLockupInfoItemExtractor(shortsLockupViewModel) {
@Override
public String getUploaderName() throws ParsingException {
return isNullOrEmpty(channelName) ? super.getUploaderName() : channelName;
}
@Override
public String getUploaderUrl() throws ParsingException {
return isNullOrEmpty(channelUrl) ? super.getUploaderName() : channelUrl;
}
@Override
public boolean isUploaderVerified() {
return channelVerifiedStatus == VerifiedStatus.VERIFIED;
}
});
}
private void commitVideo(@Nonnull final MultiInfoItemsCollector collector, private void commitVideo(@Nonnull final MultiInfoItemsCollector collector,
@Nonnull final TimeAgoParser timeAgoParser, @Nonnull final TimeAgoParser timeAgoParser,
@Nonnull final JsonObject jsonObject, @Nonnull final JsonObject jsonObject,

View File

@ -20,13 +20,19 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* A {@link StreamInfoItemExtractor} for YouTube's {@code reelItemRenderers}. * A {@link StreamInfoItemExtractor} for YouTube's {@code reelItemRenderer}s.
* *
* <p> * <p>
* {@code reelItemRenderers} are returned on YouTube for their short-form contents on almost every * {@code reelItemRenderer}s were returned on YouTube for their short-form contents on almost every
* place and every major client. They provide a limited amount of information and do not provide * place and every major client. They provide a limited amount of information and do not provide
* the exact view count, any uploader info (name, URL, avatar, verified status) and the upload date. * the exact view count, any uploader info (name, URL, avatar, verified status) and the upload date.
* </p> * </p>
*
* <p>
* At the time this documentation has been updated, they are being replaced by
* {@code shortsLockupViewModel}s. See {@link YoutubeShortsLockupInfoItemExtractor} for an
* extractor for this new UI data type.
* </p>
*/ */
public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor { public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor {

View File

@ -0,0 +1,150 @@
package org.schabi.newpipe.extractor.services.youtube.extractors;
import com.grack.nanojson.JsonObject;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.localization.DateWrapper;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.utils.Utils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
/**
* A {@link StreamInfoItemExtractor} for YouTube's {@code shortsLockupViewModel}s.
*
* <p>
* {@code shortsLockupViewModel}s are returned on YouTube for their short-form contents on almost
* every place and every major client. They provide a limited amount of information and do not
* provide the exact view count, any uploader info (name, URL, avatar, verified status) and the
* upload date.
* </p>
*
* <p>
* At the time this documentation has been written, this data UI type is not fully used (rolled
* out), so {@code reelItemRenderer}s are also returned. See {@link YoutubeReelInfoItemExtractor}
* for an extractor for this UI data type.
* </p>
*/
public class YoutubeShortsLockupInfoItemExtractor implements StreamInfoItemExtractor {
@Nonnull
private final JsonObject shortsLockupViewModel;
public YoutubeShortsLockupInfoItemExtractor(@Nonnull final JsonObject shortsLockupViewModel) {
this.shortsLockupViewModel = shortsLockupViewModel;
}
@Override
public String getName() throws ParsingException {
return shortsLockupViewModel.getObject("overlayMetadata")
.getObject("primaryText")
.getString("content");
}
@Override
public String getUrl() throws ParsingException {
String videoId = shortsLockupViewModel.getObject("onTap")
.getObject("innertubeCommand")
.getObject("reelWatchEndpoint")
.getString("videoId");
if (isNullOrEmpty(videoId)) {
videoId = shortsLockupViewModel.getObject("inlinePlayerData")
.getObject("onVisible")
.getObject("innertubeCommand")
.getObject("watchEndpoint")
.getString("videoId");
}
if (isNullOrEmpty(videoId)) {
throw new ParsingException("Could not get video ID");
}
try {
return YoutubeStreamLinkHandlerFactory.getInstance().getUrl(videoId);
} catch (final Exception e) {
throw new ParsingException("Could not get URL", e);
}
}
@Nonnull
@Override
public List<Image> getThumbnails() throws ParsingException {
return getThumbnailsFromInfoItem(shortsLockupViewModel.getObject("thumbnail")
.getObject("sources"));
}
@Override
public StreamType getStreamType() throws ParsingException {
return StreamType.VIDEO_STREAM;
}
@Override
public long getViewCount() throws ParsingException {
final String viewCountText = shortsLockupViewModel.getObject("overlayMetadata")
.getObject("secondaryText")
.getString("content");
if (!isNullOrEmpty(viewCountText)) {
// This approach is language dependent
if (viewCountText.toLowerCase().contains("no views")) {
return 0;
}
return Utils.mixedNumberWordToLong(viewCountText);
}
throw new ParsingException("Could not get short view count");
}
@Override
public boolean isShortFormContent() {
return true;
}
// All the following properties cannot be obtained from shortsLockupViewModels
@Override
public boolean isAd() throws ParsingException {
return false;
}
@Override
public long getDuration() throws ParsingException {
return -1;
}
@Override
public String getUploaderName() throws ParsingException {
return null;
}
@Override
public String getUploaderUrl() throws ParsingException {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;
}
@Nullable
@Override
public String getTextualUploadDate() throws ParsingException {
return null;
}
@Nullable
@Override
public DateWrapper getUploadDate() throws ParsingException {
return null;
}
}