Skip to content

Commit

Permalink
Adding more error handling and reporting to the ws client.
Browse files Browse the repository at this point in the history
  • Loading branch information
alfg committed Mar 11, 2021
1 parent 45d1584 commit dce91be
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.exe
*.mp4
*.mp4
*.temp
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ An FFmpeg server with a websocket API for [FFmpeg Commander](https://github.com/

The goal is to provide a simple interface for sending FFmpeg jobs from the browser (and other supported clients in the future) while reporting realtime progress details.

**Currently a work-in-progress!*
**Currently a work-in-progress! Bugs and breaking changes are expected.*

## How It Works
TODO
Expand Down Expand Up @@ -57,8 +57,12 @@ TODO
* Support all `ffmpeg-comamnder` JSON options.
* More CLI flags for server, ports, cwd and daemon mode.
* Logging levels and output
* More error handling
* API documentation
* Docker
* Test Client Demo
* Tests
* Cross-compile binaries for releases

## License
MIT
18 changes: 7 additions & 11 deletions ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,33 +114,31 @@ 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())
// fmt.Println("generated output: ", 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()
err := f.cmd.Start()
if err != nil {
return err
}

// Send progress updates.
go f.trackProgress()

// Update progress struct.
f.updateProgress(stdout)

err := f.cmd.Wait()
err = f.cmd.Wait()
if err != nil {
if f.isCancelled {
return errors.New("cancelled")
}
f.finish()
return err
return errors.New(stderr.String())
}
f.finish()
return nil
Expand Down Expand Up @@ -223,8 +221,6 @@ func (f *FFmpeg) trackProgress() {
case <-f.Progress.quit:
ticker.Stop()
return
case <-ticker.C:
// log.Info((f.Progress)
}
}
}
Expand Down
14 changes: 9 additions & 5 deletions ffprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package main

import (
"encoding/json"
"fmt"
"errors"
"os/exec"
"strings"
)

const ffprobeCmd = "ffprobe"
Expand All @@ -12,26 +13,29 @@ const ffprobeCmd = "ffprobe"
type FFProbe struct{}

// Run runs an FFProbe command.
func (f FFProbe) Run(input string) *FFProbeResponse {
func (f FFProbe) Run(input string) (*FFProbeResponse, error) {
args := []string{
"-i", input,
"-show_streams",
"-print_format", "json",
"-v", "quiet",
"-v", "error",
}

// Execute command.
cmd := exec.Command(ffprobeCmd, args...)
stdout, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(err.Error())
// Cleanup ffprobe error output.
replacer := strings.NewReplacer("{", "", "}", "")
stdout := strings.TrimSpace(replacer.Replace(string(stdout)))
return nil, errors.New(stdout)
}

dat := &FFProbeResponse{}
if err := json.Unmarshal([]byte(stdout), &dat); err != nil {
panic(err)
}
return dat
return dat, nil
}

// FFProbeResponse defines the response from ffprobe.
Expand Down
51 changes: 37 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ Usage:
progressInterval = time.Second * 1
)

var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan Message)

var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var (
clients = make(map[*websocket.Conn]bool)
broadcast = make(chan Message)
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
)

// Message payload from client.
type Message struct {
Expand All @@ -52,11 +53,13 @@ type Status struct {
Percent float64 `json:"percent"`
Speed string `json:"speed"`
FPS float64 `json:"fps"`
Err string `json:"err"`
}

var progressCh chan struct{}

func main() {
// CLI Banner.
printBanner()

// HTTP/WS Server.
Expand All @@ -74,9 +77,9 @@ func startServer() {
// Handles incoming WS messages from client.
go handleMessages()

fmt.Println(" Server started on port :8080.")
fmt.Println(" Server started on port \u001b[33m:8080\u001b[0m.")
fmt.Println(" - Go to \u001b[33mhttps://alfg.github.io/ffmpeg-commander\u001b[0m to connect!")
fmt.Println(" - \u001b[33mffmpegd\u001b[0m must be enabled in options")
fmt.Println(" - \u001b[33mffmpegd\u001b[0m must be enabled in ffmpeg-commander options")
fmt.Println("")
fmt.Printf("Waiting for connection...")
err := http.ListenAndServe(":8080", nil)
Expand Down Expand Up @@ -122,15 +125,21 @@ func handleMessages() {

func runEncode(input, output, payload string) {
probe := FFProbe{}
probeData := probe.Run(input)
probeData, err := probe.Run(input)
if err != nil {
sendError(err)
return
}

ffmpeg := &FFmpeg{}
go trackEncodeProgress(probeData, ffmpeg)
err := ffmpeg.Run(input, output, payload)
err = ffmpeg.Run(input, output, payload)

// If we get an error back from ffmpeg, send an error ws message to clients.
if err != nil {
// fmt.Println(err)
close(progressCh)
panic(err)
sendError(err)
return
}
close(progressCh)

Expand All @@ -147,6 +156,20 @@ func runEncode(input, output, payload string) {
}
}

func sendError(err error) {
for client := range clients {
p := &Status{
Err: err.Error(),
}
err := client.WriteJSON(p)
if err != nil {
fmt.Println("error: %w", err)
client.Close()
delete(clients, client)
}
}
}

func trackEncodeProgress(p *FFProbeResponse, f *FFmpeg) {
progressCh = make(chan struct{})
ticker := time.NewTicker(progressInterval)
Expand Down

0 comments on commit dce91be

Please # to comment.