-
Notifications
You must be signed in to change notification settings - Fork 0
/
ffmpeg.go
159 lines (138 loc) · 3.48 KB
/
ffmpeg.go
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
package ffgoconv
import (
"fmt"
"errors"
"io"
"io/ioutil"
"os/exec"
)
var (
ErrFFmpegNotRunning = errors.New("ffgoconv: FFmpeg: not running")
ErrFFmpegFilepathEmpty = errors.New("ffgoconv: filepath must not be empty")
)
// FFmpeg contains all the data required to keep an FFmpeg process running and usable.
type FFmpeg struct {
process *exec.Cmd
closed bool
err error
stdin io.WriteCloser
stdout io.ReadCloser
stderr io.ReadCloser
}
// NewFFmpeg returns an initialized *FFmpeg or an error if one could not be created.
//
// If filepath is empty, the FFmpeg process will not start. You can specify any location supported by FFmpeg, such as a network location or a local filepath.
//
// If args is nil or empty, the default values will be used. Do not specify your own arguments unless you understand how ffgoconv functions.
func NewFFmpeg(filepath string, args []string) (*FFmpeg, error) {
if filepath == "" {
return nil, ErrFFmpegFilepathEmpty
}
noStd := true
if len(args) == 0 {
noStd = false
args = []string{
"-hide_banner",
"-stats",
"-re", "-i", filepath,
"-map", "0:a",
"-acodec", "pcm_f64le",
"-f", "f64le",
"-vol", "256",
"-ar", "48000",
"-ac", "2",
"-threads", "1",
"pipe:1",
}
}
ffmpeg := exec.Command("ffmpeg", args...)
stderrPipe, err := ffmpeg.StderrPipe()
if err != nil {
return nil, err
}
ff := &FFmpeg{
process: ffmpeg,
stderr: stderrPipe,
}
if !noStd {
ff.stdin, err = ffmpeg.StdinPipe()
if err != nil {
return nil, err
}
ff.stdout, err = ffmpeg.StdoutPipe()
if err != nil {
return nil, err
}
}
return ff, nil
}
// Run starts and waits on the FFmpeg process, and returns an exit error if any
func (ff *FFmpeg) Run() error {
defer ff.Close()
if err := ff.process.Start(); err != nil {
ff.setError(fmt.Errorf("ffgoconv: FFmpeg: error starting process: %v", err))
return ff.Err()
}
stderrData, _ := ioutil.ReadAll(ff.stderr)
if err := ff.process.Wait(); err != nil {
ff.setError(fmt.Errorf("ffgoconv: FFmpeg: error running process: {err: \"%v\", stderr: \"%v\"}", err, stderrData))
return ff.Err()
}
return nil
}
// IsRunning returns whether or not the FFmpeg process is running, per the knowledge of ffgoconv.
func (ff *FFmpeg) IsRunning() bool {
return !ff.closed
}
// Close closes the FFmpeg process gracefully and renders the struct unusable.
func (ff *FFmpeg) Close() {
if ff.closed {
return
}
ff.process.Process.Kill()
if ff.stdin != nil {
ff.stdin.Close()
}
if ff.stdout != nil {
ff.stdout.Close()
}
ff.stderr.Close()
ff.closed = true
}
// Err returns the last stored error. Error histories are not kept, so check as soon as something goes wrong.
func (ff *FFmpeg) Err() error {
return ff.err
}
func (ff *FFmpeg) setError(err error) {
ff.err = err
}
// Read implements an io.Reader wrapper around *FFmpeg.stdout.
func (ff *FFmpeg) Read(data []byte) (n int, err error) {
if !ff.IsRunning() {
return 0, ErrFFmpegNotRunning
}
n, err = ff.stdout.Read(data)
if err != nil {
ff.Close()
}
return n, err
}
// ReadError implements an io.Reader wrapper around *FFmpeg.stderr.
func (ff *FFmpeg) ReadError(data []byte) (n int, err error) {
if !ff.IsRunning() {
return 0, ErrFFmpegNotRunning
}
n, err = ff.stderr.Read(data)
if err != nil {
ff.Close()
}
return n, err
}
// Write implements an io.Writer wrapper around *FFmpeg.stdin.
func (ff *FFmpeg) Write(data []byte) error {
if !ff.IsRunning() {
return ErrFFmpegNotRunning
}
_, err := ff.stdin.Write(data)
return err
}