yt-annotations 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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