fix: read video statistics across lockup metadata rows

This commit is contained in:
privacyguy123 2026-09-19 19:26:24 +01:00
parent c88230067b
commit 6aeb1476b1
2 changed files with 67 additions and 4 deletions

View File

@ -0,0 +1,62 @@
require "../parsers_helper.cr"
private def lockup_video_with_metadata(metadata : String)
JSON.parse(<<-JSON)
{
"lockupViewModel": {
"contentType": "LOCKUP_CONTENT_TYPE_VIDEO",
"contentId": "abcdefghijk",
"contentImage": {
"thumbnailViewModel": {
"image": {"sources": [{"url": "https://i.ytimg.com/vi/abcdefghijk/hqdefault.jpg"}]},
"overlays": [{"thumbnailBottomOverlayViewModel": {
"badges": [{"thumbnailBadgeViewModel": {"text": "12:34"}}]
}}]
}
},
"metadata": {
"lockupMetadataViewModel": {
"title": {"content": "A collaboration"},
"metadata": {"contentMetadataViewModel": #{metadata}}
}
}
}
}
JSON
end
Spectator.describe "lockup video metadata rows" do
sample({
"normal first row" => %({"metadataRows":[{"metadataParts":[{"text":{"content":"1.5M views"}},{"text":{"content":"3 days ago"}}]}]}),
"collaborators before video statistics" => %({"metadataRows":[{"metadataParts":[{"text":{"content":"Channel A and Channel B"}}]},{"metadataParts":[{"text":{"content":"1.5M views"}},{"text":{"content":"3 days ago"}}]}]}),
"collaborator names containing statistic substrings" => %({"metadataRows":[{"metadataParts":[{"text":{"content":"Chicago Reviews"}},{"text":{"content":"Tech reviews"}}]},{"metadataParts":[{"text":{"content":"1.5M views"}},{"text":{"content":"3 days ago"}}]}]}),
"views and publication date in separate rows" => %({"metadataRows":[{"metadataParts":[{"text":{"content":"1.5M views"}}]},{"metadataParts":[{"text":{"content":"3 days ago"}}]}]}),
"missing and null metadata parts before statistics" => %({"metadataRows":[{},{"metadataParts":null},{"metadataParts":[{"text":{"content":"1.5M views"}},{"text":{"content":"3 days ago"}}]}]}),
"icon labels before video statistics" => %({"metadataRows":[{"metadataParts":[{"icon":{},"text":{"content":"99 views"}},{"icon":{},"text":{"content":"9 days ago"}}]},{"metadataParts":[{"text":{"content":"1.5M views"}},{"text":{"content":"3 days ago"}}]}]}),
}) do |entry|
it "parses the video statistics and preserves other metadata" do
before = Time.utc - 3.days
video = parse_item(lockup_video_with_metadata(entry[1]), "Channel A", "UCexample").as(SearchVideo)
after = Time.utc - 3.days
expect(video.views).to eq(1_500_000)
expect(video.published).to be_between(before, after)
expect(video.title).to eq("A collaboration")
expect(video.id).to eq("abcdefghijk")
expect(video.author).to eq("Channel A")
expect(video.ucid).to eq("UCexample")
expect(video.length_seconds).to eq(754)
end
end
sample({"{}", %({"metadataRows":[]}), %({"metadataRows":null}), %({"metadataRows":[{}, {"metadataParts":null}]})}) do |metadata|
it "keeps unknown statistics defaults" do
before = Time.utc
video = parse_item(lockup_video_with_metadata(metadata)).as(SearchVideo)
after = Time.utc
expect(video.views).to eq(0)
expect(video.published).to be_between(before, after)
end
end
end

View File

@ -661,12 +661,13 @@ private module Parsers
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
title = metadata.dig("title", "content").as_s
# Contains the views of the video and the published time of the video.
metadata_parts = metadata.dig("metadata", "contentMetadataViewModel", "metadataRows", 0, "metadataParts").try &.as_a
# Collaboration videos can put authors before the views and published time.
metadata_rows = metadata.dig?("metadata", "contentMetadataViewModel", "metadataRows").try &.as_a?
metadata_parts = metadata_rows.try &.compact_map(&.dig?("metadataParts").try &.as_a?).flatten
view_count_text = metadata_parts.try &.find { |item| item["icon"]?.nil? && item.dig?("text", "content").try &.as_s.includes?("views") }
view_count_text = metadata_parts.try &.find { |item| item["icon"]?.nil? && item.dig?("text", "content").try &.as_s.matches?(/\A(?:\d+(?:[.,]\d+)*\s?[kKmMbB]?|No) views\z/) }
.try &.dig("text", "content").as_s
published = metadata_parts.try &.find { |item| item["icon"]?.nil? && item.dig?("text", "content").try &.as_s.includes?("ago") }
published = metadata_parts.try &.find { |item| item["icon"]?.nil? && item.dig?("text", "content").try &.as_s.matches?(/(?:\A|\s)\d+ ?[smhdwy]\w* ago\z/) }
.try { |item| decode_date(item.dig("text", "content").as_s) } || Time.local
view_count = short_text_to_number(view_count_text || "0")