Skip to content

Commit 7673ea0

Browse files
committed
[YouTube] Add support for premieres in lockupViewModels
1 parent 6f51a23 commit 7673ea0

1 file changed

Lines changed: 68 additions & 12 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
import org.schabi.newpipe.extractor.utils.JsonUtils;
1818
import org.schabi.newpipe.extractor.utils.Utils;
1919

20+
import java.time.LocalDateTime;
21+
import java.time.OffsetDateTime;
22+
import java.time.ZoneOffset;
23+
import java.time.format.DateTimeFormatter;
2024
import java.util.List;
2125
import java.util.Optional;
2226
import java.util.stream.Collectors;
@@ -30,20 +34,24 @@
3034
* The following features are currently not implemented because they have never been observed:
3135
* <ul>
3236
* <li>Shorts</li>
33-
* <li>Premieres</li>
3437
* <li>Paid content (Premium, members first or only)</li>
3538
* </ul>
3639
*/
3740
public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtractor {
3841

3942
private static final String NO_VIEWS_LOWERCASE = "no views";
43+
// This approach is language dependant (en-GB)
44+
// Leading end space is voluntary included
45+
private static final String PREMIERES_TEXT = "Premieres ";
46+
private static final DateTimeFormatter PREMIERES_DATE_FORMATTER =
47+
DateTimeFormatter.ofPattern("dd/MM/yyyy, HH:mm");
4048

4149
private final JsonObject lockupViewModel;
4250
private final TimeAgoParser timeAgoParser;
4351

4452
private StreamType cachedStreamType;
4553
private String cachedName;
46-
private Optional<String> cachedTextualUploadDate;
54+
private Optional<String> cachedDateText;
4755

4856
private ChannelImageViewModel cachedChannelImageViewModel;
4957
private JsonArray cachedMetadataRows;
@@ -137,7 +145,9 @@ public String getName() throws ParsingException {
137145
@Override
138146
public long getDuration() throws ParsingException {
139147
// Duration cannot be extracted for live streams, but only for normal videos
140-
if (isLive()) {
148+
// Exact duration cannot be extracted for premieres, an approximation is only available in
149+
// accessibility context label
150+
if (isLive() || isPremiere()) {
141151
return -1;
142152
}
143153

@@ -237,20 +247,37 @@ public boolean isUploaderVerified() throws ParsingException {
237247
@Nullable
238248
@Override
239249
public String getTextualUploadDate() throws ParsingException {
240-
if (cachedTextualUploadDate != null) {
241-
return cachedTextualUploadDate.orElse(null);
242-
}
243-
244250
// Live streams have no upload date
245251
if (isLive()) {
246-
cachedTextualUploadDate = Optional.empty();
247252
return null;
248253
}
249254

250-
// This might be null e.g. for live streams
251-
this.cachedTextualUploadDate = metadataPart(1, 1)
252-
.map(this::getTextContentFromMetadataPart);
253-
return cachedTextualUploadDate.orElse(null);
255+
// Date string might be null e.g. for live streams
256+
final Optional<String> dateText = getDateText();
257+
258+
if (isPremiere()) {
259+
final LocalDateTime premiereDate = getDateFromPremiere(dateText);
260+
if (premiereDate == null) {
261+
return null;
262+
}
263+
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(premiereDate);
264+
}
265+
266+
return dateText.orElse(null);
267+
}
268+
269+
private LocalDateTime getDateFromPremiere(final Optional<String> dateText) {
270+
// This approach is language dependent
271+
// Remove the premieres text from the upload date metadata part
272+
final String trimmedTextUploadDate =
273+
dateText.map(str -> str.replace(PREMIERES_TEXT, ""))
274+
.orElse(null);
275+
if (trimmedTextUploadDate == null) {
276+
return null;
277+
}
278+
279+
// As we request a UTC offset of 0 minutes, we get the UTC date
280+
return LocalDateTime.parse(trimmedTextUploadDate, PREMIERES_DATE_FORMATTER);
254281
}
255282

256283
@Nullable
@@ -265,11 +292,26 @@ public DateWrapper getUploadDate() throws ParsingException {
265292
if (textualUploadDate == null) {
266293
return null;
267294
}
295+
296+
if (isPremiere()) {
297+
final LocalDateTime premiereDate = getDateFromPremiere(getDateText());
298+
if (premiereDate == null) {
299+
throw new ParsingException("Could not get upload date from premiere");
300+
}
301+
302+
return new DateWrapper(OffsetDateTime.of(premiereDate, ZoneOffset.UTC));
303+
}
304+
268305
return timeAgoParser.parse(textualUploadDate);
269306
}
270307

271308
@Override
272309
public long getViewCount() throws ParsingException {
310+
if (isPremiere()) {
311+
// The number of people returned for premieres is the one currently waiting
312+
return -1;
313+
}
314+
273315
final Optional<String> optTextContent = metadataPart(1, 0)
274316
.map(this::getTextContentFromMetadataPart);
275317
// We could do this inline if the ParsingException would be a RuntimeException -.-
@@ -357,6 +399,20 @@ private boolean isLive() throws ParsingException {
357399
return getStreamType() != StreamType.VIDEO_STREAM;
358400
}
359401

402+
private Optional<String> getDateText() throws ParsingException {
403+
if (cachedDateText == null) {
404+
cachedDateText = metadataPart(1, 1)
405+
.map(this::getTextContentFromMetadataPart);
406+
}
407+
return cachedDateText;
408+
}
409+
410+
private boolean isPremiere() throws ParsingException {
411+
return getDateText().map(dateText -> dateText.contains(PREMIERES_TEXT))
412+
// If we can't get date text, assume it is not a premiere, it should be a livestream
413+
.orElse(false);
414+
}
415+
360416
abstract static class ChannelImageViewModel {
361417
protected JsonObject viewModel;
362418

0 commit comments

Comments
 (0)