refactor
This commit is contained in:
parent
aedc4a4518
commit
9b0e2ed6dc
15 changed files with 693 additions and 540 deletions
182
internal/format/lrc/lrc.go
Normal file
182
internal/format/lrc/lrc.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue