mirror of
https://github.com/alfg/ffmpegd.git
synced 2025-09-26 19:41:15 +08:00
ffmpeg-commander json to ffmpegd option parsing.
This commit is contained in:
@@ -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
|
||||
```
|
||||

|
||||
|
||||
## 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
281
ffmpeg.go
@@ -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
17
main.go
@@ -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 {
|
||||
|
Reference in New Issue
Block a user