mediakit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/README.md +96 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/mediakit/drivers.rb +138 -0
- data/lib/mediakit/ffmpeg/options.rb +236 -0
- data/lib/mediakit/ffmpeg.rb +64 -0
- data/lib/mediakit/ffprobe.rb +32 -0
- data/lib/mediakit/utils/popen_helper.rb +68 -0
- data/lib/mediakit/version.rb +3 -0
- data/lib/mediakit.rb +7 -0
- data/mediakit.gemspec +31 -0
- data/sample/raw_crop.rb +39 -0
- data/templates/codec.rb.erb +33 -0
- data/templates/decoder.rb.erb +27 -0
- data/templates/encoder.rb.erb +27 -0
- data/templates/format.rb.erb +9 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ba4ab75bf6e7d06e4d47117147e26dd2dccdb650
|
4
|
+
data.tar.gz: d4e1e172178d66a94c581696e00235a896567cf0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02b9db5afc15386e6398f267513cb5c7b040e540e7f6f677f5b9d9f70d410b49c16a28cea77a3006df674a95ce67d2e5ef8209629ce69e38c16bb52da3acdcb7
|
7
|
+
data.tar.gz: b42d2eb275b21769af3c90e3a8df77220cdd81abe5bc060f10912995ec6d2e0ffa230dba12f43eb34f036373aeacf7575e2e779b4d505f0c7878dd6405fd5b9b
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Mediakit
|
2
|
+
|
3
|
+
mediakit is the libraries for ffmpeg and sox backed media manipulation something.
|
4
|
+
I've design this library for following purpose.
|
5
|
+
|
6
|
+
* have low and high level interfaces as you like use
|
7
|
+
* easy testing design by separation of concern
|
8
|
+
|
9
|
+
## Development Plan
|
10
|
+
|
11
|
+
* [x] low-level interface for ffmpeg
|
12
|
+
* [ ] high-level interface for ffmpeg
|
13
|
+
* [ ] low-level interface for sox
|
14
|
+
* [ ] high-level interface for sox
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'mediakit'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install mediakit
|
31
|
+
|
32
|
+
## Requirements
|
33
|
+
|
34
|
+
This library behave command wrapper in your script.
|
35
|
+
So it need each binary command file.
|
36
|
+
|
37
|
+
* latest ffmpeg which have ffprobe command
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
### Low Level Usage
|
42
|
+
|
43
|
+
The low level means it's near command line usage.
|
44
|
+
This is a little bore interface for constructing options,
|
45
|
+
but can pass certain it.
|
46
|
+
|
47
|
+
```rb
|
48
|
+
driver = Mediakit::Drivers::FFmpeg.new
|
49
|
+
ffmpeg = Mediakit::Runners::FFmpeg.new(driver)
|
50
|
+
|
51
|
+
options = Mediakit::Runners::FFmpeg::Options.new(
|
52
|
+
Mediakit::Runners::FFmpeg::Options::GlobalOption.new(
|
53
|
+
't' => 100,
|
54
|
+
'y' => true,
|
55
|
+
),
|
56
|
+
Mediakit::Runners::FFmpeg::Options::InputFileOption.new(
|
57
|
+
options: nil,
|
58
|
+
path: input,
|
59
|
+
),
|
60
|
+
Mediakit::Runners::FFmpeg::Options::OutputFileOption.new(
|
61
|
+
options: {
|
62
|
+
'vf' => 'crop=320:320:0:0',
|
63
|
+
'ar' => '44100',
|
64
|
+
'ab' => '128k',
|
65
|
+
},
|
66
|
+
path: output,
|
67
|
+
),
|
68
|
+
)
|
69
|
+
puts "$ ffmpeg #options"
|
70
|
+
puts ffmpeg.run(options)
|
71
|
+
```
|
72
|
+
|
73
|
+
### High Level Usage
|
74
|
+
|
75
|
+
TBD
|
76
|
+
|
77
|
+
## Development
|
78
|
+
|
79
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
80
|
+
|
81
|
+
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` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
1. Fork it ( https://github.com/[my-github-username]/mediakit/fork )
|
86
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
87
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
88
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
89
|
+
5. Create a new Pull Request
|
90
|
+
|
91
|
+
## References
|
92
|
+
|
93
|
+
* [streamio/streamio-ffmpeg](https://github.com/streamio/streamio-ffmpeg)
|
94
|
+
* [ruby-av/av](https://github.com/ruby-av/av)
|
95
|
+
* [Xuggler](http://www.xuggle.com/xuggler/)
|
96
|
+
* [PHP-FFMpeg/PHP-FFMpeg](https://github.com/PHP-FFMpeg/PHP-FFMpeg)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mediakit"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "pry"
|
14
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'mediakit/utils/popen_helper'
|
3
|
+
|
4
|
+
module Mediakit
|
5
|
+
module Drivers
|
6
|
+
class DriverError < StandardError; end
|
7
|
+
class ConfigurationError < DriverError; end
|
8
|
+
|
9
|
+
class Base
|
10
|
+
attr_reader(:bin)
|
11
|
+
def initialize(bin)
|
12
|
+
@bin = bin
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*args)
|
16
|
+
raise(NotImplementedError)
|
17
|
+
end
|
18
|
+
|
19
|
+
def command(*args)
|
20
|
+
raise(NotImplementedError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PopenDriver < Base
|
25
|
+
# execute command and return result
|
26
|
+
#
|
27
|
+
# @overload run(args)
|
28
|
+
# @param args [String] arguments for command
|
29
|
+
# @overload run(*args)
|
30
|
+
# @param args [Array] arguments for command
|
31
|
+
# @return result [Bool] runners result
|
32
|
+
def run(*args)
|
33
|
+
begin
|
34
|
+
escaped_args = Mediakit::Utils::PopenHelper.escape(*args)
|
35
|
+
Mediakit::Utils::PopenHelper.run(bin, escaped_args)
|
36
|
+
rescue Mediakit::Utils::PopenHelper::CommandNotFoundError => e
|
37
|
+
raise(ConfigurationError, "cant' find bin in #{bin}.")
|
38
|
+
rescue => e
|
39
|
+
raise(DriverError, "#{self.class} catch error with command(#{Mediakit::Utils::PopenHelper.command(bin,escaped_args)}) - #{e.message}, #{e.backtrace.join("\n")}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# return command to execute
|
44
|
+
#
|
45
|
+
# @overload run(args)
|
46
|
+
# @param args [String] arguments for command
|
47
|
+
# @overload run(*args)
|
48
|
+
# @param args [Array] arguments for command
|
49
|
+
# @return result [String] runners to execute
|
50
|
+
def command(*args)
|
51
|
+
escaped_args = Mediakit::Utils::PopenHelper.escape(*args)
|
52
|
+
"#{bin} #{escaped_args}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class CocaineDriver < Base
|
57
|
+
# execute command and return result
|
58
|
+
#
|
59
|
+
# @overload run(args)
|
60
|
+
# @param args [String] arguments for command
|
61
|
+
# @overload run(*args)
|
62
|
+
# @param args [Array] arguments for command
|
63
|
+
# @return result [Bool] runners result
|
64
|
+
def run(args = '')
|
65
|
+
begin
|
66
|
+
# Force escape args string on here,
|
67
|
+
# because this design can't use Cocaine::CommandLine's safety solution.
|
68
|
+
escaped_args = Mediakit::Utils::PopenHelper.escape(args.dup)
|
69
|
+
command_line = Cocaine::CommandLine.new(bin, escaped_args, swallow_stderr: true)
|
70
|
+
command_line.run
|
71
|
+
rescue => e
|
72
|
+
raise(DriverError, "#{self.class} catch error with runners [$ #{command_line.command}] - #{e.message}, #{e.backtrace.join("\n")}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# return command to execute
|
77
|
+
#
|
78
|
+
# @overload run(args)
|
79
|
+
# @param args [String] arguments for command
|
80
|
+
# @overload run(*args)
|
81
|
+
# @param args [Array] arguments for command
|
82
|
+
# @return result [String] runners to execute
|
83
|
+
def command(args = '')
|
84
|
+
Cocaine::CommandLine.new(bin, args).command
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class FakeDriver < Base
|
89
|
+
def run(args = '')
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def command(args = '')
|
94
|
+
bin + args
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class AbstractFactory
|
99
|
+
class << self
|
100
|
+
attr_accessor(:bin_path)
|
101
|
+
|
102
|
+
def configure(&block)
|
103
|
+
yield(self)
|
104
|
+
end
|
105
|
+
|
106
|
+
def name
|
107
|
+
self.to_s.downcase.split('::').last
|
108
|
+
end
|
109
|
+
|
110
|
+
def bin
|
111
|
+
bin_path || name
|
112
|
+
end
|
113
|
+
|
114
|
+
def new(type = :popen)
|
115
|
+
case type.to_sym
|
116
|
+
when :popen
|
117
|
+
PopenDriver.new(bin)
|
118
|
+
when :cocaine
|
119
|
+
CocaineDriver.new(bin)
|
120
|
+
when :fake
|
121
|
+
FakeDriver.new(bin)
|
122
|
+
else
|
123
|
+
raise(ArgumentError)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class FFmpeg < AbstractFactory
|
130
|
+
end
|
131
|
+
|
132
|
+
class FFprobe < AbstractFactory
|
133
|
+
end
|
134
|
+
|
135
|
+
class Sox < AbstractFactory
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'active_support/ordered_hash'
|
2
|
+
|
3
|
+
module Mediakit
|
4
|
+
class FFmpeg
|
5
|
+
# presentation of ffmpeg runners option
|
6
|
+
#
|
7
|
+
# SYNOPSIS
|
8
|
+
# ffmpeg [global_options] {[input_file_options] -i input_file} ... {[output_file_options] output_file} ...
|
9
|
+
#
|
10
|
+
class Options
|
11
|
+
attr_reader(:global, :inputs, :output)
|
12
|
+
# constructor
|
13
|
+
#
|
14
|
+
# @option args [Mediakit::FFmpeg::Options::GlobalOption]
|
15
|
+
# @option args [Array<Mediakit::FFmpeg::Options::InputFileOption>] array object Mediakit::FFmpeg::Options::InputOption
|
16
|
+
# @option args [Mediakit::FFmpeg::Options::OutputFileOption]
|
17
|
+
def initialize(*args)
|
18
|
+
@global, @inputs, @output = nil, [], nil
|
19
|
+
args.each do |option|
|
20
|
+
add_option(option)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_option(option)
|
25
|
+
return if option.nil?
|
26
|
+
case option
|
27
|
+
when GlobalOption
|
28
|
+
set_global(option)
|
29
|
+
when InputFileOption
|
30
|
+
add_input(option)
|
31
|
+
when OutputFileOption
|
32
|
+
set_output(option)
|
33
|
+
else
|
34
|
+
raise(ArgumentError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def compose
|
39
|
+
composed_string = ''
|
40
|
+
composed_string << "#{global}" if global
|
41
|
+
composed_string << " #{inputs.map(&:compose).join(' ')}" if inputs && !inputs.empty?
|
42
|
+
composed_string << " #{output}" if output
|
43
|
+
composed_string
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :to_s, :compose
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def set_global(global)
|
51
|
+
@global = global
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_input(input)
|
55
|
+
@inputs.push(input)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_output(output)
|
59
|
+
@output = output
|
60
|
+
end
|
61
|
+
|
62
|
+
# Base class for Options
|
63
|
+
class OrderedHash < ActiveSupport::OrderedHash
|
64
|
+
# initializer
|
65
|
+
#
|
66
|
+
# @param options [Hash] initial option values
|
67
|
+
def initialize(options = {})
|
68
|
+
options.each { |key, value| raise_if_invalid_arg_error(key, value) } if options
|
69
|
+
self.merge!(options) if options && options.kind_of?(Hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
def []=(key, value)
|
73
|
+
raise_if_invalid_arg_error(key, value)
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def compose
|
78
|
+
self.map { |key, value| struct_option(key, value) }.compact.join(' ')
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
compose
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def raise_if_invalid_arg_error(key, value)
|
88
|
+
raise(ArgumentError, "#{self.class} can't accept nil value with key(#{key}).") if value.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def struct_option(key, value)
|
92
|
+
case value
|
93
|
+
when TrueClass, FalseClass
|
94
|
+
value ? "-#{key}" : nil
|
95
|
+
else
|
96
|
+
"-#{key} #{value}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class GlobalOption < OrderedHash
|
102
|
+
end
|
103
|
+
|
104
|
+
class OptionPathPair
|
105
|
+
attr_reader(:options, :path)
|
106
|
+
|
107
|
+
def initialize(options:, path:)
|
108
|
+
@options = options
|
109
|
+
@path = path
|
110
|
+
end
|
111
|
+
|
112
|
+
def compose
|
113
|
+
raise(NotImplementedError)
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_s
|
117
|
+
compose
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class InputFileOption < OptionPathPair
|
122
|
+
# @param options [Hash] input options
|
123
|
+
# @param path [String] input file path
|
124
|
+
def initialize(options:, path:)
|
125
|
+
ordered_hash = OrderedHash.new(options)
|
126
|
+
super(options: ordered_hash, path: path)
|
127
|
+
end
|
128
|
+
|
129
|
+
def compose
|
130
|
+
"#{options} -i #{path}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class OutputFileOption < OptionPathPair
|
135
|
+
# @param options [Hash] output options
|
136
|
+
# @param path [String] output file path
|
137
|
+
def initialize(options:, path:)
|
138
|
+
ordered_hash = OrderedHash.new(options)
|
139
|
+
super(options: ordered_hash, path: path)
|
140
|
+
end
|
141
|
+
|
142
|
+
def compose
|
143
|
+
"#{options} #{path}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class InputFileOptionCollection
|
148
|
+
attr_reader(:input_file_options)
|
149
|
+
|
150
|
+
# @param *input_file_options [Mediakit::FFmpeg::InputFileOptions]
|
151
|
+
def initialize(*input_file_options)
|
152
|
+
@options = input_file_options
|
153
|
+
end
|
154
|
+
|
155
|
+
def empty?
|
156
|
+
@options.empty?
|
157
|
+
end
|
158
|
+
|
159
|
+
def compose
|
160
|
+
@options.map(&:compose).join(' ')
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :to_s, :compose
|
164
|
+
end
|
165
|
+
|
166
|
+
# see https://www.ffmpeg.org/ffmpeg.html#Stream-specifiers-1
|
167
|
+
class StreamSpecifier
|
168
|
+
attr_reader(:stream_index, :stream_type, :program_id, :metadata_tag_key, :metadata_tag_value, :usable)
|
169
|
+
STREAM_TYPES = ['v', 'a', 's'].freeze
|
170
|
+
|
171
|
+
def initialize(stream_index: nil, stream_type: nil, program_id: nil, metadata_tag_key: nil, metadata_tag_value: nil, usable: nil)
|
172
|
+
raise(ArgumentError, "invalid args stream_type = #{stream_type}") if stream_type && !STREAM_TYPES.include?(stream_type)
|
173
|
+
@stream_index = stream_index
|
174
|
+
@stream_type = stream_type
|
175
|
+
@program_id = program_id
|
176
|
+
@metadata_tag_key = metadata_tag_key
|
177
|
+
@metadata_tag_value = metadata_tag_value
|
178
|
+
@usable = usable
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_s
|
182
|
+
case
|
183
|
+
when stream_index?, stream_type?
|
184
|
+
stream_specifier
|
185
|
+
when program?
|
186
|
+
program_specifier
|
187
|
+
when meta?
|
188
|
+
metadata_specifier
|
189
|
+
when usable?
|
190
|
+
usable_specifier
|
191
|
+
else
|
192
|
+
raise(RuntimeError)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def stream_specifier
|
199
|
+
[stream_type, stream_index].compact.join(':')
|
200
|
+
end
|
201
|
+
|
202
|
+
def program_specifier
|
203
|
+
["p:#{program_id}", stream_index].compact.join(':')
|
204
|
+
end
|
205
|
+
|
206
|
+
def metadata_specifier
|
207
|
+
[metadata_tag_key, metadata_tag_value].compact.join(':')
|
208
|
+
end
|
209
|
+
|
210
|
+
def usable_specifier
|
211
|
+
'u'
|
212
|
+
end
|
213
|
+
|
214
|
+
def stream_index?
|
215
|
+
!stream_index.nil?
|
216
|
+
end
|
217
|
+
|
218
|
+
def stream_type?
|
219
|
+
!stream_type.nil?
|
220
|
+
end
|
221
|
+
|
222
|
+
def meta?
|
223
|
+
!metadata_tag_key.nil?
|
224
|
+
end
|
225
|
+
|
226
|
+
def program?
|
227
|
+
!program_id.nil?
|
228
|
+
end
|
229
|
+
|
230
|
+
def usable?
|
231
|
+
!usable.nil?
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'mediakit/ffmpeg/options'
|
2
|
+
require 'mediakit/drivers'
|
3
|
+
|
4
|
+
module Mediakit
|
5
|
+
class FFmpeg
|
6
|
+
class FFmpegError < StandardError;
|
7
|
+
end
|
8
|
+
|
9
|
+
DELIMITER_FOR_CODECS = "\n -------\n".freeze
|
10
|
+
DELIMITER_FOR_FORMATS = "\n --\n".freeze
|
11
|
+
DELIMITER_FOR_CODER = "\n ------\n".freeze
|
12
|
+
|
13
|
+
def initialize(driver)
|
14
|
+
@driver = driver
|
15
|
+
end
|
16
|
+
|
17
|
+
# execute runners with options object
|
18
|
+
#
|
19
|
+
# @param options [Mediakit::Runners::FFmpeg::Options] options object to create CLI argument
|
20
|
+
def run(options)
|
21
|
+
args = options.compose
|
22
|
+
execute(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def command(options)
|
26
|
+
args = options.compose
|
27
|
+
@driver.command(args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def codecs
|
31
|
+
@codecs ||= run(global_options('codecs')).split(DELIMITER_FOR_CODECS)[1].each_line.to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def formats
|
35
|
+
@formats ||= run(global_options('formats')).split(DELIMITER_FOR_FORMATS)[1].each_line.to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def decoders
|
39
|
+
@decoders ||= run(global_options('decoders')).split(DELIMITER_FOR_CODER)[1].each_line.to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
def encoders
|
43
|
+
@encoders ||= run(global_options('encoders')).split(DELIMITER_FOR_CODER)[1].each_line.to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def execute(args = '')
|
49
|
+
begin
|
50
|
+
@driver.run(args)
|
51
|
+
rescue Drivers::DriverError => e
|
52
|
+
raise(FFmpegError, "#catch driver's error - #{e.message}, #{e.backtrace.join("\n")}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def global_options(flag)
|
57
|
+
Mediakit::FFmpeg::Options.new(
|
58
|
+
Mediakit::FFmpeg::Options::GlobalOptions.new(
|
59
|
+
flag => true,
|
60
|
+
),
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mediakit
|
2
|
+
class FFprobe
|
3
|
+
attr_reader(:driver)
|
4
|
+
|
5
|
+
class FFprobeError < StandardError;
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(driver)
|
9
|
+
@driver = driver
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(args = '')
|
13
|
+
begin
|
14
|
+
driver.run(args)
|
15
|
+
rescue => e
|
16
|
+
raise(FFprobeError, "#{self.class} catch error - #{e.message}, #{e.backtrace.join("\n")}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_json(path)
|
21
|
+
args = "#{default_show_options} -print_format json #{path}"
|
22
|
+
execute(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_show_options
|
26
|
+
[
|
27
|
+
'-show_streams',
|
28
|
+
'-show_format'
|
29
|
+
].join(' ')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Mediakit
|
5
|
+
module Utils
|
6
|
+
module PopenHelper
|
7
|
+
|
8
|
+
class CommandNotFoundError < StandardError;
|
9
|
+
end
|
10
|
+
|
11
|
+
# @overload run(command, *args)
|
12
|
+
# @param command [String]
|
13
|
+
# @param args [Array] args as array for safety shellescape
|
14
|
+
# @overload run(command, args)
|
15
|
+
# @param command [String] command name
|
16
|
+
# @param args [Array] args as string
|
17
|
+
# @return out [String] stdout of command
|
18
|
+
# @return err [String] stderr of command
|
19
|
+
# @return exit_status [Boolean] is succeeded
|
20
|
+
def run(bin, *args)
|
21
|
+
command = command(bin, *args)
|
22
|
+
|
23
|
+
out, err, exit_status = nil
|
24
|
+
begin
|
25
|
+
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
|
26
|
+
stdin.close
|
27
|
+
out = stdout.read
|
28
|
+
err = stderr.read
|
29
|
+
exit_status = (wait_thr.value.exitstatus == 0)
|
30
|
+
end
|
31
|
+
rescue Errno::ENOENT => e
|
32
|
+
raise(CommandNotFoundError, "Can't find command - #{command}, #{e.meessage}")
|
33
|
+
end
|
34
|
+
|
35
|
+
[out, err, exit_status]
|
36
|
+
end
|
37
|
+
|
38
|
+
module_function(:run)
|
39
|
+
|
40
|
+
def command(bin, *args)
|
41
|
+
escaped_args = escape(*args)
|
42
|
+
"#{bin} #{escaped_args}"
|
43
|
+
end
|
44
|
+
|
45
|
+
module_function(:command)
|
46
|
+
|
47
|
+
def escape(*args)
|
48
|
+
case args.size
|
49
|
+
when 1
|
50
|
+
_escape_with_split(args[0])
|
51
|
+
else
|
52
|
+
Shellwords.join(args.map { |x| Shellwords.escape(x) })
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module_function(:escape)
|
57
|
+
|
58
|
+
def _escape_with_split(string)
|
59
|
+
splits = Shellwords.split(string)
|
60
|
+
splits = splits.map { |x| Shellwords.escape(x) }
|
61
|
+
splits.join(' ')
|
62
|
+
end
|
63
|
+
|
64
|
+
module_function(:_escape_with_split)
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/mediakit.rb
ADDED
data/mediakit.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mediakit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mediakit"
|
8
|
+
spec.version = Mediakit::VERSION
|
9
|
+
spec.authors = ["ainame"]
|
10
|
+
spec.email = ["s.namai.09@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = 'mediakit is the libraries for ffmpeg and sox backed media manipulation something.'
|
13
|
+
spec.description = <<EOS
|
14
|
+
mediakit is the libraries for ffmpeg and sox backed media manipulation something.
|
15
|
+
you can create complex manipulation for media as a ruby code.
|
16
|
+
EOS
|
17
|
+
|
18
|
+
spec.homepage = "https://github.com/ainame/mediakit"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "cocaine", "~> 0.5.7"
|
26
|
+
spec.add_runtime_dependency "activesupport", "~> 4"
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
30
|
+
spec.add_development_dependency "ruby-debug-ide", "~> 0.4"
|
31
|
+
end
|
data/sample/raw_crop.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mediakit"
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
def transcode_option(input, output)
|
8
|
+
options = Mediakit::FFmpeg::Options.new(
|
9
|
+
Mediakit::FFmpeg::Options::GlobalOption.new(
|
10
|
+
't' => 100,
|
11
|
+
'y' => true,
|
12
|
+
),
|
13
|
+
Mediakit::FFmpeg::Options::InputFileOption.new(
|
14
|
+
options: nil,
|
15
|
+
path: input,
|
16
|
+
),
|
17
|
+
Mediakit::FFmpeg::Options::InputFileOption.new(
|
18
|
+
options: nil,
|
19
|
+
path: input,
|
20
|
+
),
|
21
|
+
Mediakit::FFmpeg::Options::OutputFileOption.new(
|
22
|
+
options: {
|
23
|
+
'vf' => 'crop=320:320:0:0',
|
24
|
+
'ar' => '44100',
|
25
|
+
'ab' => '128k',
|
26
|
+
},
|
27
|
+
path: output,
|
28
|
+
),
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
root = File.expand_path(File.join(File.dirname(__FILE__), '../'))
|
33
|
+
input_path = File.expand_path(File.join(root, 'test/fixtures/sample1.mp4'))
|
34
|
+
output_path = File.expand_path(File.join(root, 'out.mp4'))
|
35
|
+
driver = Mediakit::Drivers::FFmpeg.new
|
36
|
+
ffmpeg = Mediakit::FFmpeg.new(driver)
|
37
|
+
options = transcode_option(input_path, output_path)
|
38
|
+
puts "$ #{ffmpeg.command(options)}"
|
39
|
+
puts ffmpeg.run(options)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Mediakit
|
2
|
+
module Codecs
|
3
|
+
module Video
|
4
|
+
<% @items.select{|x| x.type == :video }.each do |item| %>
|
5
|
+
class Codec<%= item.name.classify %>
|
6
|
+
def name; '<%= item.name %>' end
|
7
|
+
def encoders; <%= item.encoders.inspect %>; end
|
8
|
+
def decoders; <%= item.decoders.inspect %>; end
|
9
|
+
end
|
10
|
+
<% end %>
|
11
|
+
end
|
12
|
+
|
13
|
+
module Audio
|
14
|
+
<% @items.select{|x| x.type == :audio }.each do |item| %>
|
15
|
+
class Codec<%= item.name.classify %>
|
16
|
+
def name; '<%= item.name %>' end
|
17
|
+
def encoders; <%= item.encoders.inspect %>; end
|
18
|
+
def decoders; <%= item.decoders.inspect %>; end
|
19
|
+
end
|
20
|
+
<% end %>
|
21
|
+
end
|
22
|
+
|
23
|
+
module Subtitle
|
24
|
+
<% @items.select{|x| x.type == :subtitle }.each do |item| %>
|
25
|
+
class Codec<%= item.name.classify %>
|
26
|
+
def name; '<%= item.name %>' end
|
27
|
+
def encoders; <%= item.encoders.inspect %>; end
|
28
|
+
def decoders; <%= item.decoders.inspect %>; end
|
29
|
+
end
|
30
|
+
<% end %>
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mediakit
|
2
|
+
module Decoders
|
3
|
+
module Video
|
4
|
+
<% @items.select{|x| x.type == :video }.each do |item| %>
|
5
|
+
class Decoder<%= item.name.classify %>
|
6
|
+
def name; '<%= item.name %>' end
|
7
|
+
end
|
8
|
+
<% end %>
|
9
|
+
end
|
10
|
+
|
11
|
+
module Audio
|
12
|
+
<% @items.select{|x| x.type == :audio }.each do |item| %>
|
13
|
+
class Decoder<%= item.name.classify %>
|
14
|
+
def name; '<%= item.name %>' end
|
15
|
+
end
|
16
|
+
<% end %>
|
17
|
+
end
|
18
|
+
|
19
|
+
module Subtitle
|
20
|
+
<% @items.select{|x| x.type == :subtitle }.each do |item| %>
|
21
|
+
class Decoder<%= item.name.classify %>
|
22
|
+
def name; '<%= item.name %>' end
|
23
|
+
end
|
24
|
+
<% end %>
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mediakit
|
2
|
+
module Encoders
|
3
|
+
module Video
|
4
|
+
<% @items.select{|x| x.type == :video }.each do |item| %>
|
5
|
+
class Encoder<%= item.name.classify %>
|
6
|
+
def name; '<%= item.name %>' end
|
7
|
+
end
|
8
|
+
<% end %>
|
9
|
+
end
|
10
|
+
|
11
|
+
module Audio
|
12
|
+
<% @items.select{|x| x.type == :audio }.each do |item| %>
|
13
|
+
class Encoder<%= item.name.classify %>
|
14
|
+
def name; '<%= item.name %>' end
|
15
|
+
end
|
16
|
+
<% end %>
|
17
|
+
end
|
18
|
+
|
19
|
+
module Subtitle
|
20
|
+
<% @items.select{|x| x.type == :subtitle }.each do |item| %>
|
21
|
+
class Encoder<%= item.name.classify %>
|
22
|
+
def name; '<%= item.name %>' end
|
23
|
+
end
|
24
|
+
<% end %>
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mediakit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ainame
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cocaine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.10'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.10'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ruby-debug-ide
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.4'
|
97
|
+
description: |
|
98
|
+
mediakit is the libraries for ffmpeg and sox backed media manipulation something.
|
99
|
+
you can create complex manipulation for media as a ruby code.
|
100
|
+
email:
|
101
|
+
- s.namai.09@gmail.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- lib/mediakit.rb
|
114
|
+
- lib/mediakit/drivers.rb
|
115
|
+
- lib/mediakit/ffmpeg.rb
|
116
|
+
- lib/mediakit/ffmpeg/options.rb
|
117
|
+
- lib/mediakit/ffprobe.rb
|
118
|
+
- lib/mediakit/utils/popen_helper.rb
|
119
|
+
- lib/mediakit/version.rb
|
120
|
+
- mediakit.gemspec
|
121
|
+
- sample/raw_crop.rb
|
122
|
+
- templates/codec.rb.erb
|
123
|
+
- templates/decoder.rb.erb
|
124
|
+
- templates/encoder.rb.erb
|
125
|
+
- templates/format.rb.erb
|
126
|
+
homepage: https://github.com/ainame/mediakit
|
127
|
+
licenses: []
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.4.5
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: mediakit is the libraries for ffmpeg and sox backed media manipulation something.
|
149
|
+
test_files: []
|