mirror of
https://github.com/TeamNewPipe/NewPipeExtractor
synced 2025-10-05 16:12:47 +02:00
Merge pull request #1361 from AudricV/yt_premieres-lockups-support
This commit is contained in:
@@ -17,6 +17,11 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -30,20 +35,24 @@ import javax.annotation.Nullable;
|
||||
* The following features are currently not implemented because they have never been observed:
|
||||
* <ul>
|
||||
* <li>Shorts</li>
|
||||
* <li>Premieres</li>
|
||||
* <li>Paid content (Premium, members first or only)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtractor {
|
||||
|
||||
private static final String NO_VIEWS_LOWERCASE = "no views";
|
||||
// This approach is language dependant (en-GB)
|
||||
// Leading end space is voluntary included
|
||||
private static final String PREMIERES_TEXT = "Premieres ";
|
||||
private static final DateTimeFormatter PREMIERES_DATE_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("dd/MM/yyyy, HH:mm");
|
||||
|
||||
private final JsonObject lockupViewModel;
|
||||
private final TimeAgoParser timeAgoParser;
|
||||
|
||||
private StreamType cachedStreamType;
|
||||
private String cachedName;
|
||||
private Optional<String> cachedTextualUploadDate;
|
||||
private Optional<String> cachedDateText;
|
||||
|
||||
private ChannelImageViewModel cachedChannelImageViewModel;
|
||||
private JsonArray cachedMetadataRows;
|
||||
@@ -137,7 +146,9 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
|
||||
@Override
|
||||
public long getDuration() throws ParsingException {
|
||||
// Duration cannot be extracted for live streams, but only for normal videos
|
||||
if (isLive()) {
|
||||
// Exact duration cannot be extracted for premieres, an approximation is only available in
|
||||
// accessibility context label
|
||||
if (isLive() || isPremiere()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -237,20 +248,27 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
|
||||
@Nullable
|
||||
@Override
|
||||
public String getTextualUploadDate() throws ParsingException {
|
||||
if (cachedTextualUploadDate != null) {
|
||||
return cachedTextualUploadDate.orElse(null);
|
||||
}
|
||||
|
||||
// Live streams have no upload date
|
||||
if (isLive()) {
|
||||
cachedTextualUploadDate = Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
// This might be null e.g. for live streams
|
||||
this.cachedTextualUploadDate = metadataPart(1, 1)
|
||||
.map(this::getTextContentFromMetadataPart);
|
||||
return cachedTextualUploadDate.orElse(null);
|
||||
// Date string might be null e.g. for live streams
|
||||
final Optional<String> dateText = getDateText();
|
||||
|
||||
if (isPremiere()) {
|
||||
return getDateFromPremiere(dateText);
|
||||
}
|
||||
|
||||
return dateText.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDateFromPremiere(final Optional<String> dateText) {
|
||||
// This approach is language dependent
|
||||
// Remove the premieres text from the upload date metadata part
|
||||
return dateText.map(str -> str.replace(PREMIERES_TEXT, ""))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -265,11 +283,32 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
|
||||
if (textualUploadDate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isPremiere()) {
|
||||
final String premiereDate = getDateFromPremiere(getDateText());
|
||||
if (premiereDate == null) {
|
||||
throw new ParsingException("Could not get upload date from premiere");
|
||||
}
|
||||
|
||||
try {
|
||||
// As we request a UTC offset of 0 minutes, we get the UTC date
|
||||
return new DateWrapper(OffsetDateTime.of(LocalDateTime.parse(
|
||||
premiereDate, PREMIERES_DATE_FORMATTER), ZoneOffset.UTC));
|
||||
} catch (final DateTimeParseException e) {
|
||||
throw new ParsingException("Could not parse premiere upload date", e);
|
||||
}
|
||||
}
|
||||
|
||||
return timeAgoParser.parse(textualUploadDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getViewCount() throws ParsingException {
|
||||
if (isPremiere()) {
|
||||
// The number of people returned for premieres is the one currently waiting
|
||||
return -1;
|
||||
}
|
||||
|
||||
final Optional<String> optTextContent = metadataPart(1, 0)
|
||||
.map(this::getTextContentFromMetadataPart);
|
||||
// We could do this inline if the ParsingException would be a RuntimeException -.-
|
||||
@@ -357,6 +396,20 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
|
||||
return getStreamType() != StreamType.VIDEO_STREAM;
|
||||
}
|
||||
|
||||
private Optional<String> getDateText() throws ParsingException {
|
||||
if (cachedDateText == null) {
|
||||
cachedDateText = metadataPart(1, 1)
|
||||
.map(this::getTextContentFromMetadataPart);
|
||||
}
|
||||
return cachedDateText;
|
||||
}
|
||||
|
||||
private boolean isPremiere() throws ParsingException {
|
||||
return getDateText().map(dateText -> dateText.contains(PREMIERES_TEXT))
|
||||
// If we can't get date text, assume it is not a premiere, it should be a livestream
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
abstract static class ChannelImageViewModel {
|
||||
protected JsonObject viewModel;
|
||||
|
||||
|
@@ -4,6 +4,9 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class DownloaderFactory {
|
||||
|
||||
private static final DownloaderType DEFAULT_DOWNLOADER = DownloaderType.REAL;
|
||||
@@ -36,20 +39,31 @@ public class DownloaderFactory {
|
||||
}
|
||||
|
||||
public static Downloader getDownloader(final Class<?> clazz) {
|
||||
return getDownloader(clazz, null);
|
||||
return getDownloader(getMockPath(clazz, null));
|
||||
}
|
||||
|
||||
public static Downloader getDownloader(final Class<?> clazz, final String specificUseCase) {
|
||||
public static Downloader getDownloader(final Class<?> clazz,
|
||||
@Nullable final String specificUseCase) {
|
||||
return getDownloader(getMockPath(clazz, specificUseCase));
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns a path without a trailing '/', so that it can be used both as a folder name
|
||||
* and as a filename. The {@link MockDownloader} will use it as a folder name, but other tests
|
||||
* can use it as a filename, if only one custom mock file is needed for that test.
|
||||
*/
|
||||
public static String getMockPath(final Class<?> clazz,
|
||||
@Nullable final String specificUseCase) {
|
||||
String baseName = clazz.getName();
|
||||
if (specificUseCase != null) {
|
||||
baseName += "." + specificUseCase;
|
||||
}
|
||||
return getDownloader("src/test/resources/mocks/v1/"
|
||||
+ baseName
|
||||
.toLowerCase(Locale.ENGLISH)
|
||||
.replace('$', '.')
|
||||
.replace("test", "")
|
||||
.replace('.', '/'));
|
||||
return "src/test/resources/mocks/v1/"
|
||||
+ baseName
|
||||
.toLowerCase(Locale.ENGLISH)
|
||||
.replace('$', '.')
|
||||
.replace("test", "")
|
||||
.replace('.', '/');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,83 @@
|
||||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.schabi.newpipe.downloader.DownloaderFactory.getMockPath;
|
||||
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import org.schabi.newpipe.extractor.localization.TimeAgoPatternsManager;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamInfoItemLockupExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
public class YoutubeStreamInfoItemTest {
|
||||
@Test
|
||||
void videoRendererPremiere() throws FileNotFoundException, JsonParserException {
|
||||
final var json = JsonParser.object().from(new FileInputStream(getMockPath(
|
||||
YoutubeStreamInfoItemTest.class, "videoRendererPremiere") + ".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=M_8QNw_JM4I", extractor.getUrl()),
|
||||
() -> assertEquals("This video will premiere in 6 months.", extractor.getName()),
|
||||
() -> assertEquals(33, extractor.getDuration()),
|
||||
() -> assertEquals("Blunt Brothers Productions", extractor.getUploaderName()),
|
||||
() -> assertEquals("https://www.youtube.com/channel/UCUPrbbdnot-aPgNM65svgOg", extractor.getUploaderUrl()),
|
||||
() -> assertFalse(extractor.getUploaderAvatars().isEmpty()),
|
||||
() -> assertTrue(extractor.isUploaderVerified()),
|
||||
() -> assertEquals("2026-03-15 13:12", extractor.getTextualUploadDate()),
|
||||
() -> {
|
||||
assertNotNull(extractor.getUploadDate());
|
||||
assertEquals(OffsetDateTime.of(2026, 3, 15, 13, 12, 0, 0, ZoneOffset.UTC), extractor.getUploadDate().offsetDateTime());
|
||||
},
|
||||
() -> assertEquals(-1, extractor.getViewCount()),
|
||||
() -> assertFalse(extractor.getThumbnails().isEmpty()),
|
||||
() -> assertEquals("Patience is key… MERCH SHOP : https://www.bluntbrosproductions.com Follow us on Instagram for early updates: ...", extractor.getShortDescription()),
|
||||
() -> assertFalse(extractor.isShortFormContent())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockupViewModelPremiere()
|
||||
throws FileNotFoundException, JsonParserException {
|
||||
final var json = JsonParser.object().from(new FileInputStream(getMockPath(
|
||||
YoutubeStreamInfoItemTest.class, "lockupViewModelPremiere") + ".json"));
|
||||
final var timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT);
|
||||
final var extractor = new YoutubeStreamInfoItemLockupExtractor(json, timeAgoParser);
|
||||
assertAll(
|
||||
() -> assertEquals(StreamType.VIDEO_STREAM, extractor.getStreamType()),
|
||||
() -> assertFalse(extractor.isAd()),
|
||||
() -> assertEquals("https://www.youtube.com/watch?v=VIDEO_ID", extractor.getUrl()),
|
||||
() -> assertEquals("VIDEO_TITLE", extractor.getName()),
|
||||
() -> assertEquals(-1, extractor.getDuration()),
|
||||
() -> assertEquals("VIDEO_CHANNEL_NAME", extractor.getUploaderName()),
|
||||
() -> assertEquals("https://www.youtube.com/channel/UCD_on7-zu7Zuc3zissQvrgw", extractor.getUploaderUrl()),
|
||||
() -> assertFalse(extractor.getUploaderAvatars().isEmpty()),
|
||||
() -> assertFalse(extractor.isUploaderVerified()),
|
||||
() -> assertEquals("14/08/2025, 13:00", extractor.getTextualUploadDate()),
|
||||
() -> {
|
||||
assertNotNull(extractor.getUploadDate());
|
||||
assertEquals(OffsetDateTime.of(2025, 8, 14, 13, 0, 0, 0, ZoneOffset.UTC), extractor.getUploadDate().offsetDateTime());
|
||||
},
|
||||
() -> assertEquals(-1, extractor.getViewCount()),
|
||||
() -> assertFalse(extractor.getThumbnails().isEmpty()),
|
||||
() -> assertNull(extractor.getShortDescription()),
|
||||
() -> assertFalse(extractor.isShortFormContent())
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"contentImage": {
|
||||
"thumbnailViewModel": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/0_-Nh-nOhLQ/hqdefault.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"width": 168,
|
||||
"height": 94
|
||||
},
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/0_-Nh-nOhLQ/hqdefault.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"width": 336,
|
||||
"height": 188
|
||||
}
|
||||
]
|
||||
},
|
||||
"overlays": [
|
||||
{
|
||||
"thumbnailOverlayBadgeViewModel": {
|
||||
"thumbnailBadges": [
|
||||
{
|
||||
"thumbnailBadgeViewModel": {
|
||||
"text": "Upcoming",
|
||||
"badgeStyle": "THUMBNAIL_OVERLAY_BADGE_STYLE_DEFAULT",
|
||||
"animationActivationTargetId": "VIDEO_ID",
|
||||
"animationActivationEntityKey": "REDACTED",
|
||||
"lottieData": {
|
||||
"url": "https://www.gstatic.com/youtube/img/lottie/audio_indicator/audio_indicator_v2.json",
|
||||
"settings": {
|
||||
"loop": true,
|
||||
"autoplay": true
|
||||
}
|
||||
},
|
||||
"animatedText": "Now playing",
|
||||
"animationActivationEntitySelectorType": "THUMBNAIL_BADGE_ANIMATION_ENTITY_SELECTOR_TYPE_PLAYER_STATE"
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": "THUMBNAIL_OVERLAY_BADGE_POSITION_BOTTOM_END"
|
||||
}
|
||||
},
|
||||
{
|
||||
"thumbnailHoverOverlayToggleActionsViewModel": {
|
||||
"buttons": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"lockupMetadataViewModel": {
|
||||
"title": {
|
||||
"content": "VIDEO_TITLE"
|
||||
},
|
||||
"image": {
|
||||
"decoratedAvatarViewModel": {
|
||||
"avatar": {
|
||||
"avatarViewModel": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://yt3.ggpht.com/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"width": 68,
|
||||
"height": 68
|
||||
}
|
||||
]
|
||||
},
|
||||
"avatarImageSize": "AVATAR_SIZE_M"
|
||||
}
|
||||
},
|
||||
"a11yLabel": "Go to channel",
|
||||
"rendererContext": {
|
||||
"commandContext": {
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/@deep_flow_music_ua",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCD_on7-zu7Zuc3zissQvrgw",
|
||||
"canonicalBaseUrl": "/@deep_flow_music_ua"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"contentMetadataViewModel": {
|
||||
"metadataRows": [
|
||||
{
|
||||
"metadataParts": [
|
||||
{
|
||||
"text": {
|
||||
"content": "VIDEO_CHANNEL_NAME",
|
||||
"styleRuns": [],
|
||||
"attachmentRuns": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadataParts": [
|
||||
{
|
||||
"text": {
|
||||
"content": "56 waiting"
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
"content": "Premieres 14/08/2025, 13:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"delimiter": " • "
|
||||
}
|
||||
},
|
||||
"menuButton": {}
|
||||
}
|
||||
},
|
||||
"contentId": "VIDEO_ID",
|
||||
"contentType": "LOCKUP_CONTENT_TYPE_VIDEO"
|
||||
}
|
@@ -0,0 +1,586 @@
|
||||
{
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"thumbnail": {
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/M_8QNw_JM4I/hq720.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"width": 360,
|
||||
"height": 202
|
||||
},
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/M_8QNw_JM4I/hq720.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"width": 720,
|
||||
"height": 404
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "This video will premiere in 6 months."
|
||||
}
|
||||
],
|
||||
"accessibility": {
|
||||
"accessibilityData": {
|
||||
"label": "This video will premiere in 6 months. 33 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"descriptionSnippet": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Patience is key… MERCH SHOP : https://www.bluntbrosproductions.com Follow us on Instagram for early updates: ..."
|
||||
}
|
||||
]
|
||||
},
|
||||
"longBylineText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Blunt Brothers Productions",
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/@BluntBrothersProductions",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCUPrbbdnot-aPgNM65svgOg",
|
||||
"canonicalBaseUrl": "/@BluntBrothersProductions"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"lengthText": {
|
||||
"accessibility": {
|
||||
"accessibilityData": {
|
||||
"label": "33 seconds"
|
||||
}
|
||||
},
|
||||
"simpleText": "0:33"
|
||||
},
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/watch?v=M_8QNw_JM4I&pp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"webPageType": "WEB_PAGE_TYPE_WATCH",
|
||||
"rootVe": 3832
|
||||
}
|
||||
},
|
||||
"watchEndpoint": {
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"params": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"playerParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"watchEndpointSupportedOnesieConfig": {
|
||||
"html5PlaybackOnesieConfig": {
|
||||
"commonConfig": {
|
||||
"url": "https://rr4---sn-n0ogpnx-1gie.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&oreouc=1&id=33ff10370fc93382&ip=1.1.1.1&initcwndbps=3320000&mt=1759654189&oweuc="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ownerBadges": [
|
||||
{
|
||||
"metadataBadgeRenderer": {
|
||||
"icon": {
|
||||
"iconType": "CHECK_CIRCLE_THICK"
|
||||
},
|
||||
"style": "BADGE_STYLE_TYPE_VERIFIED",
|
||||
"tooltip": "Verified",
|
||||
"trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"accessibilityData": {
|
||||
"label": "Verified"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ownerText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Blunt Brothers Productions",
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/@BluntBrothersProductions",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCUPrbbdnot-aPgNM65svgOg",
|
||||
"canonicalBaseUrl": "/@BluntBrothersProductions"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"upcomingEventData": {
|
||||
"startTime": "1773580320",
|
||||
"isReminderSet": false,
|
||||
"upcomingEventText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Premieres "
|
||||
},
|
||||
{
|
||||
"text": "DATE_PLACEHOLDER"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"shortBylineText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Blunt Brothers Productions",
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/@BluntBrothersProductions",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCUPrbbdnot-aPgNM65svgOg",
|
||||
"canonicalBaseUrl": "/@BluntBrothersProductions"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"showActionMenu": false,
|
||||
"shortViewCountText": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "1"
|
||||
},
|
||||
{
|
||||
"text": " waiting"
|
||||
}
|
||||
],
|
||||
"accessibility": {
|
||||
"accessibilityData": {
|
||||
"label": "1 waiting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "M_8QNw_JM4I",
|
||||
"listType": "PLAYLIST_EDIT_LIST_TYPE_QUEUE",
|
||||
"onCreateListCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"sendPost": true,
|
||||
"apiUrl": "/youtubei/v1/playlist/create"
|
||||
}
|
||||
},
|
||||
"createPlaylistServiceEndpoint": {
|
||||
"videoIds": [
|
||||
"M_8QNw_JM4I"
|
||||
],
|
||||
"params": "CAQ%3D"
|
||||
}
|
||||
},
|
||||
"videoIds": [
|
||||
"M_8QNw_JM4I"
|
||||
],
|
||||
"videoCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/watch?v=M_8QNw_JM4I",
|
||||
"webPageType": "WEB_PAGE_TYPE_WATCH",
|
||||
"rootVe": 3832
|
||||
}
|
||||
},
|
||||
"watchEndpoint": {
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"watchEndpointSupportedOnesieConfig": {
|
||||
"html5PlaybackOnesieConfig": {
|
||||
"commonConfig": {
|
||||
"url": "https://rr4---sn-n0ogpnx-1gie.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&oreouc=1&id=33ff10370fc93382&ip=1.1.1.1&initcwndbps=3320000&mt=1759654189&oweuc="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"trackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"menuServiceItemDownloadRenderer": {
|
||||
"serviceEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"offlineVideoEndpoint": {
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"onAddCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"getDownloadActionCommand": {
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"params": "CAIQAA%3D%3D"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "CgtNXzhRTndfSk00SQ%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": "/@BluntBrothersProductions",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCUPrbbdnot-aPgNM65svgOg",
|
||||
"canonicalBaseUrl": "/@BluntBrothersProductions"
|
||||
}
|
||||
},
|
||||
"accessibility": {
|
||||
"accessibilityData": {
|
||||
"label": "Go to channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"thumbnailOverlays": [
|
||||
{
|
||||
"thumbnailOverlayTimeStatusRenderer": {
|
||||
"text": {
|
||||
"accessibility": {
|
||||
"accessibilityData": {
|
||||
"label": "Upcoming"
|
||||
}
|
||||
},
|
||||
"simpleText": "UPCOMING"
|
||||
},
|
||||
"style": "UPCOMING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "M_8QNw_JM4I",
|
||||
"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": "M_8QNw_JM4I"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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": "M_8QNw_JM4I",
|
||||
"listType": "PLAYLIST_EDIT_LIST_TYPE_QUEUE",
|
||||
"onCreateListCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"sendPost": true,
|
||||
"apiUrl": "/youtubei/v1/playlist/create"
|
||||
}
|
||||
},
|
||||
"createPlaylistServiceEndpoint": {
|
||||
"videoIds": [
|
||||
"M_8QNw_JM4I"
|
||||
],
|
||||
"params": "CAQ%3D"
|
||||
}
|
||||
},
|
||||
"videoIds": [
|
||||
"M_8QNw_JM4I"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inlinePlaybackEndpoint": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/watch?v=M_8QNw_JM4I&pp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"webPageType": "WEB_PAGE_TYPE_WATCH",
|
||||
"rootVe": 3832
|
||||
}
|
||||
},
|
||||
"watchEndpoint": {
|
||||
"videoId": "M_8QNw_JM4I",
|
||||
"params": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"playerParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"playerExtraUrlParams": [
|
||||
{
|
||||
"key": "inline",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"watchEndpointSupportedOnesieConfig": {
|
||||
"html5PlaybackOnesieConfig": {
|
||||
"commonConfig": {
|
||||
"url": "https://rr4---sn-n0ogpnx-1gie.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&oreouc=1&id=33ff10370fc93382&ip=1.1.1.1&initcwndbps=3320000&mt=1759654189&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",
|
||||
"rendererContext": {
|
||||
"commandContext": {
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/@BluntBrothersProductions",
|
||||
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
|
||||
"rootVe": 3611,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCUPrbbdnot-aPgNM65svgOg",
|
||||
"canonicalBaseUrl": "/@BluntBrothersProductions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user