22
33import static org .schabi .newpipe .extractor .utils .Utils .isNullOrEmpty ;
44
5+ import com .grack .nanojson .JsonArray ;
56import com .grack .nanojson .JsonObject ;
67
78import org .schabi .newpipe .extractor .Image ;
2324import javax .annotation .Nonnull ;
2425import javax .annotation .Nullable ;
2526
27+ /**
28+ * Note:
29+ * This extractor is currently (2025-07) only used to extract related video streams.<br/>
30+ * The following features are currently not implemented because they have never been observed:
31+ * <ul>
32+ * <li>Shorts</li>
33+ * <li>Premiers</li>
34+ * <li>Premium content</li>
35+ * </ul>
36+ */
2637public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtractor {
2738
2839 private static final String NO_VIEWS_LOWERCASE = "no views" ;
2940
3041 private final JsonObject lockupViewModel ;
3142 private final TimeAgoParser timeAgoParser ;
3243
44+ private String cachedName ;
45+ private String cachedTextualUploadDate ;
46+
47+ private JsonArray cachedMetadataRows ;
48+
3349 /**
3450 * Creates an extractor of StreamInfoItems from a YouTube page.
3551 *
@@ -75,9 +91,6 @@ public StreamType getStreamType() throws ParsingException {
7591
7692 @ Override
7793 public boolean isAd () throws ParsingException {
78- if (isPremium ()) {
79- return true ;
80- }
8194 final String name = getName (); // only get it once
8295 return "[Private video]" .equals (name )
8396 || "[Deleted video]" .equals (name );
@@ -99,9 +112,14 @@ public String getUrl() throws ParsingException {
99112
100113 @ Override
101114 public String getName () throws ParsingException {
115+ if (cachedName != null ) {
116+ return cachedName ;
117+ }
118+
102119 final String name = JsonUtils .getString (lockupViewModel ,
103120 "metadata.lockupMetadataViewModel.title.content" );
104121 if (!isNullOrEmpty (name )) {
122+ this .cachedName = name ;
105123 return name ;
106124 }
107125 throw new ParsingException ("Could not get name" );
@@ -179,9 +197,14 @@ public boolean isUploaderVerified() throws ParsingException {
179197 @ Nullable
180198 @ Override
181199 public String getTextualUploadDate () throws ParsingException {
182- return metadataPart (1 , 1 )
200+ if (cachedTextualUploadDate != null ) {
201+ return cachedTextualUploadDate ;
202+ }
203+
204+ this .cachedTextualUploadDate = metadataPart (1 , 1 )
183205 .map (this ::getTextContentFromMetadataPart )
184206 .orElse (null );
207+ return cachedTextualUploadDate ;
185208 }
186209
187210 @ Nullable
@@ -196,11 +219,6 @@ public DateWrapper getUploadDate() throws ParsingException {
196219
197220 @ Override
198221 public long getViewCount () throws ParsingException {
199- if (isPremium () || isPremiere ()) {
200- return -1 ;
201- }
202-
203- // TODO Check if this is the same for shorts
204222 final Optional <String > optTextContent = metadataPart (1 , 0 )
205223 .map (this ::getTextContentFromMetadataPart );
206224 // We could do this inline if the ParsingException would be a RuntimeException -.-
@@ -230,21 +248,14 @@ public List<Image> getThumbnails() throws ParsingException {
230248 "contentImage.thumbnailViewModel.image.sources" ));
231249 }
232250
233- private boolean isPremium () {
234- // TODO Detect with samples
235- return false ;
236- }
237-
238- private boolean isPremiere () {
239- // TODO Detect with samples
240- return false ;
241- }
242-
243251 private Optional <JsonObject > metadataPart (final int rowIndex , final int partIndex )
244252 throws ParsingException {
245- return JsonUtils .getArray (lockupViewModel ,
253+ if (cachedMetadataRows == null ) {
254+ cachedMetadataRows = JsonUtils .getArray (lockupViewModel ,
246255 "metadata.lockupMetadataViewModel.metadata"
247- + ".contentMetadataViewModel.metadataRows" )
256+ + ".contentMetadataViewModel.metadataRows" );
257+ }
258+ return cachedMetadataRows
248259 .streamAsJsonObjects ()
249260 .skip (rowIndex )
250261 .limit (1 )
@@ -258,17 +269,4 @@ private Optional<JsonObject> metadataPart(final int rowIndex, final int partInde
258269 private String getTextContentFromMetadataPart (final JsonObject metadataPart ) {
259270 return metadataPart .getObject ("text" ).getString ("content" );
260271 }
261-
262- @ Nullable
263- @ Override
264- public String getShortDescription () {
265- // Not present
266- return null ;
267- }
268-
269- @ Override
270- public boolean isShortFormContent () throws ParsingException {
271- // TODO Detect with samples
272- return false ;
273- }
274272}
0 commit comments