Merge branch 'master' into AES-Encryption

This commit is contained in:
Fran
2020-03-30 18:27:53 +02:00
committed by GitHub
4 changed files with 335 additions and 78 deletions
+33 -6
View File
@@ -77,6 +77,7 @@ func main() {
```
## 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() {
@@ -87,19 +88,43 @@ func main() {
err := trans.InitializeEmptyTranscoder()
// Handle error...
// Set the output path on the transcoder
trans.SetOutputPath("/tmp/data/out/output.mp4")
// Create a command such that its output should be passed as stdin to ffmpeg
cmd := exec.Command("cat", "/path/to/file")
// Set a command such that its output should be passed as stdin to ffmpeg
err = trans.CreateInputPipe(exec.Command("cat", "/tmp/data/testmpeg"))
// Handle error...
// 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(true)
done := trans.Run(false)
// This channel is used to wait for the transcoding process to end
err = <-done
// Handle error...
wg.Wait()
}
```
@@ -156,6 +181,8 @@ SetRtmpLive
SetHlsListSize
SetHlsSegmentDuration
SetHlsPlaylistType
SetHlsMasterPlaylistName
SetHlsSegmentFilename
SetHttpMethod
SetHttpKeepAlive
SetOutputPath
+198 -25
View File
@@ -2,7 +2,7 @@ package models
import (
"fmt"
"os/exec"
"io"
"reflect"
"strconv"
"strings"
@@ -11,7 +11,7 @@ import (
type Mediafile struct {
aspect string
resolution string
videoBitRate int
videoBitRate string
videoBitRateTolerance int
videoMaxBitRate int
videoMinBitrate int
@@ -27,6 +27,7 @@ type Mediafile struct {
audioChannels int
audioVariableBitrate bool
bufferSize int
threadset bool
threads int
preset string
tune string
@@ -36,13 +37,20 @@ type Mediafile struct {
duration string
durationInput string
seekTime string
quality int
qscale uint32
crf uint32
strict int
muxDelay string
seekUsingTsInput bool
seekTimeInput string
inputPath string
inputPipeCommand *exec.Cmd
inputPipe bool
inputPipeReader *io.PipeReader
inputPipeWriter *io.PipeWriter
outputPipe bool
outputPipeReader *io.PipeReader
outputPipeWriter *io.PipeWriter
movFlags string
hideBanner bool
outputPath string
outputFormat string
@@ -53,8 +61,11 @@ type Mediafile struct {
hlsPlaylistType string
hlsListSize int
hlsSegmentDuration int
hlsMasterPlaylistName string
hlsSegmentFilename string
httpMethod string
httpKeepAlive bool
hwaccel string
streamIds map[int]string
metadata Metadata
videoFilter string
@@ -62,6 +73,8 @@ type Mediafile struct {
skipVideo bool
skipAudio bool
encryptionKey string
movflags string
bframe int
pixFmt string
}
@@ -87,7 +100,7 @@ func (m *Mediafile) SetResolution(v string) {
m.resolution = v
}
func (m *Mediafile) SetVideoBitRate(v int) {
func (m *Mediafile) SetVideoBitRate(v string) {
m.videoBitRate = v
}
@@ -156,6 +169,7 @@ func (m *Mediafile) SetBufferSize(v int) {
}
func (m *Mediafile) SetThreads(v int) {
m.threadset = true
m.threads = v
}
@@ -191,8 +205,13 @@ func (m *Mediafile) SetSeekTimeInput(v string) {
m.seekTimeInput = v
}
func (m *Mediafile) SetQuality(v int) {
m.quality = v
// Q Scale must be integer between 1 to 31 - https://trac.ffmpeg.org/wiki/Encode/MPEG-4
func (m *Mediafile) SetQScale(v uint32) {
m.qscale = v
}
func (m *Mediafile) SetCRF(v uint32) {
m.crf = v
}
func (m *Mediafile) SetStrict(v int) {
@@ -211,8 +230,32 @@ func (m *Mediafile) SetInputPath(val string) {
m.inputPath = val
}
func (m *Mediafile) SetInputPipeCommand(command *exec.Cmd) {
m.inputPipeCommand = command
func (m *Mediafile) SetInputPipe(val bool) {
m.inputPipe = val
}
func (m *Mediafile) SetInputPipeReader(r *io.PipeReader) {
m.inputPipeReader = r
}
func (m *Mediafile) SetInputPipeWriter(w *io.PipeWriter) {
m.inputPipeWriter = w
}
func (m *Mediafile) SetOutputPipe(val bool) {
m.outputPipe = val
}
func (m *Mediafile) SetOutputPipeReader(r *io.PipeReader) {
m.outputPipeReader = r
}
func (m *Mediafile) SetOutputPipeWriter(w *io.PipeWriter) {
m.outputPipeWriter = w
}
func (m *Mediafile) SetMovFlags(val string) {
m.movFlags = val
}
func (m *Mediafile) SetHideBanner(val bool) {
@@ -251,6 +294,14 @@ func (m *Mediafile) SetHlsPlaylistType(val string) {
m.hlsPlaylistType = val
}
func (m *Mediafile) SetHlsMasterPlaylistName(val string) {
m.hlsMasterPlaylistName = val
}
func (m *Mediafile) SetHlsSegmentFilename(val string) {
m.hlsSegmentFilename = val
}
func (m *Mediafile) SetHttpMethod(val string) {
m.httpMethod = val
}
@@ -259,6 +310,10 @@ func (m *Mediafile) SetHttpKeepAlive(val bool) {
m.httpKeepAlive = val
}
func (m *Mediafile) SetHardwareAcceleration(val string) {
m.hwaccel = val
}
func (m *Mediafile) SetInputInitialOffset(val string) {
m.inputInitialOffset = val
}
@@ -279,6 +334,14 @@ func (m *Mediafile) SetMetadata(v Metadata) {
m.metadata = v
}
func (m *Mediafile) SetMovFlags(v string) {
m.movflags = v
}
func (m *Mediafile) SetBframe(v int) {
m.bframe = v
}
/*** GETTERS ***/
// Deprecated: Use VideoFilter instead.
@@ -302,7 +365,7 @@ func (m *Mediafile) Resolution() string {
return m.resolution
}
func (m *Mediafile) VideoBitrate() int {
func (m *Mediafile) VideoBitrate() string {
return m.videoBitRate
}
@@ -406,8 +469,12 @@ func (m *Mediafile) SeekTimeInput() string {
return m.seekTimeInput
}
func (m *Mediafile) Quality() int {
return m.quality
func (m *Mediafile) QScale() uint32 {
return m.qscale
}
func (m *Mediafile) CRF() uint32 {
return m.crf
}
func (m *Mediafile) Strict() int {
@@ -430,8 +497,32 @@ func (m *Mediafile) InputPath() string {
return m.inputPath
}
func (m *Mediafile) InputPipeCommand() *exec.Cmd {
return m.inputPipeCommand
func (m *Mediafile) InputPipe() bool {
return m.inputPipe
}
func (m *Mediafile) InputPipeReader() *io.PipeReader {
return m.inputPipeReader
}
func (m *Mediafile) InputPipeWriter() *io.PipeWriter {
return m.inputPipeWriter
}
func (m *Mediafile) OutputPipe() bool {
return m.outputPipe
}
func (m *Mediafile) OutputPipeReader() *io.PipeReader {
return m.outputPipeReader
}
func (m *Mediafile) OutputPipeWriter() *io.PipeWriter {
return m.outputPipeWriter
}
func (m *Mediafile) MovFlags() string {
return m.movFlags
}
func (m *Mediafile) HideBanner() bool {
@@ -462,6 +553,14 @@ func (m *Mediafile) HlsSegmentDuration() int {
return m.hlsSegmentDuration
}
func (m *Mediafile) HlsMasterPlaylistName() string {
return m.hlsMasterPlaylistName
}
func (m *Mediafile) HlsSegmentFilename() string {
return m.hlsSegmentFilename
}
func (m *Mediafile) HlsPlaylistType() string {
return m.hlsPlaylistType
}
@@ -478,6 +577,10 @@ func (m *Mediafile) HttpKeepAlive() bool {
return m.httpKeepAlive
}
func (m *Mediafile) HardwareAcceleration() string {
return m.hwaccel
}
func (m *Mediafile) StreamIds() map[int]string {
return m.streamIds
}
@@ -513,8 +616,9 @@ func (m *Mediafile) ToStrCommand() []string {
"DurationInput",
"RtmpLive",
"InputInitialOffset",
"HardwareAcceleration",
"InputPath",
"InputPipeCommand",
"InputPipe",
"HideBanner",
"Aspect",
@@ -534,7 +638,8 @@ func (m *Mediafile) ToStrCommand() []string {
"AudioChannels",
"AudioProfile",
"SkipAudio",
"Quality",
"CRF",
"QScale",
"Strict",
"BufferSize",
"MuxDelay",
@@ -548,16 +653,22 @@ func (m *Mediafile) ToStrCommand() []string {
"Duration",
"CopyTs",
"StreamIds",
"MovFlags",
"OutputFormat",
"OutputPipe",
"HlsListSize",
"HlsSegmentDuration",
"HlsPlaylistType",
"HlsMasterPlaylistName",
"HlsSegmentFilename",
"AudioFilter",
"VideoFilter",
"HttpMethod",
"HttpKeepAlive",
"EncryptionKey",
"OutputPath",
"Bframe",
"MovFlags",
}
for _, name := range opts {
opt := reflect.ValueOf(m).MethodByName(fmt.Sprintf("Obtain%s", name))
@@ -604,6 +715,13 @@ func (m *Mediafile) ObtainAspect() []string {
return nil
}
func (m *Mediafile) ObtainHardwareAcceleration() []string {
if m.hwaccel != "" {
return []string{"-hwaccel", m.hwaccel}
}
return nil
}
func (m *Mediafile) ObtainInputPath() []string {
if m.inputPath != "" {
return []string{"-i", m.inputPath}
@@ -611,13 +729,27 @@ func (m *Mediafile) ObtainInputPath() []string {
return nil
}
func (m *Mediafile) ObtainInputPipeCommand() []string {
if m.inputPipeCommand != nil {
func (m *Mediafile) ObtainInputPipe() []string {
if m.inputPipe {
return []string{"-i", "pipe:0"}
}
return nil
}
func (m *Mediafile) ObtainOutputPipe() []string {
if m.outputPipe {
return []string{"pipe:1"}
}
return nil
}
func (m *Mediafile) ObtainMovFlags() []string {
if m.movFlags != "" {
return []string{"-movflags", m.movFlags}
}
return nil
}
func (m *Mediafile) ObtainHideBanner() []string {
if m.hideBanner {
return []string{"-hide_banner"}
@@ -633,7 +765,10 @@ func (m *Mediafile) ObtainNativeFramerateInput() []string {
}
func (m *Mediafile) ObtainOutputPath() []string {
return []string{m.outputPath}
if m.outputPath != "" {
return []string{m.outputPath}
}
return nil
}
func (m *Mediafile) ObtainVideoCodec() []string {
@@ -672,8 +807,8 @@ func (m *Mediafile) ObtainResolution() []string {
}
func (m *Mediafile) ObtainVideoBitRate() []string {
if m.videoBitRate != 0 {
return []string{"-b:v", fmt.Sprintf("%d", m.videoBitRate)}
if m.videoBitRate != "" {
return []string{"-b:v", m.videoBitRate}
}
return nil
}
@@ -734,7 +869,7 @@ func (m *Mediafile) ObtainVideoBitRateTolerance() []string {
}
func (m *Mediafile) ObtainThreads() []string {
if m.threads != 0 {
if m.threadset {
return []string{"-threads", fmt.Sprintf("%d", m.threads)}
}
return nil
@@ -796,9 +931,16 @@ func (m *Mediafile) ObtainTune() []string {
return nil
}
func (m *Mediafile) ObtainQuality() []string {
if m.quality != 0 {
return []string{"-crf", fmt.Sprintf("%d", m.quality)}
func (m *Mediafile) ObtainCRF() []string {
if m.crf != 0 {
return []string{"-crf", fmt.Sprintf("%d", m.crf)}
}
return nil
}
func (m *Mediafile) ObtainQScale() []string {
if m.qscale != 0 {
return []string{"-qscale", fmt.Sprintf("%d", m.qscale)}
}
return nil
}
@@ -888,6 +1030,22 @@ func (m *Mediafile) ObtainHlsSegmentDuration() []string {
}
}
func (m *Mediafile) ObtainHlsMasterPlaylistName() []string {
if m.hlsMasterPlaylistName != "" {
return []string{"-master_pl_name", fmt.Sprintf("%s", m.hlsMasterPlaylistName)}
} else {
return nil
}
}
func (m *Mediafile) ObtainHlsSegmentFilename() []string {
if m.hlsSegmentFilename != "" {
return []string{"-hls_segment_filename", fmt.Sprintf("%s", m.hlsSegmentFilename)}
} else {
return nil
}
}
func (m *Mediafile) ObtainHttpMethod() []string {
if m.httpMethod != "" {
return []string{"-method", m.httpMethod}
@@ -938,6 +1096,21 @@ func (m *Mediafile) ObtainStreamIds() []string {
}
return nil
}
func (m *Mediafile) ObtainEncryptionKey() []string {
return []string{"-hls_key_info_file", m.encryptionKey}
}
func (m *Mediafile) ObtainBframe() []string {
if m.bframe != 0 {
return []string{"-bf", fmt.Sprintf("%d", m.bframe)}
}
return nil
}
func (m *Mediafile) ObtainMovFlags() []string {
if m.movflags != "" {
return []string{"-movflags", m.movflags}
}
return nil
}
+41 -23
View File
@@ -1,9 +1,13 @@
package test
import (
"io/ioutil"
"os/exec"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/xfrr/goffmpeg/transcoder"
)
@@ -230,29 +234,6 @@ func TestTranscodingWMV(t *testing.T) {
}
}
func TestTranscodingInputPipe(t *testing.T) {
// Tests pipe with input mpeg, output mp4 using cat command for pipe-in
var outputPath = "/data/out/testmp4.mp4"
trans := new(transcoder.Transcoder)
err := trans.InitializeEmptyTranscoder()
trans.SetOutputPath(outputPath)
trans.CreateInputPipe(exec.Command("cat", "/data/testmpeg"))
if err != nil {
t.Error(err)
return
}
done := trans.Run(false)
err = <-done
if err != nil {
t.Error(err)
return
}
}
func TestTranscodingProgress(t *testing.T) {
var inputPath = "/data/testavi"
@@ -279,3 +260,40 @@ func TestTranscodingProgress(t *testing.T) {
return
}
}
func TestTranscodePipes(t *testing.T) {
c1 := exec.Command("cat", "/tmp/data/testmkv")
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()
}
+63 -24
View File
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
@@ -103,31 +102,53 @@ func (t *Transcoder) InitializeEmptyTranscoder() error {
// SetInputPath sets the input path for transcoding
func (t *Transcoder) SetInputPath(inputPath string) error {
if t.mediafile.InputPipeCommand() != nil {
if t.mediafile.InputPipe() {
return errors.New("cannot set an input path when an input pipe command has been set")
}
t.mediafile.SetInputPath(inputPath)
return nil
}
// CreateInputPipe creates an input pipe for the transcoding process
func (t *Transcoder) CreateInputPipe(cmd *exec.Cmd) error {
if t.mediafile.InputPath() != "" {
return errors.New("cannot set an input pipe when an input path exists")
// SetOutputPath sets the output path for transcoding
func (t *Transcoder) SetOutputPath(inputPath string) error {
if t.mediafile.OutputPipe() {
return errors.New("cannot set an input path when an input pipe command has been set")
}
t.mediafile.SetInputPipeCommand(cmd)
t.mediafile.SetOutputPath(inputPath)
return nil
}
// SetOutputPath sets the output path for transcoding
func (t *Transcoder) SetOutputPath(outputPath string) {
t.mediafile.SetOutputPath(outputPath)
// CreateInputPipe creates an input pipe for the transcoding process
func (t *Transcoder) CreateInputPipe() (*io.PipeWriter, error) {
if t.mediafile.InputPath() != "" {
return nil, errors.New("cannot set an input pipe when an input path exists")
}
inputPipeReader, inputPipeWriter := io.Pipe()
t.mediafile.SetInputPipe(true)
t.mediafile.SetInputPipeReader(inputPipeReader)
t.mediafile.SetInputPipeWriter(inputPipeWriter)
return inputPipeWriter, nil
}
// CreateOutputPipe creates an output pipe for the transcoding process
func (t *Transcoder) CreateOutputPipe(containerFormat string) (*io.PipeReader, error) {
if t.mediafile.OutputPath() != "" {
return nil, errors.New("cannot set an output pipe when an output path exists")
}
t.mediafile.SetOutputFormat(containerFormat)
t.mediafile.SetMovFlags("frag_keyframe")
outputPipeReader, outputPipeWriter := io.Pipe()
t.mediafile.SetOutputPipe(true)
t.mediafile.SetOutputPipeReader(outputPipeReader)
t.mediafile.SetOutputPipeWriter(outputPipeWriter)
return outputPipeReader, nil
}
// Initialize Init the transcoding process
func (t *Transcoder) Initialize(inputPath string, outputPath string) error {
var err error
var out bytes.Buffer
var outb, errb bytes.Buffer
var Metadata models.Metadata
cfg := t.configuration
@@ -146,14 +167,15 @@ func (t *Transcoder) Initialize(inputPath string, outputPath string) error {
command := []string{"-i", inputPath, "-print_format", "json", "-show_format", "-show_streams", "-show_error"}
cmd := exec.Command(cfg.FfprobeBin, command...)
cmd.Stdout = &out
cmd.Stdout = &outb
cmd.Stderr = &errb
err = cmd.Run()
if err != nil {
return fmt.Errorf("error executing (%s) | error: %s", command, err)
return fmt.Errorf("error executing (%s) | error: %s | message: %s %s", command, err, outb.String(), errb.String())
}
if err = json.Unmarshal([]byte(out.String()), &Metadata); err != nil {
if err = json.Unmarshal([]byte(outb.String()), &Metadata); err != nil {
return err
}
@@ -190,6 +212,7 @@ func (t *Transcoder) Run(progress bool) <-chan error {
}
}
// Set the stdinPipe in case we need to stop the transcoding
stdin, err := proc.StdinPipe()
if nil != err {
fmt.Println("Stdin not available: " + err.Error())
@@ -197,23 +220,29 @@ func (t *Transcoder) Run(progress bool) <-chan error {
t.stdStdinPipe = stdin
out := &bytes.Buffer{}
// If the user has requested progress, we send it to them on a Buffer
var outb, errb bytes.Buffer
if progress {
proc.Stdout = out
proc.Stdout = &outb
proc.Stderr = &errb
}
// If an input pipe has been set, we get the command and set it as stdin for the transcoding
if t.mediafile.InputPipeCommand() != nil {
proc.Stdin, err = t.mediafile.InputPipeCommand().StdoutPipe()
proc.Stdout = os.Stdout
// If an input pipe has been set, we set it as stdin for the transcoding
if t.mediafile.InputPipe() {
proc.Stdin = t.mediafile.InputPipeReader()
}
// If an output pipe has been set, we set it as stdout for the transcoding
if t.mediafile.OutputPipe() {
proc.Stdout = t.mediafile.OutputPipeWriter()
}
err = proc.Start()
t.SetProcess(proc)
go func(err error, out *bytes.Buffer) {
go func(err error) {
if err != nil {
done <- fmt.Errorf("Failed Start FFMPEG (%s) with %s, message %s", command, err, out.String())
done <- fmt.Errorf("Failed Start FFMPEG (%s) with %s, message %s %s", command, err, outb.String(), errb.String())
close(done)
return
}
@@ -228,12 +257,13 @@ func (t *Transcoder) Run(progress bool) <-chan error {
}
err = proc.Wait()
go t.closePipes()
if err != nil {
err = fmt.Errorf("Failed Finish FFMPEG (%s) with %s message %s", command, err, out.String())
err = fmt.Errorf("Failed Finish FFMPEG (%s) with %s message %s %s", command, err, outb.String(), errb.String())
}
done <- err
close(done)
}(err, out)
}(err)
return done
}
@@ -345,3 +375,12 @@ func (t Transcoder) Output() <-chan models.Progress {
return out
}
func (t *Transcoder) closePipes() {
if t.mediafile.InputPipe() {
t.mediafile.InputPipeReader().Close()
}
if t.mediafile.OutputPipe() {
t.mediafile.OutputPipeWriter().Close()
}
}