dmcloud-ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -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)
@@ -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.
@@ -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
+
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module DmCloud
2
+ VERSION = "0.0.65"
3
+ end
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestDmCloud < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ 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: []