dmcloud-ruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/.document +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/dm_cloud.gemspec +63 -0
- data/lib/dm_cloud/builder/media.rb +121 -0
- data/lib/dm_cloud/media.rb +107 -0
- data/lib/dm_cloud/request.rb +45 -0
- data/lib/dm_cloud/signing.rb +195 -0
- data/lib/dm_cloud/streaming.rb +89 -0
- data/lib/dm_cloud/version.rb +3 -0
- data/lib/dmcloud.rb +58 -0
- data/test/helper.rb +18 -0
- data/test/test_dm_cloud.rb +7 -0
- metadata +116 -0
data/.DS_Store
ADDED
Binary file
|
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rdoc", "~> 3.12"
|
10
|
+
gem "bundler", "~> 1.0"
|
11
|
+
gem 'jeweler', '~> 2.0.1'
|
12
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.6)
|
5
|
+
builder (3.2.2)
|
6
|
+
descendants_tracker (0.0.4)
|
7
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
8
|
+
faraday (0.9.0)
|
9
|
+
multipart-post (>= 1.2, < 3)
|
10
|
+
git (1.2.8)
|
11
|
+
github_api (0.12.1)
|
12
|
+
addressable (~> 2.3)
|
13
|
+
descendants_tracker (~> 0.0.4)
|
14
|
+
faraday (~> 0.8, < 0.10)
|
15
|
+
hashie (>= 3.2)
|
16
|
+
multi_json (>= 1.7.5, < 2.0)
|
17
|
+
nokogiri (~> 1.6.3)
|
18
|
+
oauth2
|
19
|
+
hashie (3.3.1)
|
20
|
+
highline (1.6.21)
|
21
|
+
jeweler (2.0.1)
|
22
|
+
builder
|
23
|
+
bundler (>= 1.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
github_api
|
26
|
+
highline (>= 1.6.15)
|
27
|
+
nokogiri (>= 1.5.10)
|
28
|
+
rake
|
29
|
+
rdoc
|
30
|
+
json (1.8.1)
|
31
|
+
jwt (1.0.0)
|
32
|
+
mini_portile (0.6.0)
|
33
|
+
multi_json (1.10.1)
|
34
|
+
multi_xml (0.5.5)
|
35
|
+
multipart-post (2.0.0)
|
36
|
+
nokogiri (1.6.3.1)
|
37
|
+
mini_portile (= 0.6.0)
|
38
|
+
oauth2 (1.0.0)
|
39
|
+
faraday (>= 0.8, < 0.10)
|
40
|
+
jwt (~> 1.0)
|
41
|
+
multi_json (~> 1.3)
|
42
|
+
multi_xml (~> 0.5)
|
43
|
+
rack (~> 1.2)
|
44
|
+
rack (1.5.2)
|
45
|
+
rake (10.3.2)
|
46
|
+
rdoc (3.12.2)
|
47
|
+
json (~> 1.4)
|
48
|
+
thread_safe (0.3.4)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
bundler (~> 1.0)
|
55
|
+
jeweler (~> 2.0.1)
|
56
|
+
rdoc (~> 3.12)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Amund Sivertsen
|
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.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= dm_cloud
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to dm_cloud
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2014 Amund Sivertsen. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "dmcloud-ruby"
|
18
|
+
gem.homepage = "http://github.com/amunds/dm_cloud"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Communicate with DM Cloud}
|
21
|
+
gem.description = %Q{}
|
22
|
+
gem.email = "amunds@gmail.com"
|
23
|
+
gem.authors = ["Amund Sivertsen"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rdoc/task'
|
29
|
+
Rake::RDocTask.new do |rdoc|
|
30
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
31
|
+
|
32
|
+
rdoc.rdoc_dir = 'rdoc'
|
33
|
+
rdoc.title = "dm_cloud #{version}"
|
34
|
+
rdoc.rdoc_files.include('README*')
|
35
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/dm_cloud.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "dm_cloud"
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Amund Sivertsen"]
|
12
|
+
s.date = "2014-10-08"
|
13
|
+
s.description = ""
|
14
|
+
s.email = "amunds@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".DS_Store",
|
21
|
+
".document",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"dm_cloud.gemspec",
|
29
|
+
"lib/dm_cloud/builder/media.rb",
|
30
|
+
"lib/dm_cloud/media.rb",
|
31
|
+
"lib/dm_cloud/request.rb",
|
32
|
+
"lib/dm_cloud/signing.rb",
|
33
|
+
"lib/dm_cloud/streaming.rb",
|
34
|
+
"lib/dm_cloud/version.rb",
|
35
|
+
"lib/dmcloud.rb",
|
36
|
+
"test/helper.rb",
|
37
|
+
"test/test_dm_cloud.rb"
|
38
|
+
]
|
39
|
+
s.homepage = "http://github.com/amunds/dm_cloud"
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = "1.8.23"
|
43
|
+
s.summary = "Communicate with DM Cloud"
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
50
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
51
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
54
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
55
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
56
|
+
end
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module DmCloud
|
2
|
+
module Builder
|
3
|
+
module Media
|
4
|
+
def self.create(url = '', assets_names = [], meta = {})
|
5
|
+
request = Hash.new
|
6
|
+
|
7
|
+
request['url'] = url
|
8
|
+
|
9
|
+
if not meta.empty?
|
10
|
+
request['meta'] = {}
|
11
|
+
request['meta']['author'] = meta[:author] if meta[:author]
|
12
|
+
request['meta']['author'] = meta[:title] if meta[:title]
|
13
|
+
end
|
14
|
+
|
15
|
+
request['assets_names'] = assets_names if not assets_names.empty?
|
16
|
+
|
17
|
+
request.rehash
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.info(media_id, assets_names = ['source'], fields = {}, authToken = false)
|
21
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
22
|
+
request = Hash.new
|
23
|
+
|
24
|
+
# the media id
|
25
|
+
request[:id] = media_id
|
26
|
+
|
27
|
+
# requested media meta datas
|
28
|
+
request[:fields] = []
|
29
|
+
fields[:meta] = ['title'] unless fields[:meta]
|
30
|
+
fields[:meta].each { |value| request[:fields] << "meta.#{value.to_s}" }
|
31
|
+
if not authToken
|
32
|
+
request[:fields] += ['id', 'created', 'embed_url', 'frame_ratio']
|
33
|
+
end
|
34
|
+
# the worldwide statistics on the number of views
|
35
|
+
# request['fields'] << 'stats.global.last_week' if fields[:stats][:global]
|
36
|
+
# request['fields'] << 'assets.all.progress'
|
37
|
+
|
38
|
+
# TODO: handle statistics request per country
|
39
|
+
# fields[:stats].each { |key| request << "meta.#{key.to_s}" } if fields[:meta].present?
|
40
|
+
# request['stats'][COUNTRY_CODE][TIME_INTERVAL] : the statistics on the number of views in a specific country (eg: stats.fr.total, stats.us.last_week, etc...)
|
41
|
+
# request['extended_stats'][COUNTRY_CODE][TIME_INTERVAL]
|
42
|
+
|
43
|
+
assets_names = ['source'] if assets_names.nil?
|
44
|
+
if not fields[:assets]
|
45
|
+
request = all_assets_fields(request, assets_names)
|
46
|
+
else
|
47
|
+
assets_names.each do |name|
|
48
|
+
fields[:assets].each { |value| request[:fields] << "assets.#{name}.#{value.to_s}" }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
request
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.list(page = 1, per_page = 10, fields = {})
|
55
|
+
# raise StandardError, "missing :media_id in params" unless media_id
|
56
|
+
request = Hash.new
|
57
|
+
|
58
|
+
request[:page] = page
|
59
|
+
request[:per_page] = per_page
|
60
|
+
request[:fields] = []
|
61
|
+
# requested media meta datas
|
62
|
+
fields[:meta] = ['title'] unless fields[:meta]
|
63
|
+
fields[:meta].each { |value| request[:fields] << "meta.#{value.to_s}" }
|
64
|
+
request[:fields] += ['id', 'created', 'embed_url', 'frame_ratio']
|
65
|
+
|
66
|
+
# TODO: handle global statistics request in another module
|
67
|
+
# the worldwide statistics on the number of views
|
68
|
+
# request << 'stats.global.last_week' if fields[:stats][:global]
|
69
|
+
|
70
|
+
# TODO: handle statistics request per country
|
71
|
+
# fields[:stats].each { |key| request << "meta.#{key.to_s}" } if fields[:meta].present?
|
72
|
+
# request['stats'][COUNTRY_CODE][TIME_INTERVAL] : the statistics on the number of views in a specific country (eg: stats.fr.total, stats.us.last_week, etc...)
|
73
|
+
# request['extended_stats'][COUNTRY_CODE][TIME_INTERVAL]
|
74
|
+
|
75
|
+
assets_names = ['source'] if assets_names.nil?
|
76
|
+
if not fields[:assets]
|
77
|
+
request = all_assets_fields(request, assets_names)
|
78
|
+
else
|
79
|
+
assets_names.each do |name|
|
80
|
+
fields[:assets].each { |value| request[:fields] << "assets.#{name}.#{value.to_s}" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
request
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
protected
|
89
|
+
# This method exclude stats, but return all information for a media (video or images)
|
90
|
+
# NOTE: This is outside the methods because : too long and recurent.
|
91
|
+
# It's also used as default if no fields params is submitted.
|
92
|
+
def self.all_assets_fields(request, assets_names)
|
93
|
+
assets_names.each do |name|
|
94
|
+
request[:fields] << "assets.#{name}.download_url"
|
95
|
+
request[:fields] << "assets.#{name}.status"
|
96
|
+
request[:fields] << "assets.#{name}.container"
|
97
|
+
request[:fields] << "assets.#{name}.duration"
|
98
|
+
request[:fields] << "assets.#{name}.global_bitrate"
|
99
|
+
request[:fields] << "assets.#{name}.video_codec"
|
100
|
+
request[:fields] << "assets.#{name}.video_width"
|
101
|
+
request[:fields] << "assets.#{name}.video_height"
|
102
|
+
request[:fields] << "assets.#{name}.video_bitrate"
|
103
|
+
request[:fields] << "assets.#{name}.video_rotation"
|
104
|
+
request[:fields] << "assets.#{name}.video_fps"
|
105
|
+
request[:fields] << "assets.#{name}.video_fps_mode"
|
106
|
+
request[:fields] << "assets.#{name}.video_aspect"
|
107
|
+
request[:fields] << "assets.#{name}.video_interlaced"
|
108
|
+
request[:fields] << "assets.#{name}.audio_codec"
|
109
|
+
request[:fields] << "assets.#{name}.audio_bitrate"
|
110
|
+
request[:fields] << "assets.#{name}.audio_nbr_channel"
|
111
|
+
request[:fields] << "assets.#{name}.audio_samplerate"
|
112
|
+
request[:fields] << "assets.#{name}.created"
|
113
|
+
request[:fields] << "assets.#{name}.file_extension"
|
114
|
+
request[:fields] << "assets.#{name}.file_size"
|
115
|
+
request[:fields] << "assets.#{name}.progress"
|
116
|
+
end
|
117
|
+
request
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'dm_cloud/builder/media'
|
2
|
+
|
3
|
+
module DmCloud
|
4
|
+
class Media
|
5
|
+
# Creates a new media object.
|
6
|
+
# This method can either create an empty media object
|
7
|
+
# or also download a media with the url paramater
|
8
|
+
# and use it as the source to encode the ASSET_NAME listed in assets_names
|
9
|
+
# Params :
|
10
|
+
# args:
|
11
|
+
# url: SCHEME://USER:PASSWORD@HOSTNAME/MY/PATH/FILENAME.EXTENSION (could be ftp or http)
|
12
|
+
# author: an author name
|
13
|
+
# title: a title for the film
|
14
|
+
# assets_names: (Array) – (optional) the list of ASSET_NAME you want to transcode,
|
15
|
+
# when you set this parameter you must also set the url parameter
|
16
|
+
# Return :
|
17
|
+
# media_id: return the media id of the object
|
18
|
+
def self.create(url, assets_names = [], meta = {})
|
19
|
+
call_type = "media.create"
|
20
|
+
|
21
|
+
params = {
|
22
|
+
:call => call_type,
|
23
|
+
args: Builder::Media.create(url, assets_names, meta)
|
24
|
+
}
|
25
|
+
DmCloud.config[:auto_call] == true ? DmCloud::Request.execute(call_type, params) : {call: call_type, params: params}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delete a media object with all its associated assets.
|
29
|
+
#
|
30
|
+
# Parameters:
|
31
|
+
# id (media ID) – (required) the id of the media object you want to delete.
|
32
|
+
# Return :
|
33
|
+
# Nothing
|
34
|
+
def self.delete(media_id)
|
35
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
36
|
+
call_type = "media.delete"
|
37
|
+
|
38
|
+
params = {
|
39
|
+
:call => call_type,
|
40
|
+
args: { id: media_id}
|
41
|
+
}
|
42
|
+
DmCloud.config[:auto_call] == true ? DmCloud::Request.execute(call_type, params) : {call: call_type, params: params}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gives information about a given media object.
|
46
|
+
#
|
47
|
+
# Params :
|
48
|
+
# media_id: (media ID) – (required) the id of the new media object.
|
49
|
+
# fields (Array) – (required) the list of fields to retrieve.
|
50
|
+
# Returns:
|
51
|
+
# a multi-level structure containing about the media related to the requested fields.
|
52
|
+
def self.info(media_id, assets_names = ['source'], authToken = false, fields = {})
|
53
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
54
|
+
call_type = "media.info"
|
55
|
+
|
56
|
+
params = {
|
57
|
+
:call => call_type,
|
58
|
+
args: DmCloud::Builder::Media.info(media_id, assets_names, fields, authToken)
|
59
|
+
}
|
60
|
+
|
61
|
+
if authToken
|
62
|
+
return DmCloud::Signing.identify(params)
|
63
|
+
else
|
64
|
+
DmCloud.config[:auto_call] == true ? DmCloud::Request.execute(call_type, params) : {call: call_type, params: params}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns a paginated list of media info structures.
|
69
|
+
# You must specify the fields you want to retrieve.
|
70
|
+
# The fields are described in the documentation of the method info.
|
71
|
+
#
|
72
|
+
# Parameters:
|
73
|
+
# options:
|
74
|
+
# fields (Array) – (optional default return all informations) the fields to retrieve
|
75
|
+
# page (Integer) – (optional) the page number, default: 1
|
76
|
+
# per_page (Integer) – (optional) the number of objet per page, default: 10
|
77
|
+
# Returns:
|
78
|
+
# an object with information for the pagination and the result of the query.
|
79
|
+
def self.list(page = 1, per_page = 10, fields = {})
|
80
|
+
call_type = "media.list"
|
81
|
+
|
82
|
+
params = {
|
83
|
+
:call => call_type,
|
84
|
+
args: DmCloud::Builder::Media.list( page, per_page, fields)
|
85
|
+
}
|
86
|
+
DmCloud.config[:auto_call] == true ? DmCloud::Request.execute(call_type, params) : {call: call_type, params: params}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Gets a URL pointer to the actual file...
|
90
|
+
def self.url(media_id, asset_name)
|
91
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
92
|
+
raise StandardError, "missing :asset_name in params" unless asset_name
|
93
|
+
fields = { :assets => ["download_url"] }
|
94
|
+
|
95
|
+
self.info(media_id, [asset_name], fields)["result"]["assets"][asset_name]["download_url"]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Gets the real URL that points to the download link on DMCloud's specific server
|
99
|
+
def self.download_url(media_id, asset_name)
|
100
|
+
download_url = self.url(media_id, asset_name)
|
101
|
+
response = Net::HTTP.get_response(URI.parse(download_url))
|
102
|
+
download_url = response.header["location"]
|
103
|
+
|
104
|
+
download_url
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module DmCloud
|
5
|
+
class Request
|
6
|
+
|
7
|
+
DAILYMOTION_API = 'http://api.DmCloud.net/api'
|
8
|
+
DAILYMOTION_STATIC = 'http://api.DmCloud.net/api'
|
9
|
+
|
10
|
+
# This method control signing for Media calls and handle request and response.
|
11
|
+
def self.execute(call, params = {})
|
12
|
+
url = define(call)
|
13
|
+
params['auth'] = DmCloud::Signing.identify(params)
|
14
|
+
result = send_request(params)
|
15
|
+
parse_response(result)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def self.send_request(params)
|
20
|
+
@uri = URI.parse(DAILYMOTION_API)
|
21
|
+
|
22
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
23
|
+
request = Net::HTTP::Post.new(@uri.request_uri)
|
24
|
+
request.content_type = 'application/json'
|
25
|
+
request.body = params.to_json
|
26
|
+
|
27
|
+
#puts "Request body in send_request:"
|
28
|
+
#puts request.body
|
29
|
+
|
30
|
+
#puts 'request (YAML format ): ' + request.to_yaml + "\n" + '-' * 80
|
31
|
+
|
32
|
+
http.request(request).body
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
def self.parse_response(result)
|
38
|
+
JSON.parse(result)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.define(action)
|
42
|
+
DAILYMOTION_API
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module DmCloud
|
5
|
+
class Signing
|
6
|
+
# Generate auth token for request from Media
|
7
|
+
# Params:
|
8
|
+
# request: A hash of params generated from Media methods and Media::MetaData
|
9
|
+
# Result :
|
10
|
+
# return a string which contain the auth token for the request
|
11
|
+
# <url>?auth=<expires>-<sec>-<nonce>-<md5sum>[-<pub-sec-data>]
|
12
|
+
def self.identify(request)
|
13
|
+
user_id = DmCloud.config[:user_key]
|
14
|
+
api_key = DmCloud.config[:secret_key]
|
15
|
+
|
16
|
+
#puts request
|
17
|
+
normalized_request = normalize(request).to_s
|
18
|
+
#puts 'identify:: normalized_values : ' + normalized_request + "\n" + '-' * 80
|
19
|
+
|
20
|
+
params = user_id + normalized_request + api_key
|
21
|
+
|
22
|
+
#puts 'identify:: Values before MD5 encrypt : ' + params + "\n" + '-' * 80
|
23
|
+
|
24
|
+
checksum = Digest::MD5.hexdigest(params)
|
25
|
+
auth_token = user_id + ':' + checksum
|
26
|
+
|
27
|
+
auth_token
|
28
|
+
end
|
29
|
+
|
30
|
+
# To sign a URL, the client needs a secret shared with Dailymotion Cloud.
|
31
|
+
# This secret is call client secret and is available in the back-office interface.
|
32
|
+
# Params:
|
33
|
+
# expires: An expiration timestamp.
|
34
|
+
# sec-level: A security level mask.
|
35
|
+
# url-no-query: The URL without the query-string.
|
36
|
+
# nonce: A 8 characters-long random alphanumeric lowercase string to make the signature unique.
|
37
|
+
# secret: The client secret.
|
38
|
+
# sec-data: If sec-level doesn’t have the DELEGATED bit activated,
|
39
|
+
# this component contains concatenated informations
|
40
|
+
# for all activated sec levels.
|
41
|
+
# pub-sec-data: Some sec level data have to be passed in clear in the signature.
|
42
|
+
# To generate this component the parameters are serialized using x-www-form-urlencoded, compressed with gzip and encoded in base64.
|
43
|
+
# Result :
|
44
|
+
# return a string which contain the signed url like
|
45
|
+
# <expires>-<sec>-<nonce>-<md5sum>[-<pub-sec-data>]
|
46
|
+
def self.sign(stream, security_datas = nil)
|
47
|
+
raise StandardError, "missing :stream in params" unless stream
|
48
|
+
sec_level = security(DmCloud.config[:security_level])
|
49
|
+
sec_data = security_data(DmCloud.config[:security_level], security_datas) unless security_datas.nil?
|
50
|
+
|
51
|
+
base = {
|
52
|
+
:sec_level => sec_level,
|
53
|
+
:url_no_query => stream,
|
54
|
+
:expires => (Time.now + 3.hours).to_i, # 3 hour from now
|
55
|
+
:nonce => SecureRandom.hex(16)[0,16],
|
56
|
+
:secret => DmCloud.config[:secret_key]
|
57
|
+
}
|
58
|
+
base.merge!(:sec_data => sec_data, :pub_sec_data => sec_data) unless sec_data.nil?
|
59
|
+
|
60
|
+
digest_struct = build_digest_struct(base)
|
61
|
+
check_sum = Digest::MD5.hexdigest(digest_struct)
|
62
|
+
|
63
|
+
signed_url = [base[:expires], base[:sec_level], base[:nonce], check_sum].compact
|
64
|
+
signed_url.merge!(:pub_sec_data => sec_data) unless sec_data.nil?
|
65
|
+
|
66
|
+
# puts signed_url
|
67
|
+
|
68
|
+
signed_url = signed_url.join('-')
|
69
|
+
signed_url
|
70
|
+
end
|
71
|
+
|
72
|
+
# Prepare datas for signing
|
73
|
+
# Params :
|
74
|
+
# base : contains media id and others for url signing
|
75
|
+
def self.build_digest_struct(base)
|
76
|
+
result = []
|
77
|
+
base.each_pair { |key, value| result << value }
|
78
|
+
result.join('')
|
79
|
+
end
|
80
|
+
|
81
|
+
# The client must choose a security level for the signature.
|
82
|
+
# Security level defines the mechanism used by Dailymotion Cloud architecture
|
83
|
+
# to ensure the signed URL will be used by a single end-user.
|
84
|
+
# Params :
|
85
|
+
# type :
|
86
|
+
# None: The signed URL will be valid for everyone
|
87
|
+
# ASNUM: The signed URL will only be valid for the AS of the end-user.
|
88
|
+
# The ASNUM (for Autonomous System Number) stands for the network identification,
|
89
|
+
# each ISP have a different ASNUM for instance.
|
90
|
+
# IP: The signed URL will only be valid for the IP of the end-user.
|
91
|
+
# This security level may wrongly block some users
|
92
|
+
# which have their internet access load-balanced between several proxies.
|
93
|
+
# This is the case in some office network or some ISPs.
|
94
|
+
# User-Agent: Used in addition to one of the two former levels,
|
95
|
+
# this level a limit on the exact user-agent of the end-user.
|
96
|
+
# This is more secure but in some specific condition may lead to wrongly blocked users.
|
97
|
+
# Use Once: The signed URL will only be usable once.
|
98
|
+
# Note: should not be used with stream URLs.
|
99
|
+
# Country: The URL can only be queried from specified countrie(s).
|
100
|
+
# The rule can be reversed to allow all countries except some.
|
101
|
+
# Referer: The URL can only be queried
|
102
|
+
# if the Referer HTTP header contains a specified value.
|
103
|
+
# If the URL contains a Referer header with a different value,
|
104
|
+
# the request is refused. If the Referer header is missing,
|
105
|
+
# the request is accepted in order to prevent from false positives as some browsers,
|
106
|
+
# anti-virus or enterprise proxies may remove this header.
|
107
|
+
# Delegate: This option instructs the signing algorithm
|
108
|
+
# that security level information won’t be embeded into the signature
|
109
|
+
# but gathered and lock at the first use.
|
110
|
+
# Result :
|
111
|
+
# Return a string which contain the signed url like
|
112
|
+
# http://cdn.DmCloud.net/route/<user_id>/<media_id>/<asset_name>.<asset_extension>?auth=<auth_token>
|
113
|
+
def self.security(type = nil)
|
114
|
+
type = :none unless type
|
115
|
+
type = type.to_sym if type.class == String
|
116
|
+
|
117
|
+
case type
|
118
|
+
when :none
|
119
|
+
0 # None
|
120
|
+
when :delegate
|
121
|
+
1 << 0 # None
|
122
|
+
when :asnum
|
123
|
+
1 << 1 # The number part of the end-user AS prefixed by the ‘AS’ string (ie: as=AS41690)
|
124
|
+
when :ip
|
125
|
+
1 << 2 # The end-user quad dotted IP address (ie: ip=195.8.215.138)
|
126
|
+
when :user_agent
|
127
|
+
1 << 3 # The end-user browser user-agent (parameter name is ua)
|
128
|
+
when :use_once
|
129
|
+
1 << 4 # None
|
130
|
+
when :country
|
131
|
+
1 << 5 # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
|
132
|
+
when :referer
|
133
|
+
1 << 6 # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
|
134
|
+
when :referer_strict
|
135
|
+
1 << 15 # Same as Referer (will deny direct access as well)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.security_data(type, value = nil)
|
140
|
+
type = type.to_sym if type.class == String
|
141
|
+
|
142
|
+
case type
|
143
|
+
when :asnum
|
144
|
+
"as=#{value}" # The number part of the end-user AS prefixed by the ‘AS’ string (ie: as=AS41690)
|
145
|
+
when :ip
|
146
|
+
"ip=#{value}" # The end-user quad dotted IP address (ie: ip=195.8.215.138)
|
147
|
+
when :user_agent
|
148
|
+
"ua=#{value}" # The end-user browser user-agent (parameter name is ua)
|
149
|
+
when :country
|
150
|
+
"cc=#{value}" # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
|
151
|
+
when :referer
|
152
|
+
"rf=#{value}" # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
|
153
|
+
when :referer_strict
|
154
|
+
"rf=#{value}" # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
|
155
|
+
else
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.security_pub_sec_data(type, value)
|
161
|
+
type = type.to_sym if type.class == String
|
162
|
+
|
163
|
+
case type
|
164
|
+
when :country
|
165
|
+
"cc=#{value}" # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
|
166
|
+
when :referer
|
167
|
+
"rf=#{value}" # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# This block comes from Cloudkey gem.
|
175
|
+
# I discovered this gem far after I start this one
|
176
|
+
# and I will try to add file upload from http or ftp.
|
177
|
+
# (Missing in their gem)
|
178
|
+
def self.normalize params
|
179
|
+
case params
|
180
|
+
when Array
|
181
|
+
params.collect { |element| normalize(element) }.join('')
|
182
|
+
when Hash
|
183
|
+
params.to_a.sort_by {|a,b| a.to_s }.collect {|array| array.first.to_s + normalize(array.last)}.join('')
|
184
|
+
else
|
185
|
+
params.to_s
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# def self.normalize(params)
|
190
|
+
# str = params.to_json.to_s
|
191
|
+
# str.gsub!(/[^A-Za-z0-9]/, '')
|
192
|
+
# str
|
193
|
+
# end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "time"
|
2
|
+
require "openssl"
|
3
|
+
require "base64"
|
4
|
+
require 'digest/md5'
|
5
|
+
|
6
|
+
# This module generate methods to generate video's fluxes
|
7
|
+
# before signing it and request it.
|
8
|
+
module DmCloud
|
9
|
+
class Streaming
|
10
|
+
# Default URL to get embed content ou direct url
|
11
|
+
DIRECT_STREAM = "[PROTOCOL]://cdn.dmcloud.net/route/[USER_ID]/[MEDIA_ID]/[ASSET_NAME].[ASSET_EXTENSION]".freeze
|
12
|
+
THUMBNAIL_STREAM = "[PROTOCOL]://static-ssl.dmcloud.net/[USER_ID]/[MEDIA_ID]/[ASSET_NAME].[ASSET_EXTENSION]".freeze
|
13
|
+
EMBED_STREAM = "[PROTOCOL]://api.dmcloud.net/embed/[USER_ID]/[MEDIA_ID]".freeze
|
14
|
+
EMBED_IFRAME = '<iframe width="[WIDTH]" height="[HEIGHT]" frameborder="0" scrolling="no" src="[EMBED_URL]"></iframe>'.freeze
|
15
|
+
|
16
|
+
# Get embeded player
|
17
|
+
# Params :
|
18
|
+
# media_id: this is the id of the media (eg: 4c922386dede830447000009)
|
19
|
+
# options:
|
20
|
+
# skin_id: (optional) the id of the custom skin for the video player
|
21
|
+
# width: (optional) the width for the video player frame
|
22
|
+
# height: (optional) the height for the video player frame
|
23
|
+
# Result :
|
24
|
+
# return a string which contain the signed url like
|
25
|
+
# <iframe width="848" height="480" frameborder="0" scrolling="no" src="http://api.DmCloud.net/embed/<user_id>/<media_id>?auth=<auth_token>&skin=<skin_id>"></iframe>
|
26
|
+
def self.embed(media_id, options = {}, security = {})
|
27
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
28
|
+
|
29
|
+
skin_id = options[:skin_id] ? options[:skin_id] : 'modern1'
|
30
|
+
width = options[:width] ? options[:width].to_s : '848'
|
31
|
+
height = options[:height] ? options[:height].to_s : '480'
|
32
|
+
|
33
|
+
stream = EMBED_STREAM.dup
|
34
|
+
stream.gsub!('[PROTOCOL]', DmCloud.config[:protocol])
|
35
|
+
stream.gsub!('[USER_ID]', DmCloud.config[:user_key])
|
36
|
+
stream.gsub!('[MEDIA_ID]', media_id)
|
37
|
+
signed_url = DmCloud::Signing.sign(stream, security)
|
38
|
+
signed_url = stream + "?auth=#{signed_url}"
|
39
|
+
|
40
|
+
frame = EMBED_IFRAME.dup
|
41
|
+
frame.gsub!('[WIDTH]', width)
|
42
|
+
frame.gsub!('[HEIGHT]', height)
|
43
|
+
frame.gsub!('[EMBED_URL]', signed_url)
|
44
|
+
|
45
|
+
frame.html_safe
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def self.thumbnail(media_id, asset_name, asset_extension = nil, security = {})
|
50
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
51
|
+
raise StandardError, "missing :asset_name in params" unless asset_name
|
52
|
+
asset_extension = asset_name.split('_').first unless asset_extension
|
53
|
+
|
54
|
+
stream = THUMBNAIL_STREAM.dup
|
55
|
+
stream.gsub!('[PROTOCOL]', DmCloud.config[:protocol])
|
56
|
+
stream.gsub!('[USER_ID]', DmCloud.config[:user_key])
|
57
|
+
stream.gsub!('[MEDIA_ID]', media_id)
|
58
|
+
stream.gsub!('[ASSET_NAME]', asset_name)
|
59
|
+
stream.gsub!('[ASSET_EXTENSION]', asset_extension)
|
60
|
+
|
61
|
+
stream += '?auth=[AUTH_TOKEN]'.gsub!('[AUTH_TOKEN]', DmCloud::Signing.sign(stream, security))
|
62
|
+
stream
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get media url for direct link to the file on DailyMotion Cloud
|
66
|
+
# Params :
|
67
|
+
# media_id: this is the id of the media (eg: 4c922386dede830447000009)
|
68
|
+
# asset_name: the name of the asset you want to stream (eg: mp4_h264_aac)
|
69
|
+
# asset_extension: the extension of the asset, most of the time it is the first part of the asset name (eg: mp4)
|
70
|
+
# Result :
|
71
|
+
# return a string which contain the signed url like
|
72
|
+
# http://cdn.DmCloud.net/route/<user_id>/<media_id>/<asset_name>.<asset_extension>?auth=<auth_token>
|
73
|
+
def self.url(media_id, asset_name, asset_extension = nil, security = {})
|
74
|
+
raise StandardError, "missing :media_id in params" unless media_id
|
75
|
+
raise StandardError, "missing :asset_name in params" unless asset_name
|
76
|
+
asset_extension = asset_name.split('_').first unless asset_extension
|
77
|
+
|
78
|
+
stream = DIRECT_STREAM.dup
|
79
|
+
stream.gsub!('[PROTOCOL]', DmCloud.config[:protocol])
|
80
|
+
stream.gsub!('[USER_ID]', DmCloud.config[:user_key])
|
81
|
+
stream.gsub!('[MEDIA_ID]', media_id)
|
82
|
+
stream.gsub!('[ASSET_NAME]', asset_name)
|
83
|
+
stream.gsub!('[ASSET_EXTENSION]', asset_extension)
|
84
|
+
|
85
|
+
stream += '?auth=[AUTH_TOKEN]'.gsub!('[AUTH_TOKEN]', DmCloud::Signing.sign(stream, security))
|
86
|
+
stream
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/dmcloud.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# This gem's comments come from DailyMotion Cloud API,
|
4
|
+
# that's the better way to see changes on new version and logic.
|
5
|
+
# For parts more generals and not representating DailyMotion Cloud API,
|
6
|
+
# I add some about my own opinion.
|
7
|
+
module DmCloud
|
8
|
+
|
9
|
+
# Configuration defaults
|
10
|
+
# I used this parts from Slainer68 paybox_system gem.
|
11
|
+
# I liked the concept and how he handle this part.
|
12
|
+
# Thx Slainer68, I created my first gem,
|
13
|
+
# and next one will be an update to your paybox_system gem.
|
14
|
+
@@config = {
|
15
|
+
security_level: 'none',
|
16
|
+
protocol: 'http',
|
17
|
+
auto_call: true,
|
18
|
+
user_key: nil,
|
19
|
+
secret_key: nil
|
20
|
+
}
|
21
|
+
|
22
|
+
YAML_INITIALIZER_PATH = File.dirname(__FILE__)
|
23
|
+
@valid_config_keys = @@config.keys
|
24
|
+
|
25
|
+
# Configure through hash
|
26
|
+
def self.configure(opts = {})
|
27
|
+
opts.each {|k,v| @@config[k.to_sym] = v } # if @valid_config_keys.include? k.to_sym}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Configure through yaml file
|
31
|
+
# for ruby scripting usage
|
32
|
+
def self.configure_with(yaml_file_path = nil)
|
33
|
+
yaml_file_path = YAML_INITIALIZER_PATH unless yaml_file_path
|
34
|
+
begin
|
35
|
+
config = YAML::load(IO.read(path_to_yaml_file))
|
36
|
+
rescue Errno::ENOENT
|
37
|
+
log(:warning, "YAML configuration file couldn't be found. Using defaults."); return
|
38
|
+
rescue Psych::SyntaxError
|
39
|
+
log(:warning, "YAML configuration file contains invalid syntax. Using defaults."); return
|
40
|
+
end
|
41
|
+
|
42
|
+
configure(config)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Access to config variables (security level, user_id and api_key)
|
46
|
+
def self.config
|
47
|
+
@@config = configure unless @@config
|
48
|
+
@@config
|
49
|
+
end
|
50
|
+
|
51
|
+
# Loading classes to easier access
|
52
|
+
# NOTE: I like this way to handle my classes,
|
53
|
+
# sexiest than using require 'my_class_file' everywhere
|
54
|
+
autoload(:Streaming, 'dm_cloud/streaming')
|
55
|
+
autoload(:Media, 'dm_cloud/media')
|
56
|
+
autoload(:Request, 'dm_cloud/request')
|
57
|
+
autoload(:Signing, 'dm_cloud/signing')
|
58
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'dm_cloud'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dmcloud-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Amund Sivertsen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-10-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rdoc
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.12'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.12'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: jeweler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.0.1
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.1
|
62
|
+
description: ''
|
63
|
+
email: amunds@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files:
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.rdoc
|
69
|
+
files:
|
70
|
+
- .DS_Store
|
71
|
+
- .document
|
72
|
+
- Gemfile
|
73
|
+
- Gemfile.lock
|
74
|
+
- LICENSE.txt
|
75
|
+
- README.rdoc
|
76
|
+
- Rakefile
|
77
|
+
- VERSION
|
78
|
+
- dm_cloud.gemspec
|
79
|
+
- lib/dm_cloud/builder/media.rb
|
80
|
+
- lib/dm_cloud/media.rb
|
81
|
+
- lib/dm_cloud/request.rb
|
82
|
+
- lib/dm_cloud/signing.rb
|
83
|
+
- lib/dm_cloud/streaming.rb
|
84
|
+
- lib/dm_cloud/version.rb
|
85
|
+
- lib/dmcloud.rb
|
86
|
+
- test/helper.rb
|
87
|
+
- test/test_dm_cloud.rb
|
88
|
+
homepage: http://github.com/amunds/dm_cloud
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
hash: 3795479608390353071
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.23
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Communicate with DM Cloud
|
116
|
+
test_files: []
|