From 58f4a44d92bc6a9972f38cc06b4fc9b2cac83375 Mon Sep 17 00:00:00 2001 From: Kang-Kyu Lee Date: Mon, 25 Nov 2019 11:52:28 -0800 Subject: [PATCH 1/2] Remove all except cards and endscreens YouTube removed annotations (except cards and endscreens) on January 15, 2019. Remove unnecessary code --- lib/yt/annotations/for.rb | 29 +---- lib/yt/annotations/label.rb | 10 -- lib/yt/annotations/note.rb | 44 ------- lib/yt/annotations/pause.rb | 14 -- lib/yt/annotations/promotion.rb | 30 ----- lib/yt/annotations/speech.rb | 14 -- lib/yt/annotations/spotlight.rb | 10 -- lib/yt/annotations/title.rb | 14 -- spec/yt/annotations_spec.rb | 218 +++++++------------------------- 9 files changed, 48 insertions(+), 335 deletions(-) delete mode 100644 lib/yt/annotations/label.rb delete mode 100644 lib/yt/annotations/note.rb delete mode 100644 lib/yt/annotations/pause.rb delete mode 100644 lib/yt/annotations/promotion.rb delete mode 100644 lib/yt/annotations/speech.rb delete mode 100644 lib/yt/annotations/spotlight.rb delete mode 100644 lib/yt/annotations/title.rb diff --git a/lib/yt/annotations/for.rb b/lib/yt/annotations/for.rb index 553d6c1..5e37100 100644 --- a/lib/yt/annotations/for.rb +++ b/lib/yt/annotations/for.rb @@ -1,13 +1,6 @@ require 'yt/annotations/branding' require 'yt/annotations/card' require 'yt/annotations/end_screen' -require 'yt/annotations/label' -require 'yt/annotations/note' -require 'yt/annotations/speech' -require 'yt/annotations/spotlight' -require 'yt/annotations/title' -require 'yt/annotations/pause' -require 'yt/annotations/promotion' # An object-oriented Ruby client for YouTube. # @see http://www.rubydoc.info/gems/yt/ @@ -44,7 +37,6 @@ def fetch(path) def xml_to_annotations(xml) annotations = xml['document']['annotations'] annotations = Array.wrap (annotations || {})['annotation'] - annotations = merge_highlights annotations annotations = exclude_drawers annotations annotations.map{|data| annotation_class(data).new data} end @@ -56,16 +48,10 @@ def json_to_annotations(json) def annotation_class(data) case data['style'] - when 'anchored', 'speech' then Annotations::Speech when 'branding' then Annotations::Branding - when 'highlightText' then Annotations::Spotlight - when 'label' then Annotations::Label - when 'popup' then Annotations::Note - when 'title' then Annotations::Title - else case data['type'] + else + case data['type'] when 'card' then Annotations::Card - when 'pause' then Annotations::Pause - when 'promotion' then Annotations::Promotion end end end @@ -73,17 +59,6 @@ def annotation_class(data) def exclude_drawers(annotations) annotations.reject{|a| a['type'] == 'drawer'} end - - def merge_highlights(annotations) - highlights, others = annotations.partition{|a| a['type'] == 'highlight'} - highlights.each do |highlight| - match = others.find do |a| - (a['segment'] || {})['spaceRelative'] == highlight['id'] - end - match.merge! highlight if match - end - others - end end end end diff --git a/lib/yt/annotations/label.rb b/lib/yt/annotations/label.rb deleted file mode 100644 index fc988c6..0000000 --- a/lib/yt/annotations/label.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'yt/annotations/note' - -module Yt - module Annotations - # A Label annotation is just a Note annotation with text, start/end time - # and an optional link, but shaped like a rectangle without borders. - class Label < Note - end - end -end diff --git a/lib/yt/annotations/note.rb b/lib/yt/annotations/note.rb deleted file mode 100644 index 49d7ca2..0000000 --- a/lib/yt/annotations/note.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'yt/annotations/base' - -module Yt - module Annotations - # A Note annotation is just a basic annotation with text, start/end time - # and an optional link. It's shaped like a Post-It with a curved corner. - class Note < Base - # @param [Hash] data the Hash representation of the XML data returned by - # YouTube for each annotation of a video. - def initialize(data = {}) - @text = data['TEXT'] - @starts_at = to_seconds regions_of(data)[0]['t'] - @ends_at = to_seconds regions_of(data)[1]['t'] - @link = to_link data.fetch('action', {})['url'] - end - - private - - def regions_of(data) - data['segment']['movingRegion']['rectRegion'] - end - - def to_seconds(timestamp) - timestamp.split(':').map(&:to_f).inject(0) {|sum, n| 60 * sum + n} - end - - def to_link(url) - return unless url - new_window = url['target'] != 'current' - type = case url['link_class'] - when '1' then :video - when '2' then :playlist - when '3' then :channel - when '4' then :profile - when '5' then :subscribe - when '6' then :website - when '8' then :crowdfunding - end - {url: url['value'], new_window: new_window, type: type} - end - end - end -end - diff --git a/lib/yt/annotations/pause.rb b/lib/yt/annotations/pause.rb deleted file mode 100644 index 6efb502..0000000 --- a/lib/yt/annotations/pause.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'yt/annotations/base' - -module Yt - module Annotations - # A Pause annotation is like a Note annotation with start/end time, - # but cannot have a link or text. - class Pause < Note - private - def to_link(url) - nil - end - end - end -end diff --git a/lib/yt/annotations/promotion.rb b/lib/yt/annotations/promotion.rb deleted file mode 100644 index 0fb529d..0000000 --- a/lib/yt/annotations/promotion.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'yt/annotations/base' - -module Yt - module Annotations - # A Promotion annotation. The CTA overlay is used more for paid campaigns - # because the video has to be attached to an adsense account to be used. - class Promotion < Base - # @param [Hash] data the Hash representation of the XML data returned by - # YouTube for each annotation of a video. - def initialize(data = {}) - json = JSON.parse data['data'] - @text = text_in json - @starts_at = json['start_ms'].to_i / 1000.0 - @ends_at = json['end_ms'].to_i / 1000.0 - @link = to_link data.fetch('action', {})['url'], data - end - - private - - def text_in(json) - json.values_at('text_line_1', 'text_line_2').join(': ') - end - - def to_link(url, data) - new_window = url['target'] == 'new' - {url: url['value'], new_window: new_window, type: :website} - end - end - end -end diff --git a/lib/yt/annotations/speech.rb b/lib/yt/annotations/speech.rb deleted file mode 100644 index c16785a..0000000 --- a/lib/yt/annotations/speech.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'yt/annotations/note' - -module Yt - module Annotations - # A Speech annotation is like a NOT annotation with text, start/end time - # and an optional link, but it's shaped like a speech bubble. - class Speech < Note - private - def regions_of(data) - data['segment']['movingRegion']['anchoredRegion'] - end - end - end -end diff --git a/lib/yt/annotations/spotlight.rb b/lib/yt/annotations/spotlight.rb deleted file mode 100644 index 4835591..0000000 --- a/lib/yt/annotations/spotlight.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'yt/annotations/note' - -module Yt - module Annotations - # A Spotlight annotation is like a Note annotation with text and start/end - # time, but the text is below the click area. - class Spotlight < Note - end - end -end diff --git a/lib/yt/annotations/title.rb b/lib/yt/annotations/title.rb deleted file mode 100644 index 568a926..0000000 --- a/lib/yt/annotations/title.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'yt/annotations/base' - -module Yt - module Annotations - # A Title annotation is like a Note annotation with text, start/end time, - # but cannot have a link. It's simply overlay text. - class Title < Note - private - def to_link(url) - nil - end - end - end -end diff --git a/spec/yt/annotations_spec.rb b/spec/yt/annotations_spec.rb index 608a33a..209f2d2 100644 --- a/spec/yt/annotations_spec.rb +++ b/spec/yt/annotations_spec.rb @@ -7,145 +7,61 @@ let(:video_id) { 'MsZtGuBU3vA' } it 'returns all the annotations and cards' do - expect(annotations.size).to be 17 + expect(annotations.size).to be 6 - expect(annotations[0]).to be_a Yt::Annotations::Speech - expect(annotations[0].starts_at).to be 0.0 - expect(annotations[0].ends_at).to be 1.0 - expect(annotations[0].text).to eq 'n0' - expect(annotations[0].link).to be_nil + expect(annotations[0]).to be_a Yt::Annotations::Card + expect(annotations[0].starts_at).to be 1.115 + expect(annotations[0].ends_at).to be 8.115 + expect(annotations[0].text).to include "EXCLUSIVE: 'Finding Dory' Trailer" + expect(annotations[0].link).to be_a Hash + expect(annotations[0].link[:url]).to include 'v=3JNLwlcPBPI' + expect(annotations[0].link[:type]).to be :video + expect(annotations[0].link[:new_window]).to be true - expect(annotations[1]).to be_a Yt::Annotations::Speech - expect(annotations[1].starts_at).to be 1.0 - expect(annotations[1].ends_at).to be 2.0 - expect(annotations[1].text).to eq 'n1' + expect(annotations[1]).to be_a Yt::Annotations::Card + expect(annotations[1].starts_at).to be 8.038 + expect(annotations[1].ends_at).to be 15.038 + expect(annotations[1].text).to include 'Adorable Kids' expect(annotations[1].link).to be_a Hash - expect(annotations[1].link[:url]).to include 'v=3JNLwlcPBPI' - expect(annotations[1].link[:type]).to be :video - expect(annotations[1].link[:new_window]).to be false + expect(annotations[1].link[:url]).to include 'list=PLuW4g7xujBWfU26JUTW1DGs3hk4LD5KaL' + expect(annotations[1].link[:type]).to be :playlist + expect(annotations[1].link[:new_window]).to be true expect(annotations[2]).to be_a Yt::Annotations::Card - expect(annotations[2].starts_at).to be 1.115 - expect(annotations[2].ends_at).to be 8.115 - expect(annotations[2].text).to include "EXCLUSIVE: 'Finding Dory' Trailer" + expect(annotations[2].starts_at).to be 15.038 + expect(annotations[2].ends_at).to be 22.038 + expect(annotations[2].text).to eq 'Card with link to channel' expect(annotations[2].link).to be_a Hash - expect(annotations[2].link[:url]).to include 'v=3JNLwlcPBPI' - expect(annotations[2].link[:type]).to be :video + expect(annotations[2].link[:url]).to include 'youtube.com/user/TheEllenShow' + expect(annotations[2].link[:type]).to be :channel expect(annotations[2].link[:new_window]).to be true - expect(annotations[3]).to be_a Yt::Annotations::Note - expect(annotations[3].starts_at).to be 2.0 - expect(annotations[3].ends_at).to be 3.0 - expect(annotations[3].text).to eq 'n2' - expect(annotations[3].link).to be_nil - - expect(annotations[4]).to be_a Yt::Annotations::Note - expect(annotations[4].starts_at).to be 3.0 - expect(annotations[4].ends_at).to be 4.0 - expect(annotations[4].text).to eq 'n3' + expect(annotations[3]).to be_a Yt::Annotations::Card + expect(annotations[3].starts_at).to be 22.192 + expect(annotations[3].ends_at).to be 29.192 + expect(annotations[3].text).to eq 'Cards link to Merch site' + expect(annotations[3].link).to be_a Hash + expect(annotations[3].link[:url]).to include 'bandcamp.com' + expect(annotations[3].link[:type]).to be :website + expect(annotations[3].link[:new_window]).to be true + + expect(annotations[4]).to be_a Yt::Annotations::Branding + expect(annotations[4].starts_at).to be 30.0 + expect(annotations[4].ends_at).to be 37.0 + expect(annotations[4].text).to be_empty expect(annotations[4].link).to be_a Hash - expect(annotations[4].link[:url]).to include 'list=PLuW4g7xujBWfU26JUTW1DGs3hk4LD5KaL' - expect(annotations[4].link[:type]).to be :playlist + expect(annotations[4].link[:url]).to include 'youtube.com/channel/UCwCnUcLcb9-eSrHa_RQGkQQ' + expect(annotations[4].link[:type]).to be :channel expect(annotations[4].link[:new_window]).to be true - expect(annotations[5]).to be_a Yt::Annotations::Title - expect(annotations[5].starts_at).to be 4.0 - expect(annotations[5].ends_at).to be 5.0 - expect(annotations[5].text).to eq 'n4' - expect(annotations[5].link).to be_nil - - expect(annotations[6]).to be_a Yt::Annotations::Spotlight - expect(annotations[6].starts_at).to be 5.0 - expect(annotations[6].ends_at).to be 6.0 - expect(annotations[6].text).to eq 'n5' - expect(annotations[6].link).to be_nil - - expect(annotations[7]).to be_a Yt::Annotations::Spotlight - expect(annotations[7].starts_at).to be 6.0 - expect(annotations[7].ends_at).to be 7.0 - expect(annotations[7].text).to eq 'n6' - expect(annotations[7].link).to be_a Hash - expect(annotations[7].link[:url]).to include 'youtube.com/user/TheEllenShow' - expect(annotations[7].link[:type]).to be :channel - expect(annotations[7].link[:new_window]).to be false - - expect(annotations[8]).to be_a Yt::Annotations::Label - expect(annotations[8].starts_at).to be 7.0 - expect(annotations[8].ends_at).to be 8.0 - expect(annotations[8].text).to eq 'n7' - expect(annotations[8].link).to be_nil - - expect(annotations[9]).to be_a Yt::Annotations::Label - expect(annotations[9].starts_at).to be 8.0 - expect(annotations[9].ends_at).to be 9.0 - expect(annotations[9].text).to eq 'n8' - expect(annotations[9].link).to be_a Hash - expect(annotations[9].link[:url]).to include 'https://plus.google.com/u/0/103024385626973477082' - expect(annotations[9].link[:type]).to be :profile - expect(annotations[9].link[:new_window]).to be true - - expect(annotations[10]).to be_a Yt::Annotations::Card - expect(annotations[10].starts_at).to be 8.038 - expect(annotations[10].ends_at).to be 15.038 - expect(annotations[10].text).to include 'Adorable Kids' - expect(annotations[10].link).to be_a Hash - expect(annotations[10].link[:url]).to include 'list=PLuW4g7xujBWfU26JUTW1DGs3hk4LD5KaL' - expect(annotations[10].link[:type]).to be :playlist - expect(annotations[10].link[:new_window]).to be true - - expect(annotations[11]).to be_a Yt::Annotations::Label - expect(annotations[11].starts_at).to be 9.0 - expect(annotations[11].ends_at).to be 10.0 - expect(annotations[11].text).to eq 'n9' - expect(annotations[11].link).to be_a Hash - expect(annotations[11].link[:url]).to include 'youtube.com/user/TheEllenShow' - expect(annotations[11].link[:type]).to be :subscribe - expect(annotations[11].link[:new_window]).to be true - - expect(annotations[12]).to be_a Yt::Annotations::Label - expect(annotations[12].starts_at).to be 10.0 - expect(annotations[12].ends_at).to be 11.0 - expect(annotations[12].text).to eq 'n10' - expect(annotations[12].link).to be_a Hash - expect(annotations[12].link[:url]).to include 'causes.com' - expect(annotations[12].link[:type]).to be :crowdfunding - expect(annotations[12].link[:new_window]).to be true - - expect(annotations[13]).to be_a Yt::Annotations::Card - expect(annotations[13].starts_at).to be 15.038 - expect(annotations[13].ends_at).to be 22.038 - expect(annotations[13].text).to eq 'Card with link to channel' - expect(annotations[13].link).to be_a Hash - expect(annotations[13].link[:url]).to include 'youtube.com/user/TheEllenShow' - expect(annotations[13].link[:type]).to be :channel - expect(annotations[13].link[:new_window]).to be true - - expect(annotations[14]).to be_a Yt::Annotations::Card - expect(annotations[14].starts_at).to be 22.192 - expect(annotations[14].ends_at).to be 29.192 - expect(annotations[14].text).to eq 'Cards link to Merch site' - expect(annotations[14].link).to be_a Hash - expect(annotations[14].link[:url]).to include 'bandcamp.com' - expect(annotations[14].link[:type]).to be :website - expect(annotations[14].link[:new_window]).to be true - - expect(annotations[15]).to be_a Yt::Annotations::Branding - expect(annotations[15].starts_at).to be 30.0 - expect(annotations[15].ends_at).to be 37.0 - expect(annotations[15].text).to be_empty - expect(annotations[15].link).to be_a Hash - expect(annotations[15].link[:url]).to include 'youtube.com/channel/UCwCnUcLcb9-eSrHa_RQGkQQ' - expect(annotations[15].link[:type]).to be :channel - expect(annotations[15].link[:new_window]).to be true - - expect(annotations[16]).to be_a Yt::Annotations::Card - expect(annotations[16].starts_at).to be 30.038 - expect(annotations[16].ends_at).to be 37.038 - expect(annotations[16].text).to eq 'Card links to Crowdfunding' - expect(annotations[16].link).to be_a Hash - expect(annotations[16].link[:url]).to include 'causes.com' - expect(annotations[16].link[:type]).to be :website - expect(annotations[16].link[:new_window]).to be true + expect(annotations[5]).to be_a Yt::Annotations::Card + expect(annotations[5].starts_at).to be 30.038 + expect(annotations[5].ends_at).to be 37.038 + expect(annotations[5].text).to eq 'Card links to Crowdfunding' + expect(annotations[5].link).to be_a Hash + expect(annotations[5].link[:url]).to include 'causes.com' + expect(annotations[5].link[:type]).to be :website + expect(annotations[5].link[:new_window]).to be true end end @@ -169,7 +85,8 @@ expect(annotations[1].ends_at).to be 35.005 expect(annotations[1].text).to eq 'T-Shirt Shop for Geeks, Gamer, Nerds' expect(annotations[1].link).to be_a Hash - expect(annotations[1].link[:url]).to eq 'https://3dsupply.de/en/' + redirect_to_link = URI.decode_www_form(annotations[1].link[:url]).to_h.values[0] + expect(redirect_to_link).to eq 'https://3dsupply.de/en/' expect(annotations[1].link[:type]).to be :website expect(annotations[1].link[:new_window]).to be true @@ -215,32 +132,6 @@ end end - # NOTE: Third-party video, read above. - context 'given a video an "associated website" link in a note' do - let(:video_id) { 'jeCSnH9mQFo' } - - it 'also returns the featured video' do - note = annotations.find{|a| a.starts_at == 72.9} - expect(note).to be_a Yt::Annotations::Note - expect(note.link[:type]).to be :website - - spotlight = annotations.find{|a| a.link[:url] == 'http://www.fox.com/masterchef-junior/'} - expect(spotlight).to be_a Yt::Annotations::Spotlight - expect(spotlight.link[:type]).to be :website - end - end - - # NOTE: Third-party video, read above. - context 'given a video with call to action overlay' do - let(:video_id) { 'f90eVCNIkys' } - - it 'returns promotion annotation' do - expect(annotations[0]).to be_a Yt::Annotations::Promotion - expect(annotations[0].link[:type]).to be :website - expect(annotations[0].link[:url]).to eq 'http://www.seventhgeneration.com/laundry-detergent' - end - end - # NOTE: Third-party video, read above. context 'given a video without annotations' do let(:video_id) { 'hx-gU_q1JCU' } @@ -257,21 +148,4 @@ expect(annotations).to be end end - - context 'given a video with a speech' do - let(:video_id) { 'BgUkm4xdf74' } - - it 'does not raise an error' do - expect(annotations).to be - end - end - - # NOTE: Third-party video, pause type annotation is no longer available. - context 'given a video with a pause type annotation' do - let(:video_id) { 'qLGxubfC1Ik' } - - it 'also returns pause annotation' do - expect(annotations[1]).to be_a Yt::Annotations::Pause - end - end end From 75b73adee428552428dcd6ca39e8eb8cced6ed6d Mon Sep 17 00:00:00 2001 From: Kang-Kyu Lee Date: Mon, 25 Nov 2019 12:23:50 -0800 Subject: [PATCH 2/2] Fix test --- spec/yt/annotations_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/yt/annotations_spec.rb b/spec/yt/annotations_spec.rb index 209f2d2..4694743 100644 --- a/spec/yt/annotations_spec.rb +++ b/spec/yt/annotations_spec.rb @@ -85,7 +85,7 @@ expect(annotations[1].ends_at).to be 35.005 expect(annotations[1].text).to eq 'T-Shirt Shop for Geeks, Gamer, Nerds' expect(annotations[1].link).to be_a Hash - redirect_to_link = URI.decode_www_form(annotations[1].link[:url]).to_h.values[0] + redirect_to_link = URI.decode_www_form(annotations[1].link[:url]).to_h["q"] expect(redirect_to_link).to eq 'https://3dsupply.de/en/' expect(annotations[1].link[:type]).to be :website expect(annotations[1].link[:new_window]).to be true