182 lines
4.1 KiB
Go
182 lines
4.1 KiB
Go
package lrc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"sub-cli/internal/model"
|
|
)
|
|
|
|
// Parse parses an LRC file and returns a Lyrics struct
|
|
func Parse(filePath string) (model.Lyrics, error) {
|
|
lyrics := model.Lyrics{
|
|
Metadata: make(map[string]string),
|
|
}
|
|
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return lyrics, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
metadataRegex := regexp.MustCompile(`\[([\w:]+):(.*?)\]`)
|
|
timestampRegex := regexp.MustCompile(`\[(\d+:\d+(?:\.\d+)?)\]`)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Extract metadata
|
|
metadataMatches := metadataRegex.FindAllStringSubmatch(line, -1)
|
|
for _, match := range metadataMatches {
|
|
if len(match) >= 3 {
|
|
key := match[1]
|
|
value := match[2]
|
|
lyrics.Metadata[key] = value
|
|
}
|
|
}
|
|
|
|
// Extract timestamp and content
|
|
timestampMatches := timestampRegex.FindAllStringSubmatch(line, -1)
|
|
if len(timestampMatches) > 0 {
|
|
var timestamps []model.Timestamp
|
|
lineContent := line
|
|
|
|
for _, match := range timestampMatches {
|
|
if len(match) >= 2 {
|
|
timestamp, err := ParseTimestamp(match[1])
|
|
if err == nil {
|
|
timestamps = append(timestamps, timestamp)
|
|
}
|
|
lineContent = strings.Replace(lineContent, match[0], "", 1)
|
|
}
|
|
}
|
|
|
|
lineContent = strings.TrimSpace(lineContent)
|
|
if lineContent != "" {
|
|
for range timestamps {
|
|
lyrics.Timeline = append(lyrics.Timeline, timestamps...)
|
|
lyrics.Content = append(lyrics.Content, lineContent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return lyrics, nil
|
|
}
|
|
|
|
// ParseTimestamp parses an LRC timestamp string into a Timestamp struct
|
|
func ParseTimestamp(timeStr string) (model.Timestamp, error) {
|
|
// remove brackets
|
|
timeStr = strings.Trim(timeStr, "[]")
|
|
|
|
parts := strings.Split(timeStr, ":")
|
|
var hours, minutes, seconds, milliseconds int
|
|
var err error
|
|
|
|
switch len(parts) {
|
|
case 2: // minutes:seconds.milliseconds
|
|
minutes, err = strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
secParts := strings.Split(parts[1], ".")
|
|
seconds, err = strconv.Atoi(secParts[0])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
if len(secParts) > 1 {
|
|
milliseconds, err = strconv.Atoi(secParts[1])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
// adjust milliseconds based on the number of digits
|
|
switch len(secParts[1]) {
|
|
case 1:
|
|
milliseconds *= 100
|
|
case 2:
|
|
milliseconds *= 10
|
|
}
|
|
}
|
|
case 3: // hours:minutes:seconds.milliseconds
|
|
hours, err = strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
minutes, err = strconv.Atoi(parts[1])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
secParts := strings.Split(parts[2], ".")
|
|
seconds, err = strconv.Atoi(secParts[0])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
if len(secParts) > 1 {
|
|
milliseconds, err = strconv.Atoi(secParts[1])
|
|
if err != nil {
|
|
return model.Timestamp{}, err
|
|
}
|
|
// adjust milliseconds based on the number of digits
|
|
switch len(secParts[1]) {
|
|
case 1:
|
|
milliseconds *= 100
|
|
case 2:
|
|
milliseconds *= 10
|
|
}
|
|
}
|
|
default:
|
|
return model.Timestamp{}, fmt.Errorf("invalid timestamp format: %s", timeStr)
|
|
}
|
|
|
|
return model.Timestamp{
|
|
Hours: hours,
|
|
Minutes: minutes,
|
|
Seconds: seconds,
|
|
Milliseconds: milliseconds,
|
|
}, nil
|
|
}
|
|
|
|
// Generate generates an LRC file from a Lyrics struct
|
|
func Generate(lyrics model.Lyrics, filePath string) error {
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write metadata
|
|
for key, value := range lyrics.Metadata {
|
|
fmt.Fprintf(file, "[%s:%s]\n", key, value)
|
|
}
|
|
|
|
// Write content with timestamps
|
|
for i, content := range lyrics.Content {
|
|
if i < len(lyrics.Timeline) {
|
|
timestamp := lyrics.Timeline[i]
|
|
fmt.Fprintf(file, "[%02d:%02d.%03d]%s\n",
|
|
timestamp.Minutes,
|
|
timestamp.Seconds,
|
|
timestamp.Milliseconds,
|
|
content)
|
|
} else {
|
|
fmt.Fprintln(file, content)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Format formats an LRC file
|
|
func Format(filePath string) error {
|
|
lyrics, err := Parse(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return Generate(lyrics, filePath)
|
|
}
|