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 +4 -4
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/yt/annotations.rb +14 -0
- data/lib/yt/annotations/base.rb +18 -0
- data/lib/yt/annotations/branding.rb +24 -0
- data/lib/yt/annotations/card.rb +42 -0
- data/lib/yt/annotations/featured.rb +32 -0
- data/lib/yt/annotations/for.rb +57 -0
- data/lib/yt/annotations/label.rb +10 -0
- data/lib/yt/annotations/note.rb +45 -0
- data/lib/yt/annotations/speech.rb +14 -0
- data/lib/yt/annotations/spotlight.rb +10 -0
- data/lib/yt/annotations/title.rb +14 -0
- data/lib/yt/annotations/version.rb +5 -0
- data/yt-annotations.gemspec +32 -0
- metadata +26 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a0c467d188f6e1240e4e4cac683ff2bad025eaf
|
4
|
+
data.tar.gz: 1c00e6cc2c9222098d2a2ef5b3be8bd22d400773
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c8ee7bbd73cd7c776377e00168671810fdbb451295e05b3f1dfcbf34e6771cb1936640e8189c791024666c5ddf407665096a64d4918b421cacef4ec8371c017
|
7
|
+
data.tar.gz: 7dcc2d65c21a147d77a68a524f1be8d289ea45c80bd80047427ccc31fca122145f41241bb6d75db39d3fc65e13d258141d9e4f3ed59978bf64d9261691c4eb1e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
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
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
|
+
[](https://travis-ci.org/Fullscreen/yt-annotations)
|
9
|
+
[](https://coveralls.io/r/Fullscreen/yt-annotations)
|
10
|
+
[](https://gemnasium.com/Fullscreen/yt-annotations)
|
11
|
+
[](https://codeclimate.com/github/Fullscreen/yt-annotations)
|
12
|
+
[](http://www.rubydoc.info/github/Fullscreen/yt-annotations/master/Elegant)
|
13
|
+
[](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
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,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,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,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,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.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- claudiob
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
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: :
|
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
|