ffmpeg_wrapper 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a4bfe1a276bbee90d906cf305d6d650c9e15b3b
4
+ data.tar.gz: 8e8bb0c17f3a4c9353c2af6cbc5fc91005724d38
5
+ SHA512:
6
+ metadata.gz: a06e520d53ea7485a11ed1dca162002700151976561f68ccc9e3c0f2bc7fc6ad896d74e07a1bea091668f4b4a5ccbbed438f42a48cdddf7e8fe6d4094ec0d07f
7
+ data.tar.gz: f59b4818b62a19566955661e50c4a23e3159737aee9f3f2c003e85eec2482e9234bdb48c3b6de0de3a7655ea11ea96fe468282e2843fd50c264b111b07d33809
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ tags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ -f doc
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ffmpeg_wrapper.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 slowness pc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,62 @@
1
+ # FfmpegWrapper
2
+
3
+ Ruby gem wrapping cli utility FFmpeg.
4
+ Now it lacks lots of features, but
5
+ this will change in time as I need new
6
+ functions myself or just feel like
7
+ enhancing it.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'ffmpeg_wrapper'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install ffmpeg_wrapper
24
+
25
+ ## Usage
26
+
27
+ ### FFmpeg
28
+
29
+ ```ruby
30
+ require 'ffmpeg_wrapper'
31
+
32
+ FFmpeg.run do
33
+ media 'somefile.mp4'
34
+ map(0,1).applying acodec: 'libmp3lame',
35
+ ac: 2, ar: '44.1k'
36
+ # .applying is a dynamic method of a
37
+ # String returned from #map
38
+ output 'out.mp3'
39
+ end
40
+ ```
41
+
42
+
43
+ ### FFprobe
44
+
45
+ ``ruby
46
+ info = FFprobe.run('video.mp4') do
47
+ show_streams
48
+ show_format
49
+ end
50
+ info #=> { "format" => ..., "streams"=>... }
51
+ info['format']['codec_type'] #=> 'video'
52
+ ``
53
+
54
+ Look in the documentation for details.
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it ( https://github.com/[my-github-username]/ffmpeg_wrapper/fork )
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create a new Pull Request
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ namespace :test do
4
+ task :prepare do
5
+ mkdir 'spec/output'
6
+ end
7
+
8
+ task :cleanup do
9
+ `rm -rf spec/output`
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ffmpeg_wrapper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ffmpeg_wrapper"
8
+ spec.version = FfmpegWrapper::VERSION
9
+ spec.authors = ['slowness']
10
+ spec.email = ['slma0x02@gmail.com']
11
+ spec.summary = 'Wrapper for FFmpeg.'
12
+ spec.description = 'FFmpeg is a powerful video, audio or image editing tool \
13
+ with tons of useful (and not so) functions.'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'rspec-its'
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'ffmpeg_wrapper/version'
2
+
3
+ module FfmpegWrapper
4
+ # Check if ffmpeg is present in the system
5
+ def self.method_missing(meth, *_args, &_blk)
6
+ case meth.to_s
7
+ when /has_(?<cmd>.*)\?/ then system "which #{$1} &>/dev/null"
8
+ end
9
+ end
10
+
11
+ fail 'No ffmpeg found in $PATH' unless has_ffmpeg?
12
+ end
13
+
14
+ require 'ostruct'
15
+ require 'logger'
16
+
17
+ require 'ffmpeg_wrapper/hash.rb'
18
+ require 'ffmpeg_wrapper/ffmpeg.rb'
19
+ require 'ffmpeg_wrapper/ffprobe.rb'
20
+ require 'ffmpeg_wrapper/format.rb'
21
+ require 'ffmpeg_wrapper/stream.rb'
22
+ require 'ffmpeg_wrapper/version.rb'
@@ -0,0 +1,128 @@
1
+ module FfmpegWrapper
2
+ class FFmpeg
3
+ def initialize
4
+ @command = 'ffmpeg -y -hide_banner'
5
+ @inputs = []
6
+ @audios = []
7
+ @videos = []
8
+ @mappings = []
9
+ @filters = []
10
+ @n = 0
11
+ @result = {}
12
+ @post_exec_hooks = []
13
+ end
14
+ # Construct ffmpeg command using that
15
+ # function, then execute. All opts hashe's
16
+ # option meant to be axactly the same as according
17
+ # ffmpeg flags. That is <tt>-pix_fmt => :pix_fmt</tt>
18
+ # @return [FFmpeg]
19
+ # @example
20
+ # FFmpeg.run do
21
+ # media 'somefile.mp4'
22
+ # map(0,1).applying acodec: 'libmp3lame',
23
+ # ac: 2, ar: '44.1k'
24
+ # # .applying is a dynamic method of a
25
+ # # String returned from #map
26
+ # output 'out.mp3'
27
+ # end
28
+ def self.run(*flags, &block)
29
+ lo = Logger.new(STDOUT) if flags.include?(:debug)
30
+ ff = FFmpeg.new
31
+ ff.instance_eval do
32
+ instance_eval(&block)
33
+ @command << ' ' << @inputs.join(' ')
34
+ @command << ' ' << @filters.join(' ') if @filters.any?
35
+ @command << ' ' << @mappings.join(' ') if @mappings.any?
36
+ @command << ' ' << @output if @output
37
+ lo.info @command if lo
38
+ @out = IO.popen(@command, err: [:child, :out]) do |io|
39
+ io.read
40
+ end
41
+ @post_exec_hooks.each { |h| instance_eval(&h) }
42
+ fail @out.to_s unless $?.success?
43
+ end
44
+ ff.instance_variable_get(:@result)
45
+ end
46
+
47
+ # Adds input file, containing audio and video.
48
+ # @param filename [String]
49
+ # @param opts [Hash] options for this input file. See list
50
+ # of options below. If input file is a media container,
51
+ # e. g. mpeg4 or avi you don't need to explicitly specify any.
52
+ # If file is raw video or audio, specify _V_ for video
53
+ # and _A_ for audio
54
+ # @option opts [String] :f _V_ format 'rawvideo'
55
+ # @option opts [String] :s _V_ geometry WxH(+X,Y)
56
+ # @option opts [String] :pix_fmt _V_ pixel format bgr8, rgba, etc...
57
+ # @option opts [Int] :r _V_ fps
58
+ # @option opts [String] :f _A_ format 'alaw', etc...
59
+ # @option opts [String] :ar _A_ sample rate, e.g. '44.1k'
60
+ # @option opts [Int] :ac _A_ channels
61
+ # @see FFmpeg.map .run method example for call sequence
62
+ # @return id [Integer] input file id
63
+ def media(filename, opts = {})
64
+ n = input(filename, opts)
65
+ @videos << n
66
+ @audios << n
67
+ n
68
+ end
69
+
70
+ # Adds input video file (no audio will be extracted anyway)
71
+ # @param filename [String]
72
+ # @param opts [Hash] options for this input file
73
+ # @return id [Integer] input file id
74
+ def video(filename, opts = {})
75
+ n = input(filename, opts)
76
+ @videos << n
77
+ n
78
+ end
79
+
80
+ # Adds input audio file (no video will be extracted anyway)
81
+ # @param filename [String]
82
+ # @param opts [Hash] options for this input file
83
+ # @return id [Integer] input file id
84
+ def audio(filename, opts = {})
85
+ n = input(filename, opts)
86
+ @audios << n
87
+ n
88
+ end
89
+
90
+ # Specify filename for output file
91
+ # @param filename [String]
92
+ def output(filename)
93
+ @output = " #{filename}"
94
+ end
95
+
96
+ # Specify mapping: what input stream or alias (from format, e. g. \[a\])
97
+ # @param file [String, Int] file number or alias
98
+ # @param index [Int] stream specifier
99
+ # @see FFmpeg#map #map method example for call sequence
100
+ # @see https://trac.ffmpeg.org/wiki/How%20to%20use%20-map%20option ffmpeg
101
+ # map option guide
102
+ # @return [String]
103
+ def map(file, index = nil)
104
+ line = "-map #{file}#{':' + index.to_s if index}"
105
+ @mappings << line
106
+ def line.applying(opts = {})
107
+ opts.each do |k, v|
108
+ self << " -#{k} #{v}"
109
+ end
110
+ end
111
+ line
112
+ end
113
+
114
+ private
115
+
116
+ def input(filename, opts = {})
117
+ line = ''
118
+ opts.each do |k, v|
119
+ line << " -#{k} #{v}"
120
+ end
121
+ line << " -i #{filename}"
122
+ @inputs << line
123
+ @n += 1
124
+ @n - 1
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,73 @@
1
+ require 'json'
2
+
3
+ module FfmpegWrapper
4
+ class FFprobe
5
+ class << self
6
+ # Execute ffprobe command.
7
+ # @param [String] filename
8
+ # @return [Hash]
9
+ # @example
10
+ # info = FFprobe.run('video.mp4') do
11
+ # show_streams
12
+ # show_format
13
+ # end
14
+ # info #=> { "format" => ..., "streams"=>... }
15
+ # info['format']['codec_type'] #=> 'video'
16
+ def run(filename, &block)
17
+ ff = FFprobe.new filename
18
+ ff.instance_eval do
19
+ @command << ' -v quiet -of json'
20
+ instance_eval &block if block
21
+ @command << @show_specifiers.reduce(' ') do |acc, v|
22
+ acc << " -#{v}"
23
+ acc
24
+ end
25
+ @command << ' ' << @options.to_shellflags
26
+ @command << ' ' << @input
27
+ out = `#{@command} 2>/dev/null`
28
+ begin
29
+ @result = JSON.parse out
30
+ fail if @result.keys.empty?
31
+ rescue
32
+ @result = { 'errors' => error }
33
+ # FIXME: Do not return from ensure
34
+ ensure
35
+ return @result
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # @param [String] filename
42
+ def initialize(filename)
43
+ @input = filename || fail(ArgumentError 'No input specified')
44
+ @command = 'ffprobe '
45
+ @show_specifiers = []
46
+ @options = {}
47
+ end
48
+
49
+ # Specify input file options
50
+ # @param [Hash] options
51
+ def options(hash)
52
+ @options.merge! hash
53
+ end
54
+ alias_method :option, :options
55
+
56
+ private
57
+
58
+ def method_missing(meth, *args, &blk)
59
+ case meth
60
+ when /show_(\w+)/ then
61
+ @show_specifiers << meth
62
+ else
63
+ super
64
+ end
65
+ end
66
+
67
+ def error
68
+ @command.gsub!('-v quiet', '-v error')
69
+ @command.gsub!('-of json', '')
70
+ `#{@command} 2>&1`
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ require 'ostruct'
2
+
3
+ module FfmpegWrapper
4
+ class FFmpeg
5
+ # Find intervals of blackness in a video file.
6
+ # This info then can be found in a hash, returned
7
+ # from FFmpeg.run by key :blacks.
8
+ # TODO: add argument support
9
+ # @param opts [Hash]
10
+ # @return FFmpeg self. Chainable, see example.
11
+ # @example
12
+ # FFmpeg.run do
13
+ # media 'video.mp4'
14
+ # detect_black.and_write_to 'black_intervals.txt'
15
+ # end # => { ... :blacks => [ { :start => x, :end => y, :duration => z}, ... ] }
16
+ def filter_blackdetect(opts = {})
17
+ @filters << '-vf blackdetect'
18
+ @output = ' -f null /dev/null '
19
+ @redirection = [:child, :out]
20
+ @result[:blacks] = []
21
+
22
+ # Singleton method defined on FFmpeg
23
+ # after calling #filter_blackdetect.
24
+ # Writes info about black intervals as a plain text to
25
+ # a file.
26
+ # @param (String) filename
27
+ def self.and_write_to(filename)
28
+ @post_exec_hooks << proc do
29
+ File.open(filename, 'w') do |f|
30
+ @result[:blacks].each do |black|
31
+ f.puts 'black_interval start: %f end: %f duration: %f' % [black[:start], black[:end], black[:duration]]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ @post_exec_hooks << proc do
37
+ bdlines = @out.lines.grep(/blackdetect/)
38
+ bdlines.map do |l|
39
+ /black_start:(?<st>\S+).*
40
+ black_end:(?<en>\S+).*
41
+ black_duration:(?<du>\S+)/x =~ l
42
+ @result[:blacks] << { start: st.to_f, end: en.to_f, duration: du.to_f }
43
+ end
44
+ end
45
+ self
46
+ end
47
+
48
+ alias_method :detect_black, :filter_blackdetect
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ module FfmpegWrapper
2
+ class FFmpeg
3
+ # This function extends basic {FFmpeg} functionality
4
+ # providing filter command.
5
+ # TODO: non-trivial mappings(i.e. multi-streamed inputs)
6
+ def filter_concat(_mappings = {})
7
+ line = ''
8
+ line << filter_complex(:audio)
9
+ line << filter_complex(:video)
10
+ @filters << line
11
+ mappings = []
12
+ mappings << (@videos.size > 1 ? '[v]' : @videos.first)
13
+ mappings << (@audios.size > 1 ? '[a]' : @audios.first)
14
+ mappings
15
+ end
16
+
17
+ private
18
+
19
+ def stream_map_for_video
20
+ return if @videos.empty?
21
+ line = ''
22
+ @videos.each do |file|
23
+ line << " [#{file}:0] "
24
+ end
25
+ line
26
+ end
27
+
28
+ def stream_map_for_audio
29
+ return if @audios.empty?
30
+ line = ''
31
+ @audios.each do |file|
32
+ line << " [#{file}:0] "
33
+ end
34
+ line
35
+ end
36
+
37
+ def concat_for_video
38
+ "concat=n=#{@videos.size}:v=1:a=0 [v]"
39
+ end
40
+
41
+ def concat_for_audio
42
+ "concat=n=#{@audios.size}:v=0:a=1 [a]"
43
+ end
44
+
45
+ def filter_complex(str)
46
+ fail ArgumentError unless [:audio, :video].include?(str)
47
+ str = str.to_s
48
+ return '' if instance_variable_get('@' + str + 's').count < 2
49
+ line = ' -filter_complex '
50
+ line << "'"
51
+ line << send("stream_map_for_#{str}")
52
+ line << send("concat_for_#{str}")
53
+ line << "'"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module FfmpegWrapper
2
+ class Format < ::OpenStruct
3
+ CMDLINE_NAMES = {
4
+ format_name: 'f'
5
+ }
6
+ def to_cmdline
7
+ cmd = ''
8
+ each_pair do |field, value|
9
+ field_for_cmd = CMDLINE_NAMES[field]
10
+ cmd << " #{field_for_cmd} #{value}" if field_for_cmd
11
+ end
12
+ cmd
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def to_shellflags
3
+ line = ''
4
+ each do |k, v|
5
+ line << "-#{k} #{v}"
6
+ end
7
+ line
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module FfmpegWrapper
2
+ class Stream < OpenStruct
3
+ def video?
4
+ codec_type == 'video' if codec_type
5
+ end
6
+
7
+ def audio?
8
+ codec_type == 'audio' if codec_type
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module FfmpegWrapper
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffmpeg_wrapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - slowness
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-29 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.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |-
70
+ FFmpeg is a powerful video, audio or image editing tool \
71
+ with tons of useful (and not so) functions.
72
+ email:
73
+ - slma0x02@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - ffmpeg_wrapper.gemspec
85
+ - lib/ffmpeg_wrapper.rb
86
+ - lib/ffmpeg_wrapper/ffmpeg.rb
87
+ - lib/ffmpeg_wrapper/ffprobe.rb
88
+ - lib/ffmpeg_wrapper/filters/blackdetect.rb
89
+ - lib/ffmpeg_wrapper/filters/concat.rb
90
+ - lib/ffmpeg_wrapper/format.rb
91
+ - lib/ffmpeg_wrapper/hash.rb
92
+ - lib/ffmpeg_wrapper/stream.rb
93
+ - lib/ffmpeg_wrapper/version.rb
94
+ homepage:
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.0.14
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Wrapper for FFmpeg.
118
+ test_files: []
119
+ has_rdoc: