├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── config.go
├── ffmpeg
├── config.go
├── ffmpeg.go
├── metadata.go
├── options.go
└── progress.go
├── go.mod
├── metadata.go
├── options.go
├── progress.go
├── transcoder.go
└── utils
└── utils.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | # specify the version
6 | - image: circleci/golang:1.14-buster
7 | environment:
8 | GO111MODULE: "on"
9 |
10 | working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
11 | steps:
12 | - checkout
13 |
14 | # specify any bash command here prefixed with `run: `
15 | - run: sudo apt-get update && sudo apt-get install ffmpeg
16 | - run: go mod download
17 | - run: go test -failfast -v -run=. ./...
18 | - run: go build
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | main.go
2 | build
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 FlooStack
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Golang Transcoding Library
2 |
3 | > This repository is no longer maintained. Please use [Goffmpeg](https://github.com/xfrr/goffmpeg).
4 |
5 | ## Features
6 |
7 |
8 | - Ease use
9 | - Implement your own business logic around easy interfaces
10 |
11 | - Transcoding progress
12 | - Obtain progress events generated by transcoding application process
13 |
14 |
15 | ## Download from Github
16 |
17 | ```shell
18 | go get github.com/floostack/transcoder
19 | ```
20 |
21 | ## Example
22 |
23 | ```go
24 | package main
25 |
26 | import (
27 | "log"
28 |
29 | ffmpeg "github.com/floostack/transcoder/ffmpeg"
30 | )
31 |
32 | func main() {
33 |
34 |
35 | hwaccel := "cuvid"
36 | videoCodec := "h264_cuvid"
37 |
38 | inputOpts := ffmpeg.Options{
39 | Hwaccel: &hwaccel,
40 | VideoCodec: &videoCodec,
41 | }
42 |
43 | format := "mp4"
44 | overwrite := true
45 |
46 | outputOpts := ffmpeg.Options{
47 | OutputFormat: &format,
48 | Overwrite: &overwrite,
49 | }
50 |
51 | ffmpegConf := &ffmpeg.Config{
52 | FfmpegBinPath: "/usr/local/bin/ffmpeg",
53 | FfprobeBinPath: "/usr/local/bin/ffprobe",
54 | ProgressEnabled: true,
55 | }
56 |
57 | progress, err := ffmpeg.
58 | New(ffmpegConf).
59 | Input("/tmp/avi").
60 | Output("/tmp/mp4").
61 | WithInputOptions(inputOpts).
62 | WithOutputOptions(outputOpts).
63 | Start()
64 |
65 | if err != nil {
66 | log.Fatal(err)
67 | }
68 |
69 | for msg := range progress {
70 | log.Printf("%+v", msg)
71 | }
72 | }
73 | ```
74 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Config ...
4 | type Config interface{}
5 |
--------------------------------------------------------------------------------
/ffmpeg/config.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | // Config ...
4 | type Config struct {
5 | FfmpegBinPath string
6 | FfprobeBinPath string
7 | ProgressEnabled bool
8 | Verbose bool
9 | }
10 |
--------------------------------------------------------------------------------
/ffmpeg/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "context"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "os"
12 | "os/exec"
13 | "regexp"
14 | "strconv"
15 | "strings"
16 |
17 | "github.com/floostack/transcoder"
18 | "github.com/floostack/transcoder/utils"
19 | )
20 |
21 | // Transcoder ...
22 | type Transcoder struct {
23 | config *Config
24 | input string
25 | output []string
26 | inputOptions []string
27 | outputOptions [][]string
28 | metadata transcoder.Metadata
29 | inputPipeReader *io.ReadCloser
30 | outputPipeReader *io.ReadCloser
31 | inputPipeWriter *io.WriteCloser
32 | outputPipeWriter *io.WriteCloser
33 | commandContext *context.Context
34 | }
35 |
36 | // New ...
37 | func New(cfg *Config) transcoder.Transcoder {
38 | return &Transcoder{config: cfg}
39 | }
40 |
41 | // Start ...
42 | func (t *Transcoder) Start() (<-chan transcoder.Progress, error) {
43 |
44 | var stderrIn io.ReadCloser
45 |
46 | out := make(chan transcoder.Progress)
47 |
48 | defer t.closePipes()
49 |
50 | // Validates config
51 | if err := t.validate(); err != nil {
52 | return nil, err
53 | }
54 |
55 | // Get file metadata
56 | _, err := t.GetMetadata()
57 | if err != nil {
58 | return nil, err
59 | }
60 |
61 | // Append input file and standard options
62 | var args []string
63 |
64 | if len(t.inputOptions) > 0 {
65 | args = append(args, t.inputOptions...)
66 | }
67 |
68 | args = append(args, []string{"-i", t.input}...)
69 | outputLength := len(t.output)
70 | outputOptionsLength := len(t.outputOptions)
71 |
72 | if outputLength == 1 && outputOptionsLength == 0 {
73 | // Just append the 1 output file we've got
74 | args = append(args, t.output[0])
75 | } else {
76 | for index, out := range t.output {
77 | // Get executable flags
78 | // If we are at the last output file but still have several options, append them all at once
79 | if index == outputLength-1 && outputLength < outputOptionsLength {
80 | for i := index; i < len(t.outputOptions); i++ {
81 | args = append(args, t.outputOptions[i]...)
82 | }
83 | // Otherwise just append the current options
84 | } else {
85 | args = append(args, t.outputOptions[index]...)
86 | }
87 |
88 | // Append output flag
89 | args = append(args, out)
90 | }
91 | }
92 |
93 | // Initialize command
94 | // If a context object was supplied to this Transcoder before
95 | // starting, use this context when creating the command to allow
96 | // the command to be killed when the context expires
97 | var cmd *exec.Cmd
98 | if t.commandContext == nil {
99 | cmd = exec.Command(t.config.FfmpegBinPath, args...)
100 | } else {
101 | cmd = exec.CommandContext(*t.commandContext, t.config.FfmpegBinPath, args...)
102 | }
103 |
104 | // If progresss enabled, get stderr pipe and start progress process
105 | if t.config.ProgressEnabled && !t.config.Verbose {
106 | stderrIn, err = cmd.StderrPipe()
107 | if err != nil {
108 | return nil, fmt.Errorf("Failed getting transcoding progress (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err)
109 | }
110 | }
111 |
112 | if t.config.Verbose {
113 | cmd.Stderr = os.Stdout
114 | }
115 |
116 | // Start process
117 | err = cmd.Start()
118 | if err != nil {
119 | return nil, fmt.Errorf("Failed starting transcoding (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err)
120 | }
121 |
122 | if t.config.ProgressEnabled && !t.config.Verbose {
123 | go func() {
124 | t.progress(stderrIn, out)
125 | }()
126 |
127 | go func() {
128 | defer close(out)
129 | err = cmd.Wait()
130 | }()
131 | } else {
132 | err = cmd.Wait()
133 | }
134 |
135 | return out, nil
136 | }
137 |
138 | // Input ...
139 | func (t *Transcoder) Input(arg string) transcoder.Transcoder {
140 | t.input = arg
141 | return t
142 | }
143 |
144 | // Output ...
145 | func (t *Transcoder) Output(arg string) transcoder.Transcoder {
146 | t.output = append(t.output, arg)
147 | return t
148 | }
149 |
150 | // InputPipe ...
151 | func (t *Transcoder) InputPipe(w *io.WriteCloser, r *io.ReadCloser) transcoder.Transcoder {
152 | if &t.input == nil {
153 | t.inputPipeWriter = w
154 | t.inputPipeReader = r
155 | }
156 | return t
157 | }
158 |
159 | // OutputPipe ...
160 | func (t *Transcoder) OutputPipe(w *io.WriteCloser, r *io.ReadCloser) transcoder.Transcoder {
161 | if &t.output == nil {
162 | t.outputPipeWriter = w
163 | t.outputPipeReader = r
164 | }
165 | return t
166 | }
167 |
168 | // WithInputOptions Sets the options object
169 | func (t *Transcoder) WithInputOptions(opts transcoder.Options) transcoder.Transcoder {
170 | t.inputOptions = opts.GetStrArguments()
171 | return t
172 | }
173 |
174 | // WithAdditionalInputOptions Appends an additional options object
175 | func (t *Transcoder) WithAdditionalInputOptions(opts transcoder.Options) transcoder.Transcoder {
176 | t.inputOptions = append(t.inputOptions, opts.GetStrArguments()...)
177 | return t
178 | }
179 |
180 | // WithOutputOptions Sets the options object
181 | func (t *Transcoder) WithOutputOptions(opts transcoder.Options) transcoder.Transcoder {
182 | t.outputOptions = [][]string{opts.GetStrArguments()}
183 | return t
184 | }
185 |
186 | // WithAdditionalOutputOptions Appends an additional options object
187 | func (t *Transcoder) WithAdditionalOutputOptions(opts transcoder.Options) transcoder.Transcoder {
188 | t.outputOptions = append(t.outputOptions, opts.GetStrArguments())
189 | return t
190 | }
191 |
192 | // WithContext is to be used on a Transcoder *before Starting* to
193 | // pass in a context.Context object that can be used to kill
194 | // a running transcoder process. Usage of this method is optional
195 | func (t *Transcoder) WithContext(ctx *context.Context) transcoder.Transcoder {
196 | t.commandContext = ctx
197 | return t
198 | }
199 |
200 | // validate ...
201 | func (t *Transcoder) validate() error {
202 | if t.config.FfmpegBinPath == "" {
203 | return errors.New("ffmpeg binary path not found")
204 | }
205 |
206 | if t.input == "" {
207 | return errors.New("missing input option")
208 | }
209 |
210 | outputLength := len(t.output)
211 |
212 | if outputLength == 0 {
213 | return errors.New("missing output option")
214 | }
215 |
216 | // length of output files being greater than length of options would produce an invalid ffmpeg command
217 | // unless there is only 1 output file, which obviously wouldn't be a problem
218 | if outputLength > len(t.outputOptions) && outputLength != 1 {
219 | return errors.New("number of options and output files does not match")
220 | }
221 |
222 | for index, output := range t.output {
223 | if output == "" {
224 | return fmt.Errorf("output at index %d is an empty string", index)
225 | }
226 | }
227 |
228 | return nil
229 | }
230 |
231 | // GetMetadata Returns metadata for the specified input file
232 | func (t *Transcoder) GetMetadata() (transcoder.Metadata, error) {
233 |
234 | if t.config.FfprobeBinPath != "" {
235 | var outb, errb bytes.Buffer
236 |
237 | input := t.input
238 |
239 | if t.inputPipeReader != nil {
240 | input = "pipe:"
241 | }
242 |
243 | args := []string{"-i", input, "-print_format", "json", "-show_format", "-show_streams", "-show_error"}
244 |
245 | cmd := exec.Command(t.config.FfprobeBinPath, args...)
246 | cmd.Stdout = &outb
247 | cmd.Stderr = &errb
248 |
249 | err := cmd.Run()
250 | if err != nil {
251 | return nil, fmt.Errorf("error executing (%s) with args (%s) | error: %s | message: %s %s", t.config.FfprobeBinPath, args, err, outb.String(), errb.String())
252 | }
253 |
254 | var metadata Metadata
255 |
256 | if err = json.Unmarshal([]byte(outb.String()), &metadata); err != nil {
257 | return nil, err
258 | }
259 |
260 | t.metadata = metadata
261 |
262 | return metadata, nil
263 | }
264 |
265 | return nil, errors.New("ffprobe binary not found")
266 | }
267 |
268 | // progress sends through given channel the transcoding status
269 | func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress) {
270 |
271 | defer stream.Close()
272 |
273 | split := func(data []byte, atEOF bool) (advance int, token []byte, spliterror error) {
274 | if atEOF && len(data) == 0 {
275 | return 0, nil, nil
276 | }
277 | if i := bytes.IndexByte(data, '\n'); i >= 0 {
278 | // We have a full newline-terminated line.
279 | return i + 1, data[0:i], nil
280 | }
281 | if i := bytes.IndexByte(data, '\r'); i >= 0 {
282 | // We have a cr terminated line
283 | return i + 1, data[0:i], nil
284 | }
285 | if atEOF {
286 | return len(data), data, nil
287 | }
288 |
289 | return 0, nil, nil
290 | }
291 |
292 | scanner := bufio.NewScanner(stream)
293 | scanner.Split(split)
294 |
295 | buf := make([]byte, 2)
296 | scanner.Buffer(buf, bufio.MaxScanTokenSize)
297 |
298 | for scanner.Scan() {
299 | Progress := new(Progress)
300 | line := scanner.Text()
301 |
302 | if strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
303 | var re = regexp.MustCompile(`=\s+`)
304 | st := re.ReplaceAllString(line, `=`)
305 |
306 | f := strings.Fields(st)
307 |
308 | var framesProcessed string
309 | var currentTime string
310 | var currentBitrate string
311 | var currentSpeed string
312 |
313 | for j := 0; j < len(f); j++ {
314 | field := f[j]
315 | fieldSplit := strings.Split(field, "=")
316 |
317 | if len(fieldSplit) > 1 {
318 | fieldname := strings.Split(field, "=")[0]
319 | fieldvalue := strings.Split(field, "=")[1]
320 |
321 | if fieldname == "frame" {
322 | framesProcessed = fieldvalue
323 | }
324 |
325 | if fieldname == "time" {
326 | currentTime = fieldvalue
327 | }
328 |
329 | if fieldname == "bitrate" {
330 | currentBitrate = fieldvalue
331 | }
332 | if fieldname == "speed" {
333 | currentSpeed = fieldvalue
334 | }
335 | }
336 | }
337 |
338 | timesec := utils.DurToSec(currentTime)
339 | dursec, _ := strconv.ParseFloat(t.metadata.GetFormat().GetDuration(), 64)
340 |
341 | progress := (timesec * 100) / dursec
342 | Progress.Progress = progress
343 |
344 | Progress.CurrentBitrate = currentBitrate
345 | Progress.FramesProcessed = framesProcessed
346 | Progress.CurrentTime = currentTime
347 | Progress.Speed = currentSpeed
348 |
349 | out <- *Progress
350 | }
351 | }
352 | }
353 |
354 | // closePipes Closes pipes if opened
355 | func (t *Transcoder) closePipes() {
356 | if t.inputPipeReader != nil {
357 | ipr := *t.inputPipeReader
358 | ipr.Close()
359 | }
360 |
361 | if t.outputPipeWriter != nil {
362 | opr := *t.outputPipeWriter
363 | opr.Close()
364 | }
365 | }
366 |
--------------------------------------------------------------------------------
/ffmpeg/metadata.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import "github.com/floostack/transcoder"
4 |
5 | // Metadata ...
6 | type Metadata struct {
7 | Format Format `json:"format"`
8 | Streams []Streams `json:"streams"`
9 | }
10 |
11 | // Format ...
12 | type Format struct {
13 | Filename string
14 | NbStreams int `json:"nb_streams"`
15 | NbPrograms int `json:"nb_programs"`
16 | FormatName string `json:"format_name"`
17 | FormatLongName string `json:"format_long_name"`
18 | Duration string `json:"duration"`
19 | Size string `json:"size"`
20 | BitRate string `json:"bit_rate"`
21 | ProbeScore int `json:"probe_score"`
22 | Tags Tags `json:"tags"`
23 | }
24 |
25 | // Streams ...
26 | type Streams struct {
27 | Index int
28 | ID string `json:"id"`
29 | CodecName string `json:"codec_name"`
30 | CodecLongName string `json:"codec_long_name"`
31 | Profile string `json:"profile"`
32 | CodecType string `json:"codec_type"`
33 | CodecTimeBase string `json:"codec_time_base"`
34 | CodecTagString string `json:"codec_tag_string"`
35 | CodecTag string `json:"codec_tag"`
36 | Width int `json:"width"`
37 | Height int `json:"height"`
38 | CodedWidth int `json:"coded_width"`
39 | CodedHeight int `json:"coded_height"`
40 | HasBFrames int `json:"has_b_frames"`
41 | SampleAspectRatio string `json:"sample_aspect_ratio"`
42 | DisplayAspectRatio string `json:"display_aspect_ratio"`
43 | PixFmt string `json:"pix_fmt"`
44 | Level int `json:"level"`
45 | ChromaLocation string `json:"chroma_location"`
46 | Refs int `json:"refs"`
47 | QuarterSample string `json:"quarter_sample"`
48 | DivxPacked string `json:"divx_packed"`
49 | RFrameRrate string `json:"r_frame_rate"`
50 | AvgFrameRate string `json:"avg_frame_rate"`
51 | TimeBase string `json:"time_base"`
52 | DurationTs int `json:"duration_ts"`
53 | Duration string `json:"duration"`
54 | Disposition Disposition `json:"disposition"`
55 | BitRate string `json:"bit_rate"`
56 | }
57 |
58 | // Tags ...
59 | type Tags struct {
60 | Encoder string `json:"ENCODER"`
61 | }
62 |
63 | // Disposition ...
64 | type Disposition struct {
65 | Default int `json:"default"`
66 | Dub int `json:"dub"`
67 | Original int `json:"original"`
68 | Comment int `json:"comment"`
69 | Lyrics int `json:"lyrics"`
70 | Karaoke int `json:"karaoke"`
71 | Forced int `json:"forced"`
72 | HearingImpaired int `json:"hearing_impaired"`
73 | VisualImpaired int `json:"visual_impaired"`
74 | CleanEffects int `json:"clean_effects"`
75 | }
76 |
77 | // GetFormat ...
78 | func (m Metadata) GetFormat() transcoder.Format {
79 | return m.Format
80 | }
81 |
82 | // GetStreams ...
83 | func (m Metadata) GetStreams() (streams []transcoder.Streams) {
84 | for _, element := range m.Streams {
85 | streams = append(streams, element)
86 | }
87 | return streams
88 | }
89 |
90 | // GetFilename ...
91 | func (f Format) GetFilename() string {
92 | return f.Filename
93 | }
94 |
95 | // GetNbStreams ...
96 | func (f Format) GetNbStreams() int {
97 | return f.NbStreams
98 | }
99 |
100 | // GetNbPrograms ...
101 | func (f Format) GetNbPrograms() int {
102 | return f.NbPrograms
103 | }
104 |
105 | // GetFormatName ...
106 | func (f Format) GetFormatName() string {
107 | return f.FormatName
108 | }
109 |
110 | // GetFormatLongName ...
111 | func (f Format) GetFormatLongName() string {
112 | return f.FormatLongName
113 | }
114 |
115 | // GetDuration ...
116 | func (f Format) GetDuration() string {
117 | return f.Duration
118 | }
119 |
120 | // GetSize ...
121 | func (f Format) GetSize() string {
122 | return f.Size
123 | }
124 |
125 | // GetBitRate ...
126 | func (f Format) GetBitRate() string {
127 | return f.BitRate
128 | }
129 |
130 | // GetProbeScore ...
131 | func (f Format) GetProbeScore() int {
132 | return f.ProbeScore
133 | }
134 |
135 | // GetTags ...
136 | func (f Format) GetTags() transcoder.Tags {
137 | return f.Tags
138 | }
139 |
140 | // GetEncoder ...
141 | func (t Tags) GetEncoder() string {
142 | return t.Encoder
143 | }
144 |
145 | //GetIndex ...
146 | func (s Streams) GetIndex() int {
147 | return s.Index
148 | }
149 |
150 | //GetID ...
151 | func (s Streams) GetID() string {
152 | return s.ID
153 | }
154 |
155 | //GetCodecName ...
156 | func (s Streams) GetCodecName() string {
157 | return s.CodecName
158 | }
159 |
160 | //GetCodecLongName ...
161 | func (s Streams) GetCodecLongName() string {
162 | return s.CodecLongName
163 | }
164 |
165 | //GetProfile ...
166 | func (s Streams) GetProfile() string {
167 | return s.Profile
168 | }
169 |
170 | //GetCodecType ...
171 | func (s Streams) GetCodecType() string {
172 | return s.CodecType
173 | }
174 |
175 | //GetCodecTimeBase ...
176 | func (s Streams) GetCodecTimeBase() string {
177 | return s.CodecTimeBase
178 | }
179 |
180 | //GetCodecTagString ...
181 | func (s Streams) GetCodecTagString() string {
182 | return s.CodecTagString
183 | }
184 |
185 | //GetCodecTag ...
186 | func (s Streams) GetCodecTag() string {
187 | return s.CodecTag
188 | }
189 |
190 | //GetWidth ...
191 | func (s Streams) GetWidth() int {
192 | return s.Width
193 | }
194 |
195 | //GetHeight ...
196 | func (s Streams) GetHeight() int {
197 | return s.Height
198 | }
199 |
200 | //GetCodedWidth ...
201 | func (s Streams) GetCodedWidth() int {
202 | return s.CodedWidth
203 | }
204 |
205 | //GetCodedHeight ...
206 | func (s Streams) GetCodedHeight() int {
207 | return s.CodedHeight
208 | }
209 |
210 | //GetHasBFrames ...
211 | func (s Streams) GetHasBFrames() int {
212 | return s.HasBFrames
213 | }
214 |
215 | //GetSampleAspectRatio ...
216 | func (s Streams) GetSampleAspectRatio() string {
217 | return s.SampleAspectRatio
218 | }
219 |
220 | //GetDisplayAspectRatio ...
221 | func (s Streams) GetDisplayAspectRatio() string {
222 | return s.DisplayAspectRatio
223 | }
224 |
225 | //GetPixFmt ...
226 | func (s Streams) GetPixFmt() string {
227 | return s.PixFmt
228 | }
229 |
230 | //GetLevel ...
231 | func (s Streams) GetLevel() int {
232 | return s.Level
233 | }
234 |
235 | //GetChromaLocation ...
236 | func (s Streams) GetChromaLocation() string {
237 | return s.ChromaLocation
238 | }
239 |
240 | //GetRefs ...
241 | func (s Streams) GetRefs() int {
242 | return s.Refs
243 | }
244 |
245 | //GetQuarterSample ...
246 | func (s Streams) GetQuarterSample() string {
247 | return s.QuarterSample
248 | }
249 |
250 | //GetDivxPacked ...
251 | func (s Streams) GetDivxPacked() string {
252 | return s.DivxPacked
253 | }
254 |
255 | //GetRFrameRrate ...
256 | func (s Streams) GetRFrameRrate() string {
257 | return s.RFrameRrate
258 | }
259 |
260 | //GetAvgFrameRate ...
261 | func (s Streams) GetAvgFrameRate() string {
262 | return s.AvgFrameRate
263 | }
264 |
265 | //GetTimeBase ...
266 | func (s Streams) GetTimeBase() string {
267 | return s.TimeBase
268 | }
269 |
270 | //GetDurationTs ...
271 | func (s Streams) GetDurationTs() int {
272 | return s.DurationTs
273 | }
274 |
275 | //GetDuration ...
276 | func (s Streams) GetDuration() string {
277 | return s.Duration
278 | }
279 |
280 | //GetDisposition ...
281 | func (s Streams) GetDisposition() transcoder.Disposition {
282 | return s.Disposition
283 | }
284 |
285 | //GetBitRate ...
286 | func (s Streams) GetBitRate() string {
287 | return s.BitRate
288 | }
289 |
290 | //GetDefault ...
291 | func (d Disposition) GetDefault() int {
292 | return d.Default
293 | }
294 |
295 | //GetDub ...
296 | func (d Disposition) GetDub() int {
297 | return d.Dub
298 | }
299 |
300 | //GetOriginal ...
301 | func (d Disposition) GetOriginal() int {
302 | return d.Original
303 | }
304 |
305 | //GetComment ...
306 | func (d Disposition) GetComment() int {
307 | return d.Comment
308 | }
309 |
310 | //GetLyrics ...
311 | func (d Disposition) GetLyrics() int {
312 | return d.Lyrics
313 | }
314 |
315 | //GetKaraoke ...
316 | func (d Disposition) GetKaraoke() int {
317 | return d.Karaoke
318 | }
319 |
320 | //GetForced ...
321 | func (d Disposition) GetForced() int {
322 | return d.Forced
323 | }
324 |
325 | //GetHearingImpaired ...
326 | func (d Disposition) GetHearingImpaired() int {
327 | return d.HearingImpaired
328 | }
329 |
330 | //GetVisualImpaired ...
331 | func (d Disposition) GetVisualImpaired() int {
332 | return d.VisualImpaired
333 | }
334 |
335 | //GetCleanEffects ...
336 | func (d Disposition) GetCleanEffects() int {
337 | return d.CleanEffects
338 | }
339 |
--------------------------------------------------------------------------------
/ffmpeg/options.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // Options defines allowed FFmpeg arguments
9 | type Options struct {
10 | Aspect *string `flag:"-aspect"`
11 | Resolution *string `flag:"-s"`
12 | VideoBitRate *string `flag:"-b:v"`
13 | VideoBitRateTolerance *int `flag:"-bt"`
14 | VideoMaxBitRate *int `flag:"-maxrate"`
15 | VideoMinBitrate *int `flag:"-minrate"`
16 | VideoCodec *string `flag:"-c:v"`
17 | Vframes *int `flag:"-vframes"`
18 | FrameRate *int `flag:"-r"`
19 | AudioRate *int `flag:"-ar"`
20 | KeyframeInterval *int `flag:"-g"`
21 | AudioCodec *string `flag:"-c:a"`
22 | AudioBitrate *string `flag:"-ab"`
23 | AudioChannels *int `flag:"-ac"`
24 | AudioVariableBitrate *bool `flag:"-q:a"`
25 | BufferSize *int `flag:"-bufsize"`
26 | Threadset *bool `flag:"-threads"`
27 | Threads *int `flag:"-threads"`
28 | Preset *string `flag:"-preset"`
29 | Tune *string `flag:"-tune"`
30 | AudioProfile *string `flag:"-profile:a"`
31 | VideoProfile *string `flag:"-profile:v"`
32 | Target *string `flag:"-target"`
33 | Duration *string `flag:"-t"`
34 | Qscale *uint32 `flag:"-qscale"`
35 | Crf *uint32 `flag:"-crf"`
36 | Strict *int `flag:"-strict"`
37 | MuxDelay *string `flag:"-muxdelay"`
38 | SeekTime *string `flag:"-ss"`
39 | SeekUsingTimestamp *bool `flag:"-seek_timestamp"`
40 | MovFlags *string `flag:"-movflags"`
41 | HideBanner *bool `flag:"-hide_banner"`
42 | OutputFormat *string `flag:"-f"`
43 | CopyTs *bool `flag:"-copyts"`
44 | NativeFramerateInput *bool `flag:"-re"`
45 | InputInitialOffset *string `flag:"-itsoffset"`
46 | RtmpLive *string `flag:"-rtmp_live"`
47 | HlsPlaylistType *string `flag:"-hls_playlist_type"`
48 | HlsListSize *int `flag:"-hls_list_size"`
49 | HlsSegmentDuration *int `flag:"-hls_time"`
50 | HlsMasterPlaylistName *string `flag:"-master_pl_name"`
51 | HlsSegmentFilename *string `flag:"-hls_segment_filename"`
52 | HTTPMethod *string `flag:"-method"`
53 | HTTPKeepAlive *bool `flag:"-multiple_requests"`
54 | Hwaccel *string `flag:"-hwaccel"`
55 | StreamIds map[string]string `flag:"-streamid"`
56 | VideoFilter *string `flag:"-vf"`
57 | AudioFilter *string `flag:"-af"`
58 | SkipVideo *bool `flag:"-vn"`
59 | SkipAudio *bool `flag:"-an"`
60 | CompressionLevel *int `flag:"-compression_level"`
61 | MapMetadata *string `flag:"-map_metadata"`
62 | Metadata map[string]string `flag:"-metadata"`
63 | EncryptionKey *string `flag:"-hls_key_info_file"`
64 | Bframe *int `flag:"-bf"`
65 | PixFmt *string `flag:"-pix_fmt"`
66 | WhiteListProtocols []string `flag:"-protocol_whitelist"`
67 | Overwrite *bool `flag:"-y"`
68 | ExtraArgs map[string]interface{}
69 | }
70 |
71 | // GetStrArguments ...
72 | func (opts Options) GetStrArguments() []string {
73 | f := reflect.TypeOf(opts)
74 | v := reflect.ValueOf(opts)
75 |
76 | values := []string{}
77 |
78 | for i := 0; i < f.NumField(); i++ {
79 | flag := f.Field(i).Tag.Get("flag")
80 | value := v.Field(i).Interface()
81 |
82 | if !v.Field(i).IsNil() {
83 |
84 | if _, ok := value.(*bool); ok {
85 | values = append(values, flag)
86 | }
87 |
88 | if vs, ok := value.(*string); ok {
89 | values = append(values, flag, *vs)
90 | }
91 |
92 | if va, ok := value.([]string); ok {
93 |
94 | for i := 0; i < len(va); i++ {
95 | item := va[i]
96 | values = append(values, flag, item)
97 | }
98 | }
99 |
100 | if vm, ok := value.(map[string]interface{}); ok {
101 | for k, v := range vm {
102 | values = append(values, k, fmt.Sprintf("%v", v))
103 | }
104 | }
105 |
106 | if vi, ok := value.(*int); ok {
107 | values = append(values, flag, fmt.Sprintf("%d", *vi))
108 | }
109 |
110 | }
111 | }
112 |
113 | return values
114 | }
115 |
--------------------------------------------------------------------------------
/ffmpeg/progress.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | // Progress ...
4 | type Progress struct {
5 | FramesProcessed string
6 | CurrentTime string
7 | CurrentBitrate string
8 | Progress float64
9 | Speed string
10 | }
11 |
12 | // GetFramesProcessed ...
13 | func (p Progress) GetFramesProcessed() string {
14 | return p.FramesProcessed
15 | }
16 |
17 | // GetCurrentTime ...
18 | func (p Progress) GetCurrentTime() string {
19 | return p.CurrentTime
20 | }
21 |
22 | // GetCurrentBitrate ...
23 | func (p Progress) GetCurrentBitrate() string {
24 | return p.CurrentBitrate
25 | }
26 |
27 | // GetProgress ...
28 | func (p Progress) GetProgress() float64 {
29 | return p.Progress
30 | }
31 |
32 | // GetSpeed ...
33 | func (p Progress) GetSpeed() string {
34 | return p.Speed
35 | }
36 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/floostack/transcoder
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/metadata.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Metadata ...
4 | type Metadata interface {
5 | GetFormat() Format
6 | GetStreams() []Streams
7 | }
8 |
9 | // Format ...
10 | type Format interface {
11 | GetFilename() string
12 | GetNbStreams() int
13 | GetNbPrograms() int
14 | GetFormatName() string
15 | GetFormatLongName() string
16 | GetDuration() string
17 | GetSize() string
18 | GetBitRate() string
19 | GetProbeScore() int
20 | GetTags() Tags
21 | }
22 |
23 | // Streams ...
24 | type Streams interface {
25 | GetIndex() int
26 | GetID() string
27 | GetCodecName() string
28 | GetCodecLongName() string
29 | GetProfile() string
30 | GetCodecType() string
31 | GetCodecTimeBase() string
32 | GetCodecTagString() string
33 | GetCodecTag() string
34 | GetWidth() int
35 | GetHeight() int
36 | GetCodedWidth() int
37 | GetCodedHeight() int
38 | GetHasBFrames() int
39 | GetSampleAspectRatio() string
40 | GetDisplayAspectRatio() string
41 | GetPixFmt() string
42 | GetLevel() int
43 | GetChromaLocation() string
44 | GetRefs() int
45 | GetQuarterSample() string
46 | GetDivxPacked() string
47 | GetRFrameRrate() string
48 | GetAvgFrameRate() string
49 | GetTimeBase() string
50 | GetDurationTs() int
51 | GetDuration() string
52 | GetDisposition() Disposition
53 | GetBitRate() string
54 | }
55 |
56 | // Tags ...
57 | type Tags interface {
58 | GetEncoder() string
59 | }
60 |
61 | // Disposition ...
62 | type Disposition interface {
63 | GetDefault() int
64 | GetDub() int
65 | GetOriginal() int
66 | GetComment() int
67 | GetLyrics() int
68 | GetKaraoke() int
69 | GetForced() int
70 | GetHearingImpaired() int
71 | GetVisualImpaired() int
72 | GetCleanEffects() int
73 | }
74 |
--------------------------------------------------------------------------------
/options.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Options ...
4 | type Options interface {
5 | GetStrArguments() []string
6 | }
7 |
--------------------------------------------------------------------------------
/progress.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Progress ...
4 | type Progress interface {
5 | GetFramesProcessed() string
6 | GetCurrentTime() string
7 | GetCurrentBitrate() string
8 | GetProgress() float64
9 | GetSpeed() string
10 | }
11 |
--------------------------------------------------------------------------------
/transcoder.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | import (
4 | "context"
5 | "io"
6 | )
7 |
8 | // Transcoder ...
9 | type Transcoder interface {
10 | Start() (<-chan Progress, error)
11 | Input(i string) Transcoder
12 | InputPipe(w *io.WriteCloser, r *io.ReadCloser) Transcoder
13 | Output(o string) Transcoder
14 | OutputPipe(w *io.WriteCloser, r *io.ReadCloser) Transcoder
15 | WithInputOptions(opts Options) Transcoder
16 | WithAdditionalInputOptions(opts Options) Transcoder
17 | WithOutputOptions(opts Options) Transcoder
18 | WithAdditionalOutputOptions(opts Options) Transcoder
19 | WithContext(ctx *context.Context) Transcoder
20 | GetMetadata() (Metadata, error)
21 | }
22 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | // DurToSec ...
9 | func DurToSec(dur string) (sec float64) {
10 | durAry := strings.Split(dur, ":")
11 | var secs float64
12 | if len(durAry) != 3 {
13 | return
14 | }
15 | hr, _ := strconv.ParseFloat(durAry[0], 64)
16 | secs = hr * (60 * 60)
17 | min, _ := strconv.ParseFloat(durAry[1], 64)
18 | secs += min * (60)
19 | second, _ := strconv.ParseFloat(durAry[2], 64)
20 | secs += second
21 | return secs
22 | }
23 |
--------------------------------------------------------------------------------