diff --git a/build.gradle.kts b/build.gradle.kts index 45d2e80980..b25004d94f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { apply(plugin = "java-library") - version = "v0.25.2" + version = "v0.26.0" tasks.withType { options.encoding = Charsets.UTF_8.toString() diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java index e7d88c2379..0216ba3aa0 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import java.util.Collections; import java.util.List; +import java.util.Set; /* * Copyright (C) 2018 Christian Schabesberger @@ -54,23 +55,23 @@ public abstract class StreamingService { public static class ServiceInfo { private final String name; - private final List mediaCapabilities; + private final Set mediaCapabilities; /** * Creates a new instance of a ServiceInfo * @param name the name of the service * @param mediaCapabilities the type of media this service can handle */ - public ServiceInfo(final String name, final List mediaCapabilities) { + public ServiceInfo(final String name, final Set mediaCapabilities) { this.name = name; - this.mediaCapabilities = Collections.unmodifiableList(mediaCapabilities); + this.mediaCapabilities = mediaCapabilities; } public String getName() { return name; } - public List getMediaCapabilities() { + public Set getMediaCapabilities() { return mediaCapabilities; } @@ -104,7 +105,7 @@ public enum LinkType { */ public StreamingService(final int id, final String name, - final List capabilities) { + final Set capabilities) { this.serviceId = id; this.serviceInfo = new ServiceInfo(name, capabilities); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java index 92e171d65d..c1cb168ce1 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampService.java @@ -47,12 +47,12 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; -import java.util.Arrays; +import java.util.EnumSet; public class BandcampService extends StreamingService { public BandcampService(final int id) { - super(id, "Bandcamp", Arrays.asList(AUDIO, COMMENTS)); + super(id, "Bandcamp", EnumSet.of(AUDIO, COMMENTS)); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCService.java index bb03a2c0b2..cacdd20b1b 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCService.java @@ -2,7 +2,6 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO; -import static java.util.Arrays.asList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelExtractor; @@ -38,9 +37,11 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; +import java.util.EnumSet; + public class MediaCCCService extends StreamingService { public MediaCCCService(final int id) { - super(id, "media.ccc.de", asList(AUDIO, VIDEO)); + super(id, "media.ccc.de", EnumSet.of(AUDIO, VIDEO)); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeService.java index ddd9d5395e..d393f273df 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeService.java @@ -2,7 +2,6 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO; -import static java.util.Arrays.asList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelExtractor; @@ -38,6 +37,7 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; +import java.util.EnumSet; import java.util.List; public class PeertubeService extends StreamingService { @@ -49,7 +49,7 @@ public PeertubeService(final int id) { } public PeertubeService(final int id, final PeertubeInstance instance) { - super(id, "PeerTube", asList(VIDEO, COMMENTS)); + super(id, "PeerTube", EnumSet.of(VIDEO, COMMENTS)); this.instance = instance; } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java index 4ec890d28f..40611ce5d5 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java @@ -2,7 +2,6 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; -import static java.util.Arrays.asList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelExtractor; @@ -38,12 +37,13 @@ import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; +import java.util.EnumSet; import java.util.List; public class SoundcloudService extends StreamingService { public SoundcloudService(final int id) { - super(id, "SoundCloud", asList(AUDIO, COMMENTS)); + super(id, "SoundCloud", EnumSet.of(AUDIO, COMMENTS)); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java index 128fbfdb91..7127453fda 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java @@ -4,7 +4,6 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.LIVE; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO; -import static java.util.Arrays.asList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelExtractor; @@ -57,6 +56,7 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; +import java.util.EnumSet; import java.util.List; import javax.annotation.Nonnull; @@ -84,7 +84,7 @@ public class YoutubeService extends StreamingService { public YoutubeService(final int id) { - super(id, "YouTube", asList(AUDIO, VIDEO, LIVE, COMMENTS)); + super(id, "YouTube", EnumSet.of(AUDIO, VIDEO, LIVE, COMMENTS)); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java index 7b4deaaa58..be951671d9 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java @@ -130,10 +130,15 @@ public String getUrl() throws ParsingException { @Override public String getName() throws ParsingException { - final String name = getTextFromObject(videoInfo.getObject("title")); + final JsonObject title = videoInfo.getObject("title"); + final String name = getTextFromObject(title); if (!isNullOrEmpty(name)) { return name; } + // Videos can have no title, e.g. https://www.youtube.com/watch?v=nc1kN8ZSfGQ + if (!isNullOrEmpty(title) && !title.has("runs")) { + return ""; + } throw new ParsingException("Could not get name"); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java index 416b9a7258..4f7134cb4e 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java @@ -151,17 +151,30 @@ public long getDuration() throws ParsingException { return -1; } - final List potentialDurations = JsonUtils.getArray(lockupViewModel, - "contentImage.thumbnailViewModel.overlays") - .streamAsJsonObjects() - .flatMap(jsonObject -> jsonObject - .getObject("thumbnailOverlayBadgeViewModel") - .getArray("thumbnailBadges") - .streamAsJsonObjects()) - .map(jsonObject -> jsonObject - .getObject("thumbnailBadgeViewModel") - .getString("text")) - .collect(Collectors.toList()); + final JsonArray overlays = JsonUtils.getArray(lockupViewModel, + "contentImage.thumbnailViewModel.overlays"); + + List thumbnailBadges = overlays.streamAsJsonObjects() + .flatMap(jsonObject -> jsonObject + .getObject("thumbnailOverlayBadgeViewModel") + .getArray("thumbnailBadges") + .streamAsJsonObjects()) + .collect(Collectors.toList()); + + if (thumbnailBadges.isEmpty()) { + thumbnailBadges = overlays.streamAsJsonObjects() + .flatMap(jsonObject -> jsonObject + .getObject("thumbnailBottomOverlayViewModel") + .getArray("badges") + .streamAsJsonObjects()) + .collect(Collectors.toList()); + } + + final List potentialDurations = thumbnailBadges.stream() + .map(jsonObject -> jsonObject + .getObject("thumbnailBadgeViewModel") + .getString("text")) + .collect(Collectors.toList()); if (potentialDurations.isEmpty()) { throw new ParsingException("Could not get duration: No parsable durations detected"); diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java index a2cfb7c90f..0f7100caaf 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java @@ -85,4 +85,29 @@ void lockupViewModelPremiere() () -> assertFalse(extractor.isShortFormContent()) ); } + + @Test + void emptyTitle() throws FileNotFoundException, JsonParserException { + final var json = JsonParser.object().from(new FileInputStream(getMockPath( + YoutubeStreamInfoItemTest.class, "emptyTitle") + ".json")); + final var timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT); + final var extractor = new YoutubeStreamInfoItemExtractor(json, timeAgoParser); + assertAll( + () -> assertEquals(StreamType.VIDEO_STREAM, extractor.getStreamType()), + () -> assertFalse(extractor.isAd()), + () -> assertEquals("https://www.youtube.com/watch?v=nc1kN8ZSfGQ", extractor.getUrl()), + () -> assertEquals("", extractor.getName()), + () -> assertEquals(39, extractor.getDuration()), + () -> assertEquals("hyper", extractor.getUploaderName()), + () -> assertEquals("https://www.youtube.com/channel/UCSezUnbvCLYBXuUlPcXU_QQ", extractor.getUploaderUrl()), + () -> assertFalse(extractor.getUploaderAvatars().isEmpty()), + () -> assertTrue(extractor.isUploaderVerified()), + () -> assertEquals("8 years ago", extractor.getTextualUploadDate()), + () -> assertNotNull(extractor.getUploadDate()), + () -> assertTrue(extractor.getViewCount() >= 1318193), + () -> assertFalse(extractor.getThumbnails().isEmpty()), + () -> assertNull(extractor.getShortDescription()), + () -> assertFalse(extractor.isShortFormContent()) + ); + } } diff --git a/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/emptytitle.json b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/emptytitle.json new file mode 100644 index 0000000000..d9de1ad174 --- /dev/null +++ b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/emptytitle.json @@ -0,0 +1,571 @@ +{ + "videoId": "nc1kN8ZSfGQ", + "thumbnail": { + "thumbnails": [ + { + "url": "https://i.ytimg.com/vi/nc1kN8ZSfGQ/hqdefault.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "width": 480, + "height": 270 + } + ] + }, + "title": { + "accessibility": { + "accessibilityData": { + "label": "­ 39 seconds" + } + } + }, + "longBylineText": { + "runs": [ + { + "text": "hyper", + "navigationEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/@HyperMetal101", + "webPageType": "WEB_PAGE_TYPE_CHANNEL", + "rootVe": 3611, + "apiUrl": "/youtubei/v1/browse" + } + }, + "browseEndpoint": { + "browseId": "UCSezUnbvCLYBXuUlPcXU_QQ", + "canonicalBaseUrl": "/@HyperMetal101" + } + } + } + ] + }, + "publishedTimeText": { + "simpleText": "8 years ago" + }, + "lengthText": { + "accessibility": { + "accessibilityData": { + "label": "39 seconds" + } + }, + "simpleText": "0:39" + }, + "viewCountText": { + "simpleText": "1,318,193 views" + }, + "navigationEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/watch?v=nc1kN8ZSfGQ&pp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "webPageType": "WEB_PAGE_TYPE_WATCH", + "rootVe": 3832 + } + }, + "watchEndpoint": { + "videoId": "nc1kN8ZSfGQ", + "params": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "playerParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "watchEndpointSupportedOnesieConfig": { + "html5PlaybackOnesieConfig": { + "commonConfig": { + "url": "https://rr2---sn-n02xgavv250-hxae.googlevideo.com/initplayback?source=youtube&oeis=1&c=WEB&oad=3200&ovd=3200&oaad=11000&oavd=11000&ocs=700&oewis=1&oputc=1&ofpcc=1&msp=1&odepv=1&id=9dcd6437c6527c64&ip=1.1.1.1&initcwndbps=3455000&mt=1772281053&oweuc=" + } + } + } + } + }, + "ownerBadges": [ + { + "metadataBadgeRenderer": { + "icon": { + "iconType": "CHECK_CIRCLE_THICK" + }, + "style": "BADGE_STYLE_TYPE_VERIFIED", + "tooltip": "Verified", + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "accessibilityData": { + "label": "Verified" + } + } + } + ], + "ownerText": { + "runs": [ + { + "text": "hyper", + "navigationEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/@HyperMetal101", + "webPageType": "WEB_PAGE_TYPE_CHANNEL", + "rootVe": 3611, + "apiUrl": "/youtubei/v1/browse" + } + }, + "browseEndpoint": { + "browseId": "UCSezUnbvCLYBXuUlPcXU_QQ", + "canonicalBaseUrl": "/@HyperMetal101" + } + } + } + ] + }, + "shortBylineText": { + "runs": [ + { + "text": "hyper", + "navigationEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/@HyperMetal101", + "webPageType": "WEB_PAGE_TYPE_CHANNEL", + "rootVe": 3611, + "apiUrl": "/youtubei/v1/browse" + } + }, + "browseEndpoint": { + "browseId": "UCSezUnbvCLYBXuUlPcXU_QQ", + "canonicalBaseUrl": "/@HyperMetal101" + } + } + } + ] + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "showActionMenu": false, + "shortViewCountText": { + "accessibility": { + "accessibilityData": { + "label": "1.3 million views" + } + }, + "simpleText": "1.3m views" + }, + "menu": { + "menuRenderer": { + "items": [ + { + "menuServiceItemRenderer": { + "text": { + "runs": [ + { + "text": "Add to queue" + } + ] + }, + "icon": { + "iconType": "ADD_TO_QUEUE_TAIL" + }, + "serviceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true + } + }, + "signalServiceEndpoint": { + "signal": "CLIENT_SIGNAL", + "actions": [ + { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "addToPlaylistCommand": { + "openMiniplayer": true, + "videoId": "nc1kN8ZSfGQ", + "listType": "PLAYLIST_EDIT_LIST_TYPE_QUEUE", + "onCreateListCommand": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true, + "apiUrl": "/youtubei/v1/playlist/create" + } + }, + "createPlaylistServiceEndpoint": { + "videoIds": [ + "nc1kN8ZSfGQ" + ], + "params": "CAQ%3D" + } + }, + "videoIds": [ + "nc1kN8ZSfGQ" + ], + "videoCommand": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/watch?v=nc1kN8ZSfGQ", + "webPageType": "WEB_PAGE_TYPE_WATCH", + "rootVe": 3832 + } + }, + "watchEndpoint": { + "videoId": "nc1kN8ZSfGQ", + "watchEndpointSupportedOnesieConfig": { + "html5PlaybackOnesieConfig": { + "commonConfig": { + "url": "https://rr2---sn-n02xgavv250-hxae.googlevideo.com/initplayback?source=youtube&oeis=1&c=WEB&oad=3200&ovd=3200&oaad=11000&oavd=11000&ocs=700&oewis=1&oputc=1&ofpcc=1&msp=1&odepv=1&id=9dcd6437c6527c64&ip=1.1.1.1&initcwndbps=3455000&mt=1772281053&oweuc=" + } + } + } + } + } + } + } + ] + } + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + } + }, + { + "menuServiceItemDownloadRenderer": { + "serviceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "offlineVideoEndpoint": { + "videoId": "nc1kN8ZSfGQ", + "onAddCommand": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "getDownloadActionCommand": { + "videoId": "nc1kN8ZSfGQ", + "params": "CAIQAA%3D%3D", + "isCrossDeviceDownload": false + } + } + } + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + } + }, + { + "menuServiceItemRenderer": { + "text": { + "runs": [ + { + "text": "Share" + } + ] + }, + "icon": { + "iconType": "SHARE" + }, + "serviceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true, + "apiUrl": "/youtubei/v1/share/get_share_panel" + } + }, + "shareEntityServiceEndpoint": { + "serializedShareEntity": "CgtuYzFrTjhaU2ZHUQ%3D%3D", + "commands": [ + { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "openPopupAction": { + "popup": { + "unifiedSharePanelRenderer": { + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "showLoadingSpinner": true + } + }, + "popupType": "DIALOG", + "beReused": true + } + } + ] + } + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "hasSeparator": true + } + } + ], + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "accessibility": { + "accessibilityData": { + "label": "Action menu" + } + } + } + }, + "channelThumbnailSupportedRenderers": { + "channelThumbnailWithLinkRenderer": { + "thumbnail": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "width": 68, + "height": 68 + } + ] + }, + "navigationEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/@HyperMetal101", + "webPageType": "WEB_PAGE_TYPE_CHANNEL", + "rootVe": 3611, + "apiUrl": "/youtubei/v1/browse" + } + }, + "browseEndpoint": { + "browseId": "UCSezUnbvCLYBXuUlPcXU_QQ", + "canonicalBaseUrl": "/@HyperMetal101" + } + }, + "accessibility": { + "accessibilityData": { + "label": "Go to channel hyper" + } + } + } + }, + "thumbnailOverlays": [ + { + "thumbnailOverlayTimeStatusRenderer": { + "text": { + "accessibility": { + "accessibilityData": { + "label": "39 seconds" + } + }, + "simpleText": "0:39" + }, + "style": "DEFAULT" + } + }, + { + "thumbnailOverlayToggleButtonRenderer": { + "isToggled": false, + "untoggledIcon": { + "iconType": "WATCH_LATER" + }, + "toggledIcon": { + "iconType": "CHECK" + }, + "untoggledTooltip": "Watch Later", + "toggledTooltip": "Added", + "untoggledServiceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true, + "apiUrl": "/youtubei/v1/browse/edit_playlist" + } + }, + "playlistEditEndpoint": { + "playlistId": "WL", + "actions": [ + { + "addedVideoId": "nc1kN8ZSfGQ", + "action": "ACTION_ADD_VIDEO" + } + ] + } + }, + "toggledServiceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true, + "apiUrl": "/youtubei/v1/browse/edit_playlist" + } + }, + "playlistEditEndpoint": { + "playlistId": "WL", + "actions": [ + { + "action": "ACTION_REMOVE_VIDEO_BY_VIDEO_ID", + "removedVideoId": "nc1kN8ZSfGQ" + } + ] + } + }, + "untoggledAccessibility": { + "accessibilityData": { + "label": "Watch Later" + } + }, + "toggledAccessibility": { + "accessibilityData": { + "label": "Added" + } + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "thumbnailOverlayToggleButtonRenderer": { + "untoggledIcon": { + "iconType": "ADD_TO_QUEUE_TAIL" + }, + "toggledIcon": { + "iconType": "PLAYLIST_ADD_CHECK" + }, + "untoggledTooltip": "Add to queue", + "toggledTooltip": "Added", + "untoggledServiceEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true + } + }, + "signalServiceEndpoint": { + "signal": "CLIENT_SIGNAL", + "actions": [ + { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "addToPlaylistCommand": { + "openMiniplayer": true, + "videoId": "nc1kN8ZSfGQ", + "listType": "PLAYLIST_EDIT_LIST_TYPE_QUEUE", + "onCreateListCommand": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "sendPost": true, + "apiUrl": "/youtubei/v1/playlist/create" + } + }, + "createPlaylistServiceEndpoint": { + "videoIds": [ + "nc1kN8ZSfGQ" + ], + "params": "CAQ%3D" + } + }, + "videoIds": [ + "nc1kN8ZSfGQ" + ] + } + } + ] + } + }, + "untoggledAccessibility": { + "accessibilityData": { + "label": "Add to queue" + } + }, + "toggledAccessibility": { + "accessibilityData": { + "label": "Added" + } + }, + "trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "thumbnailOverlayNowPlayingRenderer": { + "text": { + "runs": [ + { + "text": "Now playing" + } + ] + } + } + }, + { + "thumbnailOverlayLoadingPreviewRenderer": { + "text": { + "runs": [ + { + "text": "Keep hovering to play" + } + ] + } + } + } + ], + "richThumbnail": { + "movingThumbnailRenderer": { + "movingThumbnailDetails": { + "thumbnails": [ + { + "url": "https://i.ytimg.com/an_webp/nc1kN8ZSfGQ/mqdefault_6s.webp?du=3000&sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "width": 320, + "height": 180 + } + ], + "logAsMovingThumbnail": true + }, + "enableHoveredLogging": true, + "enableOverlay": true + } + }, + "inlinePlaybackEndpoint": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/watch?v=nc1kN8ZSfGQ&pp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "webPageType": "WEB_PAGE_TYPE_WATCH", + "rootVe": 3832 + } + }, + "watchEndpoint": { + "videoId": "nc1kN8ZSfGQ", + "params": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "playerParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "playerExtraUrlParams": [ + { + "key": "inline", + "value": "1" + } + ], + "watchEndpointSupportedOnesieConfig": { + "html5PlaybackOnesieConfig": { + "commonConfig": { + "url": "https://rr2---sn-n02xgavv250-hxae.googlevideo.com/initplayback?source=youtube&oeis=1&c=WEB&oad=3200&ovd=3200&oaad=11000&oavd=11000&ocs=700&oewis=1&oputc=1&ofpcc=1&msp=1&odepv=1&id=9dcd6437c6527c64&ip=1.1.1.1&initcwndbps=3455000&mt=1772281053&oweuc=" + } + } + } + } + }, + "searchVideoResultEntityKey": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "avatar": { + "decoratedAvatarViewModel": { + "avatar": { + "avatarViewModel": { + "image": { + "sources": [ + { + "url": "https://yt3.ggpht.com/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "width": 68, + "height": 68 + } + ] + }, + "avatarImageSize": "AVATAR_SIZE_M" + } + }, + "a11yLabel": "Go to channel hyper", + "rendererContext": { + "commandContext": { + "onTap": { + "innertubeCommand": { + "clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "commandMetadata": { + "webCommandMetadata": { + "url": "/@HyperMetal101", + "webPageType": "WEB_PAGE_TYPE_CHANNEL", + "rootVe": 3611, + "apiUrl": "/youtubei/v1/browse" + } + }, + "browseEndpoint": { + "browseId": "UCSezUnbvCLYBXuUlPcXU_QQ", + "canonicalBaseUrl": "/@HyperMetal101" + } + } + } + } + } + } + } +} \ No newline at end of file