mirror of
https://github.com/IllusionMan1212/ffui.git
synced 2026-10-24 02:12:35 +00:00
feat: allow selection for files to be encoded in a directory
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
- [x] query ffmpeg binary for available encoders and list the ones we know
|
- [x] query ffmpeg binary for available encoders and list the ones we know
|
||||||
- [ ] when passing in a directory, list all video files in that directory and have the user choose which ones they want encoded
|
- [x] when passing in a directory, list all video files in that directory and have the user choose which ones they want encoded
|
||||||
- This will of course have a select all checkbox for quickly encoding all the files in a dir.
|
- This will of course have a select all checkbox for quickly encoding all the files in a dir.
|
||||||
- [ ] calculate total progress based on file sizes instead of counts of files for a more accurate progress percentage.
|
- [ ] calculate total progress based on file sizes instead of counts of files for a more accurate progress percentage.
|
||||||
might not do this.
|
might not do this.
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ var X = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF2233")).Render("✖️
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
None Screen = iota
|
None Screen = iota
|
||||||
Files
|
|
||||||
Cfg
|
Cfg
|
||||||
|
Files
|
||||||
Main
|
Main
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Path string
|
||||||
Encode bool
|
Selected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
@@ -36,7 +36,7 @@ type Model struct {
|
|||||||
Path string
|
Path string
|
||||||
CurrentFileName string
|
CurrentFileName string
|
||||||
FileCount int
|
FileCount int
|
||||||
Files []string
|
Files []File
|
||||||
Spinner spinner.Model
|
Spinner spinner.Model
|
||||||
SingleFileProgressBar progress.Model
|
SingleFileProgressBar progress.Model
|
||||||
SingleFileProgress float64
|
SingleFileProgress float64
|
||||||
@@ -91,7 +91,8 @@ func initialModel(fileInfo os.FileInfo, absolutePath string) *Model {
|
|||||||
CurrentFileName: fileInfo.Name(),
|
CurrentFileName: fileInfo.Name(),
|
||||||
Path: absolutePath,
|
Path: absolutePath,
|
||||||
FileCount: 0,
|
FileCount: 0,
|
||||||
Files: make([]string, 0),
|
Files: make([]File, 0),
|
||||||
|
Screen: Cfg,
|
||||||
Spinner: s,
|
Spinner: s,
|
||||||
SingleFileProgressBar: progress.New(progress.WithGradient("#1010ff", "#00ff00")),
|
SingleFileProgressBar: progress.New(progress.WithGradient("#1010ff", "#00ff00")),
|
||||||
SingleFileProgress: 0.0,
|
SingleFileProgress: 0.0,
|
||||||
@@ -131,32 +132,61 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.FileCount = msg.fileCount
|
m.FileCount = msg.fileCount
|
||||||
m.Files = msg.files
|
m.Files = msg.files
|
||||||
|
|
||||||
if len(m.Files) == 1 {
|
|
||||||
m.Screen = Cfg
|
|
||||||
} else if len(m.Files) > 1 {
|
|
||||||
m.Screen = Files
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch m.Screen {
|
switch m.Screen {
|
||||||
case Files:
|
case Files:
|
||||||
// TODO: navigate files and select them and stuff
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
key := msg.String()
|
key := msg.String()
|
||||||
switch key {
|
switch key {
|
||||||
case "ctrl+c", "esc":
|
case "ctrl+c", "esc":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case "enter", "space":
|
case "enter", " ":
|
||||||
// TODO: change the file state to Encode = true
|
if m.FocusIndex == 0 && key == "enter" {
|
||||||
// Also change the files to be of File type
|
selectAll := !every(m.Files, func(e File) bool { return e.Selected })
|
||||||
// t := m.Files[m.FocusIndex]
|
|
||||||
|
if m.ChoiceIndex == 0 {
|
||||||
|
for i := range m.Files {
|
||||||
|
m.Files[i].Selected = selectAll
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.Files = filter(m.Files, func(f File) bool {
|
||||||
|
return f.Selected
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Screen = Main
|
||||||
|
m.FileCount = len(m.Files)
|
||||||
|
|
||||||
|
return m, tea.Batch(m.Spinner.Tick, encodeVideo)
|
||||||
|
}
|
||||||
|
} else if m.FocusIndex != 0 {
|
||||||
|
m.Files[m.FocusIndex-1].Selected = !m.Files[m.FocusIndex-1].Selected
|
||||||
|
|
||||||
|
// Reset the choice to "Select All" button when no files are selected
|
||||||
|
if !anyOf(m.Files, func(f File) bool { return f.Selected }) {
|
||||||
|
m.ChoiceIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
case "g":
|
case "g":
|
||||||
m.FocusIndex = 0
|
m.FocusIndex = 0
|
||||||
case "G":
|
case "G":
|
||||||
m.FocusIndex = len(m.Files) - 1
|
m.FocusIndex = len(m.Files)
|
||||||
|
case "left", "right", "h", "l":
|
||||||
|
if m.FocusIndex == 0 && anyOf(m.Files, func(f File) bool { return f.Selected }) {
|
||||||
|
if key == "right" || key == "l" {
|
||||||
|
m.ChoiceIndex++
|
||||||
|
} else {
|
||||||
|
m.ChoiceIndex--
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.ChoiceIndex > 1 {
|
||||||
|
m.ChoiceIndex = 0
|
||||||
|
} else if m.ChoiceIndex < 0 {
|
||||||
|
m.ChoiceIndex = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
case "tab", "shift+tab", "up", "down", "j", "k":
|
case "tab", "shift+tab", "up", "down", "j", "k":
|
||||||
if key == "up" || key == "shift+tab" || key == "k" {
|
if key == "up" || key == "shift+tab" || key == "k" {
|
||||||
m.FocusIndex--
|
m.FocusIndex--
|
||||||
@@ -164,10 +194,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.FocusIndex++
|
m.FocusIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.FocusIndex >= len(m.Files) {
|
if m.FocusIndex >= len(m.Files)+1 {
|
||||||
m.FocusIndex = 0
|
m.FocusIndex = 0
|
||||||
} else if m.FocusIndex < 0 {
|
} else if m.FocusIndex < 0 {
|
||||||
m.FocusIndex = len(m.Files) - 1
|
m.FocusIndex = len(m.Files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,14 +269,22 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.Quitting = true
|
m.Quitting = true
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case parsedCfgMsg:
|
case parsedCfgMsg:
|
||||||
m.Screen = Main
|
|
||||||
m.ParsedConfig = msg.parsedConfig
|
m.ParsedConfig = msg.parsedConfig
|
||||||
|
|
||||||
if m.DryRun {
|
if m.DryRun {
|
||||||
return m, tea.Batch(tea.ExitAltScreen, tea.Quit)
|
return m, tea.Batch(tea.ExitAltScreen, tea.Quit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.IsDirectory {
|
||||||
|
m.Screen = Files
|
||||||
|
m.FocusIndex = 0
|
||||||
|
m.ChoiceIndex = 0
|
||||||
|
} else {
|
||||||
|
m.Screen = Main
|
||||||
|
|
||||||
return m, tea.Batch(m.Spinner.Tick, encodeVideo)
|
return m, tea.Batch(m.Spinner.Tick, encodeVideo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case Main:
|
case Main:
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
@@ -262,11 +300,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, tea.Sequence(tea.ExitAltScreen, tea.Quit)
|
return m, tea.Sequence(tea.ExitAltScreen, tea.Quit)
|
||||||
}
|
}
|
||||||
case encodeVideoMsg:
|
case encodeVideoMsg:
|
||||||
m.CurrentFileName = filepath.Base(m.Files[len(m.Files)-1])
|
m.CurrentFileName = filepath.Base(m.Files[len(m.Files)-1].Path)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
fullFilePath := m.Files[len(m.Files)-1]
|
file := m.Files[len(m.Files)-1]
|
||||||
encode(fullFilePath, filepath.Base(fullFilePath), m.Program, m.ParsedConfig)
|
encode(file, filepath.Base(file.Path), m.Program, m.ParsedConfig)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -276,8 +314,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
case finishedEncodingVideo:
|
case finishedEncodingVideo:
|
||||||
if m.ParsedConfig.DeleteOldVideo {
|
if m.ParsedConfig.DeleteOldVideo {
|
||||||
m.CurrentFileName = fmt.Sprintf("Deleting: %s", filepath.Base(m.Files[len(m.Files)-1]))
|
m.CurrentFileName = fmt.Sprintf("Deleting: %s", filepath.Base(m.Files[len(m.Files)-1].Path))
|
||||||
os.Remove(m.Files[len(m.Files)-1])
|
os.Remove(m.Files[len(m.Files)-1].Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Files = m.Files[:len(m.Files)-1]
|
m.Files = m.Files[:len(m.Files)-1]
|
||||||
@@ -333,19 +371,52 @@ func parseConfig(cfg []Config) ParsedConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FilesScreenView(m Model) string {
|
func FilesScreenView(m Model) string {
|
||||||
view := ""
|
view := lipgloss.NewStyle().Margin(1, 0).Render("Select the files you wish to encode.")
|
||||||
|
var files string
|
||||||
|
var buttons string
|
||||||
|
var selectAllBtnText string
|
||||||
|
|
||||||
// TODO: Create a nice view where we can select different videos and then select the encoding options and start
|
allSelected := every(m.Files, func(e File) bool { return e.Selected })
|
||||||
// encoding.
|
noneSelected := !anyOf(m.Files, func(e File) bool { return e.Selected })
|
||||||
for i, file := range m.Files {
|
|
||||||
if m.FocusIndex == i {
|
if allSelected {
|
||||||
view += fmt.Sprintf(FocusedConfig.Render("[%s] %s"), " ", filepath.Base(file))
|
selectAllBtnText = "Deselect All"
|
||||||
} else {
|
} else {
|
||||||
view += fmt.Sprintf(BlurredConfig.Render("[%s] %s"), " ", filepath.Base(file))
|
selectAllBtnText = "Select All"
|
||||||
|
}
|
||||||
|
|
||||||
|
blurredStartButton := BlurredStartButton
|
||||||
|
focusedStartButton := FocusedStartButton
|
||||||
|
|
||||||
|
if noneSelected {
|
||||||
|
blurredStartButton = DisabledStartButton
|
||||||
|
focusedStartButton = DisabledStartButton
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.FocusIndex == 0 {
|
||||||
|
if m.ChoiceIndex == 0 {
|
||||||
|
buttons = lipgloss.JoinHorizontal(0, FocusedSelectAllButton.Render(selectAllBtnText), blurredStartButton)
|
||||||
|
} else {
|
||||||
|
buttons = lipgloss.JoinHorizontal(0, BlurredSelectAllButton.Render(selectAllBtnText), focusedStartButton)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buttons = lipgloss.JoinHorizontal(0, BlurredSelectAllButton.Render(selectAllBtnText), blurredStartButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: files should be a scrollable grid
|
||||||
|
for i, file := range m.Files {
|
||||||
|
selection := " "
|
||||||
|
if file.Selected {
|
||||||
|
selection = "x"
|
||||||
|
}
|
||||||
|
if m.FocusIndex == i+1 {
|
||||||
|
files += fmt.Sprintf(FocusedConfig.Render("[%s] %s"), selection, filepath.Base(file.Path))
|
||||||
|
} else {
|
||||||
|
files += fmt.Sprintf(BlurredConfig.Render("[%s] %s"), selection, filepath.Base(file.Path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view += "\n"
|
view += lipgloss.JoinVertical(0, buttons, files)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
@@ -379,10 +450,18 @@ func CfgScreenView(m Model) string {
|
|||||||
var dryRunButton string
|
var dryRunButton string
|
||||||
|
|
||||||
if m.FocusIndex == len(m.VisibleConfig) && m.ChoiceIndex == 0 {
|
if m.FocusIndex == len(m.VisibleConfig) && m.ChoiceIndex == 0 {
|
||||||
|
if m.IsDirectory {
|
||||||
|
startButton = FocusedNextButton
|
||||||
|
} else {
|
||||||
startButton = FocusedStartButton
|
startButton = FocusedStartButton
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if m.IsDirectory {
|
||||||
|
startButton = BlurredNextButton
|
||||||
} else {
|
} else {
|
||||||
startButton = BlurredStartButton
|
startButton = BlurredStartButton
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if m.FocusIndex == len(m.VisibleConfig) && m.ChoiceIndex == 1 {
|
if m.FocusIndex == len(m.VisibleConfig) && m.ChoiceIndex == 1 {
|
||||||
dryRunButton = FocusedDryRunButton
|
dryRunButton = FocusedDryRunButton
|
||||||
@@ -399,7 +478,7 @@ func CfgScreenView(m Model) string {
|
|||||||
func MainScreenView(m Model) string {
|
func MainScreenView(m Model) string {
|
||||||
progress := ""
|
progress := ""
|
||||||
|
|
||||||
if m.IsDirectory {
|
if m.IsDirectory && m.FileCount > 1 {
|
||||||
progress = fmt.Sprintf("%s %d/%d files encoded\nFile Progress: %s\n\nTotal Progress: %s",
|
progress = fmt.Sprintf("%s %d/%d files encoded\nFile Progress: %s\n\nTotal Progress: %s",
|
||||||
m.Spinner.View(),
|
m.Spinner.View(),
|
||||||
m.FileCount-len(m.Files),
|
m.FileCount-len(m.Files),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type ffmpegProcessStart struct {
|
|||||||
|
|
||||||
type filesStatMsg struct {
|
type filesStatMsg struct {
|
||||||
fileCount int
|
fileCount int
|
||||||
files []string
|
files []File
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) statFiles() tea.Msg {
|
func (m *Model) statFiles() tea.Msg {
|
||||||
@@ -53,7 +53,7 @@ func (m *Model) statFiles() tea.Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.FileCount++
|
m.FileCount++
|
||||||
m.Files = append(m.Files, fullFilePath)
|
m.Files = append(m.Files, File{Path: fullFilePath})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.Files) == 0 {
|
if len(m.Files) == 0 {
|
||||||
@@ -61,7 +61,7 @@ func (m *Model) statFiles() tea.Msg {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.FileCount = 1
|
m.FileCount = 1
|
||||||
m.Files = append(m.Files, m.Path)
|
m.Files = append(m.Files, File{Path: m.Path, Selected: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
return filesStatMsg{
|
return filesStatMsg{
|
||||||
@@ -119,10 +119,10 @@ func (m *Model) cleanUp() tea.Msg {
|
|||||||
return tea.Quit()
|
return tea.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fullFilePath := m.Files[len(m.Files)-1]
|
file := m.Files[len(m.Files)-1]
|
||||||
fileName := filepath.Base(fullFilePath)
|
fileName := filepath.Base(file.Path)
|
||||||
|
|
||||||
parentDir := filepath.Dir(fullFilePath)
|
parentDir := filepath.Dir(file.Path)
|
||||||
extensionIndex := strings.LastIndex(fileName, ".")
|
extensionIndex := strings.LastIndex(fileName, ".")
|
||||||
newFileName := fileName[:extensionIndex]
|
newFileName := fileName[:extensionIndex]
|
||||||
extension := fileName[extensionIndex:]
|
extension := fileName[extensionIndex:]
|
||||||
|
|||||||
@@ -51,41 +51,18 @@ func find(cfgs []Config, name string) Config {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter(cfgs []Config, names ...string) []Config {
|
|
||||||
filtered := make([]Config, 0)
|
|
||||||
|
|
||||||
cfgloop:
|
|
||||||
for _, cfg := range cfgs {
|
|
||||||
for _, name := range names {
|
|
||||||
if cfg.Name == name {
|
|
||||||
continue cfgloop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered = append(filtered, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains[T comparable](slice []T, elem T) bool {
|
|
||||||
for _, e := range slice {
|
|
||||||
if e == elem {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVisibleConfigs(cfgs []Config) []Config {
|
func getVisibleConfigs(cfgs []Config) []Config {
|
||||||
parsed := parseConfig(cfgs)
|
parsed := parseConfig(cfgs)
|
||||||
|
|
||||||
switch parsed.VideoEncoder {
|
switch parsed.VideoEncoder {
|
||||||
case "copy", "librav1e":
|
case "copy", "librav1e":
|
||||||
return filter(cfgs, "Preset", "Constant Rate Factor (CRF)")
|
return filter(cfgs, func(c Config) bool {
|
||||||
|
return c.Name != "Preset" && c.Name != "Constant Rate Factor (CRF)"
|
||||||
|
})
|
||||||
case "libvpx-vp9", "libsvtav1":
|
case "libvpx-vp9", "libsvtav1":
|
||||||
return filter(cfgs, "Preset")
|
return filter(cfgs, func(c Config) bool {
|
||||||
|
return c.Name != "Preset"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfgs
|
return cfgs
|
||||||
|
|||||||
+3
-1
@@ -3,7 +3,9 @@ package main
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestFilterConfigs(t *testing.T) {
|
func TestFilterConfigs(t *testing.T) {
|
||||||
cfgs := filter(Configs, "Preset", "Constant Rate Factor (CRF)")
|
cfgs := filter(Configs, func(c Config) bool {
|
||||||
|
return c.Name != "Preset" && c.Name != "Constant Rate Factor (CRF)"
|
||||||
|
})
|
||||||
|
|
||||||
for _, cfg := range cfgs {
|
for _, cfg := range cfgs {
|
||||||
if cfg.Name == "Preset" {
|
if cfg.Name == "Preset" {
|
||||||
|
|||||||
@@ -57,14 +57,14 @@ func main() {
|
|||||||
|
|
||||||
if finalModel.DryRun {
|
if finalModel.DryRun {
|
||||||
for _, file := range finalModel.Files {
|
for _, file := range finalModel.Files {
|
||||||
parentDir := filepath.Dir(file)
|
parentDir := filepath.Dir(file.Path)
|
||||||
fileName := filepath.Base(file)
|
fileName := filepath.Base(file.Path)
|
||||||
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]", finalModel.ParsedConfig.VideoEncoder, finalModel.ParsedConfig.AudioEncoder)+extension)
|
outFileFullPath := filepath.Join(parentDir, newFileName+fmt.Sprintf("_[%s]_[%s]", finalModel.ParsedConfig.VideoEncoder, finalModel.ParsedConfig.AudioEncoder)+extension)
|
||||||
|
|
||||||
cmd := exec.Command("ffmpeg", buildFFmpegCmdArgs(file, outFileFullPath, finalModel.ParsedConfig)...)
|
cmd := exec.Command("ffmpeg", buildFFmpegCmdArgs(file.Path, outFileFullPath, finalModel.ParsedConfig)...)
|
||||||
|
|
||||||
fmt.Println(fmt.Sprintf("%s %s", Checkmark, cmd.String()))
|
fmt.Println(fmt.Sprintf("%s %s", Checkmark, cmd.String()))
|
||||||
}
|
}
|
||||||
@@ -118,8 +118,8 @@ func buildFFmpegCmdArgs(fullFilePath string, outFullFilePath string, cfg ParsedC
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(fullFilePath string, fileName string, teaP *tea.Program, cfg ParsedConfig) {
|
func encode(file File, fileName string, teaP *tea.Program, cfg ParsedConfig) {
|
||||||
mType, err := mimetype.DetectFile(fullFilePath)
|
mType, err := mimetype.DetectFile(file.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ func encode(fullFilePath string, fileName string, teaP *tea.Program, cfg ParsedC
|
|||||||
log.Fatalf("%s is not a valid video file\n", fileName)
|
log.Fatalf("%s is not a valid video file\n", fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
parentDir := filepath.Dir(fullFilePath)
|
parentDir := filepath.Dir(file.Path)
|
||||||
extensionIndex := strings.LastIndex(fileName, ".")
|
extensionIndex := strings.LastIndex(fileName, ".")
|
||||||
newFileName := fileName[:extensionIndex]
|
newFileName := fileName[:extensionIndex]
|
||||||
extension := fileName[extensionIndex:]
|
extension := fileName[extensionIndex:]
|
||||||
@@ -144,7 +144,7 @@ func encode(fullFilePath string, fileName string, teaP *tea.Program, cfg ParsedC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := buildFFmpegCmdArgs(fullFilePath, newFileFullPath, cfg, "-progress", "unix://"+getProgressSocket(fullFilePath, teaP))
|
cmdArgs := buildFFmpegCmdArgs(file.Path, newFileFullPath, cfg, "-progress", "unix://"+getProgressSocket(file.Path, teaP))
|
||||||
cmd := exec.Command("ffmpeg", cmdArgs...)
|
cmd := exec.Command("ffmpeg", cmdArgs...)
|
||||||
teaP.Send(ffmpegProcessStart{cmd})
|
teaP.Send(ffmpegProcessStart{cmd})
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ var (
|
|||||||
Light: "#1C6DD0",
|
Light: "#1C6DD0",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisabledColor = lipgloss.AdaptiveColor{
|
||||||
|
Dark: "#444",
|
||||||
|
Light: "#AAA",
|
||||||
|
}
|
||||||
|
|
||||||
BlurredConfig = lipgloss.NewStyle().
|
BlurredConfig = lipgloss.NewStyle().
|
||||||
Foreground(PrimaryColor).
|
Foreground(PrimaryColor).
|
||||||
MarginTop(1)
|
MarginTop(1)
|
||||||
@@ -49,6 +54,32 @@ var (
|
|||||||
Padding(0, 2).
|
Padding(0, 2).
|
||||||
Align(lipgloss.Center).
|
Align(lipgloss.Center).
|
||||||
Render("Start Encoding!")
|
Render("Start Encoding!")
|
||||||
|
DisabledStartButton = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(DisabledColor).
|
||||||
|
Foreground(DisabledColor).
|
||||||
|
MarginTop(1).
|
||||||
|
Padding(0, 2).
|
||||||
|
Align(lipgloss.Center).
|
||||||
|
Render("Start Encoding!")
|
||||||
|
|
||||||
|
FocusedNextButton = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(AccentColor).
|
||||||
|
Foreground(AccentColor).
|
||||||
|
MarginTop(1).
|
||||||
|
Padding(0, 2).
|
||||||
|
Align(lipgloss.Center).
|
||||||
|
Bold(true).
|
||||||
|
Render("Next")
|
||||||
|
BlurredNextButton = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(PrimaryColor).
|
||||||
|
Foreground(PrimaryColor).
|
||||||
|
MarginTop(1).
|
||||||
|
Padding(0, 2).
|
||||||
|
Align(lipgloss.Center).
|
||||||
|
Render("Next")
|
||||||
|
|
||||||
FocusedDryRunButton = lipgloss.NewStyle().
|
FocusedDryRunButton = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder()).
|
Border(lipgloss.NormalBorder()).
|
||||||
@@ -67,4 +98,21 @@ var (
|
|||||||
Padding(0, 2).
|
Padding(0, 2).
|
||||||
Align(lipgloss.Center).
|
Align(lipgloss.Center).
|
||||||
Render("Print FFmpeg command and exit")
|
Render("Print FFmpeg command and exit")
|
||||||
|
|
||||||
|
FocusedSelectAllButton = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(AccentColor).
|
||||||
|
Foreground(AccentColor).
|
||||||
|
MarginTop(1).
|
||||||
|
Padding(0, 2).
|
||||||
|
Align(lipgloss.Center).
|
||||||
|
Bold(true)
|
||||||
|
BlurredSelectAllButton = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(PrimaryColor).
|
||||||
|
Foreground(PrimaryColor).
|
||||||
|
MarginTop(1).
|
||||||
|
Padding(0, 2).
|
||||||
|
Align(lipgloss.Center).
|
||||||
|
Bold(true)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func contains[T comparable](slice []T, elem T) bool {
|
||||||
|
for _, e := range slice {
|
||||||
|
if e == elem {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func every[T any](slice []T, pred func(elem T) bool) bool {
|
||||||
|
for _, e := range slice {
|
||||||
|
if !pred(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyOf[T any](slice []T, pred func(elem T) bool) bool {
|
||||||
|
for _, e := range slice {
|
||||||
|
if pred(e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func filter[T any](slice []T, pred func(elem T) bool) []T {
|
||||||
|
filtered := make([]T, 0)
|
||||||
|
|
||||||
|
for _, e := range slice {
|
||||||
|
if pred(e) {
|
||||||
|
filtered = append(filtered, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user