diff --git a/.circleci/config.yml b/.circleci/config.yml index 77eb9bd..08c3a89 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: docker: # specify the version - - image: circleci/golang:1.14-buster + - image: circleci/golang:1.17-buster environment: GO111MODULE: "on" diff --git a/LICENSE b/LICENSE index 2ad87fe..365e617 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,8 @@ MIT License Copyright (c) 2020 FlooStack +Copyright (c) 2021 Legion-Zver (MACROX & ITRabbit) + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.md b/README.md index 651b613..b731fe7 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,6 @@ # Golang Transcoding Library -
- -
- - - Build Status - - - - - Build Status - - -
- -
- -
- Created by FlooStack. -
+Exec ffmpeg and ffprobe ## Features @@ -34,49 +15,5 @@ ## Download from Github ```shell -go get github.com/floostack/transcoder -``` - -## Example - -```go -package main - -import ( - "log" - - ffmpeg "github.com/floostack/transcoder/ffmpeg" -) - -func main() { - - format := "mp4" - overwrite := true - - opts := ffmpeg.Options{ - OutputFormat: &format, - Overwrite: &overwrite, - } - - ffmpegConf := &ffmpeg.Config{ - FfmpegBinPath: "/usr/local/bin/ffmpeg", - FfprobeBinPath: "/usr/local/bin/ffprobe", - ProgressEnabled: true, - } - - progress, err := ffmpeg. - New(ffmpegConf). - Input("/tmp/avi"). - Output("/tmp/mp4"). - WithOptions(opts). - Start(opts) - - if err != nil { - log.Fatal(err) - } - - for msg := range progress { - log.Printf("%+v", msg) - } -} -``` +go get github.com/legion-zver/transcoder +``` \ No newline at end of file diff --git a/ffmpeg/config.go b/ffmpeg/config.go index 05eb7ff..7ccba5f 100644 --- a/ffmpeg/config.go +++ b/ffmpeg/config.go @@ -1,9 +1,61 @@ package ffmpeg +import ( + "bytes" + "errors" + "os/exec" + "strings" +) + +// Flags ... +type Flags struct { + Progress bool `json:"progress,omitempty"` + Verbose bool `json:"verbose,omitempty"` + Debug bool `json:"debug,omitempty"` +} + // Config ... type Config struct { - FfmpegBinPath string - FfprobeBinPath string - ProgressEnabled bool - Verbose bool + Flags + + // Paths + FfmpegBinPath string `json:"ffmpeg_bin_path"` + FfprobeBinPath string `json:"ffprobe_bin_path"` +} + +func mergeFlags(flags ...Flags) (result Flags) { + for _, v := range flags { + result.Progress = result.Progress || v.Progress + result.Verbose = result.Verbose || v.Verbose + result.Debug = result.Debug || v.Debug + } + return +} + +func NewAutoConfig(flags ...Flags) (*Config, error) { + out, which := bytes.Buffer{}, exec.Command("which", "ffmpeg") + which.Stdout = &out + if err := which.Run(); err != nil { + return nil, err + } + ffmpegBinPath := strings.TrimSpace(out.String()) + out.Reset() + if len(ffmpegBinPath) < 1 { + return nil, errors.New("ffmpeg binary path not found") + } + which = exec.Command("which", "ffprobe") + which.Stdout = &out + if err := which.Run(); err != nil { + return nil, err + } + ffprobeBinPath := strings.TrimSpace(out.String()) + out.Reset() + if len(ffprobeBinPath) < 1 { + return nil, errors.New("ffprobe binary path not found") + } + return &Config{ + Flags: mergeFlags(flags...), + FfmpegBinPath: ffmpegBinPath, + FfprobeBinPath: ffprobeBinPath, + }, nil } diff --git a/ffmpeg/errors.go b/ffmpeg/errors.go new file mode 100644 index 0000000..95f8877 --- /dev/null +++ b/ffmpeg/errors.go @@ -0,0 +1 @@ +package ffmpeg diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index ce262f8..e2e721d 100644 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -14,15 +14,17 @@ import ( "strconv" "strings" - "github.com/floostack/transcoder" - "github.com/floostack/transcoder/utils" + "github.com/legion-zver/transcoder" + "github.com/legion-zver/transcoder/utils" ) // Transcoder ... type Transcoder struct { config *Config input string + start []string output []string + errors []string options [][]string metadata transcoder.Metadata inputPipeReader *io.ReadCloser @@ -30,40 +32,71 @@ type Transcoder struct { inputPipeWriter *io.WriteCloser outputPipeWriter *io.WriteCloser commandContext *context.Context + skipGetMetadata bool } // New ... func New(cfg *Config) transcoder.Transcoder { - return &Transcoder{config: cfg} + return &Transcoder{config: cfg, errors: []string{}, start: []string{}} } -// Start ... -func (t *Transcoder) Start(opts transcoder.Options) (<-chan transcoder.Progress, error) { +// Errors ... +func (t *Transcoder) Error() (err error) { + if t.errors == nil || len(t.errors) < 1 { + return + } + return errors.New(strings.Join(t.errors, " |--> ")) +} - var stderrIn io.ReadCloser +// SkipMetadata ... +func (t *Transcoder) SkipMetadata() transcoder.Transcoder { + t.skipGetMetadata = true + return t +} - out := make(chan transcoder.Progress) +// WithMetadata ... +func (t *Transcoder) WithMetadata(metadata transcoder.Metadata) transcoder.Transcoder { + t.skipGetMetadata = true + t.metadata = metadata + return t +} +// Start ... +func (t *Transcoder) Start(opts transcoder.Options) (<-chan transcoder.Progress, error) { + if opts == nil { + opts = Options{} + } defer t.closePipes() + // Clear errors + t.errors = []string{} + // Validates config if err := t.validate(); err != nil { + t.errors = append(t.errors, err.Error()) return nil, err } // Get file metadata - _, err := t.GetMetadata() - if err != nil { - return nil, err + if !t.skipGetMetadata { + if _, err := t.GetMetadata(); err != nil { + t.errors = append(t.errors, err.Error()) + return nil, err + } } // Append input file and standard options - args := append([]string{"-i", t.input}, opts.GetStrArguments()...) - outputLength := len(t.output) - optionsLength := len(t.options) - + args, outputLength, optionsLength := append( + append( + append( + []string{}, t.start..., + ), + []string{"-hide_banner", "-i", t.input}..., + ), opts.GetStrArguments()..., + ), len(t.output), len(t.options) + + // Just append the 1 output file we've got if outputLength == 1 && optionsLength == 0 { - // Just append the 1 output file we've got args = append(args, t.output[0]) } else { for index, out := range t.output { @@ -73,11 +106,10 @@ func (t *Transcoder) Start(opts transcoder.Options) (<-chan transcoder.Progress, for i := index; i < len(t.options); i++ { args = append(args, t.options[i]...) } - // Otherwise just append the current options + // Otherwise, just append the current options } else { args = append(args, t.options[index]...) } - // Append output flag args = append(args, out) } @@ -93,39 +125,46 @@ func (t *Transcoder) Start(opts transcoder.Options) (<-chan transcoder.Progress, } else { cmd = exec.CommandContext(*t.commandContext, t.config.FfmpegBinPath, args...) } - - // If progresss enabled, get stderr pipe and start progress process - if t.config.ProgressEnabled && !t.config.Verbose { + // Print debug + if t.config.Debug { + fmt.Println("transcoder.Start", t.config.FfmpegBinPath, args) + } + // If progress enabled, get stderr pipe and start progress process + var ( + stderrIn io.ReadCloser + err error + ) + if t.config.Progress && !t.config.Verbose { stderrIn, err = cmd.StderrPipe() if err != nil { - return nil, fmt.Errorf("Failed getting transcoding progress (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err) + t.errors = append(t.errors, err.Error()) + return nil, fmt.Errorf("failed getting transcoding progress (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err) } } - if t.config.Verbose { cmd.Stderr = os.Stdout } - // Start process - err = cmd.Start() - if err != nil { - return nil, fmt.Errorf("Failed starting transcoding (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err) + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("failed starting transcoding (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err) } - - if t.config.ProgressEnabled && !t.config.Verbose { - go func() { - t.progress(stderrIn, out) - }() - - go func() { - defer close(out) - err = cmd.Wait() - }() - } else { - err = cmd.Wait() + if !t.config.Progress || t.config.Verbose { + if err = cmd.Wait(); err != nil { + t.errors = append(t.errors, err.Error()) + } + return nil, err } - - return out, nil + out := make(chan transcoder.Progress) + go func() { + defer close(out) + t.progress(stderrIn, out) + }() + go func() { + if err := cmd.Wait(); err != nil { + t.errors = append(t.errors, err.Error()) + } + }() + return out, err } // Input ... @@ -158,6 +197,12 @@ func (t *Transcoder) OutputPipe(w *io.WriteCloser, r *io.ReadCloser) transcoder. return t } +// WithStartOptions Sets the start options object +func (t *Transcoder) WithStartOptions(opts transcoder.Options) transcoder.Transcoder { + t.start = opts.GetStrArguments() + return t +} + // WithOptions Sets the options object func (t *Transcoder) WithOptions(opts transcoder.Options) transcoder.Transcoder { t.options = [][]string{opts.GetStrArguments()} @@ -166,10 +211,22 @@ func (t *Transcoder) WithOptions(opts transcoder.Options) transcoder.Transcoder // WithAdditionalOptions Appends an additional options object func (t *Transcoder) WithAdditionalOptions(opts transcoder.Options) transcoder.Transcoder { + if t.options == nil { + return t.WithOptions(opts) + } t.options = append(t.options, opts.GetStrArguments()) return t } +// WithAdditionalStartOptions Appends an additional start options object +func (t *Transcoder) WithAdditionalStartOptions(opts transcoder.Options) transcoder.Transcoder { + if t.start == nil { + return t.WithStartOptions(opts) + } + t.start = append(t.start, opts.GetStrArguments()...) + return t +} + // WithContext is to be used on a Transcoder *before Starting* to // pass in a context.Context object that can be used to kill // a running transcoder process. Usage of this method is optional @@ -183,13 +240,10 @@ func (t *Transcoder) validate() error { if t.config.FfmpegBinPath == "" { return errors.New("ffmpeg binary path not found") } - if t.input == "" { return errors.New("missing input option") } - outputLength := len(t.output) - if outputLength == 0 { return errors.New("missing output option") } @@ -199,58 +253,55 @@ func (t *Transcoder) validate() error { if outputLength > len(t.options) && outputLength != 1 { return errors.New("number of options and output files does not match") } - for index, output := range t.output { if output == "" { return fmt.Errorf("output at index %d is an empty string", index) } } - return nil } // GetMetadata Returns metadata for the specified input file func (t *Transcoder) GetMetadata() (transcoder.Metadata, error) { - - if t.config.FfprobeBinPath != "" { - var outb, errb bytes.Buffer - - input := t.input - - if t.inputPipeReader != nil { - input = "pipe:" - } - - args := []string{"-i", input, "-print_format", "json", "-show_format", "-show_streams", "-show_error"} - - cmd := exec.Command(t.config.FfprobeBinPath, args...) - cmd.Stdout = &outb - cmd.Stderr = &errb - - err := cmd.Run() - if err != nil { - return nil, fmt.Errorf("error executing (%s) with args (%s) | error: %s | message: %s %s", t.config.FfprobeBinPath, args, err, outb.String(), errb.String()) - } - - var metadata Metadata - - if err = json.Unmarshal([]byte(outb.String()), &metadata); err != nil { - return nil, err + if len(t.config.FfprobeBinPath) < 1 { + return nil, errors.New("ffprobe binary not found") + } + input := t.input + if t.inputPipeReader != nil { + input = "pipe:" + } + var stdOut, stdErr bytes.Buffer + args := []string{"-hide_banner", "-i", input, "-print_format", "json", "-show_format", "-show_streams", "-show_error"} + cmd := exec.Command(t.config.FfprobeBinPath, args...) + cmd.Stdout = &stdOut + cmd.Stderr = &stdErr + // Print debug info + if t.config.Debug { + fmt.Println("transcoder.GetMetadata", t.config.FfprobeBinPath, args) + } + if err := cmd.Run(); err != nil { + if t.config.Debug { + fmt.Println(stdOut.String()) + fmt.Println(stdErr.String()) } - - t.metadata = metadata - - return metadata, nil + return nil, fmt.Errorf("error executing (%s) with args (%s) | error: %s | message: %s %s", t.config.FfprobeBinPath, args, err, stdOut.String(), stdErr.String()) } - - return nil, errors.New("ffprobe binary not found") + var metadata Metadata + if err := json.Unmarshal([]byte(stdOut.String()), &metadata); err != nil { + return nil, err + } + t.metadata = metadata + return metadata, nil } // progress sends through given channel the transcoding status func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress) { - - defer stream.Close() - + if stream == nil { + return + } + defer func(stream io.ReadCloser) { + _ = stream.Close() + }(stream) split := func(data []byte, atEOF bool) (advance int, token []byte, spliterror error) { if atEOF && len(data) == 0 { return 0, nil, nil @@ -266,7 +317,6 @@ func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress if atEOF { return len(data), data, nil } - return 0, nil, nil } @@ -276,58 +326,62 @@ func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress buf := make([]byte, 2) scanner.Buffer(buf, bufio.MaxScanTokenSize) + re := regexp.MustCompile(`=\s+`) for scanner.Scan() { - Progress := new(Progress) line := scanner.Text() - + if len(line) < 1 { + continue + } + if t.config.Debug { + fmt.Println(line) + } if strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") { - var re = regexp.MustCompile(`=\s+`) - st := re.ReplaceAllString(line, `=`) - - f := strings.Fields(st) - - var framesProcessed string - var currentTime string - var currentBitrate string - var currentSpeed string - + f := strings.Fields(re.ReplaceAllString(line, `=`)) + var ( + framesProcessed string + currentBitrate string + currentSpeed string + currentTime string + ) for j := 0; j < len(f); j++ { field := f[j] fieldSplit := strings.Split(field, "=") - if len(fieldSplit) > 1 { - fieldname := strings.Split(field, "=")[0] - fieldvalue := strings.Split(field, "=")[1] - - if fieldname == "frame" { - framesProcessed = fieldvalue + fieldName, fieldValue := strings.Split(field, "=")[0], strings.Split(field, "=")[1] + if fieldName == "frame" { + framesProcessed = fieldValue } - - if fieldname == "time" { - currentTime = fieldvalue + if fieldName == "time" { + currentTime = fieldValue } - - if fieldname == "bitrate" { - currentBitrate = fieldvalue + if fieldName == "bitrate" { + currentBitrate = fieldValue } - if fieldname == "speed" { - currentSpeed = fieldvalue + if fieldName == "speed" { + currentSpeed = fieldValue } } } - - timesec := utils.DurToSec(currentTime) - dursec, _ := strconv.ParseFloat(t.metadata.GetFormat().GetDuration(), 64) - - progress := (timesec * 100) / dursec - Progress.Progress = progress - - Progress.CurrentBitrate = currentBitrate - Progress.FramesProcessed = framesProcessed - Progress.CurrentTime = currentTime - Progress.Speed = currentSpeed - - out <- *Progress + timeSec := utils.DurToSec(currentTime) + progress := timeSec + if t.metadata != nil { + if durSec, _ := strconv.ParseFloat(t.metadata.GetFormat().GetDuration(), 64); durSec != 0 { + progress = (timeSec * 100) / durSec + } + } + out <- Progress{ + Progress: progress, + CurrentBitrate: currentBitrate, + FramesProcessed: framesProcessed, + CurrentTime: currentTime, + Speed: currentSpeed, + } + continue + } else if strings.HasPrefix(line, "Error") || + strings.HasPrefix(line, "Unrecognized option") || + (strings.Contains(line, "[y/N]") && + strings.Contains(line, "exiting")) { + t.errors = append(t.errors, strings.TrimSuffix(line, ".")) } } } @@ -336,11 +390,10 @@ func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress func (t *Transcoder) closePipes() { if t.inputPipeReader != nil { ipr := *t.inputPipeReader - ipr.Close() + _ = ipr.Close() } - if t.outputPipeWriter != nil { opr := *t.outputPipeWriter - opr.Close() + _ = opr.Close() } } diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go new file mode 100644 index 0000000..4c30e27 --- /dev/null +++ b/ffmpeg/ffmpeg_test.go @@ -0,0 +1,29 @@ +package ffmpeg + +import ( + "fmt" + "testing" +) + +func TestFightingErrors(t *testing.T) { + cfg, err := NewAutoConfig(Flags{Progress: true}) + if err != nil { + t.Error(err) + } + // Create instance and fill + instance := New(cfg).Input("test").Output("test").SkipMetadata() + // Start + p, err := instance.Start(Options{"-y"}) + if err != nil { + t.Error(err) + } + if p == nil { + t.Error("waiting progress chain!") + } + for tick := range p { + fmt.Println(tick) + } + if err = instance.Error(); err == nil { + t.Error("waiting for error!") + } +} diff --git a/ffmpeg/metadata.go b/ffmpeg/metadata.go index 4e81910..f192ac9 100644 --- a/ffmpeg/metadata.go +++ b/ffmpeg/metadata.go @@ -1,6 +1,6 @@ package ffmpeg -import "github.com/floostack/transcoder" +import "github.com/legion-zver/transcoder" // Metadata ... type Metadata struct { @@ -11,53 +11,49 @@ type Metadata struct { // Format ... type Format struct { Filename string - NbStreams int `json:"nb_streams"` - NbPrograms int `json:"nb_programs"` - FormatName string `json:"format_name"` - FormatLongName string `json:"format_long_name"` - Duration string `json:"duration"` - Size string `json:"size"` - BitRate string `json:"bit_rate"` - ProbeScore int `json:"probe_score"` - Tags Tags `json:"tags"` + NbStreams int `json:"nb_streams"` + NbPrograms int `json:"nb_programs"` + FormatName string `json:"format_name"` + FormatLongName string `json:"format_long_name"` + Duration string `json:"duration"` + Size string `json:"size"` + BitRate string `json:"bit_rate"` + ProbeScore int `json:"probe_score,omitempty"` + Tags map[string]string `json:"tags,omitempty"` } // Streams ... type Streams struct { Index int - ID string `json:"id"` - CodecName string `json:"codec_name"` - CodecLongName string `json:"codec_long_name"` - Profile string `json:"profile"` - CodecType string `json:"codec_type"` - CodecTimeBase string `json:"codec_time_base"` - CodecTagString string `json:"codec_tag_string"` - CodecTag string `json:"codec_tag"` - Width int `json:"width"` - Height int `json:"height"` - CodedWidth int `json:"coded_width"` - CodedHeight int `json:"coded_height"` - HasBFrames int `json:"has_b_frames"` - SampleAspectRatio string `json:"sample_aspect_ratio"` - DisplayAspectRatio string `json:"display_aspect_ratio"` - PixFmt string `json:"pix_fmt"` - Level int `json:"level"` - ChromaLocation string `json:"chroma_location"` - Refs int `json:"refs"` - QuarterSample string `json:"quarter_sample"` - DivxPacked string `json:"divx_packed"` - RFrameRrate string `json:"r_frame_rate"` - AvgFrameRate string `json:"avg_frame_rate"` - TimeBase string `json:"time_base"` - DurationTs int `json:"duration_ts"` - Duration string `json:"duration"` - Disposition Disposition `json:"disposition"` - BitRate string `json:"bit_rate"` -} - -// Tags ... -type Tags struct { - Encoder string `json:"ENCODER"` + ID string `json:"id"` + CodecName string `json:"codec_name"` + CodecLongName string `json:"codec_long_name"` + Profile string `json:"profile"` + CodecType string `json:"codec_type"` + CodecTimeBase string `json:"codec_time_base"` + CodecTagString string `json:"codec_tag_string"` + CodecTag string `json:"codec_tag"` + Width int `json:"width"` + Height int `json:"height"` + CodedWidth int `json:"coded_width"` + CodedHeight int `json:"coded_height"` + HasBFrames int `json:"has_b_frames"` + SampleAspectRatio string `json:"sample_aspect_ratio"` + DisplayAspectRatio string `json:"display_aspect_ratio"` + PixFmt string `json:"pix_fmt"` + Level int `json:"level"` + ChromaLocation string `json:"chroma_location"` + Refs int `json:"refs"` + QuarterSample string `json:"quarter_sample"` + DivxPacked string `json:"divx_packed"` + RFrameRrate string `json:"r_frame_rate"` + AvgFrameRate string `json:"avg_frame_rate"` + TimeBase string `json:"time_base"` + DurationTs int `json:"duration_ts"` + Duration string `json:"duration"` + Disposition Disposition `json:"disposition"` + BitRate string `json:"bit_rate"` + Tags map[string]string `json:"tags,omitempty"` } // Disposition ... @@ -133,15 +129,10 @@ func (f Format) GetProbeScore() int { } // GetTags ... -func (f Format) GetTags() transcoder.Tags { +func (f Format) GetTags() map[string]string { return f.Tags } -// GetEncoder ... -func (t Tags) GetEncoder() string { - return t.Encoder -} - //GetIndex ... func (s Streams) GetIndex() int { return s.Index @@ -287,6 +278,11 @@ func (s Streams) GetBitRate() string { return s.BitRate } +// GetTags ... +func (s Streams) GetTags() map[string]string { + return s.Tags +} + //GetDefault ... func (d Disposition) GetDefault() int { return d.Default diff --git a/ffmpeg/options.go b/ffmpeg/options.go index 323d4b3..f19b289 100644 --- a/ffmpeg/options.go +++ b/ffmpeg/options.go @@ -1,114 +1,9 @@ package ffmpeg -import ( - "fmt" - "reflect" -) - // Options defines allowed FFmpeg arguments -type Options struct { - Aspect *string `flag:"-aspect"` - Resolution *string `flag:"-s"` - VideoBitRate *string `flag:"-b:v"` - VideoBitRateTolerance *int `flag:"-bt"` - VideoMaxBitRate *int `flag:"-maxrate"` - VideoMinBitrate *int `flag:"-minrate"` - VideoCodec *string `flag:"-c:v"` - Vframes *int `flag:"-vframes"` - FrameRate *int `flag:"-r"` - AudioRate *int `flag:"-ar"` - KeyframeInterval *int `flag:"-g"` - AudioCodec *string `flag:"-c:a"` - AudioBitrate *string `flag:"-ab"` - AudioChannels *int `flag:"-ac"` - AudioVariableBitrate *bool `flag:"-q:a"` - BufferSize *int `flag:"-bufsize"` - Threadset *bool `flag:"-threads"` - Threads *int `flag:"-threads"` - Preset *string `flag:"-preset"` - Tune *string `flag:"-tune"` - AudioProfile *string `flag:"-profile:a"` - VideoProfile *string `flag:"-profile:v"` - Target *string `flag:"-target"` - Duration *string `flag:"-t"` - Qscale *uint32 `flag:"-qscale"` - Crf *uint32 `flag:"-crf"` - Strict *int `flag:"-strict"` - MuxDelay *string `flag:"-muxdelay"` - SeekTime *string `flag:"-ss"` - SeekUsingTimestamp *bool `flag:"-seek_timestamp"` - MovFlags *string `flag:"-movflags"` - HideBanner *bool `flag:"-hide_banner"` - OutputFormat *string `flag:"-f"` - CopyTs *bool `flag:"-copyts"` - NativeFramerateInput *bool `flag:"-re"` - InputInitialOffset *string `flag:"-itsoffset"` - RtmpLive *string `flag:"-rtmp_live"` - HlsPlaylistType *string `flag:"-hls_playlist_type"` - HlsListSize *int `flag:"-hls_list_size"` - HlsSegmentDuration *int `flag:"-hls_time"` - HlsMasterPlaylistName *string `flag:"-master_pl_name"` - HlsSegmentFilename *string `flag:"-hls_segment_filename"` - HTTPMethod *string `flag:"-method"` - HTTPKeepAlive *bool `flag:"-multiple_requests"` - Hwaccel *string `flag:"-hwaccel"` - StreamIds map[string]string `flag:"-streamid"` - VideoFilter *string `flag:"-vf"` - AudioFilter *string `flag:"-af"` - SkipVideo *bool `flag:"-vn"` - SkipAudio *bool `flag:"-an"` - CompressionLevel *int `flag:"-compression_level"` - MapMetadata *string `flag:"-map_metadata"` - Metadata map[string]string `flag:"-metadata"` - EncryptionKey *string `flag:"-hls_key_info_file"` - Bframe *int `flag:"-bf"` - PixFmt *string `flag:"-pix_fmt"` - WhiteListProtocols []string `flag:"-protocol_whitelist"` - Overwrite *bool `flag:"-y"` - ExtraArgs map[string]interface{} -} +type Options []string // GetStrArguments ... func (opts Options) GetStrArguments() []string { - f := reflect.TypeOf(opts) - v := reflect.ValueOf(opts) - - values := []string{} - - for i := 0; i < f.NumField(); i++ { - flag := f.Field(i).Tag.Get("flag") - value := v.Field(i).Interface() - - if !v.Field(i).IsNil() { - - if _, ok := value.(*bool); ok { - values = append(values, flag) - } - - if vs, ok := value.(*string); ok { - values = append(values, flag, *vs) - } - - if va, ok := value.([]string); ok { - - for i := 0; i < len(va); i++ { - item := va[i] - values = append(values, flag, item) - } - } - - if vm, ok := value.(map[string]interface{}); ok { - for k, v := range vm { - values = append(values, k, fmt.Sprintf("%v", v)) - } - } - - if vi, ok := value.(*int); ok { - values = append(values, flag, fmt.Sprintf("%d", *vi)) - } - - } - } - - return values + return opts } diff --git a/ffmpeg/progress.go b/ffmpeg/progress.go index 62594c6..2593ce6 100644 --- a/ffmpeg/progress.go +++ b/ffmpeg/progress.go @@ -1,12 +1,16 @@ package ffmpeg +import ( + "encoding/json" +) + // Progress ... type Progress struct { - FramesProcessed string - CurrentTime string - CurrentBitrate string - Progress float64 - Speed string + FramesProcessed string `json:"f"` + CurrentTime string `json:"t"` + CurrentBitrate string `json:"b"` + Speed string `json:"s"` + Progress float64 `json:"p"` } // GetFramesProcessed ... @@ -33,3 +37,11 @@ func (p Progress) GetProgress() float64 { func (p Progress) GetSpeed() string { return p.Speed } + +func (p Progress) String() string { + data, _ := json.Marshal(&p) + if data == nil { + return "{}" + } + return string(data) +} diff --git a/go.mod b/go.mod index 4c9ade4..cbfa351 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/floostack/transcoder +module github.com/legion-zver/transcoder -go 1.13 +go 1.17 diff --git a/metadata.go b/metadata.go index d03241d..aee0f43 100644 --- a/metadata.go +++ b/metadata.go @@ -17,7 +17,7 @@ type Format interface { GetSize() string GetBitRate() string GetProbeScore() int - GetTags() Tags + GetTags() map[string]string } // Streams ... @@ -51,11 +51,7 @@ type Streams interface { GetDuration() string GetDisposition() Disposition GetBitRate() string -} - -// Tags ... -type Tags interface { - GetEncoder() string + GetTags() map[string]string } // Disposition ... diff --git a/transcoder.go b/transcoder.go index c93bc4a..e4606de 100644 --- a/transcoder.go +++ b/transcoder.go @@ -13,7 +13,12 @@ type Transcoder interface { Output(o string) Transcoder OutputPipe(w *io.WriteCloser, r *io.ReadCloser) Transcoder WithOptions(opts Options) Transcoder + WithStartOptions(opts Options) Transcoder WithAdditionalOptions(opts Options) Transcoder + WithAdditionalStartOptions(opts Options) Transcoder WithContext(ctx *context.Context) Transcoder + WithMetadata(metadata Metadata) Transcoder + SkipMetadata() Transcoder GetMetadata() (Metadata, error) + Error() error } diff --git a/utils/utils.go b/utils/utils.go index 95e9eab..473562e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -8,15 +8,14 @@ import ( // DurToSec ... func DurToSec(dur string) (sec float64) { durAry := strings.Split(dur, ":") - var secs float64 if len(durAry) != 3 { return } hr, _ := strconv.ParseFloat(durAry[0], 64) - secs = hr * (60 * 60) + sec = hr * (60 * 60) min, _ := strconv.ParseFloat(durAry[1], 64) - secs += min * (60) + sec += min * (60) second, _ := strconv.ParseFloat(durAry[2], 64) - secs += second - return secs + sec += second + return }