├── .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 | --------------------------------------------------------------------------------