ffmpeg-commander json to ffmpegd option parsing.

This commit is contained in:
Alf
2021-03-08 21:48:50 -08:00
parent f5a145fafb
commit 291b06565c
3 changed files with 215 additions and 90 deletions

View File

@@ -5,6 +5,9 @@ The goal is to provide a simple interface for sending FFmpeg jobs from the brows
**Currently a work-in-progress!*
## How It Works
TODO
## Install
```
$ go get -u github.com/alfg/ffmpegd
@@ -47,11 +50,13 @@ Encoding... 6111 / 17620 (34.68%) 3.37x @ 80.77
```
![ffmpeg-commander](screenshot.png)
## API
TODO
## TODO
* Support all `ffmpeg-comamnder` JSON options.
* More CLI flags for server, ports, cwd and daemon mode.
* Logging options and output
* Logging levels and output
* API documentation
* Docker

281
ffmpeg.go
View File

@@ -45,33 +45,66 @@ type ffmpegOptions struct {
Input string
Output string
Container string `json:"container"`
Video videoOptions `json:"video"`
Audio audioOptions `json:"audio"`
Format formatOptions `json:"format"`
Video videoOptions `json:"video"`
Audio audioOptions `json:"audio"`
Filter filterOptions `json:"filter"`
Raw []string `json:"raw"` // Raw flag options.
}
type formatOptions struct {
Container string `json:"container"`
Clip bool `json:"clip"`
StartTime string `json:"start_time"`
StopTime string `json:"stop_time"`
}
type videoOptions struct {
Codec string `json:"codec"`
Preset string `json:"preset"`
HardwareAcceleration string `json:"hardware_acceleration_option"`
Pass string `json:"pass"`
Crf int `json:"crf"`
Bitrate string `json:"bitrate"`
MinRate string `json:"minrate"`
MaxRate string `json:"maxrate"`
BufSize string `json:"bufsize"`
PixelFormat string `json:"pixel_format"`
FrameRate string `json:"frame_rate"`
Speed string `json:"speed"`
Tune string `json:"tune"`
Profile string `json:"profile"`
Level string `json:"level"`
Codec string `json:"codec"`
Preset string `json:"preset"`
Pass string `json:"pass"`
Crf int `json:"crf"`
Bitrate string `json:"bitrate"`
MinRate string `json:"minrate"`
MaxRate string `json:"maxrate"`
BufSize string `json:"bufsize"`
PixelFormat string `json:"pixel_format"`
FrameRate string `json:"frame_rate"`
Speed string `json:"speed"`
Tune string `json:"tune"`
Profile string `json:"profile"`
Level string `json:"level"`
FastStart bool `json:"faststart"`
Size string `json:"size"`
Width string `json:"width"`
Height string `json:"height"`
Format string `json:"format"`
Aspect string `json:"aspect"`
Scaling string `json:"scaling"`
CodecOptions string `json:"codec_options"`
}
type audioOptions struct {
Codec string
Codec string `json:"codec"`
Channel string `json:"channel"`
Quality string `json:"quality"`
SampleRate string `json:"sample_rate"`
Volume int `json:"volume"`
}
type filterOptions struct {
Deband bool `json:"deband"`
Deshake bool `json:"deshake"`
Deflicker bool `json:"deflicker"`
Dejudder bool `json:"dejudder"`
Denoise string `json:"denoise"`
Deinterlace string `json:"deinterlace"`
Brightness string `json:"brightness"`
Contrast string `json:"contrast"`
Saturation string `json:"saturation"`
Gamma string `json:"gamma"`
Acontrast string `json:"acontrast"`
}
// Run runs the ffmpeg encoder with options.
@@ -81,12 +114,18 @@ func (f *FFmpeg) Run(input, output, data string) error {
args := parseOptions(input, output, data)
// Execute command.
fmt.Println("final output: ", args)
f.cmd = exec.Command(ffmpegCmd, args...)
fmt.Println("OUT: ", f.cmd.String())
stdout, _ := f.cmd.StdoutPipe()
// Capture stderr (if any).
var stderr bytes.Buffer
f.cmd.Stderr = &stderr
// f.cmd.Run()
// if err != nil {
// fmt.Println("ERR: ", stderr.String())
// }
f.cmd.Start()
// Send progress updates.
@@ -194,7 +233,9 @@ func (f *FFmpeg) finish() {
close(f.Progress.quit)
}
// Utilities for parsing ffmpeg options.
// Parse options from JSON payload.
// This should match the options mapped by:
// https://github.com/alfg/ffmpeg-commander/blob/master/src/ffmpeg.js
func parseOptions(input, output, data string) []string {
args := []string{
"-hide_banner",
@@ -226,112 +267,190 @@ func parseOptions(input, output, data string) []string {
return args
}
// transformOptions converts the ffmpegOptions{} struct and converts into
// a slice of ffmpeg options to be passed to exec.Command arguments.
//
// NOTE: There is probably a better way of iterating the struct fields and values
// using reflect, but there are some tricky ffmpeg options here, such as video filters.
// TODO: Look into refactor using reflect. Example:
// fields := reflect.TypeOf(opt)
// values := reflect.ValueOf(opt)
func transformOptions(opt *ffmpegOptions) []string {
func setFormatFlags(opt formatOptions) []string {
args := []string{}
if opt.StartTime != "" {
arg := []string{"-ss", opt.StartTime}
args = append(args, arg...)
}
if opt.StopTime != "" {
arg := []string{"-to", opt.StopTime}
args = append(args, arg...)
}
return args
}
func setVideoFlags(opt videoOptions) []string {
fmt.Println("setVideoFlags: ", opt)
args := []string{}
// Video codec.
if opt.Video.Codec != "" {
arg := []string{"-c:v", opt.Video.Codec}
args = append(args, arg...)
}
// Audio codec.
if opt.Audio.Codec != "" {
arg := []string{"-c:a", opt.Audio.Codec}
args = append(args, arg...)
if opt.Codec != "" {
args = append(args, []string{"-c:v", opt.Codec}...)
}
// Video preset.
if opt.Video.Preset != "" && opt.Video.Preset != "none" {
arg := []string{"-preset", opt.Video.Preset}
args = append(args, arg...)
if opt.Preset != "" && opt.Preset != "none" {
args = append(args, []string{"-preset", opt.Preset}...)
}
// CRF.
if opt.Video.Crf != 0 && opt.Video.Pass == "crf" {
crf := strconv.Itoa(opt.Video.Crf)
arg := []string{"-crf", crf}
args = append(args, arg...)
if opt.Crf != 0 && opt.Pass == "crf" {
crf := strconv.Itoa(opt.Crf)
args = append(args, []string{"-crf", crf}...)
}
// Faststart.
if opt.FastStart {
args = append(args, []string{"-movflags", "faststart"}...)
}
// Bitrate.
if opt.Video.Bitrate != "" && opt.Video.Bitrate != "0" {
arg := []string{"-b:v", opt.Video.Bitrate}
args = append(args, arg...)
if opt.Bitrate != "" && opt.Bitrate != "0" {
args = append(args, []string{"-b:v", opt.Bitrate}...)
}
// Minrate.
if opt.Video.MinRate != "" && opt.Video.MinRate != "0" {
arg := []string{"-minrate", opt.Video.MinRate}
args = append(args, arg...)
if opt.MinRate != "" && opt.MinRate != "0" {
args = append(args, []string{"-minrate", opt.MinRate}...)
}
// Maxrate.
if opt.Video.MaxRate != "" && opt.Video.MaxRate != "0" {
arg := []string{"-maxrate", opt.Video.MaxRate}
args = append(args, arg...)
if opt.MaxRate != "" && opt.MaxRate != "0" {
args = append(args, []string{"-maxrate", opt.MaxRate}...)
}
// Buffer Size.
if opt.Video.BufSize != "" && opt.Video.BufSize != "0" {
arg := []string{"-bufsize", opt.Video.BufSize}
args = append(args, arg...)
if opt.BufSize != "" && opt.BufSize != "0" {
args = append(args, []string{"-bufsize", opt.BufSize}...)
}
// Pixel Format.
if opt.Video.PixelFormat != "" && opt.Video.PixelFormat != "auto" {
arg := []string{"-pix_fmt", opt.Video.PixelFormat}
args = append(args, arg...)
if opt.PixelFormat != "" && opt.PixelFormat != "auto" {
args = append(args, []string{"-pix_fmt", opt.PixelFormat}...)
}
// Frame Rate.
if opt.Video.FrameRate != "" && opt.Video.PixelFormat != "auto" {
arg := []string{"-r", opt.Video.FrameRate}
args = append(args, arg...)
if opt.FrameRate != "" && opt.PixelFormat != "auto" {
args = append(args, []string{"-r", opt.FrameRate}...)
}
// Tune.
if opt.Video.Tune != "" && opt.Video.Tune != "none" {
arg := []string{"-tune", opt.Video.Tune}
args = append(args, arg...)
if opt.Tune != "" && opt.Tune != "none" {
args = append(args, []string{"-tune", opt.Tune}...)
}
// Profile.
if opt.Video.Profile != "" && opt.Video.Profile != "none" {
arg := []string{"-profile:v", opt.Video.Profile}
args = append(args, arg...)
if opt.Profile != "" && opt.Profile != "none" {
args = append(args, []string{"-profile:v", opt.Profile}...)
}
// Level.
if opt.Video.Level != "" && opt.Video.Level != "none" {
arg := []string{"-level", opt.Video.Level}
if opt.Level != "" && opt.Level != "none" {
args = append(args, []string{"-level", opt.Level}...)
}
// Codec params.
if opt.CodecOptions != "" && (opt.Codec == "libx264" || opt.Codec == "libx265") {
p := strings.Replace(opt.Codec, "lib", "", 1)
args = append(args, []string{"-" + p + "-params", opt.CodecOptions}...)
}
return args
}
func setVideoFilters(vopt videoOptions, opt filterOptions) string {
args := []string{}
// Speed.
if vopt.Speed != "" && vopt.Speed != "auto" {
args = append(args, []string{"setpts=" + vopt.Speed}...)
}
// EQ filters.
eq := []string{}
if opt.Contrast != "" && opt.Contrast != "1" {
eq = append(eq, []string{"contrast=" + opt.Contrast}...)
}
if opt.Brightness != "" && opt.Brightness != "0" {
eq = append(eq, []string{"brightness=" + opt.Brightness}...)
}
if opt.Saturation != "" && opt.Saturation != "0" {
eq = append(eq, []string{"saturation=" + opt.Saturation}...)
}
if opt.Gamma != "" && opt.Gamma != "0" {
eq = append(eq, []string{"gamma=" + opt.Gamma}...)
}
if len(eq) > 0 {
eqStr := strings.Join(eq, ":")
args = append(args, []string{"eq=" + eqStr}...)
}
argsStr := strings.Join(args, ",")
return argsStr
}
func setAudioFlags(opt audioOptions) []string {
args := []string{}
// Audio codec.
if opt.Codec != "" {
args = append(args, []string{"-c:a", opt.Codec}...)
}
// Channel.
if opt.Channel != "" && opt.Channel != "source" {
args = append(args, []string{"-rematrix_maxval", "1.0", "-ac", opt.Channel}...)
}
// Bitrate.
if opt.Quality != "" && opt.Quality != "auto" {
args = append(args, []string{"-b:a", opt.Quality}...)
}
// Sample rate.
if opt.SampleRate != "" && opt.SampleRate != "auto" {
args = append(args, []string{"-ar", opt.SampleRate}...)
}
return args
}
// transformOptions converts the ffmpegOptions{} struct and converts into
// a slice of ffmpeg options to be passed to exec.Command arguments.
func transformOptions(opt *ffmpegOptions) []string {
args := []string{}
// Set format flags if clip options are set.
if opt.Format.Clip {
arg := setFormatFlags(opt.Format)
args = append(args, arg...)
}
// Video flags.
args = append(args, setVideoFlags(opt.Video)...)
// Video Filters.
vf := []string{"-vf", "\""}
// Speed.
if opt.Video.Speed != "" && opt.Video.Speed != "auto" {
arg := "setpts=" + opt.Video.Speed
vf = append(vf, arg)
}
vf = append(vf, "\"") // End of video filters.
// vf := []string{"-vf"}
vf := []string{"-vf", setVideoFilters(opt.Video, opt.Filter)}
// Only push -vf flag if there are video filter arguments.
if len(vf) > 3 {
if vf[1] != "" {
args = append(args, vf...)
}
// Audio flags.
// args = append(args, setAudioFlags(opt.Audio)...)
// fmt.Println(args)
extra := []string{
"-y",
}

17
main.go
View File

@@ -11,8 +11,7 @@ import (
)
const (
ProgressInterval = time.Second * 1
Logo = `
logo = `
███████╗███████╗███╗ ███╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██╔════╝████╗ ████║██╔══██╗██╔════╝██╔════╝ ██╔══██╗
█████╗ █████╗ ██╔████╔██║██████╔╝█████╗ ██║ ███╗██║ ██║
@@ -21,13 +20,14 @@ const (
╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═════╝
v0.0.1
`
Version = "0.0.1"
Description = "[\u001b[32mffmpegd\u001b[0m] - websocket server for ffmpeg-commander.\n"
Usage = `
version = "0.0.1"
description = "[\u001b[32mffmpegd\u001b[0m] - websocket server for ffmpeg-commander.\n"
usage = `
Usage:
ffmpegd [port]
ffmpegd version -- This version.
`
progressInterval = time.Second * 1
)
var clients = make(map[*websocket.Conn]bool)
@@ -64,8 +64,8 @@ func main() {
}
func printBanner() {
fmt.Println(Logo)
fmt.Println(Description)
fmt.Println(logo)
fmt.Println(description)
}
func startServer() {
@@ -127,6 +127,7 @@ func runEncode(input, output, payload string) {
go trackEncodeProgress(probeData, ffmpeg)
err := ffmpeg.Run(input, output, payload)
if err != nil {
// fmt.Println(err)
close(progressCh)
panic(err)
}
@@ -147,7 +148,7 @@ func runEncode(input, output, payload string) {
func trackEncodeProgress(p *FFProbeResponse, f *FFmpeg) {
progressCh = make(chan struct{})
ticker := time.NewTicker(ProgressInterval)
ticker := time.NewTicker(progressInterval)
for {
select {