s3_media_server_api 0.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 +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +188 -0
- data/Rakefile +10 -0
- data/lib/s3_media_server_api.rb +24 -0
- data/lib/s3_media_server_api/asynk_request.rb +30 -0
- data/lib/s3_media_server_api/aws_file.rb +106 -0
- data/lib/s3_media_server_api/config.rb +26 -0
- data/lib/s3_media_server_api/error.rb +9 -0
- data/lib/s3_media_server_api/file_part.rb +69 -0
- data/lib/s3_media_server_api/media.rb +11 -0
- data/lib/s3_media_server_api/media/audio.rb +56 -0
- data/lib/s3_media_server_api/media/common_media_api.rb +98 -0
- data/lib/s3_media_server_api/media/document.rb +26 -0
- data/lib/s3_media_server_api/media/image.rb +69 -0
- data/lib/s3_media_server_api/media/video.rb +72 -0
- data/lib/s3_media_server_api/uploader.rb +107 -0
- data/lib/s3_media_server_api/version.rb +3 -0
- data/s3_media_server_api.gemspec +27 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 49178c41050a932cd11ba20ab661e0e3353e64d4
|
4
|
+
data.tar.gz: bf5572843932b79e5424b649b1fa3db92432be50
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 415b6b8a82653c15cd62d9bbca6b0f4cabd12cd44951d9c5bc954fa55ab29dd302bf1fe7845bdb084d6ce2b3b4987d7f402ba9d1782f657307436cd26603d50e
|
7
|
+
data.tar.gz: 0e2df36585d3c516f74a322de409580c2c07da68a2ea1386b8b02760b4543786504fee4ffc2e524806f00e8ca70ec03ad47ef50596f224383bc0fa3f111ff7d6
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Ayrat Badykov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# S3MediaServerApi - - a Ruby client for the S3 Media Server
|
2
|
+
|
3
|
+
S3MediaServerApi helps you write apps that need to interact with S3 Media Server.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 's3_media_server_api'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install s3_media_server_api
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
### Available resources
|
24
|
+
|
25
|
+
#### S3MediaServerApi::Media::Document
|
26
|
+
Use S3MediaServerApi::Media::Document to interact Document resource
|
27
|
+
```ruby
|
28
|
+
# if you want create document from file path, use create method
|
29
|
+
created_document = S3MediaServerApi::Media::Document.create('/Users/ayrat/Development/s3_media_server_api/tmp/sample_mpeg4.mp4')
|
30
|
+
|
31
|
+
|
32
|
+
# to resolve document, use resolve method
|
33
|
+
resolved_document = S3MediaServerApi::Media::Document.resolve(created_document.uuid)
|
34
|
+
|
35
|
+
# both methods create and resolve return document object
|
36
|
+
|
37
|
+
resolved_document.uuid # uuid of document
|
38
|
+
resolved_document.url # url of document
|
39
|
+
resolved_document.size # size of document
|
40
|
+
resolved_document.name # url of document
|
41
|
+
|
42
|
+
# use destroy method, to destroy document
|
43
|
+
# this method is asynchronous, so it doesn't return anything
|
44
|
+
S3MediaServerApi::Media::Document.destroy(created_document.uuid)
|
45
|
+
```
|
46
|
+
#### S3MediaServerApi::Media::Image
|
47
|
+
Use S3MediaServerApi::Media::Image to interact Image resource
|
48
|
+
```ruby
|
49
|
+
# if you want create image from file path, use create method
|
50
|
+
created_image = S3MediaServerApi::Media::Image.create('/Users/ayrat/Development/s3_media_server_api/tmp/test_image.jpg')
|
51
|
+
|
52
|
+
# to resolve image, use resolve method
|
53
|
+
resolved_image = S3MediaServerApi::Media::Image.resolve(created_image.uuid)
|
54
|
+
|
55
|
+
# both methods create and resolve return document object
|
56
|
+
resolved_image.size
|
57
|
+
resolved_image.name
|
58
|
+
resolved_image.uuid
|
59
|
+
resolved_image.source.url
|
60
|
+
resolved_image.source.width
|
61
|
+
resolved_image.source.height
|
62
|
+
|
63
|
+
# to create thumb of image, use resize method.
|
64
|
+
# this method is asynchronous
|
65
|
+
S3MediaServerApi::Media::Image.resize(created_image.uuid)
|
66
|
+
|
67
|
+
# after thumb is created, new attributes will be available
|
68
|
+
thumbed_image = S3MediaServerApi::Media::Image.resolve(created_image.uuid)
|
69
|
+
thumbed_image.thumb.url
|
70
|
+
thumbed_image.thumb.width
|
71
|
+
thumbed_image.thumb.height
|
72
|
+
|
73
|
+
# to copy image, use copy method
|
74
|
+
copied_image = S3MediaServerApi::Media::Image.copy(created_image.uuid)
|
75
|
+
|
76
|
+
# to destroy image, use destroy method
|
77
|
+
# this method is asynchronous
|
78
|
+
S3MediaServerApi::Media::Image.destroy(created_image.uuid)
|
79
|
+
```
|
80
|
+
#### S3MediaServerApi::Media::Audio
|
81
|
+
Use S3MediaServerApi::Media::Audio to interact Audio resource
|
82
|
+
```ruby
|
83
|
+
# to create audio file from its path, use create method
|
84
|
+
created_audio = S3MediaServerApi::Media::Audio.create('/Users/ayrat/Development/s3_media_server_api/tmp/music_test.mp3')
|
85
|
+
# to resolve image, use resolve method
|
86
|
+
resolved_audio = S3MediaServerApi::Media::Audio.resolve(created_audio.uuid)
|
87
|
+
|
88
|
+
# both methods create and resolve return audio object
|
89
|
+
resolved_audio.url
|
90
|
+
resolved_audio.uuid
|
91
|
+
resolved_audio.name
|
92
|
+
resolved_audio.size
|
93
|
+
|
94
|
+
# cut method sends request to create audio sampel
|
95
|
+
# parameters: uuid - uuid of file
|
96
|
+
# audio_url - url of audio file
|
97
|
+
# duration - duration of cutted file
|
98
|
+
# start_position - position where cut wil be made
|
99
|
+
# this method is asynchronous
|
100
|
+
cut_params = { audio_url: created_audio.url,
|
101
|
+
duration: 20,
|
102
|
+
start_position: 40}
|
103
|
+
S3MediaServerApi::Media::Audio.cut(created_audio.uuid, cut_params)
|
104
|
+
|
105
|
+
# after audio is cutted, new attributes will be available
|
106
|
+
cutted_audio = S3MediaServerApi::Media::Audio.resolve(created_audio.uuid)
|
107
|
+
|
108
|
+
cutted_audio.sample_url
|
109
|
+
cutted_audio.duration
|
110
|
+
cutted_audio.sample_duration
|
111
|
+
|
112
|
+
# destroys audio file
|
113
|
+
# this method is asynchronous
|
114
|
+
S3MediaServerApi::Media::Audio.destroy(created_audio.uuid)
|
115
|
+
```
|
116
|
+
#### S3MediaServerApi::Media::Video
|
117
|
+
Use S3MediaServerApi::Media::Video to interact Video resource
|
118
|
+
```ruby
|
119
|
+
# to create video from its path, use create method
|
120
|
+
video = S3MediaServerApi::Media::Video.create('/Users/ayrat/Development/s3_media_server_api/tmp/sample_mpeg4.mp4')
|
121
|
+
|
122
|
+
# to resolve video, use resolve method
|
123
|
+
video = S3MediaServerApi::Media::Video.resolve(video.uuid)
|
124
|
+
|
125
|
+
# both methods create and resolve return video object
|
126
|
+
video.
|
127
|
+
|
128
|
+
video.name
|
129
|
+
video.embed_url
|
130
|
+
video.transcoded # true if video has all versions
|
131
|
+
# false otherwise
|
132
|
+
|
133
|
+
# after screenshots are made, screenshots attributes will be available with 3 screenshots
|
134
|
+
video = S3MediaServerApi::Media::Video.resolve(video.uuid)
|
135
|
+
video.screenshots[0].source.url
|
136
|
+
video.screenshots[0].source.width
|
137
|
+
video.screenshots[0].source.height
|
138
|
+
video.screenshots[0].thumb.url
|
139
|
+
video.screenshots[0].thumb.width
|
140
|
+
video.screenshots[0].thumb.height
|
141
|
+
|
142
|
+
# you can get available video versions in version attribute
|
143
|
+
|
144
|
+
video.versions[0].format
|
145
|
+
video.versions[0].resolution
|
146
|
+
video.versions[0].url
|
147
|
+
video.versions[0].size
|
148
|
+
|
149
|
+
# to destroy video, use destroy method
|
150
|
+
# this method is asynchronous
|
151
|
+
S3MediaServerApi::Media::Video.destroy(video.uuid)
|
152
|
+
```
|
153
|
+
|
154
|
+
#### AwsFile
|
155
|
+
```ruby
|
156
|
+
# to create aws file from its path, use upload method from S3MediaServerApi::Uploader module
|
157
|
+
aws_file = S3MediaServerApi::Uploader.upload('/Users/ayrat/Development/s3_media_server_api/tmp/test_image.jpg')
|
158
|
+
|
159
|
+
# to resolve aws file, use resolve method
|
160
|
+
resolved_aws_file = S3MediaServerApi::AwsFile.resolve(aws_file.uuid)
|
161
|
+
|
162
|
+
# both S3MediaServerApi::Uploader.upload and S3MediaServerApi::AwsFile.resolve return aws file object
|
163
|
+
aws_file.uuid
|
164
|
+
aws_file.size
|
165
|
+
aws_file.mime_type
|
166
|
+
aws_file.uploads_count
|
167
|
+
aws_file.default_part_size
|
168
|
+
aws_file.state
|
169
|
+
aws_file.public_url
|
170
|
+
aws_file.name
|
171
|
+
|
172
|
+
```
|
173
|
+
NOTE: you can't remove aws file without creating media resource
|
174
|
+
|
175
|
+
## Development
|
176
|
+
|
177
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
178
|
+
|
179
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
180
|
+
|
181
|
+
## Contributing
|
182
|
+
|
183
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/s3_media_server_api.
|
184
|
+
|
185
|
+
|
186
|
+
## License
|
187
|
+
|
188
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "s3_media_server_api/version"
|
2
|
+
require "s3_media_server_api/error"
|
3
|
+
require "s3_media_server_api/config"
|
4
|
+
require "s3_media_server_api/uploader"
|
5
|
+
require "s3_media_server_api/file_part"
|
6
|
+
require "s3_media_server_api/aws_file"
|
7
|
+
require "s3_media_server_api/media"
|
8
|
+
require "s3_media_server_api/asynk_request"
|
9
|
+
|
10
|
+
module S3MediaServerApi
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def upload_thread_count
|
15
|
+
config.upload_thread_count
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def config
|
21
|
+
@@config ||= Config.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module AsynkRequest
|
3
|
+
class << self
|
4
|
+
#
|
5
|
+
# sends asynchronous request using Asynk gem - https://github.com/konalegi/asynk
|
6
|
+
# parameters: path - base path of consumer
|
7
|
+
# action - consumer action
|
8
|
+
# params - parametes that will be passed to consumer
|
9
|
+
#
|
10
|
+
def async_request(path, action, params)
|
11
|
+
Asynk::Publisher.publish("#{server}.#{path}.#{action}", params)
|
12
|
+
end
|
13
|
+
#
|
14
|
+
# sends synchronous request using Asynk gem - https://github.com/konalegi/asynk
|
15
|
+
# parameters: path - base path of consumer
|
16
|
+
# action - consumer action
|
17
|
+
# params - parametes that will be passed to consumer
|
18
|
+
#
|
19
|
+
def sync_request(path, action, params)
|
20
|
+
Asynk::Publisher.sync_publish("#{server}.#{path}.#{action}", params)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def server
|
26
|
+
's3_media_server'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
class AwsFile
|
3
|
+
|
4
|
+
def initialize(response)
|
5
|
+
@params = response[:data]
|
6
|
+
end
|
7
|
+
|
8
|
+
def uuid
|
9
|
+
@params[:uuid]
|
10
|
+
end
|
11
|
+
|
12
|
+
def size
|
13
|
+
@params[:size]
|
14
|
+
end
|
15
|
+
|
16
|
+
def mime_type
|
17
|
+
@params[:mime_type]
|
18
|
+
end
|
19
|
+
|
20
|
+
def uploads_count
|
21
|
+
@params[:uploads_count]
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_part_size
|
25
|
+
@params[:default_part_size]
|
26
|
+
end
|
27
|
+
|
28
|
+
def state
|
29
|
+
@params[:state]
|
30
|
+
end
|
31
|
+
|
32
|
+
def public_url
|
33
|
+
@params[:public_url]
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
@params[:name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def as_hash
|
41
|
+
@params
|
42
|
+
end
|
43
|
+
|
44
|
+
class << self
|
45
|
+
class AwsFileError < S3MediaServerApiError; end
|
46
|
+
class FileCreationError < AwsFileError; end
|
47
|
+
class CompleteUploadError < AwsFileError; end
|
48
|
+
#
|
49
|
+
# creates file on s3_media_server
|
50
|
+
# parameters: file_path - path on file system to file
|
51
|
+
#
|
52
|
+
# returns: response with created file
|
53
|
+
#
|
54
|
+
def create_from_path(file_path)
|
55
|
+
params = {
|
56
|
+
size: File.size(file_path),
|
57
|
+
mime_type: file_mime_type(file_path),
|
58
|
+
name: File.basename(file_path)
|
59
|
+
}
|
60
|
+
response = AsynkRequest.sync_request(base_path, :create, params)
|
61
|
+
raise FileCreationError.message_from_asynk_response(response) unless response.success?
|
62
|
+
AwsFile.new(response)
|
63
|
+
end
|
64
|
+
#
|
65
|
+
# fetches media file
|
66
|
+
# parameters: uuid - uuid of file
|
67
|
+
#
|
68
|
+
# returns: response with file information
|
69
|
+
#
|
70
|
+
def resolve(uuid)
|
71
|
+
AsynkRequest.sync_request(base_path, :resolve, uuid: uuid)
|
72
|
+
end
|
73
|
+
#
|
74
|
+
# fetches signed upload url to upload specified part number
|
75
|
+
# parameters: uuid - file uuid
|
76
|
+
# part_numer - part number that will be uploaded
|
77
|
+
#
|
78
|
+
# returns: signed upload url
|
79
|
+
#
|
80
|
+
def get_signed_upload_url(uuid, part_number)
|
81
|
+
response = AsynkRequest.sync_request(:uploads, :show, aws_file_uuid: uuid, uuid: part_number)
|
82
|
+
response[:data][:upload_url]
|
83
|
+
end
|
84
|
+
#
|
85
|
+
# completes multipart upload
|
86
|
+
# parameters: uuid - file uuid
|
87
|
+
#
|
88
|
+
def complete_upload(uuid)
|
89
|
+
response = AsynkRequest.sync_request(base_path, :complete_upload, uuid: uuid)
|
90
|
+
raise CompleteUploadError.message_from_asynk_response(response) unless response.success?
|
91
|
+
AwsFile.new(response)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def file_mime_type(file_source_path)
|
97
|
+
mime_magic = MimeMagic.by_magic(File.open(file_source_path))
|
98
|
+
mime_magic ? mime_magic.type : 'application/octet-stream'
|
99
|
+
end
|
100
|
+
|
101
|
+
def base_path
|
102
|
+
'aws_file'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module S3MediaServerApi
|
4
|
+
class ConfigError < S3MediaServerApiError; end
|
5
|
+
#
|
6
|
+
# By default 4 threads is used for file uploading. To change
|
7
|
+
# this value you can create config/s3_media_server_api.yml and specify
|
8
|
+
# upload_thread_count
|
9
|
+
#
|
10
|
+
# Example
|
11
|
+
# prosuction:
|
12
|
+
# upload_thread_count: 10
|
13
|
+
#
|
14
|
+
class Config
|
15
|
+
attr_reader :upload_thread_count
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@upload_thread_count = nil
|
19
|
+
environment = ENV['RUBY_ENV'] || ENV['RAILS_ENV']
|
20
|
+
if File.exists?('config/s3_media_server_api.yml')
|
21
|
+
@attributes = YAML.load_file('config/s3_media_server_api.yml')[environment][:upload_thread_count]
|
22
|
+
end
|
23
|
+
@upload_thread_count ||= 4
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Uploader
|
3
|
+
|
4
|
+
class FilePart
|
5
|
+
# @option options [required,String,Pathname,File,Tempfile] :source
|
6
|
+
# @option options [required,Integer] :offset The file part will read
|
7
|
+
# starting at this byte offset.
|
8
|
+
# @option options [required,Integer] :size The maximum number of bytes to
|
9
|
+
# read from the `:offset`.
|
10
|
+
def initialize(options = {})
|
11
|
+
@source = options[:source]
|
12
|
+
@first_byte = options[:offset]
|
13
|
+
@last_byte = @first_byte + options[:size]
|
14
|
+
@size = options[:size]
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String,Pathname,File,Tempfile]
|
18
|
+
attr_reader :source
|
19
|
+
|
20
|
+
# @return [Integer]
|
21
|
+
attr_reader :first_byte
|
22
|
+
|
23
|
+
# @return [Integer]
|
24
|
+
attr_reader :last_byte
|
25
|
+
|
26
|
+
# @return [Integer]
|
27
|
+
attr_reader :size
|
28
|
+
|
29
|
+
def read(bytes = nil, output_buffer = nil)
|
30
|
+
open_file unless @file
|
31
|
+
read_from_file(bytes, output_buffer)
|
32
|
+
end
|
33
|
+
|
34
|
+
def rewind
|
35
|
+
if @file
|
36
|
+
@file.seek(@first_byte)
|
37
|
+
@position = @first_byte
|
38
|
+
end
|
39
|
+
0
|
40
|
+
end
|
41
|
+
|
42
|
+
def close
|
43
|
+
@file.close if @file
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def open_file
|
49
|
+
@file = File.open(@source, 'rb')
|
50
|
+
rewind
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_from_file(bytes, output_buffer)
|
54
|
+
if bytes
|
55
|
+
data = @file.read([remaining_bytes, bytes].min)
|
56
|
+
data = nil if data == ''
|
57
|
+
else
|
58
|
+
data = @file.read(remaining_bytes)
|
59
|
+
end
|
60
|
+
@position += data ? data.bytesize : 0
|
61
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
62
|
+
end
|
63
|
+
|
64
|
+
def remaining_bytes
|
65
|
+
@last_byte - @position
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 's3_media_server_api/media/common_media_api'
|
2
|
+
require 's3_media_server_api/media/image'
|
3
|
+
require 's3_media_server_api/media/video'
|
4
|
+
require 's3_media_server_api/media/audio'
|
5
|
+
require 's3_media_server_api/media/document'
|
6
|
+
|
7
|
+
|
8
|
+
module S3MediaServerApi
|
9
|
+
module Media
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Media
|
3
|
+
class Audio < CommonMediaApi
|
4
|
+
AUDIO = 'audio'
|
5
|
+
|
6
|
+
def sample_url
|
7
|
+
@params[:sample_url]
|
8
|
+
end
|
9
|
+
|
10
|
+
def url
|
11
|
+
@params[:url]
|
12
|
+
end
|
13
|
+
|
14
|
+
def sample_url
|
15
|
+
@params[:sample_url]
|
16
|
+
end
|
17
|
+
|
18
|
+
def duration
|
19
|
+
@params[:duration]
|
20
|
+
end
|
21
|
+
|
22
|
+
def sample_duration
|
23
|
+
@params[:sample_duration]
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def create(path)
|
28
|
+
Audio.new(super(path))
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve(uuid)
|
32
|
+
Audio.new(super(uuid))
|
33
|
+
end
|
34
|
+
#
|
35
|
+
# sends request to cut audio file
|
36
|
+
# parameters: uuid - uuid of file
|
37
|
+
# audio_url - url of audio file
|
38
|
+
# duration - duration of audio file
|
39
|
+
# start_position - position where cut wil be made
|
40
|
+
#
|
41
|
+
def cut(uuid, audio_url: audio_url, duration: duration, start_position: start_position)
|
42
|
+
params = {
|
43
|
+
uuid: uuid,
|
44
|
+
audio_url: audio_url,
|
45
|
+
duration: duration,
|
46
|
+
start_position: start_position
|
47
|
+
}
|
48
|
+
custom_async_request(:cut, params)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def media_type; AUDIO; end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Media
|
3
|
+
class CommonMediaApiError < S3MediaServerApiError; end
|
4
|
+
class CreationError < CommonMediaApiError ; end
|
5
|
+
#
|
6
|
+
# Parent module for all media api, implements common methods for all media files
|
7
|
+
# To use methods from this module new module must be inherited from this module and
|
8
|
+
# media_type method with existing media type must be overwritten
|
9
|
+
#
|
10
|
+
class CommonMediaApi
|
11
|
+
|
12
|
+
def initialize(response)
|
13
|
+
@params = response[:data]
|
14
|
+
end
|
15
|
+
|
16
|
+
def uuid
|
17
|
+
@params[:uuid]
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
@params[:name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@params[:size]
|
26
|
+
end
|
27
|
+
|
28
|
+
def as_hash
|
29
|
+
@params
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
#
|
34
|
+
# creates media file
|
35
|
+
# parameters: path - file path in file system
|
36
|
+
#
|
37
|
+
# returns: response with created AwsFile information
|
38
|
+
#
|
39
|
+
def create(path)
|
40
|
+
aws_file = Uploader.upload(path)
|
41
|
+
uuid = aws_file.uuid
|
42
|
+
params = (media_type == 'video') ? { uuid: uuid } : { aws_file_uuid: uuid }
|
43
|
+
response = AsynkRequest.sync_request(base_path, :create, params)
|
44
|
+
raise CreationError.message_from_asynk_response(response) unless response.success?
|
45
|
+
response
|
46
|
+
end
|
47
|
+
#
|
48
|
+
# destroys media file
|
49
|
+
# parameters: uuid - uuid of file
|
50
|
+
#
|
51
|
+
def destroy(uuid)
|
52
|
+
AsynkRequest.async_request(base_path, :destroy, uuid: uuid)
|
53
|
+
end
|
54
|
+
#
|
55
|
+
# fetches media file
|
56
|
+
# parameters: uuid - uuid of file
|
57
|
+
#
|
58
|
+
# returns: response with file information
|
59
|
+
#
|
60
|
+
def resolve(uuid)
|
61
|
+
AsynkRequest.sync_request(base_path, :resolve, uuid: uuid)
|
62
|
+
end
|
63
|
+
#
|
64
|
+
# this method should be used to send custom synchronous request to
|
65
|
+
# s3_media_server. For example, to copy image file
|
66
|
+
# parameters: action - method that should be called
|
67
|
+
# params - parameters for specified method
|
68
|
+
# Example:
|
69
|
+
# custom_sync_request (:copy, 'image')
|
70
|
+
#
|
71
|
+
def custom_sync_request(action, params)
|
72
|
+
AsynkRequest.sync_request(base_path, action, params)
|
73
|
+
end
|
74
|
+
#
|
75
|
+
# this method should be used to send custom asynchronous request to
|
76
|
+
# s3_media_server. For example, to cut audio file
|
77
|
+
# parameters: action - method that should be called
|
78
|
+
# params - parameters for specified method
|
79
|
+
# Example:
|
80
|
+
# custom_async_request (:cut, 'image')
|
81
|
+
#
|
82
|
+
def custom_async_request(action, params)
|
83
|
+
AsynkRequest.async_request(base_path, action, params)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
#
|
88
|
+
# specifies media type which methods will be called
|
89
|
+
#
|
90
|
+
def media_type; end
|
91
|
+
#
|
92
|
+
# base path of media consumers on s3_media_server
|
93
|
+
#
|
94
|
+
def base_path; "media.#{media_type}"; end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Media
|
3
|
+
class Document< CommonMediaApi
|
4
|
+
DOCUMENT = 'document'
|
5
|
+
|
6
|
+
def url
|
7
|
+
@params[:url]
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def create(path)
|
13
|
+
Document.new(super(path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def resolve(uuid)
|
17
|
+
Document.new(super(uuid))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def media_type; DOCUMENT; end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Media
|
3
|
+
class Image< CommonMediaApi
|
4
|
+
IMAGE = 'image'
|
5
|
+
|
6
|
+
class ImageObject
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def url
|
13
|
+
@params[:url]
|
14
|
+
end
|
15
|
+
|
16
|
+
def width
|
17
|
+
@params[:width]
|
18
|
+
end
|
19
|
+
|
20
|
+
def height
|
21
|
+
@params[:height]
|
22
|
+
end
|
23
|
+
|
24
|
+
def as_hash
|
25
|
+
@params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def source
|
30
|
+
ImageObject.new(@params[:source])
|
31
|
+
end
|
32
|
+
|
33
|
+
def thumb
|
34
|
+
ImageObject.new(@params[:thumb])
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
|
39
|
+
def create(path)
|
40
|
+
Image.new(super(path))
|
41
|
+
end
|
42
|
+
|
43
|
+
def resolve(uuid)
|
44
|
+
Image.new(super(uuid))
|
45
|
+
end
|
46
|
+
#
|
47
|
+
# copies image file
|
48
|
+
# parameters: uuid - uuid of file
|
49
|
+
# aduio - url of image file
|
50
|
+
# returns: response with copied image
|
51
|
+
#
|
52
|
+
def copy(uuid)
|
53
|
+
Image.new(custom_sync_request(:copy, uuid: uuid))
|
54
|
+
end
|
55
|
+
#
|
56
|
+
# sends request to make thumb of image
|
57
|
+
# parameters: uuid - uuid of file
|
58
|
+
#
|
59
|
+
def resize(uuid)
|
60
|
+
custom_async_request(:resize, uuid: uuid)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def media_type; IMAGE; end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module S3MediaServerApi
|
2
|
+
module Media
|
3
|
+
class Video< CommonMediaApi
|
4
|
+
VIDEO = 'video'
|
5
|
+
|
6
|
+
class Version
|
7
|
+
def initialize(params)
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def url
|
12
|
+
@params[:url]
|
13
|
+
end
|
14
|
+
|
15
|
+
def format
|
16
|
+
@params[:format]
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolution
|
20
|
+
@params[:resolution]
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
@params[:size]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def preview
|
29
|
+
@params[:preview]
|
30
|
+
end
|
31
|
+
|
32
|
+
def duration
|
33
|
+
@params[:duration]
|
34
|
+
end
|
35
|
+
|
36
|
+
def transcoded
|
37
|
+
@params[:transcoded]
|
38
|
+
end
|
39
|
+
|
40
|
+
def embed_url
|
41
|
+
@params[:embed_url]
|
42
|
+
end
|
43
|
+
|
44
|
+
def provider
|
45
|
+
@params[:provider]
|
46
|
+
end
|
47
|
+
|
48
|
+
def screenshots
|
49
|
+
@params[:screenshots].map { |screenshot| Image::ImageObject.new(screenshot) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def versions
|
53
|
+
@params[:versions].map { |version| Version.new(version) }
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
|
58
|
+
def create(path)
|
59
|
+
Video.new(super(path))
|
60
|
+
end
|
61
|
+
|
62
|
+
def resolve(uuid)
|
63
|
+
Video.new(super(uuid))
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def media_type; VIDEO; end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'mimemagic'
|
2
|
+
require 'asynk'
|
3
|
+
require 'parallel'
|
4
|
+
require 'faraday'
|
5
|
+
|
6
|
+
module S3MediaServerApi
|
7
|
+
|
8
|
+
# Module with file uplod finctionality implementation
|
9
|
+
|
10
|
+
module Uploader
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
class UploaderError < S3MediaServerApiError; end
|
15
|
+
class PartUploadError < UploaderError; end
|
16
|
+
#
|
17
|
+
# uploads file to amazon s3 and create AwsFile object
|
18
|
+
# parameter : filepath - file path in file system
|
19
|
+
# returns: [AwsFile object]
|
20
|
+
#
|
21
|
+
# Example
|
22
|
+
#
|
23
|
+
# file = S3MediaServerApi::Uploader.upload(/home/vasya/my_awesome_file.awesome)
|
24
|
+
#
|
25
|
+
def upload(file_path)
|
26
|
+
parts = []
|
27
|
+
file = AwsFile.create_from_path(file_path)
|
28
|
+
default_part_size = file.default_part_size
|
29
|
+
aws_file_uuid = file.uuid
|
30
|
+
uploads_count = file.uploads_count
|
31
|
+
parts = compute_parts(file_path, default_part_size)
|
32
|
+
Parallel.each(parts, in_threads: S3MediaServerApi.upload_thread_count) do |part|
|
33
|
+
signed_upload_url = AwsFile.get_signed_upload_url(aws_file_uuid, part[:part_number])
|
34
|
+
|
35
|
+
raise PartUploadError.new("Part #{part[:part_number]} wasn't uploaded") unless upload_part(signed_upload_url, part[:body].read)
|
36
|
+
end
|
37
|
+
|
38
|
+
AwsFile.complete_upload(aws_file_uuid)
|
39
|
+
ensure
|
40
|
+
close_all_parts(parts)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
#
|
45
|
+
# closes all parts of file that were used in multipart upload to prevent memory leak
|
46
|
+
#
|
47
|
+
def close_all_parts(parts)
|
48
|
+
parts.each do |part|
|
49
|
+
part[:body].close
|
50
|
+
end
|
51
|
+
end
|
52
|
+
#
|
53
|
+
# divides file into parts for multipart upload
|
54
|
+
# parameter:
|
55
|
+
# source - path of source file
|
56
|
+
# default_part_size - wanted part size (in bytes)
|
57
|
+
# returns: [ array of parts ]
|
58
|
+
#
|
59
|
+
def compute_parts(source, default_part_size)
|
60
|
+
size = File.size(source)
|
61
|
+
offset, part_number, parts = 0, 1, []
|
62
|
+
while offset < size
|
63
|
+
parts << {
|
64
|
+
part_number: part_number,
|
65
|
+
body: FilePart.new(source: source, offset: offset, size: part_size(size, default_part_size, offset))
|
66
|
+
}
|
67
|
+
|
68
|
+
part_number += 1
|
69
|
+
offset += default_part_size
|
70
|
+
end
|
71
|
+
parts
|
72
|
+
end
|
73
|
+
#
|
74
|
+
# calculates size of one part - last part often has smaller size than other parts
|
75
|
+
# parameters: total_size - totol size of file (in bytes)
|
76
|
+
# part_size - default size of one part (in bytes)
|
77
|
+
# offset - offset from beginning of file (in bytes)
|
78
|
+
# returns: part size
|
79
|
+
#
|
80
|
+
def part_size(total_size, part_size, offset)
|
81
|
+
if offset + part_size > total_size
|
82
|
+
total_size - offset
|
83
|
+
else
|
84
|
+
part_size
|
85
|
+
end
|
86
|
+
end
|
87
|
+
#
|
88
|
+
# uploads data to specified url
|
89
|
+
# parameters: url - upload url
|
90
|
+
# data - data to upload
|
91
|
+
# returns: true if upload was successful
|
92
|
+
# false otherwise
|
93
|
+
#
|
94
|
+
def upload_part(url, data)
|
95
|
+
conn = Faraday.new(url: url) do |faraday|
|
96
|
+
faraday.adapter :net_http
|
97
|
+
end
|
98
|
+
resp = conn.put do |req|
|
99
|
+
req.body = data
|
100
|
+
# to prevent Faraday from adding garbage header
|
101
|
+
req.headers['Content-Type'] = ''
|
102
|
+
end
|
103
|
+
resp.success?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 's3_media_server_api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "s3_media_server_api"
|
8
|
+
spec.version = S3MediaServerApi::VERSION
|
9
|
+
spec.authors = ["Ayrat Badykov"]
|
10
|
+
spec.email = ["ayratin555@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = 'S3MediaServerApi helps you write apps that need to interact with S3 Media Server.'
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
23
|
+
spec.add_dependency 'asynk', '>= 0.0.1'
|
24
|
+
spec.add_dependency 'parallel'
|
25
|
+
spec.add_dependency 'mimemagic'
|
26
|
+
spec.add_dependency 'faraday'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: s3_media_server_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ayrat Badykov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: asynk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parallel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mimemagic
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: faraday
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- ayratin555@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- Gemfile
|
119
|
+
- LICENSE.txt
|
120
|
+
- README.md
|
121
|
+
- Rakefile
|
122
|
+
- lib/s3_media_server_api.rb
|
123
|
+
- lib/s3_media_server_api/asynk_request.rb
|
124
|
+
- lib/s3_media_server_api/aws_file.rb
|
125
|
+
- lib/s3_media_server_api/config.rb
|
126
|
+
- lib/s3_media_server_api/error.rb
|
127
|
+
- lib/s3_media_server_api/file_part.rb
|
128
|
+
- lib/s3_media_server_api/media.rb
|
129
|
+
- lib/s3_media_server_api/media/audio.rb
|
130
|
+
- lib/s3_media_server_api/media/common_media_api.rb
|
131
|
+
- lib/s3_media_server_api/media/document.rb
|
132
|
+
- lib/s3_media_server_api/media/image.rb
|
133
|
+
- lib/s3_media_server_api/media/video.rb
|
134
|
+
- lib/s3_media_server_api/uploader.rb
|
135
|
+
- lib/s3_media_server_api/version.rb
|
136
|
+
- s3_media_server_api.gemspec
|
137
|
+
homepage:
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.4.5.1
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: S3MediaServerApi helps you write apps that need to interact with S3 Media
|
161
|
+
Server.
|
162
|
+
test_files: []
|