diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 0c5b444..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -jobs: - build: - docker: - # specify the version - - image: circleci/golang:1.14-buster - environment: - GO111MODULE: "on" - - working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} - steps: - - checkout - - # specify any bash command here prefixed with `run: ` - - run: sudo apt-get update && sudo apt-get install ffmpeg - - run: go mod download - - run: go test -failfast -v -run=. ./... diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..09e7dc4 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,25 @@ +name: Go +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: "1.21.x" + - name: Install dependencies + run: go get . + - name: Install FFmpeg + run: sudo apt-get update && sudo apt-get install ffmpeg + - name: Build + run: go build -v ./... + - name: Test with the Go CLI + run: go test --failfast -v ./... diff --git a/.gitignore b/.gitignore index bc8c2e1..37f5172 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,10 @@ main-test.go goffmpeg coverage.out +# IDE .DS_Store .vscode -.idea \ No newline at end of file +.idea + +# test results +test_results diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fbdb003 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ + + +test: + go test -v ./... -coverprofile=coverage.out -covermode=atomic -coverpkg=./... + +coverage-html: + go tool cover -html=coverage.out \ No newline at end of file diff --git a/README.md b/README.md index 5305dc0..f49a8b3 100644 --- a/README.md +++ b/README.md @@ -1,273 +1,41 @@ # Goffmpeg [![Codacy Badge](https://api.codacy.com/project/badge/Grade/93e018e5008b4439acbb30d715b22e7f)](https://www.codacy.com/app/francisco.romero/goffmpeg?utm_source=github.com&utm_medium=referral&utm_content=xfrr/goffmpeg&utm_campaign=Badge_Grade) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/xfrr/goffmpeg/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/xfrr/goffmpeg/tree/master) +[![Go Report Card](https://goreportcard.com/badge/github.com/xfrr/goffmpeg)](https://goreportcard.com/report/github.com/xfrr/goffmpeg) +[![GoDoc](https://godoc.org/github.com/xfrr/goffmpeg?status.svg)](https://godoc.org/github.com/xfrr/goffmpeg) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)]( -FFMPEG wrapper written in GO which allows to obtain the progress. +FFMPEG wrapper written in GO -## V2 -New implementation with an easy-to-use API and interfaces to extend the transcoding capabilities. +> New implementation with an easy-to-use API and interfaces to extend the transcoding capabilities. > https://github.com/floostack/transcoder -# Dependencies +## Features + +- [x] Transcoding +- [x] Streaming +- [x] Progress +- [x] Filters +- [x] Thumbnails +- [x] Watermark +- [ ] Concatenation +- [ ] Subtitles + +## Dependencies - [FFmpeg](https://www.ffmpeg.org/) - [FFProbe](https://www.ffmpeg.org/ffprobe.html) -# Supported platforms +## Supported platforms - Linux - OS X - Windows -# Getting started -## How to transcode a media file +## Installation +Install the package with the following command: ```shell go get github.com/xfrr/goffmpeg ``` -```go -package main - -import ( - "github.com/xfrr/goffmpeg/transcoder" -) - -var inputPath = "/data/testmov" -var outputPath = "/data/testmp4.mp4" - -func main() { - - // Create new instance of transcoder - trans := new(transcoder.Transcoder) - - // Initialize transcoder passing the input file path and output file path - err := trans.Initialize( inputPath, outputPath ) - // Handle error... - - // Start transcoder process without checking progress - done := trans.Run(false) - - // This channel is used to wait for the process to end - err = <-done - // Handle error... - -} -``` -## How to get the transcoding progress -```go -... -func main() { - - // Create new instance of transcoder - trans := new(transcoder.Transcoder) - - // Initialize transcoder passing the input file path and output file path - err := trans.Initialize( inputPath, outputPath ) - // Handle error... - - // Start transcoder process with progress checking - done := trans.Run(true) - - // Returns a channel to get the transcoding progress - progress := trans.Output() - - // Example of printing transcoding progress - for msg := range progress { - fmt.Println(msg) - } - - // This channel is used to wait for the transcoding process to end - err = <-done - -} -``` - -## How to pipe in data using the [pipe protocol](https://ffmpeg.org/ffmpeg-protocols.html#pipe) -Creating an input pipe will return [\*io.PipeReader](https://golang.org/pkg/io/#PipeReader), and creating an output pipe will return [\*io.PipeWriter](https://golang.org/pkg/io/#PipeWriter). An example is shown which uses `cat` to pipe in data, and [ioutil.ReadAll](https://golang.org/pkg/io/ioutil/#ReadAll) to read data as bytes from the pipe. -```go -func main() { - - // Create new instance of transcoder - trans := new(transcoder.Transcoder) - - // Initialize an empty transcoder - err := trans.InitializeEmptyTranscoder() - // Handle error... - - // Create a command such that its output should be passed as stdin to ffmpeg - cmd := exec.Command("cat", "/path/to/file") - - // Create an input pipe to write to, which will return *io.PipeWriter - w, err := trans.CreateInputPipe() - - cmd.Stdout = w - - // Create an output pipe to read from, which will return *io.PipeReader. - // Must also specify the output container format - r, err := trans.CreateOutputPipe("mp4") - - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer r.Close() - defer wg.Done() - - // Read data from output pipe - data, err := ioutil.ReadAll(r) - // Handle error and data... - }() - - go func() { - defer w.Close() - err := cmd.Run() - // Handle error... - }() - - // Start transcoder process without checking progress - done := trans.Run(false) - - // This channel is used to wait for the transcoding process to end - err = <-done - // Handle error... - - wg.Wait() -} -``` - -# Progress properties -```go -type Progress struct { - FramesProcessed string - CurrentTime string - CurrentBitrate string - Progress float64 - Speed string -} -``` -# Media setters -Those options can be set before starting the transcoding. -```js -SetAspect -SetResolution -SetVideoBitRate -SetVideoBitRateTolerance -SetVideoMaxBitrate -SetVideoMinBitRate -SetVideoCodec -SetVframes -SetFrameRate -SetAudioRate -SetSkipAudio -SetSkipVideo -SetMaxKeyFrame -SetMinKeyFrame -SetKeyframeInterval -SetAudioCodec -SetAudioBitRate -SetAudioChannels -SetBufferSize -SetThreads -SetPreset -SetTune -SetAudioProfile -SetVideoProfile -SetDuration -SetDurationInput -SetSeekTime -SetSeekTimeInput -SetSeekUsingTsInput -SetQuality -SetStrict -SetSingleFile -SetCopyTs -SetMuxDelay -SetHideBanner -SetInputPath -SetNativeFramerateInput -SetRtmpLive -SetHlsListSize -SetHlsSegmentDuration -SetHlsPlaylistType -SetHlsMasterPlaylistName -SetHlsSegmentFilename -SetHttpMethod -SetHttpKeepAlive -SetOutputPath -SetOutputFormat -SetAudioFilter -SetAudioVariableBitrate -SetCompressionLevel -SetFilter -SetInputInitialOffset -SetInputPipeCommand -SetMapMetadata -SetMetadata -SetStreamIds -SetTags -SetVideoFilter -``` -Example -```golang -func main() { - - // Create new instance of transcoder - trans := new(transcoder.Transcoder) - - // Initialize transcoder passing the input file path and output file path - err := trans.Initialize( inputPath, outputPath ) - // Handle error... - - // SET FRAME RATE TO MEDIAFILE - trans.MediaFile().SetFrameRate(70) - // SET ULTRAFAST PRESET TO MEDIAFILE - trans.MediaFile().SetPreset("ultrafast") - - // Start transcoder process to check progress - done := trans.Run(true) - - // Returns a channel to get the transcoding progress - progress := trans.Output() - - // Example of printing transcoding progress - for msg := range progress { - fmt.Println(msg) - } - - // This channel is used to wait for the transcoding process to end - err = <-done - -} -``` - -Example with AES encryption : - -More information about [HLS encryption with FFMPEG](https://hlsbook.net/how-to-encrypt-hls-video-with-ffmpeg/) - -```bash -# Generate key -openssl rand 16 > enc.key -``` - -Create key file info : - -```enc.keyinfo -Key URI -Path to key file -``` - -```golang -func main() { - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - - trans.MediaFile().SetVideoCodec("libx264") - - trans.MediaFile().SetHlsSegmentDuration(4) - - trans.MediaFile().SetEncryptionKey(keyinfoPath) - - progress := trans.Output() - - err = <-done -} -``` +## Usage +Check the [examples](./examples) \ No newline at end of file diff --git a/config.go b/config.go new file mode 100644 index 0000000..e0b350b --- /dev/null +++ b/config.go @@ -0,0 +1,67 @@ +package goffmpeg + +import ( + "context" + "errors" + "runtime" + "strings" + + "github.com/xfrr/goffmpeg/pkg/cmd" +) + +const ( + ffmpegCommand = "ffmpeg" + ffprobeCommand = "ffprobe" +) + +type Configuration struct { + ffprobeBinPath string + ffmpegBinPath string +} + +func (cfg Configuration) FFmpegBinPath() string { + return cfg.ffmpegBinPath +} + +func (cfg Configuration) FFprobeBinPath() string { + return cfg.ffprobeBinPath +} + +func Configure(ctx context.Context) (Configuration, error) { + ffmpegBin, err := cmd.FindBinPath(ctx, ffmpegCommand) + if err != nil { + return Configuration{}, err + } + + if ffmpegBin == "" { + return Configuration{}, errors.New("ffmpeg not found, please install it before using goffmpeg") + } + + ffprobeBin, err := cmd.FindBinPath(ctx, ffprobeCommand) + if err != nil { + return Configuration{}, err + } + + if ffprobeBin == "" { + return Configuration{}, errors.New("ffprobe not found, please install it before using goffmpeg") + } + + return Configuration{ + ffmpegBinPath: normalizeBinPath(ffmpegBin), + ffprobeBinPath: normalizeBinPath(ffprobeBin), + }, nil +} + +func normalizeBinPath(binPath string) string { + binPath = strings.ReplaceAll(binPath, lineSeparator(), " ") + return strings.TrimSpace(binPath) +} + +func lineSeparator() string { + switch runtime.GOOS { + case "windows": + return "\r\n" + default: + return "\n" + } +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..8390576 --- /dev/null +++ b/config_test.go @@ -0,0 +1,18 @@ +package goffmpeg + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigure(t *testing.T) { + t.Run("Should set the correct command", func(t *testing.T) { + ctx := context.Background() + cfg, err := Configure(ctx) + assert.Nil(t, err) + assert.NotEmpty(t, cfg.FFmpegBinPath()) + assert.NotEmpty(t, cfg.FFprobeBinPath()) + }) +} diff --git a/e2e/fixtures/input.3gp b/e2e/fixtures/input.3gp new file mode 100644 index 0000000..7b2e319 Binary files /dev/null and b/e2e/fixtures/input.3gp differ diff --git a/e2e/transcoding_test.go b/e2e/transcoding_test.go new file mode 100755 index 0000000..6b30f6c --- /dev/null +++ b/e2e/transcoding_test.go @@ -0,0 +1,104 @@ +package test + +import ( + "io/ioutil" + "os/exec" + "path" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/xfrr/goffmpeg/transcoder" +) + +const ( + fixturePath = "./fixtures" + resultsPath = "./test_results" +) + +var ( + // Input files + input3gp = path.Join(fixturePath, "input.3gp") +) + +func TestInputNotFound(t *testing.T) { + createResultsDir(t) + var outputPath = path.Join(resultsPath, "notfound.mp4") + + trans := new(transcoder.Transcoder) + + err := trans.Initialize("notfound.3gp", outputPath) + assert.NotNil(t, err) +} + +func TestTranscodingProgress(t *testing.T) { + createResultsDir(t) + + outputPath := path.Join(resultsPath, "progress.mp4") + trans := new(transcoder.Transcoder) + + err := trans.Initialize(input3gp, outputPath) + assert.Nil(t, err) + + errCh := trans.Run(true) + + progress := []transcoder.Progress{} + for val := range trans.Output() { + progress = append(progress, val) + } + err = <-errCh + assert.Nil(t, err) + assert.GreaterOrEqual(t, len(progress), 1) + checkFileExists(t, outputPath) +} + +func TestTranscodePipes(t *testing.T) { + createResultsDir(t) + + c1 := exec.Command("cat", input3gp) + + trans := new(transcoder.Transcoder) + + err := trans.InitializeEmptyTranscoder() + assert.Nil(t, err) + + w, err := trans.CreateInputPipe() + assert.Nil(t, err) + c1.Stdout = w + + r, err := trans.CreateOutputPipe("mp4") + assert.Nil(t, err) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + _, err := ioutil.ReadAll(r) + assert.Nil(t, err) + + r.Close() + wg.Done() + }() + + go func() { + err := c1.Run() + assert.Nil(t, err) + w.Close() + }() + done := trans.Run(false) + err = <-done + assert.Nil(t, err) + + wg.Wait() +} + +func createResultsDir(t *testing.T) { + err := exec.Command("mkdir", "-p", resultsPath).Run() + assert.Nil(t, err) +} + +func checkFileExists(t *testing.T, filepath string) { + res, err := exec.Command("cat", filepath).Output() + assert.Nil(t, err) + assert.Greater(t, len(res), 0) +} diff --git a/examples/fixtures/input.3gp b/examples/fixtures/input.3gp new file mode 100644 index 0000000..7b2e319 Binary files /dev/null and b/examples/fixtures/input.3gp differ diff --git a/examples/hls/main.go b/examples/hls/main.go new file mode 100644 index 0000000..b8dc315 --- /dev/null +++ b/examples/hls/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + + "github.com/xfrr/goffmpeg/transcoder" +) + +const ( + inputPath = "../fixtures/input.3gp" + outputPath = "../test_results/hls-output.mp4" + keyinfoPath = "keyinfo" +) + +func main() { + trans := new(transcoder.Transcoder) + + err := trans.Initialize(inputPath, outputPath) + if err != nil { + panic(err) + } + + trans.MediaFile().SetVideoCodec("libx264") + trans.MediaFile().SetHlsSegmentDuration(4) + trans.MediaFile().SetEncryptionKey(keyinfoPath) + + done := trans.Run(true) + progress := trans.Output() + for p := range progress { + fmt.Println(p) + } + + fmt.Println(<-done) +} diff --git a/examples/ultrafast-preset/main.go b/examples/ultrafast-preset/main.go new file mode 100644 index 0000000..beec42b --- /dev/null +++ b/examples/ultrafast-preset/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/xfrr/goffmpeg/transcoder" +) + +const ( + inputPath = "../fixtures/input.3gp" + outputPath = "../test_results/ultrafast-output.mp4" +) + +func main() { + trans := new(transcoder.Transcoder) + + err := trans.Initialize(inputPath, outputPath) + if err != nil { + panic(err) + } + + trans.MediaFile().SetPreset("ultrafast") + + done := trans.Run(true) + progress := trans.Output() + for p := range progress { + fmt.Println(p) + } + + fmt.Println(<-done) +} diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go deleted file mode 100644 index 89292e8..0000000 --- a/ffmpeg/ffmpeg.go +++ /dev/null @@ -1,39 +0,0 @@ -package ffmpeg - -import ( - "bytes" - "strings" - - "github.com/xfrr/goffmpeg/utils" -) - -// Configuration ... -type Configuration struct { - FfmpegBin string - FfprobeBin string -} - -// Configure Get and set FFmpeg and FFprobe bin paths -func Configure() (Configuration, error) { - var outFFmpeg bytes.Buffer - var outProbe bytes.Buffer - - execFFmpegCommand := utils.GetFFmpegExec() - execFFprobeCommand := utils.GetFFprobeExec() - - outFFmpeg, err := utils.TestCmd(execFFmpegCommand[0], execFFmpegCommand[1]) - if err != nil { - return Configuration{}, err - } - - outProbe, err = utils.TestCmd(execFFprobeCommand[0], execFFprobeCommand[1]) - if err != nil { - return Configuration{}, err - } - - ffmpeg := strings.Replace(strings.Split(outFFmpeg.String(), "\n")[0], utils.LineSeparator(), "", -1) - ffprobe := strings.Replace(strings.Split(outProbe.String(), "\n")[0], utils.LineSeparator(), "", -1) - - cnf := Configuration{ffmpeg, ffprobe} - return cnf, nil -} diff --git a/go.mod b/go.mod index 84c2326..d45f358 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,11 @@ module github.com/xfrr/goffmpeg -go 1.14 +go 1.20 require github.com/stretchr/testify v1.5.1 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/models/media.go b/media/file.go similarity index 58% rename from models/media.go rename to media/file.go index cdddd10..51f1875 100644 --- a/models/media.go +++ b/media/file.go @@ -1,4 +1,4 @@ -package models +package media import ( "fmt" @@ -8,7 +8,7 @@ import ( "strings" ) -type Mediafile struct { +type File struct { aspect string resolution string videoBitRate string @@ -85,578 +85,578 @@ type Mediafile struct { } /*** SETTERS ***/ -func (m *Mediafile) SetAudioFilter(v string) { +func (m *File) SetAudioFilter(v string) { m.audioFilter = v } -func (m *Mediafile) SetVideoFilter(v string) { +func (m *File) SetVideoFilter(v string) { m.videoFilter = v } // Deprecated: Use SetVideoFilter instead. -func (m *Mediafile) SetFilter(v string) { +func (m *File) SetFilter(v string) { m.SetVideoFilter(v) } -func (m *Mediafile) SetAspect(v string) { +func (m *File) SetAspect(v string) { m.aspect = v } -func (m *Mediafile) SetResolution(v string) { +func (m *File) SetResolution(v string) { m.resolution = v } -func (m *Mediafile) SetVideoBitRate(v string) { +func (m *File) SetVideoBitRate(v string) { m.videoBitRate = v } -func (m *Mediafile) SetVideoBitRateTolerance(v int) { +func (m *File) SetVideoBitRateTolerance(v int) { m.videoBitRateTolerance = v } -func (m *Mediafile) SetVideoMaxBitrate(v int) { +func (m *File) SetVideoMaxBitrate(v int) { m.videoMaxBitRate = v } -func (m *Mediafile) SetVideoMinBitRate(v int) { +func (m *File) SetVideoMinBitRate(v int) { m.videoMinBitrate = v } -func (m *Mediafile) SetVideoCodec(v string) { +func (m *File) SetVideoCodec(v string) { m.videoCodec = v } -func (m *Mediafile) SetVframes(v int) { +func (m *File) SetVframes(v int) { m.vframes = v } -func (m *Mediafile) SetFrameRate(v int) { +func (m *File) SetFrameRate(v int) { m.frameRate = v } -func (m *Mediafile) SetAudioRate(v int) { +func (m *File) SetAudioRate(v int) { m.audioRate = v } -func (m *Mediafile) SetAudioVariableBitrate() { +func (m *File) SetAudioVariableBitrate() { m.audioVariableBitrate = true } -func (m *Mediafile) SetMaxKeyFrame(v int) { +func (m *File) SetMaxKeyFrame(v int) { m.maxKeyframe = v } -func (m *Mediafile) SetMinKeyFrame(v int) { +func (m *File) SetMinKeyFrame(v int) { m.minKeyframe = v } -func (m *Mediafile) SetKeyframeInterval(v int) { +func (m *File) SetKeyframeInterval(v int) { m.keyframeInterval = v } -func (m *Mediafile) SetAudioCodec(v string) { +func (m *File) SetAudioCodec(v string) { m.audioCodec = v } -func (m *Mediafile) SetAudioBitRate(v string) { +func (m *File) SetAudioBitRate(v string) { m.audioBitrate = v } -func (m *Mediafile) SetAudioChannels(v int) { +func (m *File) SetAudioChannels(v int) { m.audioChannels = v } -func (m *Mediafile) SetPixFmt(v string) { +func (m *File) SetPixFmt(v string) { m.pixFmt = v } -func (m *Mediafile) SetBufferSize(v int) { +func (m *File) SetBufferSize(v int) { m.bufferSize = v } -func (m *Mediafile) SetThreads(v int) { +func (m *File) SetThreads(v int) { m.threadset = true m.threads = v } -func (m *Mediafile) SetPreset(v string) { +func (m *File) SetPreset(v string) { m.preset = v } -func (m *Mediafile) SetTune(v string) { +func (m *File) SetTune(v string) { m.tune = v } -func (m *Mediafile) SetAudioProfile(v string) { +func (m *File) SetAudioProfile(v string) { m.audioProfile = v } -func (m *Mediafile) SetVideoProfile(v string) { +func (m *File) SetVideoProfile(v string) { m.videoProfile = v } -func (m *Mediafile) SetDuration(v string) { +func (m *File) SetDuration(v string) { m.duration = v } -func (m *Mediafile) SetDurationInput(v string) { +func (m *File) SetDurationInput(v string) { m.durationInput = v } -func (m *Mediafile) SetSeekTime(v string) { +func (m *File) SetSeekTime(v string) { m.seekTime = v } -func (m *Mediafile) SetSeekTimeInput(v string) { +func (m *File) SetSeekTimeInput(v string) { m.seekTimeInput = v } // Q Scale must be integer between 1 to 31 - https://trac.ffmpeg.org/wiki/Encode/MPEG-4 -func (m *Mediafile) SetQScale(v uint32) { +func (m *File) SetQScale(v uint32) { m.qscale = v } -func (m *Mediafile) SetCRF(v uint32) { +func (m *File) SetCRF(v uint32) { m.crf = v } -func (m *Mediafile) SetStrict(v int) { +func (m *File) SetStrict(v int) { m.strict = v } -func (m *Mediafile) SetSingleFile(v int) { +func (m *File) SetSingleFile(v int) { m.singleFile = v } -func (m *Mediafile) SetSeekUsingTsInput(val bool) { +func (m *File) SetSeekUsingTsInput(val bool) { m.seekUsingTsInput = val } -func (m *Mediafile) SetCopyTs(val bool) { +func (m *File) SetCopyTs(val bool) { m.copyTs = val } -func (m *Mediafile) SetInputPath(val string) { +func (m *File) SetInputPath(val string) { m.inputPath = val } -func (m *Mediafile) SetInputPipe(val bool) { +func (m *File) SetInputPipe(val bool) { m.inputPipe = val } -func (m *Mediafile) SetInputPipeReader(r io.ReadCloser) { +func (m *File) SetInputPipeReader(r io.ReadCloser) { m.inputPipeReader = r } -func (m *Mediafile) SetInputPipeWriter(w io.Writer) { +func (m *File) SetInputPipeWriter(w io.Writer) { m.inputPipeWriter = w } -func (m *Mediafile) SetOutputPipe(val bool) { +func (m *File) SetOutputPipe(val bool) { m.outputPipe = val } -func (m *Mediafile) SetOutputPipeReader(r io.Reader) { +func (m *File) SetOutputPipeReader(r io.Reader) { m.outputPipeReader = r } -func (m *Mediafile) SetOutputPipeWriter(w io.WriteCloser) { +func (m *File) SetOutputPipeWriter(w io.WriteCloser) { m.outputPipeWriter = w } -func (m *Mediafile) SetMovFlags(val string) { +func (m *File) SetMovFlags(val string) { m.movFlags = val } -func (m *Mediafile) SetHideBanner(val bool) { +func (m *File) SetHideBanner(val bool) { m.hideBanner = val } -func (m *Mediafile) SetMuxDelay(val string) { +func (m *File) SetMuxDelay(val string) { m.muxDelay = val } -func (m *Mediafile) SetOutputPath(val string) { +func (m *File) SetOutputPath(val string) { m.outputPath = val } -func (m *Mediafile) SetOutputFormat(val string) { +func (m *File) SetOutputFormat(val string) { m.outputFormat = val } -func (m *Mediafile) SetNativeFramerateInput(val bool) { +func (m *File) SetNativeFramerateInput(val bool) { m.nativeFramerateInput = val } -func (m *Mediafile) SetRtmpLive(val string) { +func (m *File) SetRtmpLive(val string) { m.rtmpLive = val } -func (m *Mediafile) SetHlsListSize(val int) { +func (m *File) SetHlsListSize(val int) { m.hlsListSize = val } -func (m *Mediafile) SetHlsSegmentDuration(val int) { +func (m *File) SetHlsSegmentDuration(val int) { m.hlsSegmentDuration = val } -func (m *Mediafile) SetHlsPlaylistType(val string) { +func (m *File) SetHlsPlaylistType(val string) { m.hlsPlaylistType = val } -func (m *Mediafile) SetHlsMasterPlaylistName(val string) { +func (m *File) SetHlsMasterPlaylistName(val string) { m.hlsMasterPlaylistName = val } -func (m *Mediafile) SetHlsSegmentFilename(val string) { +func (m *File) SetHlsSegmentFilename(val string) { m.hlsSegmentFilename = val } -func (m *Mediafile) SetHttpMethod(val string) { +func (m *File) SetHttpMethod(val string) { m.httpMethod = val } -func (m *Mediafile) SetHttpKeepAlive(val bool) { +func (m *File) SetHttpKeepAlive(val bool) { m.httpKeepAlive = val } -func (m *Mediafile) SetHardwareAcceleration(val string) { +func (m *File) SetHardwareAcceleration(val string) { m.hwaccel = val } -func (m *Mediafile) SetInputInitialOffset(val string) { +func (m *File) SetInputInitialOffset(val string) { m.inputInitialOffset = val } -func (m *Mediafile) SetStreamIds(val map[int]string) { +func (m *File) SetStreamIds(val map[int]string) { m.streamIds = val } -func (m *Mediafile) SetSkipVideo(val bool) { +func (m *File) SetSkipVideo(val bool) { m.skipVideo = val } -func (m *Mediafile) SetSkipAudio(val bool) { +func (m *File) SetSkipAudio(val bool) { m.skipAudio = val } -func (m *Mediafile) SetMetadata(v Metadata) { +func (m *File) SetMetadata(v Metadata) { m.metadata = v } -func (m *Mediafile) SetCompressionLevel(val int) { +func (m *File) SetCompressionLevel(val int) { m.compressionLevel = val } -func (m *Mediafile) SetMapMetadata(val string) { +func (m *File) SetMapMetadata(val string) { m.mapMetadata = val } -func (m *Mediafile) SetTags(val map[string]string) { +func (m *File) SetTags(val map[string]string) { m.tags = val } -func (m *Mediafile) SetBframe(v int) { +func (m *File) SetBframe(v int) { m.bframe = v } -func (m *Mediafile) SetRawInputArgs(args []string) { +func (m *File) SetRawInputArgs(args []string) { m.rawInputArgs = args } -func (m *Mediafile) SetRawOutputArgs(args []string) { +func (m *File) SetRawOutputArgs(args []string) { m.rawOutputArgs = args } /*** GETTERS ***/ // Deprecated: Use VideoFilter instead. -func (m *Mediafile) Filter() string { +func (m *File) Filter() string { return m.VideoFilter() } -func (m *Mediafile) VideoFilter() string { +func (m *File) VideoFilter() string { return m.videoFilter } -func (m *Mediafile) AudioFilter() string { +func (m *File) AudioFilter() string { return m.audioFilter } -func (m *Mediafile) Aspect() string { +func (m *File) Aspect() string { return m.aspect } -func (m *Mediafile) Resolution() string { +func (m *File) Resolution() string { return m.resolution } -func (m *Mediafile) VideoBitrate() string { +func (m *File) VideoBitrate() string { return m.videoBitRate } -func (m *Mediafile) VideoBitRateTolerance() int { +func (m *File) VideoBitRateTolerance() int { return m.videoBitRateTolerance } -func (m *Mediafile) VideoMaxBitRate() int { +func (m *File) VideoMaxBitRate() int { return m.videoMaxBitRate } -func (m *Mediafile) VideoMinBitRate() int { +func (m *File) VideoMinBitRate() int { return m.videoMinBitrate } -func (m *Mediafile) VideoCodec() string { +func (m *File) VideoCodec() string { return m.videoCodec } -func (m *Mediafile) Vframes() int { +func (m *File) Vframes() int { return m.vframes } -func (m *Mediafile) FrameRate() int { +func (m *File) FrameRate() int { return m.frameRate } -func (m *Mediafile) GetPixFmt() string { +func (m *File) GetPixFmt() string { return m.pixFmt } -func (m *Mediafile) AudioRate() int { +func (m *File) AudioRate() int { return m.audioRate } -func (m *Mediafile) MaxKeyFrame() int { +func (m *File) MaxKeyFrame() int { return m.maxKeyframe } -func (m *Mediafile) MinKeyFrame() int { +func (m *File) MinKeyFrame() int { return m.minKeyframe } -func (m *Mediafile) KeyFrameInterval() int { +func (m *File) KeyFrameInterval() int { return m.keyframeInterval } -func (m *Mediafile) AudioCodec() string { +func (m *File) AudioCodec() string { return m.audioCodec } -func (m *Mediafile) AudioBitrate() string { +func (m *File) AudioBitrate() string { return m.audioBitrate } -func (m *Mediafile) AudioChannels() int { +func (m *File) AudioChannels() int { return m.audioChannels } -func (m *Mediafile) BufferSize() int { +func (m *File) BufferSize() int { return m.bufferSize } -func (m *Mediafile) Threads() int { +func (m *File) Threads() int { return m.threads } -func (m *Mediafile) Target() string { +func (m *File) Target() string { return m.target } -func (m *Mediafile) Duration() string { +func (m *File) Duration() string { return m.duration } -func (m *Mediafile) DurationInput() string { +func (m *File) DurationInput() string { return m.durationInput } -func (m *Mediafile) SeekTime() string { +func (m *File) SeekTime() string { return m.seekTime } -func (m *Mediafile) Preset() string { +func (m *File) Preset() string { return m.preset } -func (m *Mediafile) AudioProfile() string { +func (m *File) AudioProfile() string { return m.audioProfile } -func (m *Mediafile) VideoProfile() string { +func (m *File) VideoProfile() string { return m.videoProfile } -func (m *Mediafile) Tune() string { +func (m *File) Tune() string { return m.tune } -func (m *Mediafile) SeekTimeInput() string { +func (m *File) SeekTimeInput() string { return m.seekTimeInput } -func (m *Mediafile) QScale() uint32 { +func (m *File) QScale() uint32 { return m.qscale } -func (m *Mediafile) CRF() uint32 { +func (m *File) CRF() uint32 { return m.crf } -func (m *Mediafile) Strict() int { +func (m *File) Strict() int { return m.strict } -func (m *Mediafile) SingleFile() int { +func (m *File) SingleFile() int { return m.singleFile } -func (m *Mediafile) MuxDelay() string { +func (m *File) MuxDelay() string { return m.muxDelay } -func (m *Mediafile) SeekUsingTsInput() bool { +func (m *File) SeekUsingTsInput() bool { return m.seekUsingTsInput } -func (m *Mediafile) CopyTs() bool { +func (m *File) CopyTs() bool { return m.copyTs } -func (m *Mediafile) InputPath() string { +func (m *File) InputPath() string { return m.inputPath } -func (m *Mediafile) InputPipe() bool { +func (m *File) InputPipe() bool { return m.inputPipe } -func (m *Mediafile) InputPipeReader() io.ReadCloser { +func (m *File) InputPipeReader() io.ReadCloser { return m.inputPipeReader } -func (m *Mediafile) InputPipeWriter() io.Writer { +func (m *File) InputPipeWriter() io.Writer { return m.inputPipeWriter } -func (m *Mediafile) OutputPipe() bool { +func (m *File) OutputPipe() bool { return m.outputPipe } -func (m *Mediafile) OutputPipeReader() io.Reader { +func (m *File) OutputPipeReader() io.Reader { return m.outputPipeReader } -func (m *Mediafile) OutputPipeWriter() io.WriteCloser { +func (m *File) OutputPipeWriter() io.WriteCloser { return m.outputPipeWriter } -func (m *Mediafile) MovFlags() string { +func (m *File) MovFlags() string { return m.movFlags } -func (m *Mediafile) HideBanner() bool { +func (m *File) HideBanner() bool { return m.hideBanner } -func (m *Mediafile) OutputPath() string { +func (m *File) OutputPath() string { return m.outputPath } -func (m *Mediafile) OutputFormat() string { +func (m *File) OutputFormat() string { return m.outputFormat } -func (m *Mediafile) NativeFramerateInput() bool { +func (m *File) NativeFramerateInput() bool { return m.nativeFramerateInput } -func (m *Mediafile) RtmpLive() string { +func (m *File) RtmpLive() string { return m.rtmpLive } -func (m *Mediafile) HlsListSize() int { +func (m *File) HlsListSize() int { return m.hlsListSize } -func (m *Mediafile) HlsSegmentDuration() int { +func (m *File) HlsSegmentDuration() int { return m.hlsSegmentDuration } -func (m *Mediafile) HlsMasterPlaylistName() string { +func (m *File) HlsMasterPlaylistName() string { return m.hlsMasterPlaylistName } -func (m *Mediafile) HlsSegmentFilename() string { +func (m *File) HlsSegmentFilename() string { return m.hlsSegmentFilename } -func (m *Mediafile) HlsPlaylistType() string { +func (m *File) HlsPlaylistType() string { return m.hlsPlaylistType } -func (m *Mediafile) InputInitialOffset() string { +func (m *File) InputInitialOffset() string { return m.inputInitialOffset } -func (m *Mediafile) HttpMethod() string { +func (m *File) HttpMethod() string { return m.httpMethod } -func (m *Mediafile) HttpKeepAlive() bool { +func (m *File) HttpKeepAlive() bool { return m.httpKeepAlive } -func (m *Mediafile) HardwareAcceleration() string { +func (m *File) HardwareAcceleration() string { return m.hwaccel } -func (m *Mediafile) StreamIds() map[int]string { +func (m *File) StreamIds() map[int]string { return m.streamIds } -func (m *Mediafile) SkipVideo() bool { +func (m *File) SkipVideo() bool { return m.skipVideo } -func (m *Mediafile) SkipAudio() bool { +func (m *File) SkipAudio() bool { return m.skipAudio } -func (m *Mediafile) Metadata() Metadata { +func (m *File) Metadata() Metadata { return m.metadata } -func (m *Mediafile) CompressionLevel() int { +func (m *File) CompressionLevel() int { return m.compressionLevel } -func (m *Mediafile) MapMetadata() string { +func (m *File) MapMetadata() string { return m.mapMetadata } -func (m *Mediafile) Tags() map[string]string { +func (m *File) Tags() map[string]string { return m.tags } -func (m *Mediafile) SetEncryptionKey(v string) { +func (m *File) SetEncryptionKey(v string) { m.encryptionKey = v } -func (m *Mediafile) EncryptionKey() string { +func (m *File) EncryptionKey() string { return m.encryptionKey } -func (m *Mediafile) RawInputArgs() []string { +func (m *File) RawInputArgs() []string { return m.rawInputArgs } -func (m *Mediafile) RawOutputArgs() []string { +func (m *File) RawOutputArgs() []string { return m.rawOutputArgs } /** OPTS **/ -func (m *Mediafile) ToStrCommand() []string { +func (m *File) ToStrCommand() []string { var strCommand []string opts := []string{ @@ -740,21 +740,21 @@ func (m *Mediafile) ToStrCommand() []string { return strCommand } -func (m *Mediafile) ObtainAudioFilter() []string { +func (m *File) ObtainAudioFilter() []string { if m.audioFilter != "" { return []string{"-af", m.audioFilter} } return nil } -func (m *Mediafile) ObtainVideoFilter() []string { +func (m *File) ObtainVideoFilter() []string { if m.videoFilter != "" { return []string{"-vf", m.videoFilter} } return nil } -func (m *Mediafile) ObtainAspect() []string { +func (m *File) ObtainAspect() []string { // Set aspect if m.resolution != "" { resolution := strings.Split(m.resolution, "x") @@ -771,112 +771,112 @@ func (m *Mediafile) ObtainAspect() []string { return nil } -func (m *Mediafile) ObtainHardwareAcceleration() []string { +func (m *File) ObtainHardwareAcceleration() []string { if m.hwaccel != "" { return []string{"-hwaccel", m.hwaccel} } return nil } -func (m *Mediafile) ObtainInputPath() []string { +func (m *File) ObtainInputPath() []string { if m.inputPath != "" { return []string{"-i", m.inputPath} } return nil } -func (m *Mediafile) ObtainInputPipe() []string { +func (m *File) ObtainInputPipe() []string { if m.inputPipe { return []string{"-i", "pipe:0"} } return nil } -func (m *Mediafile) ObtainOutputPipe() []string { +func (m *File) ObtainOutputPipe() []string { if m.outputPipe { return []string{"pipe:1"} } return nil } -func (m *Mediafile) ObtainMovFlags() []string { +func (m *File) ObtainMovFlags() []string { if m.movFlags != "" { return []string{"-movflags", m.movFlags} } return nil } -func (m *Mediafile) ObtainHideBanner() []string { +func (m *File) ObtainHideBanner() []string { if m.hideBanner { return []string{"-hide_banner"} } return nil } -func (m *Mediafile) ObtainNativeFramerateInput() []string { +func (m *File) ObtainNativeFramerateInput() []string { if m.nativeFramerateInput { return []string{"-re"} } return nil } -func (m *Mediafile) ObtainOutputPath() []string { +func (m *File) ObtainOutputPath() []string { if m.outputPath != "" { return []string{m.outputPath} } return nil } -func (m *Mediafile) ObtainVideoCodec() []string { +func (m *File) ObtainVideoCodec() []string { if m.videoCodec != "" { return []string{"-c:v", m.videoCodec} } return nil } -func (m *Mediafile) ObtainVframes() []string { +func (m *File) ObtainVframes() []string { if m.vframes != 0 { return []string{"-vframes", fmt.Sprintf("%d", m.vframes)} } return nil } -func (m *Mediafile) ObtainFrameRate() []string { +func (m *File) ObtainFrameRate() []string { if m.frameRate != 0 { return []string{"-r", fmt.Sprintf("%d", m.frameRate)} } return nil } -func (m *Mediafile) ObtainAudioRate() []string { +func (m *File) ObtainAudioRate() []string { if m.audioRate != 0 { return []string{"-ar", fmt.Sprintf("%d", m.audioRate)} } return nil } -func (m *Mediafile) ObtainResolution() []string { +func (m *File) ObtainResolution() []string { if m.resolution != "" { return []string{"-s", m.resolution} } return nil } -func (m *Mediafile) ObtainVideoBitRate() []string { +func (m *File) ObtainVideoBitRate() []string { if m.videoBitRate != "" { return []string{"-b:v", m.videoBitRate} } return nil } -func (m *Mediafile) ObtainAudioCodec() []string { +func (m *File) ObtainAudioCodec() []string { if m.audioCodec != "" { return []string{"-c:a", m.audioCodec} } return nil } -func (m *Mediafile) ObtainAudioBitRate() []string { +func (m *File) ObtainAudioBitRate() []string { switch { case !m.audioVariableBitrate && m.audioBitrate != "": return []string{"-b:a", m.audioBitrate} @@ -889,175 +889,175 @@ func (m *Mediafile) ObtainAudioBitRate() []string { } } -func (m *Mediafile) ObtainAudioChannels() []string { +func (m *File) ObtainAudioChannels() []string { if m.audioChannels != 0 { return []string{"-ac", fmt.Sprintf("%d", m.audioChannels)} } return nil } -func (m *Mediafile) ObtainVideoMaxBitRate() []string { +func (m *File) ObtainVideoMaxBitRate() []string { if m.videoMaxBitRate != 0 { return []string{"-maxrate", fmt.Sprintf("%dk", m.videoMaxBitRate)} } return nil } -func (m *Mediafile) ObtainVideoMinBitRate() []string { +func (m *File) ObtainVideoMinBitRate() []string { if m.videoMinBitrate != 0 { return []string{"-minrate", fmt.Sprintf("%dk", m.videoMinBitrate)} } return nil } -func (m *Mediafile) ObtainBufferSize() []string { +func (m *File) ObtainBufferSize() []string { if m.bufferSize != 0 { return []string{"-bufsize", fmt.Sprintf("%dk", m.bufferSize)} } return nil } -func (m *Mediafile) ObtainVideoBitRateTolerance() []string { +func (m *File) ObtainVideoBitRateTolerance() []string { if m.videoBitRateTolerance != 0 { return []string{"-bt", fmt.Sprintf("%dk", m.videoBitRateTolerance)} } return nil } -func (m *Mediafile) ObtainThreads() []string { +func (m *File) ObtainThreads() []string { if m.threadset { return []string{"-threads", fmt.Sprintf("%d", m.threads)} } return nil } -func (m *Mediafile) ObtainTarget() []string { +func (m *File) ObtainTarget() []string { if m.target != "" { return []string{"-target", m.target} } return nil } -func (m *Mediafile) ObtainDuration() []string { +func (m *File) ObtainDuration() []string { if m.duration != "" { return []string{"-t", m.duration} } return nil } -func (m *Mediafile) ObtainDurationInput() []string { +func (m *File) ObtainDurationInput() []string { if m.durationInput != "" { return []string{"-t", m.durationInput} } return nil } -func (m *Mediafile) ObtainKeyframeInterval() []string { +func (m *File) ObtainKeyframeInterval() []string { if m.keyframeInterval != 0 { return []string{"-g", fmt.Sprintf("%d", m.keyframeInterval)} } return nil } -func (m *Mediafile) ObtainSeekTime() []string { +func (m *File) ObtainSeekTime() []string { if m.seekTime != "" { return []string{"-ss", m.seekTime} } return nil } -func (m *Mediafile) ObtainSeekTimeInput() []string { +func (m *File) ObtainSeekTimeInput() []string { if m.seekTimeInput != "" { return []string{"-ss", m.seekTimeInput} } return nil } -func (m *Mediafile) ObtainPreset() []string { +func (m *File) ObtainPreset() []string { if m.preset != "" { return []string{"-preset", m.preset} } return nil } -func (m *Mediafile) ObtainTune() []string { +func (m *File) ObtainTune() []string { if m.tune != "" { return []string{"-tune", m.tune} } return nil } -func (m *Mediafile) ObtainCRF() []string { +func (m *File) ObtainCRF() []string { if m.crf != 0 { return []string{"-crf", fmt.Sprintf("%d", m.crf)} } return nil } -func (m *Mediafile) ObtainQScale() []string { +func (m *File) ObtainQScale() []string { if m.qscale != 0 { return []string{"-qscale", fmt.Sprintf("%d", m.qscale)} } return nil } -func (m *Mediafile) ObtainStrict() []string { +func (m *File) ObtainStrict() []string { if m.strict != 0 { return []string{"-strict", fmt.Sprintf("%d", m.strict)} } return nil } -func (m *Mediafile) ObtainSingleFile() []string { +func (m *File) ObtainSingleFile() []string { if m.singleFile != 0 { return []string{"-single_file", fmt.Sprintf("%d", m.singleFile)} } return nil } -func (m *Mediafile) ObtainVideoProfile() []string { +func (m *File) ObtainVideoProfile() []string { if m.videoProfile != "" { return []string{"-profile:v", m.videoProfile} } return nil } -func (m *Mediafile) ObtainAudioProfile() []string { +func (m *File) ObtainAudioProfile() []string { if m.audioProfile != "" { return []string{"-profile:a", m.audioProfile} } return nil } -func (m *Mediafile) ObtainCopyTs() []string { +func (m *File) ObtainCopyTs() []string { if m.copyTs { return []string{"-copyts"} } return nil } -func (m *Mediafile) ObtainOutputFormat() []string { +func (m *File) ObtainOutputFormat() []string { if m.outputFormat != "" { return []string{"-f", m.outputFormat} } return nil } -func (m *Mediafile) ObtainMuxDelay() []string { +func (m *File) ObtainMuxDelay() []string { if m.muxDelay != "" { return []string{"-muxdelay", m.muxDelay} } return nil } -func (m *Mediafile) ObtainSeekUsingTsInput() []string { +func (m *File) ObtainSeekUsingTsInput() []string { if m.seekUsingTsInput { return []string{"-seek_timestamp", "1"} } return nil } -func (m *Mediafile) ObtainRtmpLive() []string { +func (m *File) ObtainRtmpLive() []string { if m.rtmpLive != "" { return []string{"-rtmp_live", m.rtmpLive} } else { @@ -1065,7 +1065,7 @@ func (m *Mediafile) ObtainRtmpLive() []string { } } -func (m *Mediafile) ObtainHlsPlaylistType() []string { +func (m *File) ObtainHlsPlaylistType() []string { if m.hlsPlaylistType != "" { return []string{"-hls_playlist_type", m.hlsPlaylistType} } else { @@ -1073,7 +1073,7 @@ func (m *Mediafile) ObtainHlsPlaylistType() []string { } } -func (m *Mediafile) ObtainInputInitialOffset() []string { +func (m *File) ObtainInputInitialOffset() []string { if m.inputInitialOffset != "" { return []string{"-itsoffset", m.inputInitialOffset} } else { @@ -1081,11 +1081,11 @@ func (m *Mediafile) ObtainInputInitialOffset() []string { } } -func (m *Mediafile) ObtainHlsListSize() []string { +func (m *File) ObtainHlsListSize() []string { return []string{"-hls_list_size", fmt.Sprintf("%d", m.hlsListSize)} } -func (m *Mediafile) ObtainHlsSegmentDuration() []string { +func (m *File) ObtainHlsSegmentDuration() []string { if m.hlsSegmentDuration != 0 { return []string{"-hls_time", fmt.Sprintf("%d", m.hlsSegmentDuration)} } else { @@ -1093,7 +1093,7 @@ func (m *Mediafile) ObtainHlsSegmentDuration() []string { } } -func (m *Mediafile) ObtainHlsMasterPlaylistName() []string { +func (m *File) ObtainHlsMasterPlaylistName() []string { if m.hlsMasterPlaylistName != "" { return []string{"-master_pl_name", fmt.Sprintf("%s", m.hlsMasterPlaylistName)} } else { @@ -1101,7 +1101,7 @@ func (m *Mediafile) ObtainHlsMasterPlaylistName() []string { } } -func (m *Mediafile) ObtainHlsSegmentFilename() []string { +func (m *File) ObtainHlsSegmentFilename() []string { if m.hlsSegmentFilename != "" { return []string{"-hls_segment_filename", fmt.Sprintf("%s", m.hlsSegmentFilename)} } else { @@ -1109,7 +1109,7 @@ func (m *Mediafile) ObtainHlsSegmentFilename() []string { } } -func (m *Mediafile) ObtainHttpMethod() []string { +func (m *File) ObtainHttpMethod() []string { if m.httpMethod != "" { return []string{"-method", m.httpMethod} } else { @@ -1117,7 +1117,7 @@ func (m *Mediafile) ObtainHttpMethod() []string { } } -func (m *Mediafile) ObtainPixFmt() []string { +func (m *File) ObtainPixFmt() []string { if m.pixFmt != "" { return []string{"-pix_fmt", m.pixFmt} } else { @@ -1125,7 +1125,7 @@ func (m *Mediafile) ObtainPixFmt() []string { } } -func (m *Mediafile) ObtainHttpKeepAlive() []string { +func (m *File) ObtainHttpKeepAlive() []string { if m.httpKeepAlive { return []string{"-multiple_requests", "1"} } else { @@ -1133,7 +1133,7 @@ func (m *Mediafile) ObtainHttpKeepAlive() []string { } } -func (m *Mediafile) ObtainSkipVideo() []string { +func (m *File) ObtainSkipVideo() []string { if m.skipVideo { return []string{"-vn"} } else { @@ -1141,7 +1141,7 @@ func (m *Mediafile) ObtainSkipVideo() []string { } } -func (m *Mediafile) ObtainSkipAudio() []string { +func (m *File) ObtainSkipAudio() []string { if m.skipAudio { return []string{"-an"} } else { @@ -1149,7 +1149,7 @@ func (m *Mediafile) ObtainSkipAudio() []string { } } -func (m *Mediafile) ObtainStreamIds() []string { +func (m *File) ObtainStreamIds() []string { if m.streamIds != nil && len(m.streamIds) != 0 { result := []string{} for i, val := range m.streamIds { @@ -1160,21 +1160,21 @@ func (m *Mediafile) ObtainStreamIds() []string { return nil } -func (m *Mediafile) ObtainCompressionLevel() []string { +func (m *File) ObtainCompressionLevel() []string { if m.compressionLevel != 0 { return []string{"-compression_level", fmt.Sprintf("%d", m.compressionLevel)} } return nil } -func (m *Mediafile) ObtainMapMetadata() []string { +func (m *File) ObtainMapMetadata() []string { if m.mapMetadata != "" { return []string{"-map_metadata", m.mapMetadata} - } - return nil + } + return nil } - -func (m *Mediafile) ObtainEncryptionKey() []string { + +func (m *File) ObtainEncryptionKey() []string { if m.encryptionKey != "" { return []string{"-hls_key_info_file", m.encryptionKey} } @@ -1182,14 +1182,14 @@ func (m *Mediafile) ObtainEncryptionKey() []string { return nil } -func (m *Mediafile) ObtainBframe() []string { +func (m *File) ObtainBframe() []string { if m.bframe != 0 { return []string{"-bf", fmt.Sprintf("%d", m.bframe)} } return nil } -func (m *Mediafile) ObtainTags() []string { +func (m *File) ObtainTags() []string { if m.tags != nil && len(m.tags) != 0 { result := []string{} for key, val := range m.tags { @@ -1200,10 +1200,21 @@ func (m *Mediafile) ObtainTags() []string { return nil } -func (m *Mediafile) ObtainRawInputArgs() []string { +func (m *File) ObtainRawInputArgs() []string { return m.rawInputArgs } -func (m *Mediafile) ObtainRawOutputArgs() []string { +func (m *File) ObtainRawOutputArgs() []string { return m.rawOutputArgs } + +func CheckFileType(streams []Streams) string { + for i := 0; i < len(streams); i++ { + st := streams[i] + if st.CodecType == "video" { + return "video" + } + } + + return "audio" +} diff --git a/media/format.go b/media/format.go new file mode 100644 index 0000000..557f6a7 --- /dev/null +++ b/media/format.go @@ -0,0 +1,18 @@ +package media + +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"` +} + +type Tags struct { + Encoder string `json:"ENCODER"` +} diff --git a/media/metadata.go b/media/metadata.go new file mode 100644 index 0000000..b89e4d6 --- /dev/null +++ b/media/metadata.go @@ -0,0 +1,6 @@ +package media + +type Metadata struct { + Streams []Streams `json:"streams"` + Format Format `json:"format"` +} diff --git a/models/models.go b/media/stream.go similarity index 70% rename from models/models.go rename to media/stream.go index 0e64c3f..24f9746 100644 --- a/models/models.go +++ b/media/stream.go @@ -1,14 +1,4 @@ -package models - -type Ffmpeg struct { - FfmpegBinPath string - FfprobeBinPath string -} - -type Metadata struct { - Streams []Streams `json:"streams"` - Format Format `json:"format"` -} +package media type Streams struct { Index int @@ -54,28 +44,3 @@ type Disposition struct { VisualImpaired int `json:"visual_impaired"` CleanEffects int `json:"clean_effects"` } - -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"` -} - -type Progress struct { - FramesProcessed string - CurrentTime string - CurrentBitrate string - Progress float64 - Speed string -} - -type Tags struct { - Encoder string `json:"ENCODER"` -} diff --git a/models/media_test.go b/models/media_test.go deleted file mode 100644 index ce613b4..0000000 --- a/models/media_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package models - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func TestMedia(t *testing.T) { - t.Run("#ObtainEncryptionKey", func(t *testing.T) { - t.Run("Should get nil if encryptionKey is not set", func(t *testing.T) { - mediaFile := Mediafile{} - - require.Nil(t, mediaFile.ObtainEncryptionKey()) - }) - - t.Run("Should return file.keyinfo if it's set", func(t *testing.T) { - mediaFile := Mediafile{encryptionKey: "file.keyinfo"} - require.Equal(t, []string{"-hls_key_info_file", "file.keyinfo"}, mediaFile.ObtainEncryptionKey()) - }) - }) -} diff --git a/pkg/cmd/exec.go b/pkg/cmd/exec.go new file mode 100644 index 0000000..5d2b45e --- /dev/null +++ b/pkg/cmd/exec.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "bytes" + "context" + "fmt" + "os/exec" +) + +func FindBinPath(ctx context.Context, command string) (string, error) { + if command == "" { + return "", fmt.Errorf("command cannot be empty") + } + + path, err := execBufferOutput(ctx, getFindCommand(), command) + if err != nil { + return "", err + } + + return path, nil +} + +func execBufferOutput(ctx context.Context, command string, args ...string) (string, error) { + var out bytes.Buffer + + c := exec.CommandContext(ctx, command, args...) + c.Stdout = &out + + err := c.Run() + if err != nil { + return "", fmt.Errorf("%s: %s", c.String(), err) + } + + return out.String(), nil +} diff --git a/pkg/cmd/find.go b/pkg/cmd/find.go new file mode 100644 index 0000000..e259886 --- /dev/null +++ b/pkg/cmd/find.go @@ -0,0 +1,16 @@ +package cmd + +import "runtime" + +var ( + platform = runtime.GOOS +) + +func getFindCommand() string { + switch platform { + case "windows": + return "where" + default: + return "which" + } +} diff --git a/pkg/duration/duration.go b/pkg/duration/duration.go new file mode 100644 index 0000000..15bcb78 --- /dev/null +++ b/pkg/duration/duration.go @@ -0,0 +1,21 @@ +package duration + +import ( + "strconv" + "strings" +) + +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) + min, _ := strconv.ParseFloat(durAry[1], 64) + secs += min * (60) + second, _ := strconv.ParseFloat(durAry[2], 64) + secs += second + return secs +} diff --git a/tests/transcoding_test.go b/tests/transcoding_test.go deleted file mode 100755 index 3381f55..0000000 --- a/tests/transcoding_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package test - -import ( - "io/ioutil" - "os/exec" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/xfrr/goffmpeg/transcoder" -) - -func TestInputNotFound(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/nf" - var outputPath = "/tmp/ffmpeg/out/nf.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.NotNil(t, err) -} - -func TestTranscoding3GP(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/3gp" - var outputPath = "/tmp/ffmpeg/out/3gp.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingAVI(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/avi" - var outputPath = "/tmp/ffmpeg/out/avi.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingFLV(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/flv" - var outputPath = "/tmp/ffmpeg/out/flv.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingMKV(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/mkv" - var outputPath = "/tmp/ffmpeg/out/mkv.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingMOV(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/mov" - var outputPath = "/tmp/ffmpeg/out/mov.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingMPEG(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/mpeg" - var outputPath = "/tmp/ffmpeg/out/mpeg.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingOGG(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/ogg" - var outputPath = "/tmp/ffmpeg/out/ogg.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingWAV(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/wav" - var outputPath = "/tmp/ffmpeg/out/wav.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingWEBM(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/webm" - var outputPath = "/tmp/ffmpeg/out/webm.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingWMV(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/wmv" - var outputPath = "/tmp/ffmpeg/out/wmv.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(false) - err = <-done - assert.Nil(t, err) -} - -func TestTranscodingProgress(t *testing.T) { - - var inputPath = "/tmp/ffmpeg/avi" - var outputPath = "/tmp/ffmpeg/out/avi.mp4" - - trans := new(transcoder.Transcoder) - - err := trans.Initialize(inputPath, outputPath) - assert.Nil(t, err) - - done := trans.Run(true) - for val := range trans.Output() { - if &val != nil { - break - } - } - - err = <-done - assert.Nil(t, err) -} - -func TestTranscodePipes(t *testing.T) { - c1 := exec.Command("cat", "/tmp/ffmpeg/mkv") - - trans := new(transcoder.Transcoder) - - err := trans.InitializeEmptyTranscoder() - assert.Nil(t, err) - - w, err := trans.CreateInputPipe() - assert.Nil(t, err) - c1.Stdout = w - - r, err := trans.CreateOutputPipe("mp4") - assert.Nil(t, err) - - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - _, err := ioutil.ReadAll(r) - assert.Nil(t, err) - - r.Close() - wg.Done() - }() - - go func() { - err := c1.Run() - assert.Nil(t, err) - w.Close() - }() - done := trans.Run(false) - err = <-done - assert.Nil(t, err) - - wg.Wait() -} diff --git a/transcoder/progress.go b/transcoder/progress.go new file mode 100644 index 0000000..7ea42d3 --- /dev/null +++ b/transcoder/progress.go @@ -0,0 +1,9 @@ +package transcoder + +type Progress struct { + FramesProcessed string + CurrentTime string + CurrentBitrate string + Progress float64 + Speed string +} diff --git a/transcoder/transcoder.go b/transcoder/transcoder.go index 2940eb9..51c8254 100644 --- a/transcoder/transcoder.go +++ b/transcoder/transcoder.go @@ -3,6 +3,7 @@ package transcoder import ( "bufio" "bytes" + "context" "encoding/json" "errors" "fmt" @@ -12,18 +13,18 @@ import ( "strconv" "strings" - "github.com/xfrr/goffmpeg/ffmpeg" - "github.com/xfrr/goffmpeg/models" - "github.com/xfrr/goffmpeg/utils" + "github.com/xfrr/goffmpeg" + "github.com/xfrr/goffmpeg/media" + "github.com/xfrr/goffmpeg/pkg/duration" ) // Transcoder Main struct type Transcoder struct { - stdErrPipe io.ReadCloser - stdStdinPipe io.WriteCloser - process *exec.Cmd - mediafile *models.Mediafile - configuration ffmpeg.Configuration + stdErrPipe io.ReadCloser + stdStdinPipe io.WriteCloser + process *exec.Cmd + mediafile *media.File + configuration goffmpeg.Configuration whiteListProtocols []string } @@ -43,12 +44,12 @@ func (t *Transcoder) SetProcess(cmd *exec.Cmd) { } // SetMediaFile Set the media file -func (t *Transcoder) SetMediaFile(v *models.Mediafile) { +func (t *Transcoder) SetMediaFile(v *media.File) { t.mediafile = v } // SetConfiguration Set the transcoding configuration -func (t *Transcoder) SetConfiguration(v ffmpeg.Configuration) { +func (t *Transcoder) SetConfiguration(v goffmpeg.Configuration) { t.configuration = v } @@ -62,18 +63,18 @@ func (t Transcoder) Process() *exec.Cmd { } // MediaFile Get the ttranscoding media file. -func (t Transcoder) MediaFile() *models.Mediafile { +func (t Transcoder) MediaFile() *media.File { return t.mediafile } // FFmpegExec Get FFmpeg Bin path func (t Transcoder) FFmpegExec() string { - return t.configuration.FfmpegBin + return t.configuration.FFmpegBinPath() } // FFprobeExec Get FFprobe Bin path func (t Transcoder) FFprobeExec() string { - return t.configuration.FfprobeBin + return t.configuration.FFprobeBinPath() } // GetCommand Build and get command @@ -90,18 +91,18 @@ func (t Transcoder) GetCommand() []string { // InitializeEmptyTranscoder initializes the fields necessary for a blank transcoder func (t *Transcoder) InitializeEmptyTranscoder() error { - var Metadata models.Metadata + var Metadata media.Metadata var err error cfg := t.configuration - if len(cfg.FfmpegBin) == 0 || len(cfg.FfprobeBin) == 0 { - cfg, err = ffmpeg.Configure() + if len(cfg.FFmpegBinPath()) == 0 || len(cfg.FFprobeBinPath()) == 0 { + cfg, err = goffmpeg.Configure(context.Background()) if err != nil { return err } } - // Set new Mediafile - MediaFile := new(models.Mediafile) + // Set new File + MediaFile := new(media.File) MediaFile.SetMetadata(Metadata) // Set transcoder configuration @@ -159,12 +160,12 @@ func (t *Transcoder) CreateOutputPipe(containerFormat string) (*io.PipeReader, e func (t *Transcoder) Initialize(inputPath string, outputPath string) error { var err error var outb, errb bytes.Buffer - var Metadata models.Metadata + var Metadata media.Metadata cfg := t.configuration - if len(cfg.FfmpegBin) == 0 || len(cfg.FfprobeBin) == 0 { - cfg, err = ffmpeg.Configure() + if len(cfg.FFmpegBinPath()) == 0 || len(cfg.FFprobeBinPath()) == 0 { + cfg, err = goffmpeg.Configure(context.Background()) if err != nil { return err } @@ -180,7 +181,7 @@ func (t *Transcoder) Initialize(inputPath string, outputPath string) error { command = append([]string{"-protocol_whitelist", strings.Join(t.whiteListProtocols, ",")}, command...) } - cmd := exec.Command(cfg.FfprobeBin, command...) + cmd := exec.Command(cfg.FFprobeBinPath(), command...) cmd.Stdout = &outb cmd.Stderr = &errb @@ -189,12 +190,12 @@ func (t *Transcoder) Initialize(inputPath string, outputPath string) error { return fmt.Errorf("error executing (%s) | error: %s | message: %s %s", command, err, outb.String(), errb.String()) } - if err = json.Unmarshal([]byte(outb.String()), &Metadata); err != nil { + if err = json.Unmarshal(outb.Bytes(), &Metadata); err != nil { return err } - // Set new Mediafile - MediaFile := new(models.Mediafile) + // Set new File + MediaFile := new(media.File) MediaFile.SetMetadata(Metadata) MediaFile.SetInputPath(inputPath) MediaFile.SetOutputPath(outputPath) @@ -216,7 +217,7 @@ func (t *Transcoder) Run(progress bool) <-chan error { command = append([]string{"-nostats", "-loglevel", "0"}, command...) } - proc := exec.Command(t.configuration.FfmpegBin, command...) + proc := exec.Command(t.configuration.FFmpegBinPath(), command...) if progress { errStream, err := proc.StderrPipe() if err != nil { @@ -256,7 +257,7 @@ func (t *Transcoder) Run(progress bool) <-chan error { go func(err error) { if err != nil { - done <- fmt.Errorf("Failed Start FFMPEG (%s) with %s, message %s %s", command, err, outb.String(), errb.String()) + done <- fmt.Errorf("failed start ffmpeg (%s) with %s, message %s %s", command, err, outb.String(), errb.String()) close(done) return } @@ -266,7 +267,7 @@ func (t *Transcoder) Run(progress bool) <-chan error { go t.closePipes() if err != nil { - err = fmt.Errorf("Failed Finish FFMPEG (%s) with %s message %s %s", command, err, outb.String(), errb.String()) + err = fmt.Errorf("failed finish ffmpeg (%s) with %s message %s %s", command, err, outb.String(), errb.String()) } done <- err close(done) @@ -288,13 +289,13 @@ func (t *Transcoder) Stop() error { } // Output Returns the transcoding progress channel -func (t Transcoder) Output() <-chan models.Progress { - out := make(chan models.Progress) +func (t Transcoder) Output() <-chan Progress { + out := make(chan Progress) go func() { defer close(out) if t.stdErrPipe == nil { - out <- models.Progress{} + out <- Progress{} return } @@ -328,7 +329,7 @@ func (t Transcoder) Output() <-chan models.Progress { scanner.Buffer(buf, bufio.MaxScanTokenSize) for scanner.Scan() { - Progress := new(models.Progress) + Progress := new(Progress) line := scanner.Text() if strings.Contains(line, "frame=") && strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") { var re = regexp.MustCompile(`=\s+`) @@ -365,7 +366,7 @@ func (t Transcoder) Output() <-chan models.Progress { } } - timesec := utils.DurToSec(currentTime) + timesec := duration.DurToSec(currentTime) dursec, _ := strconv.ParseFloat(t.MediaFile().Metadata().Format.Duration, 64) //live stream check if dursec != 0 { diff --git a/transcoder/transcored_test.go b/transcoder/transcored_test.go index 694e2a9..d224272 100644 --- a/transcoder/transcored_test.go +++ b/transcoder/transcored_test.go @@ -1,9 +1,10 @@ package transcoder import ( - "github.com/stretchr/testify/require" - "github.com/xfrr/goffmpeg/models" "testing" + + "github.com/stretchr/testify/require" + "github.com/xfrr/goffmpeg/media" ) func TestTranscoder(t *testing.T) { @@ -11,7 +12,7 @@ func TestTranscoder(t *testing.T) { t.Run("Should not set -protocol_whitelist option if it isn't present", func(t *testing.T) { ts := Transcoder{} - ts.SetMediaFile(&models.Mediafile{}) + ts.SetMediaFile(&media.File{}) require.NotEqual(t, ts.GetCommand()[0:2], []string{"-protocol_whitelist", "file,http,https,tcp,tls"}) require.NotContains(t, ts.GetCommand(), "protocol_whitelist") }) @@ -19,8 +20,8 @@ func TestTranscoder(t *testing.T) { t.Run("Should set -protocol_whitelist option if it's present", func(t *testing.T) { ts := Transcoder{} - ts.SetMediaFile(&models.Mediafile{}) - ts.SetWhiteListProtocols([]string{"file","http","https","tcp","tls"}) + ts.SetMediaFile(&media.File{}) + ts.SetWhiteListProtocols([]string{"file", "http", "https", "tcp", "tls"}) require.Equal(t, ts.GetCommand()[0:2], []string{"-protocol_whitelist", "file,http,https,tcp,tls"}) }) diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index 8f5f709..0000000 --- a/utils/utils.go +++ /dev/null @@ -1,94 +0,0 @@ -package utils - -import ( - "bytes" - "fmt" - "os/exec" - "runtime" - "strconv" - "strings" - - "github.com/xfrr/goffmpeg/models" -) - -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) - min, _ := strconv.ParseFloat(durAry[1], 64) - secs += min * (60) - second, _ := strconv.ParseFloat(durAry[2], 64) - secs += second - return secs -} - -func GetFFmpegExec() []string { - var platform = runtime.GOOS - var command = []string{"", "ffmpeg"} - - switch platform { - case "windows": - command[0] = "where" - break - default: - command[0] = "which" - break - } - - return command -} - -func GetFFprobeExec() []string { - var platform = runtime.GOOS - var command = []string{"", "ffprobe"} - - switch platform { - case "windows": - command[0] = "where" - break - default: - command[0] = "which" - break - } - return command -} - -func CheckFileType(streams []models.Streams) string { - for i := 0; i < len(streams); i++ { - st := streams[i] - if st.CodecType == "video" { - return "video" - } - } - - return "audio" -} - -func LineSeparator() string { - switch runtime.GOOS { - case "windows": - return "\r\n" - default: - return "\n" - } -} - -// TestCmd ... -func TestCmd(command string, args string) (bytes.Buffer, error) { - var out bytes.Buffer - - cmd := exec.Command(command, args) - - cmd.Stdout = &out - - err := cmd.Run() - if err != nil { - return out, fmt.Errorf("%s: %s", cmd.String(), err) - } - - return out, nil -}