ffmpeg_progress 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.rdoc +29 -0
  4. data/lib/ffmpeg_progress.rb +253 -0
  5. metadata +48 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: be912ce3dcef0d9351c5499cee46cae82a6d6f02
4
+ data.tar.gz: 2fd3d86091bc2c7e6b26e802a6f0a500a0ffaed3
5
+ SHA512:
6
+ metadata.gz: fa668255e546bc6905bba6acbd365d98fcba0dfd168c34c4e7172d2d9c6e627e648872827a590252747d13412d60c45b2f8363a3b6e24555a20a19231d6cc3f4
7
+ data.tar.gz: 5aa94f631e4848560fb61f05e328acef07b73608e23012c6171930108ff91c321272ff091291ae2a6d5090e65370280e8c2e2d4b6b1b68c93ab490ab6afa1adb
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Winterbraid
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.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ =FfmpegProgress
2
+
3
+ Execute +ffmpeg+ commands while displaying a pretty progress bar instead of the
4
+ usual garbage.
5
+
6
+ Convert from the shell with default options:
7
+ # Will save to ./converted/test.mp4
8
+ ruby -rffmpeg_progress -e "FfmpegProgress::Ffmpeg.new('test.avi').run"
9
+
10
+ ==Example Use
11
+ require 'ffmpeg_progress'
12
+
13
+ include FfmpegProgress
14
+
15
+ f = Ffmpeg.new 'input.avi'
16
+
17
+ # Occurrences of '_INPUT_' will be replaced with the input file name.
18
+ f.options = '-y -threads 0 -c:v libx264 -crf 22 -preset medium -c:a copy'
19
+
20
+ # Directories will be created if necessary.
21
+ f.output = 'output.mkv'
22
+
23
+ f.command
24
+ # => "ffmpeg -i input.avi -y -threads 0 -c:v libx264 -crf 22 " \
25
+ # "-preset medium -c:a copy output.mkv &> ffmpeg.log"
26
+
27
+ # The optional block is passed self as a parameter and appended to
28
+ # the progress bar.
29
+ f.run { |ffmpeg| ffmpeg.output }
@@ -0,0 +1,253 @@
1
+ require 'fileutils'
2
+
3
+ # {include:file:README.rdoc}
4
+ module FfmpegProgress
5
+ # The current software version.
6
+ VERSION = '1.0.0'
7
+
8
+ # The date of the current version.
9
+ DATE = '2014-10-31'
10
+
11
+ # A short description of the software.
12
+ ABOUT = 'A fancy progress bar for ffmpeg.'
13
+
14
+ # Contains various examples of +ffmpeg+ options.
15
+ module Presets
16
+ # The default options. Convert the video to x264 and audio to AAC.
17
+ DEFAULT_OPTS = '-y -threads 0 -strict -2 -c:a aac -b:a 96k ' \
18
+ '-c:v libx264 -preset fast -tune fastdecode -crf 22 '
19
+
20
+ # Read the default subtitles embedded in an input MKV file and hardcode
21
+ # them into the video. Any embedded fonts must be extracted and installed
22
+ # first to preserve them in the output, for example:
23
+ # mkvextract attachments input.mkv {1..10}
24
+ # cp *.ttf *.ttc *.otf ~/.fonts
25
+ BURN_MKV_SUBS = '-y -threads 0 -strict -2 -sn -c:a copy ' \
26
+ '-c:v libx264 -preset fast -tune fastdecode -crf 22 ' \
27
+ '-vf "subtitles=_INPUT_"'
28
+
29
+ # Like {BURN_MKV_SUBS}, but also downscale the video to 800px width.
30
+ BURN_800 = '-y -threads 0 -strict -2 -sn -c:a aac -b:a 96k ' \
31
+ '-c:v libx264 -preset fast -tune fastdecode -crf 22 ' \
32
+ '-vf "[in]scale=800:-2[tmp];[tmp]subtitles=_INPUT_[out]"'
33
+ end
34
+
35
+ # Helper methods.
36
+ module Utils
37
+ # Colorize a string for the terminal (256-color mode).
38
+ #
39
+ # @param [String] string
40
+ # @param [Integer] color
41
+ # @return [String]
42
+ def colorize(string, color)
43
+ "\x1b[38;5;#{color}m#{string}\x1b[0m"
44
+ end
45
+
46
+ # Parse a time string in the +ffmpeg+ HH:MM:SS.ms format and return seconds.
47
+ #
48
+ # @param [String] time_string
49
+ # @return [Integer]
50
+ def parse_ffmpeg_time(time_string)
51
+ array = time_string.rpartition('.').first.split(':').map(&:to_i)
52
+
53
+ array[0] * 3600 + array[1] * 60 + array[2]
54
+ end
55
+ end
56
+
57
+ # The class that does the actual work.
58
+ class Ffmpeg
59
+ include Presets
60
+ include Utils
61
+
62
+ # The log file for ffmpeg. Progress data will be read from here.
63
+ LOG_FILE = 'ffmpeg.log'
64
+
65
+ # The extension for the default output file.
66
+ DEFAULT_EXT = 'mp4'
67
+
68
+ # The directory for the default output file.
69
+ DEFAULT_DIR = 'converted/'
70
+
71
+ # The default theme, change this with {#theme=}. {FfmpegProgress} expects
72
+ # a 256-color capable terminal.
73
+ DEFAULT_THEME = {
74
+ bars: 63, head: '[', full: '=', empty: '-', tail: ']',
75
+ end_fg: 202, full_fg: 214, empty_fg: 202,
76
+ time_fg: 214, block_fg: 214, finish_fg: 40, cancel_fg: 1
77
+ }
78
+
79
+ # The input file.
80
+ # @return [String]
81
+ attr_reader :input
82
+
83
+ # The output file. Directories will be auto-created on execution.
84
+ # @return [String]
85
+ attr_accessor :output
86
+
87
+ # Returns the duration (in seconds) of the input file.
88
+ # @return [Integer]
89
+ attr_reader :duration
90
+
91
+ # Returns the duration of the input file as a +ffmpeg+ format string
92
+ # (+HH:MM:SS.ms+).
93
+ #
94
+ # @return [Integer]
95
+ attr_reader :duration_ff
96
+
97
+ # The options to pass to +ffmpeg+. Any occurrences of the string +_INPUT_+
98
+ # will be converted to the value of the {#input} attribute.
99
+ # @return [String]
100
+ attr_accessor :options
101
+
102
+ # The theme for the progress bar. See {DEFAULT_THEME} for details.
103
+ # @return [Hash]
104
+ attr_accessor :theme
105
+
106
+ # The PID of the last spawned +ffmpeg+ process, or +nil+ if none spawned
107
+ # yet.
108
+ # @return [Integer]
109
+ attr_reader :pid
110
+
111
+ # Creates a new {Ffmpeg} instance.
112
+ #
113
+ # @param [String] input_file the source file name.
114
+ # @param [String] ffmpeg_options the options to pass to ffmpeg.
115
+ # See {#options} for details and {Presets} for examples.
116
+ # @return [Ffmpeg]
117
+ def initialize(input_file = nil, ffmpeg_options = DEFAULT_OPTS)
118
+ self.input = input_file
119
+ @options = ffmpeg_options
120
+
121
+ @output ||= nil
122
+ @duration_ff ||= nil
123
+ @duration ||= nil
124
+
125
+ @block = nil
126
+
127
+ @pid = nil
128
+ @theme = DEFAULT_THEME
129
+
130
+ @last_bar = nil
131
+ end
132
+
133
+ # Set the input file.
134
+ #
135
+ # @param [String] string
136
+ # @return [String]
137
+ def input=(string)
138
+ @input = string
139
+
140
+ return @input unless @input
141
+
142
+ @duration_ff = `ffmpeg -i \'#{@input}\' 2>&1`.scan(/..:..:..\.../).first
143
+ @duration = parse_ffmpeg_time(@duration_ff)
144
+
145
+ @output ||= "#{DEFAULT_DIR}#{@input.rpartition('.').first}.#{DEFAULT_EXT}"
146
+
147
+ @input
148
+ end
149
+
150
+ # Returns the current +ffmpeg+ command that will be executed by {#run}.
151
+ #
152
+ # @return [String]
153
+ def command
154
+ "ffmpeg -i \'#{@input}\' #{@options} \'#{@output}\' &> #{LOG_FILE}"
155
+ .gsub('_INPUT_', "'#{@input}'")
156
+ end
157
+
158
+ # Run +ffmpeg+ while printing a progress bar to the terminal.
159
+ # If a block is passed, its return value will be attached to the
160
+ # progress bar. The current {Ffmpeg} instance will be passed to
161
+ # the block.
162
+ #
163
+ # @return [0]
164
+ def run(&block)
165
+ File.delete(LOG_FILE) if File.exist?(LOG_FILE)
166
+
167
+ FileUtils.mkpath(@output.rpartition('/').first) if output.include?('/')
168
+
169
+ @pid = spawn command
170
+ @block = block
171
+
172
+ sleep 1 until current_time
173
+ monitor_progress
174
+
175
+ cleanup
176
+
177
+ 0
178
+ end
179
+
180
+ private
181
+
182
+ # Display the progress bar while waiting for +ffmpeg+ to finish.
183
+ def monitor_progress
184
+ until Process.waitpid(@pid, Process::WNOHANG)
185
+ print "\r#{progress_bar}"
186
+ sleep 1
187
+ end
188
+
189
+ puts "\r#{progress_bar(@theme[:finish_fg], @dur_string)}"
190
+ rescue Interrupt
191
+ puts "\r#{progress_bar(@theme[:cancel_fg])}"
192
+ end
193
+
194
+ # Display the progress bar.
195
+ def progress_bar(time_fg = @theme[:time_fg], time = current_time)
196
+ elements = bar_elements(time_fg, time)
197
+
198
+ bar =
199
+ "#{elements.head}#{elements.full}#{elements.empty}#{elements.tail}" \
200
+ "#{elements.head}#{elements.time}#{elements.tail}"
201
+
202
+ if @block
203
+ block_value = colorize(@block.call(self), @theme[:block_fg])
204
+ bar << "#{elements.head}#{block_value}#{elements.tail}"
205
+ end
206
+
207
+ bar
208
+ end
209
+
210
+ # Get the current time (in +ffmpeg+ format) from the log file.
211
+ # Returns +nil+ if the actual transcoding process has not started yet.
212
+ #
213
+ # @return [String]
214
+ def current_time
215
+ return nil unless File.exist?(LOG_FILE)
216
+ match = File.read(LOG_FILE).scan(/time=..:..:..\.../).last
217
+
218
+ return nil unless match
219
+ match.delete('time=')
220
+ end
221
+
222
+ # Generate bar elements for a given +ffmpeg+ time.
223
+ def bar_elements(time_fg, time)
224
+ elements = Struct.new(:head, :tail, :full, :empty, :time)
225
+ bars = (@theme[:bars] * parse_ffmpeg_time(time) / @duration).round
226
+
227
+ elements.new(
228
+ colorize(@theme[:head], @theme[:end_fg]),
229
+ colorize(@theme[:tail], @theme[:end_fg]),
230
+ colorize(@theme[:full] * bars, @theme[:full_fg]),
231
+ colorize(@theme[:empty] * (@theme[:bars] - bars), @theme[:empty_fg]),
232
+ colorize(time, time_fg)
233
+ )
234
+ end
235
+
236
+ # Kill +ffmpeg+ if still running, and delete the log file.
237
+ def cleanup
238
+ Process.detach(@pid)
239
+
240
+ ffmpeg_alive =
241
+ begin
242
+ Process.kill(0, @pid)
243
+ true
244
+ rescue Errno::ESRCH
245
+ false
246
+ end
247
+
248
+ Process.kill('TERM', @pid) if ffmpeg_alive
249
+
250
+ File.delete(LOG_FILE) if File.exist?(LOG_FILE)
251
+ end
252
+ end
253
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffmpeg_progress
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Winterbraid
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Execute ffmpeg commands and display a progress bar.
15
+ email: Winterbraid@users.noreply.github.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - README.rdoc
22
+ - lib/ffmpeg_progress.rb
23
+ homepage: https://github.com/Winterbraid/ffmpeg_progress
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.2.2
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: A fancy progress bar for ffmpeg.
47
+ test_files: []
48
+ has_rdoc: