feat: git init
This commit is contained in:
56
pkg/audio/audio.go
Normal file
56
pkg/audio/audio.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SupportedAudioFormats lists the audio formats that can be processed
|
||||
type SupportedAudioFormats []string
|
||||
|
||||
var DefaultSupportedFormats = SupportedAudioFormats{
|
||||
".mp3",
|
||||
".wav",
|
||||
".flac",
|
||||
".m4a",
|
||||
".ogg",
|
||||
".opus",
|
||||
}
|
||||
|
||||
// IsSupported checks if a file has a supported audio format
|
||||
type AudioFile struct {
|
||||
Path string
|
||||
Format string
|
||||
Size int64
|
||||
}
|
||||
|
||||
func NewAudioFile(path string) (*AudioFile, error) {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
if !IsSupported(ext) {
|
||||
return nil, errors.New("unsupported audio format: " + ext)
|
||||
}
|
||||
|
||||
return &AudioFile{
|
||||
Path: path,
|
||||
Format: ext,
|
||||
Size: fileInfo.Size(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsSupported checks if the given extension is in supported formats
|
||||
func IsSupported(ext string) bool {
|
||||
ext = strings.ToLower(ext)
|
||||
for _, format := range DefaultSupportedFormats {
|
||||
if ext == format {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
33
pkg/output/formatter.go
Normal file
33
pkg/output/formatter.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"transcribe/internal/whisper"
|
||||
)
|
||||
|
||||
// Formatter interface for converting transcription results to various output formats
|
||||
type Formatter interface {
|
||||
Format(result *whisper.TranscriptionResult) (string, error)
|
||||
}
|
||||
|
||||
// FormatType represents the output format type
|
||||
type FormatType string
|
||||
|
||||
const (
|
||||
FormatText FormatType = "text"
|
||||
FormatSRT FormatType = "srt"
|
||||
FormatJSON FormatType = "json"
|
||||
)
|
||||
|
||||
// NewFormatter creates a formatter for the given format type
|
||||
func NewFormatter(format FormatType) Formatter {
|
||||
switch format {
|
||||
case FormatSRT:
|
||||
return &SRTFormatter{}
|
||||
case FormatJSON:
|
||||
return &JSONFormatter{}
|
||||
case FormatText:
|
||||
fallthrough
|
||||
default:
|
||||
return &TextFormatter{}
|
||||
}
|
||||
}
|
||||
19
pkg/output/json.go
Normal file
19
pkg/output/json.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"transcribe/internal/whisper"
|
||||
)
|
||||
|
||||
// JSONFormatter formats transcription results as JSON
|
||||
type JSONFormatter struct{}
|
||||
|
||||
// Format converts transcription result to JSON format
|
||||
func (f *JSONFormatter) Format(result *whisper.TranscriptionResult) (string, error) {
|
||||
data, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
49
pkg/output/srt.go
Normal file
49
pkg/output/srt.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"transcribe/internal/whisper"
|
||||
)
|
||||
|
||||
// SRTFormatter formats transcription results as SRT subtitles
|
||||
type SRTFormatter struct{}
|
||||
|
||||
// Format converts transcription result to SRT format
|
||||
func (f *SRTFormatter) Format(result *whisper.TranscriptionResult) (string, error) {
|
||||
var builder strings.Builder
|
||||
|
||||
for i, seg := range result.Segments {
|
||||
// Subtitle number (1-indexed)
|
||||
builder.WriteString(fmt.Sprintf("%d\n", i+1))
|
||||
|
||||
// Timestamps in SRT format: HH:MM:SS,mmm --> HH:MM:SS,mmm
|
||||
startTime := formatSRTTimestamp(seg.Start)
|
||||
endTime := formatSRTTimestamp(seg.End)
|
||||
builder.WriteString(fmt.Sprintf("%s --> %s\n", startTime, endTime))
|
||||
|
||||
// Text with optional speaker label
|
||||
text := strings.TrimSpace(seg.Text)
|
||||
if seg.Speaker != "" {
|
||||
text = fmt.Sprintf("[%s] %s", seg.Speaker, text)
|
||||
}
|
||||
builder.WriteString(text)
|
||||
builder.WriteString("\n\n")
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(builder.String(), "\n"), nil
|
||||
}
|
||||
|
||||
// formatSRTTimestamp converts seconds to SRT timestamp format (HH:MM:SS,mmm)
|
||||
func formatSRTTimestamp(seconds float64) string {
|
||||
totalMs := int64(seconds * 1000)
|
||||
ms := totalMs % 1000
|
||||
totalSeconds := totalMs / 1000
|
||||
s := totalSeconds % 60
|
||||
totalMinutes := totalSeconds / 60
|
||||
m := totalMinutes % 60
|
||||
h := totalMinutes / 60
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d,%03d", h, m, s, ms)
|
||||
}
|
||||
41
pkg/output/text.go
Normal file
41
pkg/output/text.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"transcribe/internal/whisper"
|
||||
)
|
||||
|
||||
// TextFormatter formats transcription results as plain text with timestamps
|
||||
type TextFormatter struct{}
|
||||
|
||||
// Format converts transcription result to plain text with timestamps
|
||||
func (f *TextFormatter) Format(result *whisper.TranscriptionResult) (string, error) {
|
||||
var builder strings.Builder
|
||||
|
||||
for _, seg := range result.Segments {
|
||||
// Format: [MM:SS - MM:SS] [Speaker] Text
|
||||
startTime := formatTextTimestamp(seg.Start)
|
||||
endTime := formatTextTimestamp(seg.End)
|
||||
|
||||
text := strings.TrimSpace(seg.Text)
|
||||
if seg.Speaker != "" {
|
||||
builder.WriteString(fmt.Sprintf("[%s - %s] [%s] %s\n", startTime, endTime, seg.Speaker, text))
|
||||
} else {
|
||||
builder.WriteString(fmt.Sprintf("[%s - %s] %s\n", startTime, endTime, text))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(builder.String(), "\n"), nil
|
||||
}
|
||||
|
||||
// formatTextTimestamp converts seconds to MM:SS.s format
|
||||
func formatTextTimestamp(seconds float64) string {
|
||||
totalSeconds := int(seconds)
|
||||
m := totalSeconds / 60
|
||||
s := totalSeconds % 60
|
||||
tenths := int((seconds - float64(totalSeconds)) * 10)
|
||||
|
||||
return fmt.Sprintf("%02d:%02d.%d", m, s, tenths)
|
||||
}
|
||||
84
pkg/progress/spinner.go
Normal file
84
pkg/progress/spinner.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Spinner displays an animated spinner with a message
|
||||
type Spinner struct {
|
||||
message string
|
||||
frames []string
|
||||
interval time.Duration
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
}
|
||||
|
||||
// NewSpinner creates a new spinner with the given message
|
||||
func NewSpinner(message string) *Spinner {
|
||||
return &Spinner{
|
||||
message: message,
|
||||
frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
interval: 80 * time.Millisecond,
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins the spinner animation
|
||||
func (s *Spinner) Start() {
|
||||
s.mu.Lock()
|
||||
if s.running {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.running = true
|
||||
s.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
case <-s.stop:
|
||||
// Clear the line and signal done
|
||||
fmt.Print("\r\033[K")
|
||||
close(s.done)
|
||||
return
|
||||
default:
|
||||
fmt.Printf("\r%s %s", s.frames[i%len(s.frames)], s.message)
|
||||
i++
|
||||
time.Sleep(s.interval)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops the spinner and clears the line
|
||||
func (s *Spinner) Stop() {
|
||||
s.mu.Lock()
|
||||
if !s.running {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.running = false
|
||||
s.mu.Unlock()
|
||||
|
||||
close(s.stop)
|
||||
<-s.done
|
||||
}
|
||||
|
||||
// StopWithMessage stops the spinner and prints a final message
|
||||
func (s *Spinner) StopWithMessage(message string) {
|
||||
s.Stop()
|
||||
fmt.Println(message)
|
||||
}
|
||||
|
||||
// UpdateMessage updates the spinner message while running
|
||||
func (s *Spinner) UpdateMessage(message string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.message = message
|
||||
}
|
||||
Reference in New Issue
Block a user