mirror of
https://github.com/xfrr/goffmpeg.git
synced 2025-10-06 16:36:55 +08:00
cleanup & fix ci (#82)
* cleanup readme & examples * upgrade go version * add makefile with basic commands * add e2e test * add gha * update .gitignore
This commit is contained in:
@@ -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=. ./...
|
|
25
.github/workflows/build_and_test.yml
vendored
Normal file
25
.github/workflows/build_and_test.yml
vendored
Normal file
@@ -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 ./...
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,6 +20,10 @@ main-test.go
|
|||||||
goffmpeg
|
goffmpeg
|
||||||
coverage.out
|
coverage.out
|
||||||
|
|
||||||
|
# IDE
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# test results
|
||||||
|
test_results
|
||||||
|
7
Makefile
Normal file
7
Makefile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v ./... -coverprofile=coverage.out -covermode=atomic -coverpkg=./...
|
||||||
|
|
||||||
|
coverage-html:
|
||||||
|
go tool cover -html=coverage.out
|
278
README.md
278
README.md
@@ -1,273 +1,41 @@
|
|||||||
# Goffmpeg
|
# Goffmpeg
|
||||||
[](https://www.codacy.com/app/francisco.romero/goffmpeg?utm_source=github.com&utm_medium=referral&utm_content=xfrr/goffmpeg&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/app/francisco.romero/goffmpeg?utm_source=github.com&utm_medium=referral&utm_content=xfrr/goffmpeg&utm_campaign=Badge_Grade)
|
||||||
|
[](https://dl.circleci.com/status-badge/redirect/gh/xfrr/goffmpeg/tree/master)
|
||||||
|
[](https://goreportcard.com/report/github.com/xfrr/goffmpeg)
|
||||||
|
[](https://godoc.org/github.com/xfrr/goffmpeg)
|
||||||
|
[](
|
||||||
|
|
||||||
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
|
> 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/)
|
- [FFmpeg](https://www.ffmpeg.org/)
|
||||||
- [FFProbe](https://www.ffmpeg.org/ffprobe.html)
|
- [FFProbe](https://www.ffmpeg.org/ffprobe.html)
|
||||||
|
|
||||||
# Supported platforms
|
## Supported platforms
|
||||||
|
|
||||||
- Linux
|
- Linux
|
||||||
- OS X
|
- OS X
|
||||||
- Windows
|
- Windows
|
||||||
|
|
||||||
# Getting started
|
## Installation
|
||||||
## How to transcode a media file
|
Install the package with the following command:
|
||||||
```shell
|
```shell
|
||||||
go get github.com/xfrr/goffmpeg
|
go get github.com/xfrr/goffmpeg
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
## Usage
|
||||||
package main
|
Check the [examples](./examples)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
67
config.go
Normal file
67
config.go
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
18
config_test.go
Normal file
18
config_test.go
Normal file
@@ -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())
|
||||||
|
})
|
||||||
|
}
|
BIN
e2e/fixtures/input.3gp
Normal file
BIN
e2e/fixtures/input.3gp
Normal file
Binary file not shown.
104
e2e/transcoding_test.go
Executable file
104
e2e/transcoding_test.go
Executable file
@@ -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)
|
||||||
|
}
|
BIN
examples/fixtures/input.3gp
Normal file
BIN
examples/fixtures/input.3gp
Normal file
Binary file not shown.
34
examples/hls/main.go
Normal file
34
examples/hls/main.go
Normal file
@@ -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)
|
||||||
|
}
|
31
examples/ultrafast-preset/main.go
Normal file
31
examples/ultrafast-preset/main.go
Normal file
@@ -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)
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
|
8
go.mod
8
go.mod
@@ -1,5 +1,11 @@
|
|||||||
module github.com/xfrr/goffmpeg
|
module github.com/xfrr/goffmpeg
|
||||||
|
|
||||||
go 1.14
|
go 1.20
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.5.1
|
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
|
||||||
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
18
media/format.go
Normal file
18
media/format.go
Normal file
@@ -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"`
|
||||||
|
}
|
6
media/metadata.go
Normal file
6
media/metadata.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package media
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Streams []Streams `json:"streams"`
|
||||||
|
Format Format `json:"format"`
|
||||||
|
}
|
@@ -1,14 +1,4 @@
|
|||||||
package models
|
package media
|
||||||
|
|
||||||
type Ffmpeg struct {
|
|
||||||
FfmpegBinPath string
|
|
||||||
FfprobeBinPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Streams []Streams `json:"streams"`
|
|
||||||
Format Format `json:"format"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Streams struct {
|
type Streams struct {
|
||||||
Index int
|
Index int
|
||||||
@@ -54,28 +44,3 @@ type Disposition struct {
|
|||||||
VisualImpaired int `json:"visual_impaired"`
|
VisualImpaired int `json:"visual_impaired"`
|
||||||
CleanEffects int `json:"clean_effects"`
|
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"`
|
|
||||||
}
|
|
@@ -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())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
35
pkg/cmd/exec.go
Normal file
35
pkg/cmd/exec.go
Normal file
@@ -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
|
||||||
|
}
|
16
pkg/cmd/find.go
Normal file
16
pkg/cmd/find.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
var (
|
||||||
|
platform = runtime.GOOS
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFindCommand() string {
|
||||||
|
switch platform {
|
||||||
|
case "windows":
|
||||||
|
return "where"
|
||||||
|
default:
|
||||||
|
return "which"
|
||||||
|
}
|
||||||
|
}
|
21
pkg/duration/duration.go
Normal file
21
pkg/duration/duration.go
Normal file
@@ -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
|
||||||
|
}
|
@@ -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()
|
|
||||||
}
|
|
9
transcoder/progress.go
Normal file
9
transcoder/progress.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package transcoder
|
||||||
|
|
||||||
|
type Progress struct {
|
||||||
|
FramesProcessed string
|
||||||
|
CurrentTime string
|
||||||
|
CurrentBitrate string
|
||||||
|
Progress float64
|
||||||
|
Speed string
|
||||||
|
}
|
@@ -3,6 +3,7 @@ package transcoder
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,18 +13,18 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xfrr/goffmpeg/ffmpeg"
|
"github.com/xfrr/goffmpeg"
|
||||||
"github.com/xfrr/goffmpeg/models"
|
"github.com/xfrr/goffmpeg/media"
|
||||||
"github.com/xfrr/goffmpeg/utils"
|
"github.com/xfrr/goffmpeg/pkg/duration"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transcoder Main struct
|
// Transcoder Main struct
|
||||||
type Transcoder struct {
|
type Transcoder struct {
|
||||||
stdErrPipe io.ReadCloser
|
stdErrPipe io.ReadCloser
|
||||||
stdStdinPipe io.WriteCloser
|
stdStdinPipe io.WriteCloser
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
mediafile *models.Mediafile
|
mediafile *media.File
|
||||||
configuration ffmpeg.Configuration
|
configuration goffmpeg.Configuration
|
||||||
whiteListProtocols []string
|
whiteListProtocols []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,12 +44,12 @@ func (t *Transcoder) SetProcess(cmd *exec.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetMediaFile Set the media file
|
// SetMediaFile Set the media file
|
||||||
func (t *Transcoder) SetMediaFile(v *models.Mediafile) {
|
func (t *Transcoder) SetMediaFile(v *media.File) {
|
||||||
t.mediafile = v
|
t.mediafile = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConfiguration Set the transcoding configuration
|
// SetConfiguration Set the transcoding configuration
|
||||||
func (t *Transcoder) SetConfiguration(v ffmpeg.Configuration) {
|
func (t *Transcoder) SetConfiguration(v goffmpeg.Configuration) {
|
||||||
t.configuration = v
|
t.configuration = v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,18 +63,18 @@ func (t Transcoder) Process() *exec.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MediaFile Get the ttranscoding media file.
|
// MediaFile Get the ttranscoding media file.
|
||||||
func (t Transcoder) MediaFile() *models.Mediafile {
|
func (t Transcoder) MediaFile() *media.File {
|
||||||
return t.mediafile
|
return t.mediafile
|
||||||
}
|
}
|
||||||
|
|
||||||
// FFmpegExec Get FFmpeg Bin path
|
// FFmpegExec Get FFmpeg Bin path
|
||||||
func (t Transcoder) FFmpegExec() string {
|
func (t Transcoder) FFmpegExec() string {
|
||||||
return t.configuration.FfmpegBin
|
return t.configuration.FFmpegBinPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FFprobeExec Get FFprobe Bin path
|
// FFprobeExec Get FFprobe Bin path
|
||||||
func (t Transcoder) FFprobeExec() string {
|
func (t Transcoder) FFprobeExec() string {
|
||||||
return t.configuration.FfprobeBin
|
return t.configuration.FFprobeBinPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommand Build and get command
|
// GetCommand Build and get command
|
||||||
@@ -90,18 +91,18 @@ func (t Transcoder) GetCommand() []string {
|
|||||||
|
|
||||||
// InitializeEmptyTranscoder initializes the fields necessary for a blank transcoder
|
// InitializeEmptyTranscoder initializes the fields necessary for a blank transcoder
|
||||||
func (t *Transcoder) InitializeEmptyTranscoder() error {
|
func (t *Transcoder) InitializeEmptyTranscoder() error {
|
||||||
var Metadata models.Metadata
|
var Metadata media.Metadata
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
cfg := t.configuration
|
cfg := t.configuration
|
||||||
if len(cfg.FfmpegBin) == 0 || len(cfg.FfprobeBin) == 0 {
|
if len(cfg.FFmpegBinPath()) == 0 || len(cfg.FFprobeBinPath()) == 0 {
|
||||||
cfg, err = ffmpeg.Configure()
|
cfg, err = goffmpeg.Configure(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set new Mediafile
|
// Set new File
|
||||||
MediaFile := new(models.Mediafile)
|
MediaFile := new(media.File)
|
||||||
MediaFile.SetMetadata(Metadata)
|
MediaFile.SetMetadata(Metadata)
|
||||||
|
|
||||||
// Set transcoder configuration
|
// 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 {
|
func (t *Transcoder) Initialize(inputPath string, outputPath string) error {
|
||||||
var err error
|
var err error
|
||||||
var outb, errb bytes.Buffer
|
var outb, errb bytes.Buffer
|
||||||
var Metadata models.Metadata
|
var Metadata media.Metadata
|
||||||
|
|
||||||
cfg := t.configuration
|
cfg := t.configuration
|
||||||
|
|
||||||
if len(cfg.FfmpegBin) == 0 || len(cfg.FfprobeBin) == 0 {
|
if len(cfg.FFmpegBinPath()) == 0 || len(cfg.FFprobeBinPath()) == 0 {
|
||||||
cfg, err = ffmpeg.Configure()
|
cfg, err = goffmpeg.Configure(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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...)
|
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.Stdout = &outb
|
||||||
cmd.Stderr = &errb
|
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())
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new Mediafile
|
// Set new File
|
||||||
MediaFile := new(models.Mediafile)
|
MediaFile := new(media.File)
|
||||||
MediaFile.SetMetadata(Metadata)
|
MediaFile.SetMetadata(Metadata)
|
||||||
MediaFile.SetInputPath(inputPath)
|
MediaFile.SetInputPath(inputPath)
|
||||||
MediaFile.SetOutputPath(outputPath)
|
MediaFile.SetOutputPath(outputPath)
|
||||||
@@ -216,7 +217,7 @@ func (t *Transcoder) Run(progress bool) <-chan error {
|
|||||||
command = append([]string{"-nostats", "-loglevel", "0"}, command...)
|
command = append([]string{"-nostats", "-loglevel", "0"}, command...)
|
||||||
}
|
}
|
||||||
|
|
||||||
proc := exec.Command(t.configuration.FfmpegBin, command...)
|
proc := exec.Command(t.configuration.FFmpegBinPath(), command...)
|
||||||
if progress {
|
if progress {
|
||||||
errStream, err := proc.StderrPipe()
|
errStream, err := proc.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -256,7 +257,7 @@ func (t *Transcoder) Run(progress bool) <-chan error {
|
|||||||
|
|
||||||
go func(err error) {
|
go func(err error) {
|
||||||
if err != nil {
|
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)
|
close(done)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -266,7 +267,7 @@ func (t *Transcoder) Run(progress bool) <-chan error {
|
|||||||
go t.closePipes()
|
go t.closePipes()
|
||||||
|
|
||||||
if err != nil {
|
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
|
done <- err
|
||||||
close(done)
|
close(done)
|
||||||
@@ -288,13 +289,13 @@ func (t *Transcoder) Stop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output Returns the transcoding progress channel
|
// Output Returns the transcoding progress channel
|
||||||
func (t Transcoder) Output() <-chan models.Progress {
|
func (t Transcoder) Output() <-chan Progress {
|
||||||
out := make(chan models.Progress)
|
out := make(chan Progress)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
if t.stdErrPipe == nil {
|
if t.stdErrPipe == nil {
|
||||||
out <- models.Progress{}
|
out <- Progress{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +329,7 @@ func (t Transcoder) Output() <-chan models.Progress {
|
|||||||
scanner.Buffer(buf, bufio.MaxScanTokenSize)
|
scanner.Buffer(buf, bufio.MaxScanTokenSize)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
Progress := new(models.Progress)
|
Progress := new(Progress)
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if strings.Contains(line, "frame=") && strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
|
if strings.Contains(line, "frame=") && strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
|
||||||
var re = regexp.MustCompile(`=\s+`)
|
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)
|
dursec, _ := strconv.ParseFloat(t.MediaFile().Metadata().Format.Duration, 64)
|
||||||
//live stream check
|
//live stream check
|
||||||
if dursec != 0 {
|
if dursec != 0 {
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
package transcoder
|
package transcoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/xfrr/goffmpeg/models"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/xfrr/goffmpeg/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTranscoder(t *testing.T) {
|
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) {
|
t.Run("Should not set -protocol_whitelist option if it isn't present", func(t *testing.T) {
|
||||||
ts := Transcoder{}
|
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.NotEqual(t, ts.GetCommand()[0:2], []string{"-protocol_whitelist", "file,http,https,tcp,tls"})
|
||||||
require.NotContains(t, ts.GetCommand(), "protocol_whitelist")
|
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) {
|
t.Run("Should set -protocol_whitelist option if it's present", func(t *testing.T) {
|
||||||
ts := Transcoder{}
|
ts := Transcoder{}
|
||||||
|
|
||||||
ts.SetMediaFile(&models.Mediafile{})
|
ts.SetMediaFile(&media.File{})
|
||||||
ts.SetWhiteListProtocols([]string{"file","http","https","tcp","tls"})
|
ts.SetWhiteListProtocols([]string{"file", "http", "https", "tcp", "tls"})
|
||||||
|
|
||||||
require.Equal(t, ts.GetCommand()[0:2], []string{"-protocol_whitelist", "file,http,https,tcp,tls"})
|
require.Equal(t, ts.GetCommand()[0:2], []string{"-protocol_whitelist", "file,http,https,tcp,tls"})
|
||||||
})
|
})
|
||||||
|
@@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user