package srt import ( "bufio" "fmt" "os" "strconv" "strings" "time" "sub-cli/internal/model" ) // Parse parses an SRT file and returns a slice of SRTEntries func Parse(filePath string) ([]model.SRTEntry, error) { file, err := os.Open(filePath) if err != nil { return nil, err } defer file.Close() scanner := bufio.NewScanner(file) var entries []model.SRTEntry var currentEntry model.SRTEntry var isContent bool var contentBuffer strings.Builder for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" { if currentEntry.Number != 0 { currentEntry.Content = contentBuffer.String() entries = append(entries, currentEntry) currentEntry = model.SRTEntry{} isContent = false contentBuffer.Reset() } continue } if currentEntry.Number == 0 { currentEntry.Number, _ = strconv.Atoi(line) } else if isEntryTimeStampUnset(currentEntry) { times := strings.Split(line, " --> ") if len(times) == 2 { currentEntry.StartTime = parseSRTTimestamp(times[0]) currentEntry.EndTime = parseSRTTimestamp(times[1]) isContent = true } } else if isContent { if contentBuffer.Len() > 0 { contentBuffer.WriteString("\n") } contentBuffer.WriteString(line) } } // Don't forget the last entry if currentEntry.Number != 0 && contentBuffer.Len() > 0 { currentEntry.Content = contentBuffer.String() entries = append(entries, currentEntry) } if err := scanner.Err(); err != nil { return nil, err } return entries, nil } // isEntryTimeStampUnset checks if timestamp is unset func isEntryTimeStampUnset(entry model.SRTEntry) bool { return entry.StartTime.Hours == 0 && entry.StartTime.Minutes == 0 && entry.StartTime.Seconds == 0 && entry.StartTime.Milliseconds == 0 } // parseSRTTimestamp parses an SRT timestamp string into a Timestamp struct func parseSRTTimestamp(timeStr string) model.Timestamp { timeStr = strings.Replace(timeStr, ",", ".", 1) format := "15:04:05.000" t, err := time.Parse(format, timeStr) if err != nil { return model.Timestamp{} } return model.Timestamp{ Hours: t.Hour(), Minutes: t.Minute(), Seconds: t.Second(), Milliseconds: t.Nanosecond() / 1000000, } } // Generate generates an SRT file from a slice of SRTEntries func Generate(entries []model.SRTEntry, filePath string) error { file, err := os.Create(filePath) if err != nil { return err } defer file.Close() for _, entry := range entries { fmt.Fprintf(file, "%d\n", entry.Number) fmt.Fprintf(file, "%s --> %s\n", formatSRTTimestamp(entry.StartTime), formatSRTTimestamp(entry.EndTime)) fmt.Fprintf(file, "%s\n\n", entry.Content) } return nil } // formatSRTTimestamp formats a Timestamp struct as an SRT timestamp string func formatSRTTimestamp(ts model.Timestamp) string { return fmt.Sprintf("%02d:%02d:%02d,%03d", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds) } // Format standardizes and formats an SRT file func Format(filePath string) error { // Parse the file entries, err := Parse(filePath) if err != nil { return fmt.Errorf("error parsing SRT file: %w", err) } // Standardize entry numbering and ensure consistent formatting for i := range entries { entries[i].Number = i + 1 // Ensure sequential numbering } // Write back the formatted content return Generate(entries, filePath) } // ConvertToLyrics converts SRT entries to a Lyrics structure func ConvertToLyrics(entries []model.SRTEntry) model.Lyrics { lyrics := model.Lyrics{ Metadata: make(map[string]string), } for _, entry := range entries { lyrics.Timeline = append(lyrics.Timeline, entry.StartTime) lyrics.Content = append(lyrics.Content, entry.Content) } return lyrics }