From 7673ea07b60fcc9bfa811da96ae6387682b608ca Mon Sep 17 00:00:00 2001
From: AudricV <74829229+AudricV@users.noreply.github.com>
Date: Fri, 8 Aug 2025 15:49:20 +0200
Subject: [PATCH 1/5] [YouTube] Add support for premieres in lockupViewModels
---
.../YoutubeStreamInfoItemLockupExtractor.java | 80 ++++++++++++++++---
1 file changed, 68 insertions(+), 12 deletions(-)
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 a05f7b96b..5866c5c51 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
@@ -17,6 +17,10 @@ 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.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -30,20 +34,24 @@ import javax.annotation.Nullable;
* The following features are currently not implemented because they have never been observed:
*
* - Shorts
- * - Premieres
* - Paid content (Premium, members first or only)
*
*/
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 cachedTextualUploadDate;
+ private Optional cachedDateText;
private ChannelImageViewModel cachedChannelImageViewModel;
private JsonArray cachedMetadataRows;
@@ -137,7 +145,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 +247,37 @@ 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 dateText = getDateText();
+
+ if (isPremiere()) {
+ final LocalDateTime premiereDate = getDateFromPremiere(dateText);
+ if (premiereDate == null) {
+ return null;
+ }
+ return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(premiereDate);
+ }
+
+ return dateText.orElse(null);
+ }
+
+ private LocalDateTime getDateFromPremiere(final Optional dateText) {
+ // This approach is language dependent
+ // Remove the premieres text from the upload date metadata part
+ final String trimmedTextUploadDate =
+ dateText.map(str -> str.replace(PREMIERES_TEXT, ""))
+ .orElse(null);
+ if (trimmedTextUploadDate == null) {
+ return null;
+ }
+
+ // As we request a UTC offset of 0 minutes, we get the UTC date
+ return LocalDateTime.parse(trimmedTextUploadDate, PREMIERES_DATE_FORMATTER);
}
@Nullable
@@ -265,11 +292,26 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
if (textualUploadDate == null) {
return null;
}
+
+ if (isPremiere()) {
+ final LocalDateTime premiereDate = getDateFromPremiere(getDateText());
+ if (premiereDate == null) {
+ throw new ParsingException("Could not get upload date from premiere");
+ }
+
+ return new DateWrapper(OffsetDateTime.of(premiereDate, ZoneOffset.UTC));
+ }
+
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 optTextContent = metadataPart(1, 0)
.map(this::getTextContentFromMetadataPart);
// We could do this inline if the ParsingException would be a RuntimeException -.-
@@ -357,6 +399,20 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
return getStreamType() != StreamType.VIDEO_STREAM;
}
+ private Optional 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;
From e50f4997979b6a8daf8a0fffc7042b1de4a24aa9 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Sun, 5 Oct 2025 13:44:23 +0200
Subject: [PATCH 2/5] [YouTube] Return orignial text date in lockupViewModels
premieres
---
.../YoutubeStreamInfoItemLockupExtractor.java | 24 +++++++------------
1 file changed, 8 insertions(+), 16 deletions(-)
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 5866c5c51..88a027d4b 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
@@ -256,28 +256,18 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
final Optional dateText = getDateText();
if (isPremiere()) {
- final LocalDateTime premiereDate = getDateFromPremiere(dateText);
- if (premiereDate == null) {
- return null;
- }
- return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(premiereDate);
+ return getDateFromPremiere(dateText);
}
return dateText.orElse(null);
}
- private LocalDateTime getDateFromPremiere(final Optional dateText) {
+ @Nullable
+ private String getDateFromPremiere(final Optional dateText) {
// This approach is language dependent
// Remove the premieres text from the upload date metadata part
- final String trimmedTextUploadDate =
- dateText.map(str -> str.replace(PREMIERES_TEXT, ""))
+ return dateText.map(str -> str.replace(PREMIERES_TEXT, ""))
.orElse(null);
- if (trimmedTextUploadDate == null) {
- return null;
- }
-
- // As we request a UTC offset of 0 minutes, we get the UTC date
- return LocalDateTime.parse(trimmedTextUploadDate, PREMIERES_DATE_FORMATTER);
}
@Nullable
@@ -294,12 +284,14 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
}
if (isPremiere()) {
- final LocalDateTime premiereDate = getDateFromPremiere(getDateText());
+ final String premiereDate = getDateFromPremiere(getDateText());
if (premiereDate == null) {
throw new ParsingException("Could not get upload date from premiere");
}
- return new DateWrapper(OffsetDateTime.of(premiereDate, ZoneOffset.UTC));
+ // As we request a UTC offset of 0 minutes, we get the UTC data
+ return new DateWrapper(OffsetDateTime.of(
+ LocalDateTime.parse(premiereDate, PREMIERES_DATE_FORMATTER), ZoneOffset.UTC));
}
return timeAgoParser.parse(textualUploadDate);
From 3c404e575af66de3b3a95248994ba44ef833ac7a Mon Sep 17 00:00:00 2001
From: Stypox
Date: Sun, 5 Oct 2025 13:45:38 +0200
Subject: [PATCH 3/5] [YouTube] Add custom mock tests for Premiere
StreamInfoItems
The JSON data for these tests was obtained manually.
---
.../newpipe/downloader/DownloaderFactory.java | 30 +-
.../youtube/YoutubeStreamInfoItemTest.java | 83 +++
.../lockupviewmodelpremiere.json | 133 ++++
.../videorendererpremiere.json | 586 ++++++++++++++++++
4 files changed, 824 insertions(+), 8 deletions(-)
create mode 100644 extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java
create mode 100644 extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/lockupviewmodelpremiere.json
create mode 100644 extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
diff --git a/extractor/src/test/java/org/schabi/newpipe/downloader/DownloaderFactory.java b/extractor/src/test/java/org/schabi/newpipe/downloader/DownloaderFactory.java
index eb9ee7cb4..07545b52b 100644
--- a/extractor/src/test/java/org/schabi/newpipe/downloader/DownloaderFactory.java
+++ b/extractor/src/test/java/org/schabi/newpipe/downloader/DownloaderFactory.java
@@ -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('.', '/');
}
/**
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
new file mode 100644
index 000000000..0782bbd56
--- /dev/null
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemTest.java
@@ -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())
+ );
+ }
+}
diff --git a/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/lockupviewmodelpremiere.json b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/lockupviewmodelpremiere.json
new file mode 100644
index 000000000..836f704ff
--- /dev/null
+++ b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/lockupviewmodelpremiere.json
@@ -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"
+}
diff --git a/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
new file mode 100644
index 000000000..9a7d1b862
--- /dev/null
+++ b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
@@ -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"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
From 393e811470c29e1c76bc391a4457c73694559f6e Mon Sep 17 00:00:00 2001
From: Stypox
Date: Sun, 5 Oct 2025 13:53:10 +0200
Subject: [PATCH 4/5] Wrap LocalDateTime.parse in try-catch
---
.../YoutubeStreamInfoItemLockupExtractor.java | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
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 88a027d4b..4ee2ae08e 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
@@ -21,6 +21,7 @@ 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;
@@ -289,9 +290,13 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
throw new ParsingException("Could not get upload date from premiere");
}
- // As we request a UTC offset of 0 minutes, we get the UTC data
- return new DateWrapper(OffsetDateTime.of(
- LocalDateTime.parse(premiereDate, PREMIERES_DATE_FORMATTER), ZoneOffset.UTC));
+ try {
+ // As we request a UTC offset of 0 minutes, we get the UTC data
+ 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);
From 97957250aa632a637b1a8c2ce023af6bc6b52a57 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Sun, 5 Oct 2025 14:13:46 +0200
Subject: [PATCH 5/5] Address review comments
---
.../extractors/YoutubeStreamInfoItemLockupExtractor.java | 2 +-
.../youtube/youtubestreaminfoitem/videorendererpremiere.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
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 4ee2ae08e..f685f33de 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
@@ -291,7 +291,7 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
}
try {
- // As we request a UTC offset of 0 minutes, we get the UTC data
+ // 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) {
diff --git a/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
index 9a7d1b862..35e85055d 100644
--- a/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
+++ b/extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubestreaminfoitem/videorendererpremiere.json
@@ -583,4 +583,4 @@
}
}
}
-}
\ No newline at end of file
+}