ffmprb 0.10.1 → 0.11.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.
- checksums.yaml +4 -4
- data/Guardfile +1 -1
- data/README.md +160 -67
- data/exe/ffmprb +1 -1
- data/ffmprb.gemspec +6 -4
- data/lib/defaults.rb +2 -0
- data/lib/ffmprb.rb +2 -2
- data/lib/ffmprb/execution.rb +29 -13
- data/lib/ffmprb/process.rb +5 -3
- data/lib/ffmprb/process/input.rb +9 -3
- data/lib/ffmprb/process/input/looping.rb +9 -14
- data/lib/ffmprb/process/output.rb +8 -2
- data/lib/ffmprb/util.rb +10 -2
- data/lib/ffmprb/util/proc_vis.rb +1 -0
- data/lib/ffmprb/util/threaded_io_buffer.rb +72 -61
- data/lib/ffmprb/version.rb +4 -1
- metadata +22 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 428b5ce07b48c960ffe05fded9e45d6f458e405b
|
4
|
+
data.tar.gz: 686389df8fd80d665105cdd12682f5f547ec48ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1addc97d33e9e6799081b19617a8f24c00f1f37d6ebeec5dca87893201090a4e2d2a14be72f50d1e80457ff88e7e655befd59cb3f3cda3184e1febdb51ddb980
|
7
|
+
data.tar.gz: 943f57a80800d690cb742c0264668dca0a7816cef88d222cacd5a03daeb0d9e81071bb6bcd6fbaa4df0b886a5d3a20707d25b57a96b44cef035121959d8b83e8
|
data/Guardfile
CHANGED
@@ -6,7 +6,7 @@ guard :rspec,
|
|
6
6
|
:failed_mode => :focus do
|
7
7
|
|
8
8
|
watch(%r{^spec/.+_spec\.rb$})
|
9
|
-
watch(%r{^lib/(
|
9
|
+
watch(%r{^lib/([^/]+).+\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
10
10
|
watch('spec/spec_helper.rb') { 'spec' }
|
11
11
|
watch('.rspec') { 'spec' }
|
12
12
|
end
|
data/README.md
CHANGED
@@ -3,30 +3,31 @@
|
|
3
3
|
[](https://circleci.com/gh/showbox-oss/ffmprb)
|
4
4
|
## your audio/video montage pal, based on [ffmpeg](https://ffmpeg.org)
|
5
5
|
|
6
|
-
A DSL (Damn-Simple Language) and a micro-engine for ffmpeg and ffriends
|
6
|
+
A video and audio composing DSL (Damn-Simple Language) and a micro-engine for ffmpeg and ffriends (with CLI)
|
7
7
|
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
Ffmprb.process do
|
8
|
+
If you're neither a video technologist, nor a video artist, you neither need cumbersome low level tools (like ffmpeg), nor heavy and costly high level tools (like Premiere).
|
11
9
|
|
12
|
-
|
13
|
-
in_sound = input('track.mp3')
|
14
|
-
output 'cine.flv', video: {resolution: '1280x720'} do
|
15
|
-
roll in_main.crop(0.25).cut(from: 2, to: 5), transition: {blend: 1}
|
16
|
-
roll in_main.volume(2).cut(from: 6, to: 16), after: 2, transition: {blend: 1}
|
17
|
-
overlay in_sound.volume(0.8)
|
18
|
-
end
|
10
|
+
Any script-able person can manipulate video/audio media -- or automate processing thereof -- with ffmprb.
|
19
11
|
|
12
|
+
Allows for scripts like
|
13
|
+
```ruby
|
14
|
+
in_main = input('flick.mp4')
|
15
|
+
output 'cine.flv', video: {resolution: '1280x720'} do
|
16
|
+
roll in_main.crop(0.25).cut(from: 2, to: 5), transition: {blend: 1}
|
17
|
+
roll in_main.volume(2).cut(from: 6, to: 16), after: 2, transition: {blend: 1}
|
18
|
+
overlay input('track.mp3').volume(0.8)
|
20
19
|
end
|
21
20
|
```
|
22
21
|
and saves you from the horror of...
|
23
22
|
```
|
24
|
-
ffmpeg -y -noautorotate -i flick.mp4 -i track.
|
23
|
+
ffmpeg -y -noautorotate -i flick.mp4 -i track.mp3 -filter_complex "[0:v] fps=fps=16 [cptmo0rl0:v]; [0:a] anull [cptmo0rl0:a]; [cptmo0rl0:v] crop=x=in_w*0.25:y=in_h*0.25:w=in_w*0.5:h=in_h*0.5 [tmptmo0rl0:v]; [tmptmo0rl0:v] scale=iw*min(1280/iw\,720/ih):ih*min(1280/iw\,720/ih), setsar=1, pad=1280:720:(1280-iw*min(1280/iw\,720/ih))/2:(720-ih*min(1280/iw\,720/ih))/2, setsar=1 [tmo0rl0:v]; [cptmo0rl0:a] anull [tmo0rl0:a]; color=0x000000@0:d=3:s=1280x720:r=16 [blo0rl0:v]; [tmo0rl0:v] [blo0rl0:v] concat=2:v=1:a=0 [pdo0rl0:v]; [pdo0rl0:v] trim=2:5, setpts=PTS-STARTPTS [o0rl0:v]; aevalsrc=0:d=3 [blo0rl0:a]; [tmo0rl0:a] [blo0rl0:a] concat=2:v=0:a=1 [pdo0rl0:a]; [pdo0rl0:a] atrim=2:5, asetpts=PTS-STARTPTS [o0rl0:a]; color=0x000000@0:d=1.0:s=1280x720:r=16 [bl0:v]; aevalsrc=0:d=1.0 [bl0:a]; [bl0:v] trim=0:1.0, setpts=PTS-STARTPTS [o0tm0b:v]; [bl0:a] atrim=0:1.0, asetpts=PTS-STARTPTS [o0tm0b:a]; color=0xFFFFFF@1:d=1.0:s=1280x720:r=16 [blndo0tm0b:v]; [o0tm0b:v] [blndo0tm0b:v] alphamerge, fade=out:d=1.0:alpha=1 [xblndo0tm0b:v]; [o0rl0:v] [xblndo0tm0b:v] overlay=x=0:y=0:eof_action=pass [o0tn0:v]; [o0tm0b:a] afade=out:d=1.0:curve=hsin [blndo0tm0b:a]; [o0rl0:a] afade=in:d=1.0:curve=hsin [xblndo0tm0b:a]; [blndo0tm0b:a] apad [apdblndo0tm0b:a]; [xblndo0tm0b:a] [apdblndo0tm0b:a] amix=2:duration=shortest:dropout_transition=0, volume=2 [o0tn0:a]; [0:v] scale=iw*min(1280/iw\,720/ih):ih*min(1280/iw\,720/ih), setsar=1, pad=1280:720:(1280-iw*min(1280/iw\,720/ih))/2:(720-ih*min(1280/iw\,720/ih))/2, setsar=1, fps=fps=16 [ldtmo0rl1:v]; [0:a] anull [ldtmo0rl1:a]; [ldtmo0rl1:v] copy [tmo0rl1:v]; [ldtmo0rl1:a] volume='2':eval=frame [tmo0rl1:a]; color=0x000000@0:d=10:s=1280x720:r=16 [blo0rl1:v]; [tmo0rl1:v] [blo0rl1:v] concat=2:v=1:a=0 [pdo0rl1:v]; [pdo0rl1:v] trim=6:16, setpts=PTS-STARTPTS [o0rl1:v]; aevalsrc=0:d=10 [blo0rl1:a]; [tmo0rl1:a] [blo0rl1:a] concat=2:v=0:a=1 [pdo0rl1:a]; [pdo0rl1:a] atrim=6:16, asetpts=PTS-STARTPTS [o0rl1:a]; color=0x000000@0:d=3.0:s=1280x720:r=16 [blo0tn01:v]; aevalsrc=0:d=3.0 [blo0tn01:a]; [o0tn0:v] [blo0tn01:v] concat=2:v=1:a=0 [pdo0tn01:v]; [o0tn0:a] [blo0tn01:a] concat=2:v=0:a=1 [pdo0tn01:a]; [pdo0tn01:v] split [pdo0tn01a:v] [pdo0tn01b:v]; [pdo0tn01:a] asplit [pdo0tn01a:a] [pdo0tn01b:a]; [pdo0tn01a:v] trim=0:2, setpts=PTS-STARTPTS [tmo0tn01a:v]; [pdo0tn01a:a] atrim=0:2, asetpts=PTS-STARTPTS [tmo0tn01a:a]; [pdo0tn01b:v] trim=2:3.0, setpts=PTS-STARTPTS [o0tm1b:v]; [pdo0tn01b:a] atrim=2:3.0, asetpts=PTS-STARTPTS [o0tm1b:a]; color=0xFFFFFF@1:d=1.0:s=1280x720:r=16 [blndo0tm1b:v]; [o0tm1b:v] [blndo0tm1b:v] alphamerge, fade=out:d=1.0:alpha=1 [xblndo0tm1b:v]; [o0rl1:v] [xblndo0tm1b:v] overlay=x=0:y=0:eof_action=pass [o0tn1:v]; [o0tm1b:a] afade=out:d=1.0:curve=hsin [blndo0tm1b:a]; [o0rl1:a] afade=in:d=1.0:curve=hsin [xblndo0tm1b:a]; [blndo0tm1b:a] apad [apdblndo0tm1b:a]; [xblndo0tm1b:a] [apdblndo0tm1b:a] amix=2:duration=shortest:dropout_transition=0, volume=2 [o0tn1:a]; [tmo0tn01a:v] [o0tn1:v] concat=2:v=1:a=0 [o0o:v]; [tmo0tn01a:a] [o0tn1:a] concat=2:v=0:a=1 [o0o:a]; [1:a] anull [ldo0l0:a]; [ldo0l0:a] volume='0.8':eval=frame [o0l0:a]; [o0o:v] copy [o0o0:v]; [o0l0:a] apad [apdo0l0:a]; [o0o:a] [apdo0l0:a] amix=2:duration=shortest:dropout_transition=0, volume=2 [o0o0:a]" -map "[o0o0:v]" -map "[o0o0:a]" -c:a libmp3lame cine.flv
|
25
24
|
```
|
26
25
|
...that's the idea, but there's much more to it.
|
26
|
+
|
27
27
|
The docs, as well as any other part of this gem, are a work in progress.
|
28
28
|
So you're very welcome to look around the [specs](https://github.com/showbox-oss/ffmprb/tree/master/spec) for the actual functionality coverage.
|
29
29
|
|
30
|
+
|
30
31
|
## Installation
|
31
32
|
|
32
33
|
Add this line to your application's Gemfile:
|
@@ -43,105 +44,197 @@ Or install it yourself as:
|
|
43
44
|
|
44
45
|
$ gem install ffmprb
|
45
46
|
|
47
|
+
|
46
48
|
## DSL & Usage
|
47
49
|
|
48
50
|
The DSL strives to provide for the most common script cases in the most natural way:
|
49
51
|
you just describe what should be shown -- in an action sequence, like the following.
|
50
52
|
|
51
|
-
Play your _episode_ teaser snippet:
|
52
53
|
```ruby
|
54
|
+
# Play your _episode_ teaser snippet:
|
53
55
|
lay episode.cut(to: 60), transition: {blend: 3}
|
54
|
-
|
55
|
-
Overlay anything after that with your channel _logo_:
|
56
|
-
```ruby
|
56
|
+
|
57
|
+
# Overlay anything after that with your channel _logo_:
|
57
58
|
overlay logo.loop.cut(to: 33), after: 3, transition: {blend: 1} # both ways
|
58
|
-
|
59
|
-
Start with rolling some _intro_ flick:
|
60
|
-
```ruby
|
59
|
+
|
60
|
+
# Start with rolling some _intro_ flick:
|
61
61
|
lay intro, transition: {blend: 1}
|
62
|
-
|
63
|
-
Overlay it with some special _badge_ sprite:
|
64
|
-
```ruby
|
62
|
+
|
63
|
+
# Overlay it with some special _badge_ sprite:
|
65
64
|
overlay badge.loop, at: 1, transition: {burn: 1}
|
66
|
-
|
67
|
-
Show _title_:
|
68
|
-
```ruby
|
65
|
+
|
66
|
+
# Show _title_:
|
69
67
|
lay title, transition: {blend: 2}
|
70
|
-
|
71
|
-
Play some of your _episode_:
|
72
|
-
```ruby
|
68
|
+
|
69
|
+
# Play some of your _episode_:
|
73
70
|
lay episode.cut(from: 60, to: 540)
|
74
|
-
|
75
|
-
Oh well, roll some _promo_ material:
|
76
|
-
```ruby
|
71
|
+
|
72
|
+
# Oh well, roll some _promo_ material:
|
77
73
|
lay promo, transition: {pixel: 2}
|
78
|
-
|
79
|
-
Play most of your _episode_:
|
80
|
-
```ruby
|
74
|
+
|
75
|
+
# Play most of your _episode_:
|
81
76
|
lay episode.cut(from: 540, to: 1080)
|
82
|
-
|
83
|
-
Roll the _credits_:
|
84
|
-
```ruby
|
77
|
+
|
78
|
+
# Roll the _credits_:
|
85
79
|
overlay credits, at: 1075
|
80
|
+
|
81
|
+
# Finish by playing your special _outro_:
|
82
|
+
lay outro, transition: {blend: 1}
|
83
|
+
|
84
|
+
# Fin
|
86
85
|
```
|
87
|
-
|
86
|
+
|
87
|
+
### In the code
|
88
|
+
|
89
|
+
The block above is to be given to an `Ffmprb.process` call:
|
90
|
+
|
88
91
|
```ruby
|
89
|
-
|
92
|
+
Ffmprb.process do
|
93
|
+
|
94
|
+
# Play your _episode_ teaser snippet:
|
95
|
+
lay episode.cut(to: 60), transition: {blend: 3}
|
96
|
+
|
97
|
+
...
|
98
|
+
|
99
|
+
end
|
90
100
|
```
|
91
101
|
|
92
|
-
|
102
|
+
The block runs in the context of a new `Ffmprb::Process`, so any instance data shall be passed by value as follows:
|
103
|
+
|
93
104
|
```ruby
|
94
|
-
|
95
|
-
|
96
|
-
|
105
|
+
Ffmprb.process @episode, @teaser_length do
|
106
|
+
|episode, teaser_length|
|
107
|
+
|
108
|
+
# Play your _episode_ teaser snippet:
|
109
|
+
lay episode.cut(to: teaser_length), transition: {blend: 3}
|
110
|
+
|
111
|
+
...
|
112
|
+
|
97
113
|
end
|
98
114
|
```
|
99
|
-
|
100
|
-
|
115
|
+
|
116
|
+
### Command line
|
117
|
+
|
118
|
+
The `ffmprb` command-line utility expects a script on its standard input:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
# episode_01.ffmprb
|
122
|
+
|
123
|
+
# Play your _episode_ teaser snippet:
|
124
|
+
lay input('episode_01.mov').cut(to: 60), transition: {blend: 3}
|
125
|
+
|
126
|
+
...
|
127
|
+
```
|
128
|
+
|
129
|
+
$ ffmprb < episode_01.ffmprb
|
130
|
+
|
131
|
+
|
132
|
+
And it can take parameters for the sake of automation convenience:
|
133
|
+
|
101
134
|
```ruby
|
102
|
-
#
|
135
|
+
# episode_make.ffmprb
|
103
136
|
|episode, logo, intro, badge, title, promo, credits, outro|
|
104
137
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
lay title, transition: {blend: 2}
|
110
|
-
lay episode.cut(from: 60, to: 540)
|
111
|
-
lay promo, transition: {pixel: 2}
|
112
|
-
lay episode.cut(from: 540, to: 1080)
|
113
|
-
overlay credits, at: 535
|
114
|
-
lay outro, transition: {blend: 1}
|
138
|
+
# Play your _episode_ teaser snippet:
|
139
|
+
lay input(episode).cut(to: 60), transition: {blend: 3}
|
140
|
+
|
141
|
+
...
|
115
142
|
```
|
116
143
|
|
117
|
-
|
144
|
+
$ ffmprb ep01raw.mov logo.png intro.avi new_new.gif ep01tit.mov showbox_promo.mp4 ep01creds.avi ep01out.mov < episode_make.ffmprb
|
118
145
|
|
119
|
-
- Ffmprb is a work in progress, and even more so than Ffmpeg itself;
|
120
|
-
use at your own risk and check thoroughly for production fitness in your project.
|
121
|
-
- Ffmprb uses threads internally, however, it is not thread-safe interface-wise:
|
122
|
-
you must not share its objects between different threads.
|
123
146
|
|
147
|
+
### The defaults
|
124
148
|
|
149
|
+
The defaults [defaults](https://github.com/showbox-oss/ffmprb/tree/master/lib/defaults.rb) are provided for every possible configuration option (optional options' defaults for the methods below in particular), you're welcome to config anything in your ffmprb scripts.
|
125
150
|
|
126
|
-
### General structure
|
127
151
|
|
128
|
-
|
152
|
+
### Advanced usage
|
153
|
+
|
154
|
+
Anything ruby-valid will work -- the script may be generated on the fly:
|
155
|
+
```ruby
|
156
|
+
transitions = [:blend, :burn, :zoom]
|
157
|
+
photos.shuffle.each do |photo|
|
158
|
+
lay photo.loop.cut(to: rand * 3), transition: {transitions.shuffle.first => 1}
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
### Inputs/outputs
|
163
|
+
|
164
|
+
Inside a `process` block, there are `input` definitions and `output` definitions;
|
129
165
|
naturally, the latter use the former:
|
130
166
|
```ruby
|
131
|
-
Ffmprb.process do
|
167
|
+
Ffmprb.process do
|
132
168
|
|
133
|
-
in_main = input(
|
134
|
-
output(
|
135
|
-
|
169
|
+
in_main = input(av_input1)
|
170
|
+
output(av_output1, video: {resolution: Ffmprb::HD_720p, fps: 25}) do
|
171
|
+
lay in_main.crop(0.05), transition: {blend: 1}
|
136
172
|
end
|
137
173
|
|
138
174
|
end
|
139
175
|
```
|
140
176
|
|
177
|
+
`input`(_file_, [video: {false | {[auto_rotate:], [fps:]}}], [audio: false])
|
178
|
+
|
179
|
+
`input` returns a _reel_.
|
180
|
+
|
181
|
+
`output`(_file_, [video: {false | {[resolution:], [fps:]}}], [audio: {false | {[encoder:], [sampling_freq:]}}])
|
182
|
+
|
183
|
+
`output` also takes a block where you get to use `lay` and `overlay` methods:
|
184
|
+
|
185
|
+
`lay`(_reel_[, after: _sec_[, transition: {blend: _sec_}])
|
186
|
+
|
187
|
+
`lay` renders the reel full screen after the previously `lay`ed reel.
|
188
|
+
|
189
|
+
`overlay`(_reel_[, at: _sec_][, duck: :audio])
|
190
|
+
|
191
|
+
`overlay` is currently functional just for audio reels, sorry.
|
192
|
+
|
193
|
+
### Available reel modifier (filter) methods
|
194
|
+
|
195
|
+
`crop`({_ratio_ | {[top: _ratio_][, left: _ratio_][, bottom: _ratio_][, right: _ratio_][, width: _ratio_][, height: _ratio_]}})
|
196
|
+
|
197
|
+
`crop` crops the reel frames (e.g. `in1.crop(0.1)` will remove 1/10th of the frame from each side)
|
198
|
+
|
199
|
+
`cut`([from: _sec_][, to: _sec_])
|
200
|
+
|
201
|
+
`cut` cuts the reel from `from:` to `to:`.
|
202
|
+
|
203
|
+
`loop`([_times_])
|
204
|
+
|
205
|
+
`loop` loops(!) the reel so many times (no _times_ param means maximum times currently possible).
|
206
|
+
|
207
|
+
`mute`
|
208
|
+
|
209
|
+
`volume`(_ratio_)
|
210
|
+
|
211
|
+
`volume` changes the volume proportionally to the source. `mute` mutes.
|
212
|
+
|
213
|
+
`video`
|
214
|
+
|
215
|
+
`video` channels just the video from the reel.
|
216
|
+
|
217
|
+
`audio`
|
218
|
+
|
219
|
+
`audio` channels just the audio from the reel.
|
220
|
+
|
221
|
+
`copy`(_reel_)
|
222
|
+
|
223
|
+
`copy` copies the reel's modifier chain _onto_ the given reel.
|
224
|
+
|
225
|
+
|
226
|
+
### Attention
|
227
|
+
|
228
|
+
- Ffmprb is a work in progress, and even more so than Ffmpeg itself;
|
229
|
+
use at your own risk and check thoroughly for production fitness in your project.
|
230
|
+
- Ffmprb uses threads internally, however, it is not thread-safe interface-wise:
|
231
|
+
you must not share its objects between different threads.
|
232
|
+
|
141
233
|
### ProcVis support (experimental)
|
142
234
|
|
143
235
|
To enable [ProcVis](https://procvis.io) support (source), define `FFMPRB_PROC_VIS_FIREBASE_URL=my-proc-vis-io` (replace with your Firebase instance) in your running environment and watch the log for `You may view your process visualised at: https://proc-vis-io.firebaseapp.com/?pid=70311657638000 (a sample ProcVis snapshot of a full specs run).
|
144
236
|
|
237
|
+
|
145
238
|
## Development
|
146
239
|
|
147
240
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/exe/ffmprb
CHANGED
data/ffmprb.gemspec
CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['costa@mouldwarp.com']
|
11
11
|
|
12
12
|
spec.summary = "ffmprb is your audio/video montage friend, based on https://ffmpeg.org"
|
13
|
-
spec.description = "A DSL (Damn-Simple Language) and a micro-engine for ffmpeg and ffriends"
|
14
|
-
spec.homepage =
|
13
|
+
spec.description = "A video and audio composing DSL (Damn-Simple Language) and a micro-engine for ffmpeg and ffriends"
|
14
|
+
spec.homepage = Ffmprb::GEM_GITHUB_URL
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
17
|
spec.bindir = 'exe'
|
@@ -20,10 +20,12 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
# NOTE I'm not happy with this dependency, and there's nothing crossplatform (= for windoze too) at the moment
|
22
22
|
spec.add_dependency 'mkfifo', '~> 0.1.1'
|
23
|
+
# NOTE make it into an optional dependency? Nah for now
|
24
|
+
spec.add_dependency 'thor', '~> 0.19.1'
|
23
25
|
|
24
26
|
spec.add_development_dependency 'bundler', '>= 1.11.2'
|
25
|
-
spec.add_development_dependency 'byebug', '>= 8.2.
|
26
|
-
spec.add_development_dependency 'simplecov', '>= 0.
|
27
|
+
spec.add_development_dependency 'byebug', '>= 8.2.4'
|
28
|
+
spec.add_development_dependency 'simplecov', '>= 0.11.2'
|
27
29
|
spec.add_development_dependency 'guard-rspec', '>= 4.6.5'
|
28
30
|
spec.add_development_dependency 'guard-bundler', '>= 2.1.0'
|
29
31
|
spec.add_development_dependency 'rake', '>= 11.1.2'
|
data/lib/defaults.rb
CHANGED
@@ -21,6 +21,7 @@ module Ffmprb
|
|
21
21
|
Process.output_video_resolution = CGA
|
22
22
|
Process.output_video_fps = 16
|
23
23
|
Process.output_audio_encoder = 'libmp3lame'
|
24
|
+
Process.output_audio_sampling_freq = nil # NOTE Use ffmpeg default by default, specify otherwise e.g. 44100
|
24
25
|
|
25
26
|
Util.cmd_timeout = 30
|
26
27
|
Util.ffmpeg_cmd = %w[ffmpeg -y]
|
@@ -39,6 +40,7 @@ module Ffmprb
|
|
39
40
|
|
40
41
|
# NOTE http://12factor.net etc
|
41
42
|
|
43
|
+
Ffmprb.log_level = Logger::INFO
|
42
44
|
Ffmprb.ffmpeg_debug = ENV.fetch('FFMPRB_FFMPEG_DEBUG', '') !~ Ffmprb::ENV_VAR_FALSE_REGEX
|
43
45
|
Ffmprb.debug = ENV.fetch('FFMPRB_DEBUG', '') !~ Ffmprb::ENV_VAR_FALSE_REGEX
|
44
46
|
|
data/lib/ffmprb.rb
CHANGED
@@ -35,11 +35,11 @@ module Ffmprb
|
|
35
35
|
end
|
36
36
|
alias :action! :process # ;)
|
37
37
|
|
38
|
-
attr_accessor :debug, :ffmpeg_debug
|
38
|
+
attr_accessor :debug, :ffmpeg_debug, :log_level
|
39
39
|
|
40
40
|
def logger
|
41
41
|
@logger ||= Logger.new(STDERR).tap do |logger|
|
42
|
-
logger.level = debug ? Logger::DEBUG :
|
42
|
+
logger.level = debug ? Logger::DEBUG : Ffmprb.log_level
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/ffmprb/execution.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
1
3
|
module Ffmprb
|
2
4
|
|
3
|
-
class Execution
|
5
|
+
class Execution < Thor
|
4
6
|
|
5
|
-
def
|
6
|
-
@params = params
|
7
|
-
@script = eval("lambda{#{script}}")
|
8
|
-
end
|
7
|
+
def self.exit_on_failure?; true; end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
class_option :debug, :type => :boolean, :default => false
|
10
|
+
class_option :verbose, :aliases => '-v', :type => :boolean, :default => false
|
11
|
+
class_option :quiet, :aliases => '-q', :type => :boolean, :default => false
|
13
12
|
|
14
|
-
|
13
|
+
default_task :process
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
desc :process, "Reads an ffmprb script from STDIN and carries it out. See #{GEM_GITHUB_URL}"
|
16
|
+
def process(*ios)
|
17
|
+
script = eval("lambda{#{STDIN.read}}")
|
18
|
+
Ffmprb.log_level =
|
19
|
+
if options[:debug]
|
20
|
+
Logger::DEBUG
|
21
|
+
elsif options[:verbose]
|
22
|
+
Logger::INFO
|
23
|
+
elsif options[:quiet]
|
24
|
+
Logger::ERROR
|
25
|
+
else
|
26
|
+
Logger::WARN
|
27
|
+
end
|
28
|
+
Ffmprb.process *ios, ignore_broken_pipes: false, &script
|
29
|
+
end
|
30
|
+
|
31
|
+
# NOTE a hack from http://stackoverflow.com/a/23955971/714287
|
32
|
+
def method_missing(method, *args)
|
33
|
+
args = [:process, method.to_s] + args
|
34
|
+
self.class.start(args)
|
35
|
+
end
|
19
36
|
|
20
|
-
Execution.new(*ARGV, script: STDIN.read).run
|
21
37
|
end
|
22
38
|
|
23
39
|
end
|
data/lib/ffmprb/process.rb
CHANGED
@@ -16,13 +16,14 @@ module Ffmprb
|
|
16
16
|
attr_accessor :output_video_resolution
|
17
17
|
attr_accessor :output_video_fps
|
18
18
|
attr_accessor :output_audio_encoder
|
19
|
+
attr_accessor :output_audio_sampling_freq
|
19
20
|
|
20
21
|
attr_accessor :timeout
|
21
22
|
|
22
23
|
def intermediate_channel_extname(video:, audio:)
|
23
24
|
if video
|
24
25
|
if audio
|
25
|
-
'.flv'
|
26
|
+
'.flv' # TODO optimise this by using http://superuser.com/a/522853 or something
|
26
27
|
else
|
27
28
|
'.y4m'
|
28
29
|
end
|
@@ -53,7 +54,8 @@ module Ffmprb
|
|
53
54
|
end
|
54
55
|
def output_audio_options
|
55
56
|
{
|
56
|
-
encoder: output_audio_encoder
|
57
|
+
encoder: output_audio_encoder,
|
58
|
+
sampling_freq: output_audio_sampling_freq
|
57
59
|
}
|
58
60
|
end
|
59
61
|
|
@@ -114,7 +116,7 @@ module Ffmprb
|
|
114
116
|
@parent = opts.delete(:parent)
|
115
117
|
parent.proc_vis_node self if parent
|
116
118
|
self.ignore_broken_pipes = opts.delete(:ignore_broken_pipes)
|
117
|
-
|
119
|
+
Util.assert_options_empty! opts
|
118
120
|
@inputs, @outputs = [], []
|
119
121
|
end
|
120
122
|
|
data/lib/ffmprb/process/input.rb
CHANGED
@@ -22,14 +22,14 @@ module Ffmprb
|
|
22
22
|
fps = nil # NOTE ah, ruby
|
23
23
|
args.concat %W[-noautorotate] unless video.delete(:auto_rotate)
|
24
24
|
args.concat %W[-r #{fps}] if (fps = video.delete(:fps))
|
25
|
-
|
25
|
+
Util.assert_options_empty! video
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def audio_args(audio=nil)
|
30
30
|
audio = Process.input_audio_options.merge(audio.to_h)
|
31
31
|
[].tap do |args|
|
32
|
-
|
32
|
+
Util.assert_options_empty! audio
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -74,8 +74,14 @@ module Ffmprb
|
|
74
74
|
else
|
75
75
|
Filter.copy "#{in_lbl}:v", "#{lbl}:v"
|
76
76
|
end
|
77
|
+
elsif video
|
78
|
+
fail Error, "No video stream to provide"
|
77
79
|
end),
|
78
|
-
*(audio && channel?(:audio)
|
80
|
+
*(if audio && channel?(:audio)
|
81
|
+
Filter.anull "#{in_lbl}:a", "#{lbl}:a"
|
82
|
+
elsif audio
|
83
|
+
fail Error, "No audio stream to provide"
|
84
|
+
end)
|
79
85
|
]
|
80
86
|
end
|
81
87
|
|
@@ -30,16 +30,12 @@ module Ffmprb
|
|
30
30
|
# 2) Tee+buffer the original raw input io: one stream goes back into the process throw the raw input io replacement fifo; the other is fed into the filtering process
|
31
31
|
# 3) Which uses the same underlying filters to produce a filtered and parameterised stream, which is fed into the looping process through a N-Tee+buffer
|
32
32
|
# 4) Invoke the looping process which just concatenates its N inputs and produces the new raw input (the aux input)
|
33
|
-
# XXX
|
34
|
-
# -) If the consumer is broken of the:
|
35
|
-
# a. raw input - the Tee+buffer is resilient - unless the f-p-l breaks too;
|
36
|
-
# b. the f-p-l stream - the looping process fails, the N-Tee+buffer breaks, the filtering process fails, and the Tee+buffer may fail
|
37
33
|
|
38
34
|
# Looping
|
39
35
|
# NOTE all the processing is done before looping
|
40
36
|
|
41
37
|
aux_input(video: video, audio: audio).filters_for lbl,
|
42
|
-
video: OpenStruct.new, audio: OpenStruct.new
|
38
|
+
video: video && OpenStruct.new, audio: audio && OpenStruct.new
|
43
39
|
end
|
44
40
|
|
45
41
|
protected
|
@@ -91,12 +87,11 @@ module Ffmprb
|
|
91
87
|
|
92
88
|
buff_ios = (1..times).map{File.temp_fifo intermediate_extname}
|
93
89
|
Ffmprb.logger.debug "Preprocessed #{dst_io.path} will be teed to #{buff_ios.map(&:path).join '; '}"
|
94
|
-
looping = true
|
95
90
|
Util::Thread.new "cloning buffer watcher" do
|
96
|
-
dst_io.threaded_buffered_copy_to
|
97
|
-
|
98
|
-
|
99
|
-
|
91
|
+
dst_io.threaded_buffered_copy_to(*buff_ios).tap do |io_buff|
|
92
|
+
Util::Thread.join_children!
|
93
|
+
Ffmprb.logger.warn "Looping ~from #{src_io.path} finished before its consumer: if you just wanted to loop input #{Util.ffmpeg_inputs_max} times, that's fine, but if you expected it to loop indefinitely... #{Util.ffmpeg_inputs_max} is the maximum #loop can do at the moment, and it may just not be enough in this case (workaround by concatting or file a complaint at #{Ffmprb::GEM_GITHUB_URL}/issues please)." if times == Util.ffmpeg_inputs_max && io_buff.stats.blocks_buff == 0
|
94
|
+
end
|
100
95
|
end
|
101
96
|
|
102
97
|
# Ffmprb.logger.debug "Concatenation of #{buff_ios.map(&:path).join '; '} will go to #{@io.io.path} to be fed to this process"
|
@@ -110,8 +105,8 @@ module Ffmprb
|
|
110
105
|
Ffmprb.logger.debug "Looping #{buff_ios.size} times"
|
111
106
|
|
112
107
|
Ffmprb.logger.debug "(L4) Looping (#{buff_ios.map &:path}) into (#{aux_io.path})"
|
113
|
-
begin
|
114
|
-
Ffmprb.process parent: @raw.process
|
108
|
+
begin # NOTE may not write its entire output, it's ok
|
109
|
+
Ffmprb.process parent: @raw.process, ignore_broken_pipes: false do
|
115
110
|
|
116
111
|
ins = buff_ios.map{ |i| input i }
|
117
112
|
output(aux_io, video: nil, audio: nil) do
|
@@ -119,8 +114,8 @@ module Ffmprb
|
|
119
114
|
end
|
120
115
|
|
121
116
|
end
|
122
|
-
|
123
|
-
|
117
|
+
rescue Util::BrokenPipeError
|
118
|
+
looping_max = false # NOTE see the above warning
|
124
119
|
end
|
125
120
|
end
|
126
121
|
|
@@ -16,7 +16,7 @@ module Ffmprb
|
|
16
16
|
args.concat %W[-pix_fmt #{pixel_format}] if (pixel_format = video.delete(:pixel_format))
|
17
17
|
video.delete :resolution # NOTE is handled otherwise
|
18
18
|
video.delete :fps # NOTE is handled otherwise
|
19
|
-
|
19
|
+
Util.assert_options_empty! video
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -25,7 +25,8 @@ module Ffmprb
|
|
25
25
|
[].tap do |args|
|
26
26
|
encoder = nil
|
27
27
|
args.concat %W[-c:a #{encoder}] if (encoder = audio.delete(:encoder))
|
28
|
-
|
28
|
+
args.concat %W[-ar #{sampling_freq}] if (sampling_freq = audio.delete(:sampling_freq))
|
29
|
+
Util.assert_options_empty! audio
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -83,6 +84,7 @@ module Ffmprb
|
|
83
84
|
# NOTE Image-Padding to match the target resolution
|
84
85
|
# TODO full screen only at the moment (see exception above)
|
85
86
|
|
87
|
+
Ffmprb.logger.debug "#{self} asking for filters of #{curr_reel.reel.io.inspect} video: #{channel(:video)}, audio: #{channel(:audio)}"
|
86
88
|
@filters.concat(
|
87
89
|
curr_reel.reel.filters_for lbl, video: channel(:video), audio: channel(:audio)
|
88
90
|
)
|
@@ -315,6 +317,10 @@ module Ffmprb
|
|
315
317
|
end
|
316
318
|
end
|
317
319
|
|
320
|
+
def input(io, video: true, audio: true)
|
321
|
+
process.input io, video: video, audio: audio
|
322
|
+
end
|
323
|
+
|
318
324
|
def roll(
|
319
325
|
reel,
|
320
326
|
onto: :full_screen,
|
data/lib/ffmprb/util.rb
CHANGED
@@ -6,6 +6,7 @@ module Ffmprb
|
|
6
6
|
|
7
7
|
module Util
|
8
8
|
|
9
|
+
class BrokenPipeError < Error; end
|
9
10
|
class TimeLimitError < Error; end
|
10
11
|
|
11
12
|
class << self
|
@@ -41,8 +42,12 @@ module Ffmprb
|
|
41
42
|
value = wait_thr.value
|
42
43
|
status = value.exitstatus # NOTE blocking
|
43
44
|
if status != 0
|
44
|
-
if
|
45
|
-
|
45
|
+
if value.signaled? && value.termsig == Signal.list['PIPE']
|
46
|
+
if ignore_broken_pipes
|
47
|
+
Ffmprb.logger.info "Ignoring broken pipe: #{cmd_str}"
|
48
|
+
else
|
49
|
+
fail BrokenPipeError, cmd_str
|
50
|
+
end
|
46
51
|
else
|
47
52
|
status ||= "sig##{value.termsig}"
|
48
53
|
fail Error, "#{cmd_str} (#{status}):\n#{stderr_r.read}"
|
@@ -63,6 +68,9 @@ module Ffmprb
|
|
63
68
|
thr.value
|
64
69
|
end
|
65
70
|
|
71
|
+
def assert_options_empty!(opts)
|
72
|
+
fail ArgumentError, "Unknown options: #{opts}" unless opts.empty?
|
73
|
+
end
|
66
74
|
protected
|
67
75
|
|
68
76
|
# NOTE a best guess kinda method
|
data/lib/ffmprb/util/proc_vis.rb
CHANGED
@@ -115,6 +115,7 @@ module Ffmprb
|
|
115
115
|
prev_t = Time.now
|
116
116
|
while @_proc_vis_upq.deq # NOTE currently, runs forever (nil terminator needed)
|
117
117
|
proc_vis_do_update
|
118
|
+
Thread.current.live! # XXX not the best we can do here
|
118
119
|
while Time.now - prev_t < UPDATE_PERIOD_SEC
|
119
120
|
@_proc_vis_upq.deq # NOTE drains the queue
|
120
121
|
end
|
@@ -20,6 +20,9 @@ module Ffmprb
|
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
|
+
attr_reader :stats
|
24
|
+
|
25
|
+
|
23
26
|
# NOTE input/output can be lambdas for single asynchronic io evaluation
|
24
27
|
# the lambdas must be timeout-interrupt-safe (since they are wrapped in timeout blocks)
|
25
28
|
# NOTE all ios are being opened and closed as soon as possible
|
@@ -33,7 +36,6 @@ module Ffmprb
|
|
33
36
|
OpenStruct.new _io: outp, q: SizedQueue.new(ThreadedIoBuffer.blocks_max)
|
34
37
|
end
|
35
38
|
@stats = Stats.new(self)
|
36
|
-
@terminate = false
|
37
39
|
@keep_outputs_open_on_input_idle_limit = keep_outputs_open_on_input_idle_limit
|
38
40
|
# @events = {}
|
39
41
|
|
@@ -45,7 +47,7 @@ module Ffmprb
|
|
45
47
|
end
|
46
48
|
|
47
49
|
Thread.join_children!.tap do
|
48
|
-
Ffmprb.logger.debug "ThreadedIoBuffer (#{@input.path}->#{@outputs.map(&:io).map(&:path)}) terminated successfully (#{
|
50
|
+
Ffmprb.logger.debug "ThreadedIoBuffer (#{@input.path}->#{@outputs.map(&:io).map(&:path)}) terminated successfully (#{stats})"
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
@@ -66,7 +68,7 @@ module Ffmprb
|
|
66
68
|
# handle_synchronously :once
|
67
69
|
#
|
68
70
|
# def reader_done!
|
69
|
-
# Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{
|
71
|
+
# Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{stats})"
|
70
72
|
# fire! :reader_done
|
71
73
|
# end
|
72
74
|
#
|
@@ -92,7 +94,7 @@ module Ffmprb
|
|
92
94
|
#
|
93
95
|
|
94
96
|
def label
|
95
|
-
"IObuff: Curr/Peak/Max=#{
|
97
|
+
"IObuff: Curr/Peak/Max=#{stats.blocks_buff}/#{stats.blocks_max}/#{ThreadedIoBuffer.blocks_max} In/Out=#{stats.bytes_in}/#{stats.bytes_out}"
|
96
98
|
end
|
97
99
|
|
98
100
|
private
|
@@ -123,61 +125,64 @@ module Ffmprb
|
|
123
125
|
Thread.new("buffer reader") do
|
124
126
|
begin
|
125
127
|
input_io = reader_input!
|
126
|
-
loop do
|
128
|
+
loop do # NOTE until EOFError, see below
|
127
129
|
s = ''
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
if
|
138
|
-
|
139
|
-
|
140
|
-
s = '' # NOTE let's see if it helps outputting an incomplete block
|
141
|
-
else
|
142
|
-
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) giving up after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{@stats.bytes_in}b closing outputs"
|
143
|
-
@terminate = true
|
144
|
-
output_enq! nil # NOTE EOF signal
|
145
|
-
end
|
130
|
+
while s.length < ThreadedIoBuffer.block_size
|
131
|
+
timeouts = 0
|
132
|
+
logged_timeouts = 1
|
133
|
+
begin
|
134
|
+
ss = input_io.read_nonblock(ThreadedIoBuffer.block_size - s.length)
|
135
|
+
stats.add_bytes_in ss.length
|
136
|
+
s += ss
|
137
|
+
rescue IO::WaitReadable
|
138
|
+
if @keep_outputs_open_on_input_idle_limit && stats.bytes_in > 0 && stats.blocks_buff == 0 && timeouts * ThreadedIoBuffer.io_wait_timeout > @keep_outputs_open_on_input_idle_limit
|
139
|
+
if s.length > 0 # NOTE let's see if it helps outputting an incomplete block
|
140
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) giving a chance to write #{s.length}/#{ThreadedIoBuffer.block_size}b after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b"
|
141
|
+
break
|
146
142
|
else
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
143
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) giving up after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b, closing outputs"
|
144
|
+
raise EOFError
|
145
|
+
end
|
146
|
+
else
|
147
|
+
Thread.current.live!
|
148
|
+
timeouts += 1
|
149
|
+
if timeouts > 2 * logged_timeouts
|
150
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) retrying... (#{timeouts} reads): #{$!.class}"
|
151
|
+
logged_timeouts = timeouts
|
154
152
|
end
|
155
|
-
|
156
|
-
Ffmprb.logger.error "ThreadedIoBuffer reader (from #{input_io.path}) gets a #{$!} - should not really happen."
|
157
|
-
IO.select nil, [input_io], nil, ThreadedIoBuffer.io_wait_timeout
|
153
|
+
IO.select [input_io], nil, nil, ThreadedIoBuffer.io_wait_timeout
|
158
154
|
retry
|
159
155
|
end
|
156
|
+
rescue EOFError
|
157
|
+
output_enq! s
|
158
|
+
raise
|
159
|
+
rescue IO::WaitWritable # NOTE should not really happen, so just for conformance
|
160
|
+
Ffmprb.logger.error "ThreadedIoBuffer reader (from #{input_io.path}) gets a #{$!} - should not really happen."
|
161
|
+
IO.select nil, [input_io], nil, ThreadedIoBuffer.io_wait_timeout
|
162
|
+
retry
|
160
163
|
end
|
161
|
-
ensure
|
162
|
-
output_enq! s unless @terminate
|
163
164
|
end
|
165
|
+
output_enq! s
|
164
166
|
end
|
165
167
|
rescue EOFError
|
166
|
-
|
167
|
-
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) breaking off"
|
168
|
-
@terminate = true
|
169
|
-
output_enq! nil # NOTE EOF signal
|
170
|
-
end
|
168
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) breaking off"
|
171
169
|
rescue AllOutputsBrokenError
|
172
170
|
Ffmprb.logger.info "All outputs broken"
|
171
|
+
rescue Exception
|
172
|
+
@reader_failed = Error.new("Reader failed: #{$!}")
|
173
|
+
raise
|
173
174
|
ensure
|
174
175
|
begin
|
175
|
-
|
176
|
+
output_enq! nil # NOTE EOF signal
|
177
|
+
rescue
|
178
|
+
end
|
179
|
+
begin
|
180
|
+
input_io.close if input_io.respond_to?(:close)
|
176
181
|
rescue
|
177
182
|
Ffmprb.logger.error "#{$!.class.name} closing ThreadedIoBuffer input: #{$!.message}"
|
178
183
|
end
|
179
184
|
# reader_done!
|
180
|
-
Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{
|
185
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{stats})"
|
181
186
|
end
|
182
187
|
end
|
183
188
|
end
|
@@ -189,7 +194,7 @@ module Ffmprb
|
|
189
194
|
Ffmprb.logger.debug "Opening buffer output"
|
190
195
|
output.io =
|
191
196
|
Thread.timeout_or_live nil, log: "in the buffer writer helper thread", timeout: ThreadedIoBuffer.timeout do |time|
|
192
|
-
fail Error, "giving up buffer writer init since the reader has failed (#{@
|
197
|
+
fail Error, "giving up buffer writer init since the reader has failed (#{@reader_failed.message})" if @reader_failed
|
193
198
|
output._io.call
|
194
199
|
end
|
195
200
|
Ffmprb.logger.debug "Opened buffer output: #{output.io.path}"
|
@@ -201,21 +206,21 @@ module Ffmprb
|
|
201
206
|
Thread.new("buffer writer") do
|
202
207
|
begin
|
203
208
|
output_io = writer_output!(output)
|
204
|
-
while s = output
|
205
|
-
@stats.blocks_for output, output.q.length
|
209
|
+
while s = output_deq!(output) # NOTE until EOF signal
|
206
210
|
timeouts = 0
|
207
211
|
logged_timeouts = 1
|
208
212
|
begin
|
209
|
-
fail @
|
210
|
-
written = output_io.write_nonblock(s)
|
211
|
-
|
213
|
+
fail @reader_failed if @reader_failed # NOTE otherwise, output_io should not be nil
|
214
|
+
written = output_io.write_nonblock(s)
|
215
|
+
stats.add_bytes_out written
|
212
216
|
|
213
|
-
if written != s.length
|
217
|
+
if written != s.length
|
214
218
|
s = s[written..-1]
|
215
219
|
raise IO::EAGAINWaitWritable
|
216
220
|
end
|
217
221
|
|
218
222
|
rescue IO::WaitWritable
|
223
|
+
Thread.current.live!
|
219
224
|
timeouts += 1
|
220
225
|
if timeouts > 2 * logged_timeouts
|
221
226
|
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) retrying... (#{timeouts} writes): #{$!.class}"
|
@@ -236,12 +241,12 @@ module Ffmprb
|
|
236
241
|
ensure
|
237
242
|
# terminated!
|
238
243
|
begin
|
239
|
-
|
240
|
-
output.broken = true
|
244
|
+
output_io.close if !output.broken && output_io && output_io.respond_to?(:close)
|
241
245
|
rescue
|
242
246
|
Ffmprb.logger.error "#{$!.class.name} closing ThreadedIoBuffer output: #{$!.message}"
|
243
247
|
end
|
244
|
-
|
248
|
+
output.broken = true
|
249
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io && output_io.path}) terminated (#{stats})"
|
245
250
|
end
|
246
251
|
end
|
247
252
|
end
|
@@ -263,8 +268,7 @@ module Ffmprb
|
|
263
268
|
Timeout.timeout(ThreadedIoBuffer.timeout) do
|
264
269
|
output.q.enq item
|
265
270
|
end
|
266
|
-
|
267
|
-
true
|
271
|
+
stats.blocks_for output
|
268
272
|
|
269
273
|
rescue Timeout::Error
|
270
274
|
next if output.broken
|
@@ -277,19 +281,26 @@ module Ffmprb
|
|
277
281
|
|
278
282
|
retry unless timeouts >= ThreadedIoBuffer.timeout_limit # NOTE the queue has probably overflown
|
279
283
|
|
280
|
-
@
|
284
|
+
@reader_failed ||= Error.new("the writer has failed with timeout limit while queuing") # NOTE screw the race condition
|
281
285
|
# timeout!
|
282
286
|
fail Error, "Looks like we're stuck (>#{ThreadedIoBuffer.timeout_limit*ThreadedIoBuffer.timeout}s idle) with #{ThreadedIoBuffer.blocks_max}x#{ThreadedIoBuffer.block_size}b blocks (buffering #{reader_input!.path}->...)..."
|
283
287
|
end
|
284
288
|
end.empty?
|
285
289
|
end
|
286
290
|
|
291
|
+
def output_deq!(outp)
|
292
|
+
outp.q.deq.tap do
|
293
|
+
stats.blocks_for outp
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
287
297
|
class Stats < OpenStruct
|
288
298
|
include MonitorMixin
|
289
299
|
|
290
300
|
def initialize(proc)
|
291
301
|
@proc = proc
|
292
|
-
|
302
|
+
@output_blocks = {}
|
303
|
+
super blocks_buff: 0, blocks_max: 0, bytes_in: 0, bytes_out: 0
|
293
304
|
end
|
294
305
|
|
295
306
|
def add_bytes_in(n)
|
@@ -306,14 +317,14 @@ module Ffmprb
|
|
306
317
|
end
|
307
318
|
end
|
308
319
|
|
309
|
-
def blocks_for(outp
|
320
|
+
def blocks_for(outp)
|
310
321
|
synchronize do
|
311
|
-
|
312
|
-
|
322
|
+
blocks = @output_blocks[outp.object_id] = outp.q.length
|
323
|
+
if blocks > blocks_max
|
324
|
+
self.blocks_max = blocks
|
313
325
|
@proc.proc_vis_node @proc # NOTE update
|
314
326
|
end
|
315
|
-
(
|
316
|
-
self.blocks_buff = @_outp_blocks.values.reduce(0, :+)
|
327
|
+
self.blocks_buff = @output_blocks.values.reduce(0, :+)
|
317
328
|
end
|
318
329
|
end
|
319
330
|
|
data/lib/ffmprb/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffmprb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- showbox.com
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-04-
|
12
|
+
date: 2016-04-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mkfifo
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 0.1.1
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: thor
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.19.1
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.19.1
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: bundler
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,28 +59,28 @@ dependencies:
|
|
45
59
|
requirements:
|
46
60
|
- - ">="
|
47
61
|
- !ruby/object:Gem::Version
|
48
|
-
version: 8.2.
|
62
|
+
version: 8.2.4
|
49
63
|
type: :development
|
50
64
|
prerelease: false
|
51
65
|
version_requirements: !ruby/object:Gem::Requirement
|
52
66
|
requirements:
|
53
67
|
- - ">="
|
54
68
|
- !ruby/object:Gem::Version
|
55
|
-
version: 8.2.
|
69
|
+
version: 8.2.4
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: simplecov
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
59
73
|
requirements:
|
60
74
|
- - ">="
|
61
75
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
76
|
+
version: 0.11.2
|
63
77
|
type: :development
|
64
78
|
prerelease: false
|
65
79
|
version_requirements: !ruby/object:Gem::Requirement
|
66
80
|
requirements:
|
67
81
|
- - ">="
|
68
82
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
83
|
+
version: 0.11.2
|
70
84
|
- !ruby/object:Gem::Dependency
|
71
85
|
name: guard-rspec
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,7 +165,8 @@ dependencies:
|
|
151
165
|
- - ">="
|
152
166
|
- !ruby/object:Gem::Version
|
153
167
|
version: 0.2.6
|
154
|
-
description: A DSL (Damn-Simple Language) and a micro-engine
|
168
|
+
description: A video and audio composing DSL (Damn-Simple Language) and a micro-engine
|
169
|
+
for ffmpeg and ffriends
|
155
170
|
email:
|
156
171
|
- costa@mouldwarp.com
|
157
172
|
executables:
|