diff --git a/main.go b/main.go index 630207b..944005a 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ type model struct { interval textinput.Model duration textinput.Model output textinput.Model + fps textinput.Model focusIndex int inputs []textinput.Model spinner spinner.Model @@ -60,6 +61,8 @@ type model struct { recordingDone bool finalMessage string err error + intervalValue float64 + fpsValue float64 } // Styles @@ -102,7 +105,7 @@ var ( func initialModel() model { m := model{ state: stateMenu, - inputs: make([]textinput.Model, 3), + inputs: make([]textinput.Model, 4), spinner: spinner.New(), logs: make([]string, 0), } @@ -124,6 +127,13 @@ func initialModel() model { m.duration.Width = 30 m.duration.Prompt = "│ " + // Setup fps input + m.fps = textinput.New() + m.fps.Placeholder = "30" // Default to 30 FPS + m.fps.CharLimit = 10 + m.fps.Width = 30 + m.fps.Prompt = "│ " + // Setup output input m.output = textinput.New() m.output.Placeholder = "timelapse.mp4" @@ -133,7 +143,8 @@ func initialModel() model { m.inputs[0] = m.interval m.inputs[1] = m.duration - m.inputs[2] = m.output + m.inputs[2] = m.fps + m.inputs[3] = m.output m.spinner.Spinner = spinner.Dot m.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#7D56F4")) @@ -214,26 +225,29 @@ func (m model) updateMenu(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m.startRecording() } - // Cycle through inputs + // Cycle through inputs and button if s == "up" || s == "shift+tab" { m.focusIndex-- } else { m.focusIndex++ } - if m.focusIndex > len(m.inputs) { + // Wrap around + if m.focusIndex > len(m.inputs) { // len(m.inputs) is 4. indices 0,1,2,3 for inputs, 4 for button m.focusIndex = 0 } else if m.focusIndex < 0 { - m.focusIndex = len(m.inputs) + m.focusIndex = len(m.inputs) // Go to button } - cmds := make([]tea.Cmd, len(m.inputs)) - for i := 0; i <= len(m.inputs)-1; i++ { + var cmds []tea.Cmd + for i := 0; i < len(m.inputs); i++ { if i == m.focusIndex { - cmds[i] = m.inputs[i].Focus() + // Set focused state + cmds = append(cmds, m.inputs[i].Focus()) m.inputs[i].PromptStyle = focusedStyle m.inputs[i].TextStyle = focusedStyle } else { + // Remove focused state m.inputs[i].Blur() m.inputs[i].PromptStyle = noStyle m.inputs[i].TextStyle = noStyle @@ -243,9 +257,13 @@ func (m model) updateMenu(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } - // Handle character input for focused field - cmd := m.updateInputs(msg) - return m, cmd + // Handle character input for focused field if it's an input field + if m.focusIndex < len(m.inputs) { + cmd := m.updateInputs(msg) + return m, cmd + } + + return m, nil } func (m model) updateRecording(msg tea.KeyMsg) (tea.Model, tea.Cmd) { @@ -294,25 +312,38 @@ func (m model) startRecording() (tea.Model, tea.Cmd) { return m, nil } - output := m.inputs[2].Value() + output := m.inputs[3].Value() if output == "" { output = "timelapse.mp4" } + fps := m.inputs[2].Value() + if fps == "" { + fps = "30" + } + fpsFloat, err := strconv.ParseFloat(fps, 64) + if err != nil || fpsFloat <= 0 { + m.state = stateError + m.finalMessage = "Invalid FPS value (use a positive number)" + return m, nil + } + // Change state to recording m.state = stateRecording m.startTime = time.Now() m.recordingDone = false + m.intervalValue = intervalFloat + m.fpsValue = fpsFloat // Start the Python subprocess return m, tea.Batch( m.spinner.Tick, tick(), - m.runTimelapse(intervalFloat, durationFloat, output), + m.runTimelapse(intervalFloat, durationFloat, fpsFloat, output), ) } -func (m model) runTimelapse(interval, duration float64, output string) tea.Cmd { +func (m model) runTimelapse(interval, duration, fps float64, output string) tea.Cmd { return func() tea.Msg { // Build command cmd := exec.Command( @@ -320,6 +351,7 @@ func (m model) runTimelapse(interval, duration float64, output string) tea.Cmd { "timelapse.py", "-i", fmt.Sprintf("%.2f", interval), "-d", fmt.Sprintf("%.2f", duration), + "-f", fmt.Sprintf("%.2f", fps), "-o", output, ) @@ -438,8 +470,8 @@ func (m model) viewMenu() string { b.WriteString(label + "\n") b.WriteString(m.inputs[1].View() + "\n\n") - // Output input - label = "Output file:" + // FPS input + label = "Output FPS:" if m.focusIndex == 2 { label = focusedStyle.Render("▸ " + label) } else { @@ -448,9 +480,19 @@ func (m model) viewMenu() string { b.WriteString(label + "\n") b.WriteString(m.inputs[2].View() + "\n\n") + // Output input + label = "Output file:" + if m.focusIndex == 3 { + label = focusedStyle.Render("▸ " + label) + } else { + label = blurredStyle.Render(" " + label) + } + b.WriteString(label + "\n") + b.WriteString(m.inputs[3].View() + "\n\n") + // Start button button := "[ Start Recording ]" - if m.focusIndex == 3 { + if m.focusIndex == 4 { button = focusedStyle.Render("▸ " + button) } else { button = blurredStyle.Render(" " + button) @@ -483,6 +525,10 @@ func (m model) viewRecording() string { } b.WriteString(fmt.Sprintf("\nElapsed: %s\n", elapsed.Round(time.Second))) + if m.fpsValue > 0 { + recordedTime := time.Duration(float64(m.progress.current)/m.fpsValue*float64(time.Second)) + b.WriteString(fmt.Sprintf("Recorded: %s\n", recordedTime.Round(time.Second))) + } // Show recent logs if len(m.logs) > 0 {