yt-annotations 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e68838daeb5a5768c0ec8c7836bc3085131498fe
4
- data.tar.gz: 220f22ea8162ab35147ffbb4dd7993b25450cbbb
3
+ metadata.gz: 1a0c467d188f6e1240e4e4cac683ff2bad025eaf
4
+ data.tar.gz: 1c00e6cc2c9222098d2a2ef5b3be8bd22d400773
5
5
  SHA512:
6
- metadata.gz: 9b068c9d62e220f86119f8c53ae0663eb2803964624f7405f2e8b8da3f95de9ced9cdb1b30c6f2a1de0353e8c062e905823256b07ee979a27dce3823b8c258b8
7
- data.tar.gz: 4b87f181807c71e378f6ae2338966c6cb35206c5bd7b77d609b8dbfd581fb702e4c8d099764565c92752dba364183c66cdea59c2c074b81a6313ede752a015ce
6
+ metadata.gz: 9c8ee7bbd73cd7c776377e00168671810fdbb451295e05b3f1dfcbf34e6771cb1936640e8189c791024666c5ddf407665096a64d4918b421cacef4ec8371c017
7
+ data.tar.gz: 7dcc2d65c21a147d77a68a524f1be8d289ea45c80bd80047427ccc31fca122145f41241bb6d75db39d3fc65e13d258141d9e4f3ed59978bf64d9261691c4eb1e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.0.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ For more information about changelogs, check
6
+ [Keep a Changelog](http://keepachangelog.com) and
7
+ [Vandamme](http://tech-angels.github.io/vandamme).
8
+
9
+ ## 1.1.0 - 2015.11.12
10
+
11
+ * [FEATURE] Fetches annotations and cards of YouTube videos
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yt-annotations.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Fullscreen, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ A Ruby gem to fetch YouTube annotations
2
+ =======================================
3
+
4
+ Yt::Annotations is a Ruby library to fetch annotations and cards of YouTube videos.
5
+
6
+ The **source code** is available on [GitHub](https://github.com/Fullscreen/yt-annotations) and the **documentation** on [RubyDoc](http://www.rubydoc.info/github/Fullscreen/yt-annotations/master/Elegant/Interface).
7
+
8
+ [![Build Status](http://img.shields.io/travis/Fullscreen/yt-annotations/master.svg)](https://travis-ci.org/Fullscreen/yt-annotations)
9
+ [![Coverage Status](http://img.shields.io/coveralls/Fullscreen/yt-annotations/master.svg)](https://coveralls.io/r/Fullscreen/yt-annotations)
10
+ [![Dependency Status](http://img.shields.io/gemnasium/Fullscreen/yt-annotations.svg)](https://gemnasium.com/Fullscreen/yt-annotations)
11
+ [![Code Climate](http://img.shields.io/codeclimate/github/Fullscreen/yt-annotations.svg)](https://codeclimate.com/github/Fullscreen/yt-annotations)
12
+ [![Online docs](http://img.shields.io/badge/docs-✓-green.svg)](http://www.rubydoc.info/github/Fullscreen/yt-annotations/master/Elegant)
13
+ [![Gem Version](http://img.shields.io/gem/v/yt-annotations.svg)](http://rubygems.org/gems/yt-annotations)
14
+
15
+ How to use
16
+ ==========
17
+
18
+ Simply call `Yt::Annotations.for` with the ID of a YouTube video:
19
+
20
+ ```ruby
21
+ annotations = Yt::Annotations.for 'MESycYJytkU'
22
+ # => [#<Yt::Annotations::Featured …>]
23
+ annotations.first.text
24
+ # => "Suggested by Fullscreen: What is Fullscreen?"
25
+ annotations.first.starts_at
26
+ # => 76.0
27
+ annotations.first.ends_at
28
+ # => 86.0
29
+ annotations.first.link
30
+ # {url: "https://www.youtube.com/watch?v=NeMlqbX2Ifg", new_window: true, type: :video}
31
+ ```
32
+
33
+ How to install
34
+ ==============
35
+
36
+ Elegant requires **Ruby 2.0 or higher**.
37
+
38
+ To include in your project, add `gem 'yt-annotations', ~> '1.0'` to the `Gemfile` file of your Ruby project.
39
+
40
+ How to contribute
41
+ =================
42
+
43
+ If you’ve made it this far in the README… thanks! :v:
44
+ Feel free to try it the gem, explore the code, and send issues or pull requests.
45
+
46
+ All pull requests will have to make Travis and Code Climate happy in order to be accepted. :kissing_smiling_eyes:
47
+
48
+ You can also run the tests locally with `bundle exec rspec`.
49
+
50
+ Happy hacking!
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require "rspec/core/rake_task"
4
+ require "rspec/core/version"
5
+
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new :spec
8
+
9
+ task default: [:spec]
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'yt/annotations'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,14 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'active_support' # required
4
+ require 'active_support/core_ext/hash/conversions' # for Hash.from_xml
5
+ require 'active_support/core_ext/string/inflections' # for demodulize
6
+
7
+ require 'yt/annotations/for'
8
+
9
+ module Yt
10
+ module Annotations
11
+ # Auto-load For to have the easy syntax Yt::Annotations.for(video_id).
12
+ extend For
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Yt
2
+ module Annotations
3
+ # An abstract class with common attributes for every type of annotation.
4
+ class Base
5
+ # @return [String] the text of the annotation.
6
+ attr_reader :text
7
+
8
+ # @return [Float] when the annotation appears (in seconds).
9
+ attr_reader :starts_at
10
+
11
+ # @return [Float] when the annotation disappears (in seconds).
12
+ attr_reader :ends_at
13
+
14
+ # @return [Hash, nil] what the annotation links.
15
+ attr_reader :link
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ require 'yt/annotations/card'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Branding annotation is similar to a card, except that it does not have
6
+ # text, only a channel-linked image displayed in the bottom-right corner.
7
+ class Branding < Card
8
+ private
9
+ def text_in(data)
10
+ ''
11
+ end
12
+
13
+ def ends_at_in(data)
14
+ data['end_ms'] / 1000.0
15
+ end
16
+
17
+ def to_link(data, json)
18
+ return unless url = data.fetch('action', {})['url']
19
+ new_window = url['target'] != 'current'
20
+ {url: url['value'], new_window: new_window, type: :channel}
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ require 'yt/annotations/base'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Card annotation is different from a Note annotation in the way data is
6
+ # represented in the XML under a new 'data' key with JSON-formatted content.
7
+ class Card < Base
8
+ # @param [Hash] data the Hash representation of the XML data returned by
9
+ # YouTube for each card of a video.
10
+ def initialize(data = {})
11
+ json = JSON.parse data['data']
12
+ @text = text_in json
13
+ @starts_at = json['start_ms'] / 1000.0
14
+ @ends_at = ends_at_in json
15
+ @link = to_link data, json
16
+ end
17
+
18
+ private
19
+
20
+ def text_in(json)
21
+ json['teaser_text']
22
+ end
23
+
24
+ def ends_at_in(json)
25
+ (json['start_ms'] + json['teaser_duration_ms']) / 1000.0
26
+ end
27
+
28
+ def to_link(data, json)
29
+ link_type = case json['card_type']
30
+ when 'collaborator' then :channel
31
+ when 'playlist' then :playlist
32
+ when 'video' then :video
33
+ when 'merch' then :merch
34
+ when 'fundraising' then :crowdfunding
35
+ when 'associated' then :website
36
+ end
37
+
38
+ {url: json['url'], new_window: true, type: link_type}
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ require 'yt/annotations/card'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Featured annotation is similar to a card, except that it displays in
6
+ # the bottom-left corner and only shows if no cards are present.
7
+ class Featured < Card
8
+ private
9
+ def text_in(json)
10
+ json.values_at('text_line_1', 'text_line_2').join(': ')
11
+ end
12
+
13
+ def ends_at_in(data)
14
+ data['end_ms'] / 1000.0
15
+ end
16
+
17
+ def to_link(data, json)
18
+ return unless url = data.fetch('action', {})['url']
19
+ new_window = url['target'] != 'current'
20
+ {url: url['value'], new_window: new_window, type: link_type(json)}
21
+ end
22
+
23
+ def link_type(json)
24
+ if json['playlist_length']
25
+ :playlist
26
+ elsif json['video_duration']
27
+ :video
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ require 'yt/annotations/branding'
2
+ require 'yt/annotations/card'
3
+ require 'yt/annotations/featured'
4
+ require 'yt/annotations/label'
5
+ require 'yt/annotations/note'
6
+ require 'yt/annotations/speech'
7
+ require 'yt/annotations/spotlight'
8
+ require 'yt/annotations/title'
9
+
10
+ module Yt
11
+ module Annotations
12
+ module For
13
+ def for(video_id)
14
+ request = Net::HTTP::Get.new "/annotations_invideo?video_id=#{video_id}"
15
+ options = ['www.youtube.com', 443, {use_ssl: true}]
16
+ response = Net::HTTP.start(*options) {|http| http.request request}
17
+ xml_to_annotations(Hash.from_xml response.body).sort_by &:starts_at
18
+ end
19
+
20
+ private
21
+
22
+ def xml_to_annotations(xml)
23
+ annotations = xml['document']['annotations']
24
+ annotations = Array.wrap (annotations || {})['annotation']
25
+ annotations = merge_highlights annotations
26
+ annotations.map{|data| annotation_class(data).new data}
27
+ end
28
+
29
+
30
+ def annotation_class(data)
31
+ case data['style']
32
+ when 'anchored' then Annotations::Speech
33
+ when 'branding' then Annotations::Branding
34
+ when 'highlightText' then Annotations::Spotlight
35
+ when 'label' then Annotations::Label
36
+ when 'popup' then Annotations::Note
37
+ when 'title' then Annotations::Title
38
+ else case data['type']
39
+ when 'card' then Annotations::Card
40
+ when 'promotion' then Annotations::Featured
41
+ end
42
+ end
43
+ end
44
+
45
+ def merge_highlights(annotations)
46
+ highlights, others = annotations.partition{|a| a['type'] == 'highlight'}
47
+ highlights.each do |highlight|
48
+ match = others.find do |a|
49
+ a.fetch('segment', {})['spaceRelative'] == highlight['id']
50
+ end
51
+ match.merge! highlight if match
52
+ end
53
+ others
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ require 'yt/annotations/note'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Label annotation is just a Note annotation with text, start/end time
6
+ # and an optional link, but shaped like a rectangle without borders.
7
+ class Label < Note
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ require 'yt/annotations/base'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Note annotation is just a basic annotation with text, start/end time
6
+ # and an optional link. It's shaped like a Post-It with a curved corner.
7
+ class Note < Base
8
+ # @param [Hash] data the Hash representation of the XML data returned by
9
+ # YouTube for each annotation of a video.
10
+ def initialize(data = {})
11
+ @text = data['TEXT']
12
+ @starts_at = to_seconds regions_of(data)[0]['t']
13
+ @ends_at = to_seconds regions_of(data)[1]['t']
14
+ @link = to_link data.fetch('action', {})['url']
15
+ end
16
+
17
+ private
18
+
19
+ def regions_of(data)
20
+ data['segment']['movingRegion']['rectRegion']
21
+ end
22
+
23
+ def to_seconds(timestamp)
24
+ timestamp.split(':').map(&:to_f).inject(0) {|sum, n| 60 * sum + n}
25
+ end
26
+
27
+ def to_link(url)
28
+ return unless url
29
+ new_window = url['target'] != 'current'
30
+ type = case url['link_class']
31
+ when '1' then :video
32
+ when '2' then :playlist
33
+ when '3' then :channel
34
+ when '4' then :profile
35
+ when '5' then :subscribe
36
+ when '6' then :website
37
+ when '8' then :crowdfunding
38
+ when '12' then :website
39
+ end
40
+ {url: url['value'], new_window: new_window, type: type}
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,14 @@
1
+ require 'yt/annotations/note'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Speech annotation is like a NOT annotation with text, start/end time
6
+ # and an optional link, but it's shaped like a speech bubble.
7
+ class Speech < Note
8
+ private
9
+ def regions_of(data)
10
+ data['segment']['movingRegion']['anchoredRegion']
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ require 'yt/annotations/note'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Spotlight annotation is like a Note annotation with text and start/end
6
+ # time, but the text is below the click area.
7
+ class Spotlight < Note
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ require 'yt/annotations/base'
2
+
3
+ module Yt
4
+ module Annotations
5
+ # A Title annotation is like a Note annotation with text, start/end time,
6
+ # but cannot have a link. It's simply overlay text.
7
+ class Title < Note
8
+ private
9
+ def to_link(url)
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Yt
2
+ module Annotations
3
+ VERSION = '1.1.0'
4
+ end
5
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'yt/annotations/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'yt-annotations'
8
+ spec.version = Yt::Annotations::VERSION
9
+ spec.authors = ['claudiob']
10
+ spec.email = ['claudiob@gmail.com']
11
+
12
+ spec.summary = %q{Fetch annotations and cards from YouTube videos.}
13
+ spec.description = %q{A Ruby library to retrieve every type of annotation from
14
+ any YouTube video, including branding, featured content and info cards.}
15
+ spec.homepage = 'https://github.com/fullscreen/yt-annotations'
16
+ spec.license = 'MIT'
17
+
18
+ spec.required_ruby_version = '>= 2.0'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'activesupport', '~> 4.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.7'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.3'
30
+ spec.add_development_dependency 'coveralls', '~> 0.8.2'
31
+ spec.add_development_dependency 'pry-nav', '~> 0.2.4'
32
+ end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yt-annotations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2015-11-12 00:00:00.000000000 Z
12
12
  dependencies:
@@ -17,7 +17,7 @@ dependencies:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
- type: :development
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -102,7 +102,29 @@ email:
102
102
  executables: []
103
103
  extensions: []
104
104
  extra_rdoc_files: []
105
- files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".travis.yml"
108
+ - CHANGELOG.md
109
+ - Gemfile
110
+ - MIT-LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/setup
115
+ - lib/yt/annotations.rb
116
+ - lib/yt/annotations/base.rb
117
+ - lib/yt/annotations/branding.rb
118
+ - lib/yt/annotations/card.rb
119
+ - lib/yt/annotations/featured.rb
120
+ - lib/yt/annotations/for.rb
121
+ - lib/yt/annotations/label.rb
122
+ - lib/yt/annotations/note.rb
123
+ - lib/yt/annotations/speech.rb
124
+ - lib/yt/annotations/spotlight.rb
125
+ - lib/yt/annotations/title.rb
126
+ - lib/yt/annotations/version.rb
127
+ - yt-annotations.gemspec
106
128
  homepage: https://github.com/fullscreen/yt-annotations
107
129
  licenses:
108
130
  - MIT