Transcoding files

Added getters & setters
Get file's metada using ffprobe
Getting ffmpeg proccess info: frames processed, bitrate, current time and progress percentage through channel
This commit is contained in:
frr
2018-02-01 14:39:38 +01:00
parent e545a08502
commit 37d274517b
4 changed files with 667 additions and 48 deletions

360
models/media.go Normal file
View File

@@ -0,0 +1,360 @@
package models
import (
"strings"
"strconv"
"fmt"
"reflect"
)
type Mediafile struct {
aspect string
resolution string
videoBitRate int
videoBitRateTolerance int
videoMaxBitRate int
videoMinBitrate int
videoCodec string
frameRate int
maxKeyframe int
minKeyframe int
keyframeInterval int
audioCodec string
audioBitrate int
audioChannels int
bufferSize int
threads int
target string
duration string
seekTime string
quality int
metadata Metadata
}
/*** SETTERS ***/
func (m *Mediafile) SetAspect(v string) {
m.aspect = v
}
func (m *Mediafile) SetResolution(v string) {
m.resolution = v
}
func (m *Mediafile) SetVideoBitRate(v int) {
m.videoBitRate = v
}
func (m *Mediafile) SetVideoBitRateTolerance(v int) {
m.videoBitRateTolerance = v
}
func (m *Mediafile) SetVideoMaxBitrate(v int) {
m.videoMaxBitRate = v
}
func (m *Mediafile) SetVideoMinBitRate(v int) {
m.videoMinBitrate = v
}
func (m *Mediafile) SetVideoCodec(v string) {
m.videoCodec = v
}
func (m *Mediafile) SetFrameRate(v int) {
m.frameRate = v
}
func (m *Mediafile) SetMaxKeyFrame(v int) {
m.maxKeyframe = v
}
func (m *Mediafile) SetMinKeyFrame(v int) {
m.minKeyframe = v
}
func (m *Mediafile) SetKeyframeInterval(v int) {
m.keyframeInterval = v
}
func (m *Mediafile) SetAudioCodec(v string) {
m.audioCodec = v
}
func (m *Mediafile) SetAudioBitRate(v int) {
m.audioBitrate = v
}
func (m *Mediafile) SetAudioChannels(v int) {
m.audioChannels = v
}
func (m *Mediafile) SetBufferSize(v int) {
m.bufferSize = v
}
func (m *Mediafile) SetThreads(v int) {
m.threads = v
}
func (m *Mediafile) SetDuration(v string) {
m.duration = v
}
func (m *Mediafile) SetSeekTime(v string) {
m.seekTime = v
}
func (m *Mediafile) SetQuality(v int) {
m.quality = v
}
func (m *Mediafile) SetMetadata(v Metadata) {
m.metadata = v
}
/*** GETTERS ***/
func (m Mediafile) Aspect() string {
return m.aspect
}
func (m Mediafile) Resolution() string {
return m.resolution
}
func (m Mediafile) VideoBitrate() int {
return m.videoBitRate
}
func (m Mediafile) VideoBitRateTolerance() int {
return m.videoBitRateTolerance
}
func (m Mediafile) VideoMaxBitRate() int {
return m.videoMaxBitRate
}
func (m Mediafile) VideoMinBitRate() int {
return m.videoMinBitrate
}
func (m Mediafile) VideoCodec() string {
return m.videoCodec
}
func (m Mediafile) FrameRate() int {
return m.frameRate
}
func (m Mediafile) MaxKeyFrame() int {
return m.maxKeyframe
}
func (m Mediafile) MinKeyFrame() int {
return m.minKeyframe
}
func (m Mediafile) KeyFrameInterval() int {
return m.keyframeInterval
}
func (m Mediafile) AudioCodec() string {
return m.audioCodec
}
func (m Mediafile) AudioBitrate() int {
return m.audioBitrate
}
func (m Mediafile) AudioChannels() int {
return m.audioChannels
}
func (m Mediafile) BufferSize() int {
return m.bufferSize
}
func (m Mediafile) Threads() int {
return m.threads
}
func (m Mediafile) Target() string {
return m.target
}
func (m Mediafile) Duration() string {
return m.duration
}
func (m Mediafile) SeekTime() string {
return m.seekTime
}
func (m Mediafile) Quality() int {
return m.quality
}
func (m Mediafile) Metadata() Metadata {
return m.metadata
}
/** OPTS **/
func (m Mediafile) ToStrCommand() string {
var strCommand string
opts := []string{"Aspect", "VideoCodec", "FrameRate", "Resolution", "VideoBitRate", "VideoBitRateTolerance", "AudioCodec", "AudioBitRate", "AudioChannels", "VideoMaxBitRate", "VideoMinBitRate", "BufferSize", "Threads", "Target", "Duration", "KeyframeInterval", "SeekTime", "Quality"}
for _, name := range opts {
opt := reflect.ValueOf(&m).MethodByName(fmt.Sprintf("Obtain%s", name))
if (opt != reflect.Value{}) {
result := opt.Call([]reflect.Value{})
if result[0].String() != "" {
strCommand += " "
strCommand += result[0].String()
}
}
}
return strCommand
}
func (m *Mediafile) ObtainAspect() string {
// Set aspect
if m.resolution != "" {
resolution := strings.Split(m.resolution, "x")
if len(resolution) != 0 {
width, _ := strconv.ParseFloat(resolution[0], 64)
height, _ := strconv.ParseFloat(resolution[1], 64)
return fmt.Sprintf("-aspect %f", width/height)
}
}
if m.aspect != "" {
return fmt.Sprintf("-aspect %s", m.aspect)
} else {
return ""
}
}
func (m *Mediafile) ObtainVideoCodec() string {
if m.videoCodec != "" {
return fmt.Sprintf("-vcodec %s", m.videoCodec)
}
return ""
}
func (m *Mediafile) ObtainFrameRate() string {
if m.frameRate != 0 {
return fmt.Sprintf("-r %d", m.frameRate)
}
return ""
}
func (m *Mediafile) ObtainResolution() string {
if m.resolution != "" {
return fmt.Sprintf("-s %s", m.resolution)
}
return ""
}
func (m *Mediafile) ObtainVideoBitRate() string {
if m.videoBitRate != 0 {
return fmt.Sprintf("-b:v %d", m.videoBitRate)
}
return ""
}
func (m *Mediafile) ObtainAudioCodec() string {
if m.audioCodec != "" {
return fmt.Sprintf("-acodec %s", m.audioCodec)
}
return ""
}
func (m *Mediafile) ObtainAudioBitRate() string {
if m.audioBitrate != 0 {
return fmt.Sprintf("-b:a %d", m.audioBitrate)
}
return ""
}
func (m *Mediafile) ObtainAudioChannels() string {
if m.audioChannels != 0 {
return fmt.Sprintf("-ac %d", m.audioChannels)
}
return ""
}
func (m *Mediafile) ObtainVideoMaxBitRate() string {
if m.videoMaxBitRate != 0 {
return fmt.Sprintf("-maxrate %dk", m.videoMaxBitRate)
}
return ""
}
func (m *Mediafile) ObtainVideoMinBitRate() string {
if m.videoMinBitrate != 0 {
return fmt.Sprintf("-minrate %dk", m.videoMinBitrate)
}
return ""
}
func (m *Mediafile) ObtainBufferSize() string {
if m.bufferSize != 0 {
return fmt.Sprintf("-bufsize %dk", m.bufferSize)
}
return ""
}
func (m *Mediafile) ObtainVideoBitRateTolerance() string {
if m.videoBitRateTolerance != 0 {
return fmt.Sprintf("-bt %dk", m.videoBitRateTolerance)
}
return ""
}
func (m *Mediafile) ObtainThreads() string {
if m.threads != 0 {
return fmt.Sprintf("-threads %d", m.threads)
}
return ""
}
func (m *Mediafile) ObtainTarget() string {
if m.target != "" {
return fmt.Sprintf("-target %s", m.target)
}
return ""
}
func (m *Mediafile) ObtainDuration() string {
if m.duration != "" {
return fmt.Sprintf("-t %s", m.duration)
}
return ""
}
func (m *Mediafile) ObtainKeyframeInterval() string {
if m.keyframeInterval != 0 {
return fmt.Sprintf("-g %d", m.keyframeInterval)
}
return ""
}
func (m *Mediafile) ObtainSeekTime() string {
if m.seekTime != "" {
return fmt.Sprintf("-ss %s", m.seekTime)
}
return ""
}
func (m *Mediafile) ObtainQuality() string {
if m.quality != 0 {
return fmt.Sprintf("-q:v %d", m.quality)
}
return ""
}

View File

@@ -1,28 +1,78 @@
package models
type Ffmpeg struct {
FfmpegBinPath string
FfprobeBinPath string
FfmpegBinPath string
FfprobeBinPath string
}
type Mediafile struct {
Aspect string
Resolution string
VideoBitRate string
VideoMaxBitrate string
VideoMinBitrate string
VideoCodec string
FrameRate string
MaxKeyframe string
MinKeyframe string
AudioCodec string
AudioBitrate string
AudioSampleRate string
AudioChannels string
BufferSize string
Threads string
Target string
Duration string
SeekTime string
Quality string
type Metadata struct {
Streams []Streams `json:"streams"`
Format Format `json:"format"`
}
type Streams struct {
Index int
CodecName string `json:"codec_name"`
CodecLongName string `json:"codec_long_name"`
Profile string `json:"profile"`
CodecType string `json:"codec_type"`
CodecTimeBase string `json:"codec_time_base"`
CodecTagString string `json:"codec_tag_string"`
CodecTag string `json:"codec_tag"`
Width int `json:"width"`
Height int `json:"height"`
CodedWidth int `json:"coded_width"`
CodedHeight int `json:"coded_height"`
HasBFrames int `json:"has_b_frames"`
SampleAspectRatio string `json:"sample_aspect_ratio"`
DisplayAspectRatio string `json:"display_aspect_ratio"`
PixFmt string `json:"pix_fmt"`
Level int `json:"level"`
ChromaLocation string `json:"chroma_location"`
Refs int `json:"refs"`
QuarterSample string `json:"quarter_sample"`
DivxPacked string `json:"divx_packed"`
RFrameRrate string `json:"r_frame_rate"`
AvgFrameRate string `json:"avg_frame_rate"`
TimeBase string `json:"time_base"`
DurationTs int `json:"duration_ts"`
Duration string `json:"duration"`
Disposition Disposition `json:"disposition"`
}
type Disposition struct {
Default int `json:"default"`
Dub int `json:"dub"`
Original int `json:"original"`
Comment int `json:"comment"`
Lyrics int `json:"lyrics"`
Karaoke int `json:"karaoke"`
Forced int `json:"forced"`
HearingImpaired int `json:"hearing_impaired"`
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
}
type Tags struct {
Encoder string `json:"ENCODER"`
}

View File

@@ -1,41 +1,107 @@
package transcoder
import (
//"fmt"
//"os"
"errors"
"os"
"goffmpeg/models"
"os/exec"
"fmt"
"goffmpeg/ffmpeg"
"goffmpeg/utils"
"bytes"
"encoding/json"
"bufio"
"strings"
"regexp"
"strconv"
"log"
)
type Transcoder struct {
Process *os.Process
InputPath string
OutputPath string
MediaFile *models.Mediafile
process *exec.Cmd
inputPath string
outputPath string
mediafile *models.Mediafile
configuration ffmpeg.Configuration
}
func New(inputPath *string, configuration *ffmpeg.Configuration) (*Transcoder, error) {
transcoding := new(Transcoder)
func (t *Transcoder) SetProccess(v *exec.Cmd) {
t.process = v
}
if inputPath == nil {
return nil, errors.New("error: transcoder.Initialize -> inputPath missing")
func (t *Transcoder) SetInputPath(v string) {
t.inputPath = v
}
func (t *Transcoder) SetOutputPath(v string) {
t.outputPath = v
}
func (t *Transcoder) SetMediaFile(v *models.Mediafile) {
t.mediafile = v
}
func (t *Transcoder) SetConfiguration(v ffmpeg.Configuration) {
t.configuration = v
}
/*** GETTERS ***/
func (t Transcoder) Process() *exec.Cmd {
return t.process
}
func (t Transcoder) InputPath() string {
return t.inputPath
}
func (t Transcoder) OutputPath() string {
return t.outputPath
}
func (t Transcoder) MediaFile() *models.Mediafile {
return t.mediafile
}
func (t Transcoder) FFmpegExec() string {
return t.configuration.FfmpegBin
}
func (t Transcoder) FFprobeExec() string {
return t.configuration.FfprobeBin
}
func (t Transcoder) GetCommand() string {
var rcommand string
rcommand = fmt.Sprintf("%s -y -i %s ", t.configuration.FfmpegBin, t.inputPath)
media := t.mediafile
rcommand += media.ToStrCommand()
rcommand += " " + t.outputPath
fmt.Println(rcommand)
return rcommand
}
/*** FUNCTIONS ***/
func (t *Transcoder) Initialize(inputPath string, outputPath string, configuration *ffmpeg.Configuration) (error) {
if inputPath == "" {
return errors.New("error: transcoder.Initialize -> inputPath missing")
}
_, err := os.Stat(*inputPath)
_, err := os.Stat(inputPath)
if os.IsNotExist(err) {
return nil, errors.New("error: transcoder.Initialize -> input file not found")
return errors.New("error: transcoder.Initialize -> input file not found")
}
// Set input path
transcoding.InputPath = *inputPath
// TODO: Get file metadata from ffprobe and set MediaFile
command := fmt.Sprintf("%s -i %s -print_format json -show_format -show_streams -show_error", configuration.FfprobeBin, *inputPath)
command := fmt.Sprintf("%s -i %s -print_format json -show_format -show_streams -show_error", configuration.FfprobeBin, inputPath)
cmd := exec.Command("/bin/sh", "-c", command)
@@ -46,25 +112,144 @@ func New(inputPath *string, configuration *ffmpeg.Configuration) (*Transcoder, e
cmdErr := cmd.Start()
if cmdErr != nil {
return nil, cmdErr
return cmdErr
}
_, errProc := cmd.Process.Wait()
if errProc != nil {
return nil, errProc
return errProc
}
stdout := out.String()
var Metadata models.Metadata
fmt.Println(stdout)
if err := json.Unmarshal([]byte(out.String()), &Metadata); err != nil {
return err
}
transcoding.MediaFile = new(models.Mediafile)
// Set new Mediafile
MediaFile := new(models.Mediafile)
MediaFile.SetMetadata(Metadata)
return transcoding, nil
// Set transcoder configuration
t.SetInputPath(inputPath)
t.SetOutputPath(outputPath)
t.SetMediaFile(MediaFile)
t.SetConfiguration(*configuration)
return nil
}
func (t *Transcoder) SetBitRate(v *string) string {
t.MediaFile.VideoBitRate = *v
return t.MediaFile.VideoBitRate
func (t *Transcoder) Run() (<-chan bool) {
done := make(chan bool)
command := t.GetCommand()
proc := exec.Command("/bin/sh", "-c", command)
t.SetProccess(proc)
go func() {
perror := proc.Start()
if perror != nil {
log.Fatal(perror)
return
}
proc.Wait()
done <- true
}()
return done
}
// TODO: ONLY WORKS FOR VIDEO FILES
func (t Transcoder) Output() (chan models.Progress) {
out := make(chan models.Progress)
go func() {
stderr, serr := t.Process().StderrPipe()
if serr != nil {
log.Fatal(serr)
return
}
scanner := bufio.NewScanner(stderr)
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if err != nil {
log.Fatal(err)
return 0, nil, nil
}
if atEOF && len(data) == 0 {
return 0, nil, nil
}
fr := strings.Index(string(data), "frame=")
if fr > 0 {
return fr + 1, data[fr:], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
scanner.Split(split)
buf := make([]byte, 2)
scanner.Buffer(buf, bufio.MaxScanTokenSize)
var lastProgress float64
var lastFrames string
for scanner.Scan() {
Progress := new(models.Progress)
line := scanner.Text()
if strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
var re= regexp.MustCompile(`=\s+`)
st := re.ReplaceAllString(line, `=`)
f := strings.Fields(st)
// Frames processed
framesProcessed := strings.Split(f[0], "=")[1]
// Current time processed
time := strings.Split(f[4], "=")[1]
timesec := utils.DurToSec(time)
dursec, _ := strconv.ParseFloat(t.MediaFile().Metadata().Format.Duration, 64)
// Progress calculation
progress := (timesec * 100) / dursec
// Current bitrate
currentBitrate := strings.Split(f[5], "=")[1]
Progress.Progress = progress
Progress.CurrentBitrate = currentBitrate
Progress.FramesProcessed = framesProcessed
Progress.CurrentTime = time
if progress != lastProgress && framesProcessed != lastFrames{
lastProgress = progress
lastFrames = framesProcessed
out <- *Progress
}
}
}
defer close(out)
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}()
return out
}

24
utils/utils.go Normal file
View File

@@ -0,0 +1,24 @@
package utils
import (
"strings"
"strconv"
)
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
}