Skip to content

Commit 119b912

Browse files
committed
[YouTube] Fix extraction of like count with the new data model
In the new data model currently A/B tested or deployed, the like count button data is the same, but its path has been changed. The extraction of the like count has been also improved, by using the multiple accessibility data available instead of the only one which was used before. This accessibility data has been also deprioritized, because it is language dependent when there is no view. Like count is always known, even for age-restricted videos, so returning -1 if the like count extraction fails on this type of videos is not needed and hides an extraction error: that's the reason why this return has been removed and the exception will always be thrown, even if a video is age-restricted.
1 parent 6a85836 commit 119b912

1 file changed

Lines changed: 73 additions & 16 deletions

File tree

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

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -417,28 +417,88 @@ public long getViewCount() throws ParsingException {
417417
@Override
418418
public long getLikeCount() throws ParsingException {
419419
assertPageFetched();
420+
421+
// If ratings are not allowed, there is no like count available
422+
if (!playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
423+
return -1;
424+
}
425+
420426
String likesString = "";
427+
421428
try {
422-
likesString = getVideoPrimaryInfoRenderer()
429+
final JsonArray topLevelButtons = getVideoPrimaryInfoRenderer()
423430
.getObject("videoActions")
424431
.getObject("menuRenderer")
425-
.getArray("topLevelButtons")
426-
.getObject(0)
427-
.getObject("toggleButtonRenderer")
428-
.getObject("defaultText")
429-
.getObject("accessibility")
432+
.getArray("topLevelButtons");
433+
434+
// Try first with the new video actions buttons data structure
435+
JsonObject likeToggleButtonRenderer = topLevelButtons.stream()
436+
.filter(JsonObject.class::isInstance)
437+
.map(JsonObject.class::cast)
438+
.map(button -> button.getObject("segmentedLikeDislikeButtonRenderer")
439+
.getObject("likeButton")
440+
.getObject("toggleButtonRenderer"))
441+
.filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer))
442+
.findFirst()
443+
.orElse(null);
444+
445+
// Use the old video actions buttons data structure if the new one isn't returned
446+
if (likeToggleButtonRenderer == null) {
447+
/*
448+
In the old video actions buttons data structure, there are 3 ways to detect whether
449+
a button is the like button, using its toggleButtonRenderer:
450+
- checking whether toggleButtonRenderer.targetId is equal to watch-like;
451+
- checking whether toggleButtonRenderer.defaultIcon.iconType is equal to LIKE;
452+
- checking whether
453+
toggleButtonRenderer.toggleButtonSupportedData.toggleButtonIdData.id
454+
is equal to TOGGLE_BUTTON_ID_TYPE_LIKE.
455+
*/
456+
likeToggleButtonRenderer = topLevelButtons.stream()
457+
.filter(JsonObject.class::isInstance)
458+
.map(JsonObject.class::cast)
459+
.map(topLevelButton -> topLevelButton.getObject("toggleButtonRenderer"))
460+
.filter(toggleButtonRenderer -> toggleButtonRenderer.getString("targetId")
461+
.equalsIgnoreCase("watch-like")
462+
|| toggleButtonRenderer.getObject("defaultIcon")
463+
.getString("iconType")
464+
.equalsIgnoreCase("LIKE")
465+
|| toggleButtonRenderer.getObject("toggleButtonSupportedData")
466+
.getObject("toggleButtonIdData")
467+
.getString("id")
468+
.equalsIgnoreCase("TOGGLE_BUTTON_ID_TYPE_LIKE"))
469+
.findFirst()
470+
.orElseThrow(() -> new ParsingException(
471+
"The like button is missing even though ratings are enabled"));
472+
}
473+
474+
// Use one of the accessibility strings available (this one has the same path as the
475+
// one used for comments' like count extraction)
476+
likesString = likeToggleButtonRenderer.getObject("accessibilityData")
430477
.getObject("accessibilityData")
431478
.getString("label");
432479

480+
// Use the other accessibility string available which contains the exact like count
433481
if (likesString == null) {
434-
// If this kicks in our button has no content and therefore ratings must be disabled
435-
if (playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
436-
throw new ParsingException(
437-
"Ratings are enabled even though the like button is missing");
438-
}
439-
return -1;
482+
likesString = likeToggleButtonRenderer.getObject("accessibility")
483+
.getString("label");
484+
}
485+
486+
// Last method: use the defaultText's accessibility data, which contains the exact like
487+
// count too, except when it is equal to 0, where a localized string is returned instead
488+
if (likesString == null) {
489+
likesString = likeToggleButtonRenderer.getObject("defaultText")
490+
.getObject("accessibility")
491+
.getObject("accessibilityData")
492+
.getString("label");
440493
}
441494

495+
// If ratings are allowed and the likes string is null, it means that we couldn't
496+
// extract the (real) like count from accessibility data
497+
if (likesString == null) {
498+
throw new ParsingException("Could not get like count from accessibility data");
499+
}
500+
501+
// This check only works with English localizations!
442502
if (likesString.toLowerCase().contains("no likes")) {
443503
return 0;
444504
}
@@ -448,10 +508,7 @@ public long getLikeCount() throws ParsingException {
448508
throw new ParsingException("Could not parse \"" + likesString + "\" as an Integer",
449509
nfe);
450510
} catch (final Exception e) {
451-
if (getAgeLimit() == NO_AGE_LIMIT) {
452-
throw new ParsingException("Could not get like count", e);
453-
}
454-
return -1;
511+
throw new ParsingException("Could not get like count", e);
455512
}
456513
}
457514

0 commit comments

Comments
 (0)