-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
Copy pathvideo_capture.coffee
164 lines (127 loc) · 4.04 KB
/
video_capture.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
_ = require("lodash")
utils = require("fluent-ffmpeg/lib/utils")
debug = require("debug")("cypress:server:video")
ffmpeg = require("fluent-ffmpeg")
stream = require("stream")
Promise = require("bluebird")
ffmpegPath = require("@ffmpeg-installer/ffmpeg").path
fs = require("./util/fs")
ffmpeg.setFfmpegPath(ffmpegPath)
module.exports = {
copy: (src, dest) ->
fs
.copyAsync(src, dest, {overwrite: true})
.catch {code: "ENOENT"}, ->
## dont yell about ENOENT errors
start: (name, options = {}) ->
pt = stream.PassThrough()
started = Promise.pending()
ended = Promise.pending()
done = false
errored = false
written = false
logErrors = true
wantsWrite = true
skipped = 0
_.defaults(options, {
onError: ->
})
end = ->
done = true
if not written
## when no data has been written this will
## result in an 'pipe:0: End of file' error
## for so we need to account for that
## and not log errors to the console
logErrors = false
pt.end()
## return the ended promise which will eventually
## get resolve or rejected
return ended.promise
write = (data) ->
## make sure we haven't ended
## our stream yet because paint
## events can linger beyond
## finishing the actual video
return if done
## we have written at least 1 byte
written = true
if wantsWrite
if not wantsWrite = pt.write(data)
pt.once "drain", ->
wantsWrite = true
else
skipped += 1
# console.log("skipping frame. total is", skipped)
cmd = ffmpeg({
source: pt
priority: 20
})
.inputFormat("image2pipe")
.inputOptions("-use_wallclock_as_timestamps 1")
.videoCodec("libx264")
.outputOptions("-preset ultrafast")
.on "start", (command) ->
debug("capture started %o", { command })
started.resolve(new Date)
.on "codecData", (data) ->
debug("capture codec data: %o", data)
.on "stderr", (stderr) ->
debug("capture stderr log %o", { message: stderr })
.on "error", (err, stdout, stderr) ->
debug("capture errored: %o", { error: err.message, stdout, stderr })
## if we're supposed log errors then
## bubble them up
if logErrors
options.onError(err, stdout, stderr)
err.recordingVideoFailed = true
## reject the ended promise
ended.reject(err)
.on "end", ->
debug("capture ended")
ended.resolve()
.save(name)
return {
cmd: cmd
end: end
start: started.promise
write: write
}
process: (name, cname, videoCompression, onProgress = ->) ->
total = null
new Promise (resolve, reject) ->
cmd = ffmpeg()
.input(name)
.videoCodec("libx264")
.outputOptions([
"-preset fast"
"-crf #{videoCompression}"
])
.on "start", (command) ->
debug("compression started %o", { command })
.on "codecData", (data) ->
debug("compression codec data: %o", data)
total = utils.timemarkToSeconds(data.duration)
.on "stderr", (stderr) ->
debug("compression stderr log %o", { message: stderr })
.on "progress", (progress) ->
## bail if we dont have total yet
return if not total
debug("compression progress: %o", progress)
progressed = utils.timemarkToSeconds(progress.timemark)
onProgress(progressed / total)
.on "error", (err, stdout, stderr) ->
debug("compression errored: %o", { error: err.message, stdout, stderr })
reject(err)
.on "end", ->
debug("compression ended")
## we are done progressing
onProgress(1)
## rename and obliterate the original
fs.moveAsync(cname, name, {
overwrite: true
})
.then ->
resolve()
.save(cname)
}