feat: send SIGINT to ffmpeg process on ctrl+c

Also show user a message to indicate they cancelled the encoding and that they should clean up the created file(s).
fix: print ffmpeg command for every video file when dry-running if a directory was passed in
This commit is contained in:
2024-12-19 11:17:02 +02:00
parent 76ef469443
commit 039e30bacc
4 changed files with 44 additions and 19 deletions
-1
View File
@@ -1,4 +1,3 @@
## TODO: Don't show incompatible encoding options for encoders ## TODO: Don't show incompatible encoding options for encoders
## TODO: send a SIGINT to the ffmpeg process on ctrl+c
## TODO: calculate total progress based on file sizes instead of counts of files for a more accurate progress percentage. ## TODO: calculate total progress based on file sizes instead of counts of files for a more accurate progress percentage.
## TODO: when passing in a directory, list all video files in that directory and have the user choose which ones they want encoded ## TODO: when passing in a directory, list all video files in that directory and have the user choose which ones they want encoded
+33 -11
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -36,7 +37,9 @@ type model struct {
TotalProgress float64 TotalProgress float64
Estimate int Estimate int
Program *tea.Program Program *tea.Program
Command *exec.Cmd
Quitting bool Quitting bool
Cancelled bool
Screen Screen Screen Screen
Config map[int]Config Config map[int]Config
FocusIndex int FocusIndex int
@@ -83,12 +86,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd var cmd tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
// TODO: cancel the ffmpeg command context to terminate the process
return m, tea.Quit
}
case errQuitMsg: case errQuitMsg:
m.ErrQuitMessage = msg.msg m.ErrQuitMessage = msg.msg
m.ErrQuit = true m.ErrQuit = true
@@ -101,6 +98,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg: case tea.KeyMsg:
key := msg.String() key := msg.String()
switch key { switch key {
case "ctrl+c", "esc":
return m, tea.Quit
case "enter", " ": case "enter", " ":
// parse config and switch to main screen if we're focused on the start button // parse config and switch to main screen if we're focused on the start button
if m.FocusIndex == len(Configs) { if m.FocusIndex == len(Configs) {
@@ -149,6 +148,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
case Main: case Main:
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg:
key := msg.String()
switch key {
case "ctrl+c":
m.Cancelled = true
err := m.Command.Process.Signal(os.Interrupt)
if err != nil {
log.Println("An error occurred when sending SIGINT to the ffmpeg process:")
log.Println(err)
}
return m, tea.Quit
}
case initUi: case initUi:
m.FileCount = msg.fileCount m.FileCount = msg.fileCount
m.Files = msg.files m.Files = msg.files
@@ -166,6 +177,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
encode(fullFilePath, filepath.Base(fullFilePath), m.Program, m.ParsedConfig) encode(fullFilePath, filepath.Base(fullFilePath), m.Program, m.ParsedConfig)
}() }()
return m, nil
case ffmpegProcessStart:
log.Printf("Running command: %s\n", msg.cmd.String())
m.Command = msg.cmd
return m, nil return m, nil
case finishedEncodingVideo: case finishedEncodingVideo:
if m.ParsedConfig.DeleteOldVideo { if m.ParsedConfig.DeleteOldVideo {
@@ -300,20 +315,27 @@ func formatEstimate(estimate int) string {
func (m model) View() string { func (m model) View() string {
if m.Quitting { if m.Quitting {
if m.DryRun { if m.DryRun {
fullFilePath := m.Files[len(m.Files)-1] sb := strings.Builder{}
parentDir := filepath.Dir(fullFilePath) for _, file := range m.Files {
fileName := filepath.Base(fullFilePath) parentDir := filepath.Dir(file)
fileName := filepath.Base(file)
extensionIndex := strings.LastIndex(fileName, ".") extensionIndex := strings.LastIndex(fileName, ".")
newFileName := fileName[:extensionIndex] newFileName := fileName[:extensionIndex]
extension := fileName[extensionIndex:] extension := fileName[extensionIndex:]
outFileFullPath := filepath.Join(parentDir, newFileName+fmt.Sprintf("_[%s]_[%s]", m.ParsedConfig.VideoEncoder, m.ParsedConfig.AudioEncoder)+extension) outFileFullPath := filepath.Join(parentDir, newFileName+fmt.Sprintf("_[%s]_[%s]", m.ParsedConfig.VideoEncoder, m.ParsedConfig.AudioEncoder)+extension)
cmd := exec.Command("ffmpeg", buildFFmpegCmdArgs(fullFilePath, outFileFullPath, m.ParsedConfig)...) cmd := exec.Command("ffmpeg", buildFFmpegCmdArgs(file, outFileFullPath, m.ParsedConfig)...)
return fmt.Sprintf("%s %s\n", checkmark, cmd.String()) sb.WriteString(cmd.String())
sb.WriteByte('\n')
}
return fmt.Sprintf("%s %s\n", checkmark, sb.String())
} else { } else {
return fmt.Sprintf("%s %d/%d files encoded\n", checkmark, m.FileCount-len(m.Files), m.FileCount) return fmt.Sprintf("%s %d/%d files encoded\n", checkmark, m.FileCount-len(m.Files), m.FileCount)
} }
} else if m.Cancelled {
return fmt.Sprintf("%s Encoding cancelled. Stopped ffmpeg process.\n Make sure to clean up the created file\n", x)
} }
if m.ErrQuit { if m.ErrQuit {
+5
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@@ -20,6 +21,10 @@ type updateEstimate struct {
estimate int estimate int
} }
type ffmpegProcessStart struct {
cmd *exec.Cmd
}
type initUi struct { type initUi struct {
fileCount int fileCount int
files []string files []string
+2 -3
View File
@@ -120,7 +120,7 @@ func encode(fullFilePath string, fileName string, teaP *tea.Program, cfg ParsedC
if _, err := os.Stat(newFileFullPath); err == nil { if _, err := os.Stat(newFileFullPath); err == nil {
if cfg.SkipEncodedVid { if cfg.SkipEncodedVid {
log.Printf("%s already exists with the exact same encodings (crf and preset might be different though), skipping.", newFileFullPath) log.Printf("Skipping \"%s\" because it already exists with the exact same encodings (crf and preset might be different though)", newFileFullPath)
teaP.Send(finishedEncodingVideo{}) teaP.Send(finishedEncodingVideo{})
return return
} else { } else {
@@ -130,8 +130,7 @@ func encode(fullFilePath string, fileName string, teaP *tea.Program, cfg ParsedC
cmdArgs := buildFFmpegCmdArgs(fullFilePath, newFileFullPath, cfg, "-progress", "unix://"+getProgressSocket(fullFilePath, teaP)) cmdArgs := buildFFmpegCmdArgs(fullFilePath, newFileFullPath, cfg, "-progress", "unix://"+getProgressSocket(fullFilePath, teaP))
cmd := exec.Command("ffmpeg", cmdArgs...) cmd := exec.Command("ffmpeg", cmdArgs...)
// TODO: send the cmd obj as a tea msg so we can send interrupt signals to the process when ctrl+c is pressed teaP.Send(ffmpegProcessStart{cmd})
//teaP.Send()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {