feat: support srt

This commit is contained in:
CDN18 2024-09-27 20:59:59 +08:00
parent 00deeaf425
commit 7d5a8bdf54
Signed by: CDN
GPG key ID: 0C656827F9F80080
5 changed files with 205 additions and 21 deletions

View file

@ -3,8 +3,32 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings"
) )
func convert(args []string) {
if len(args) < 2 {
fmt.Println(CONVERT_USAGE)
return
}
sourceFile := args[0]
targetFile := args[1]
sourceFmt := strings.TrimPrefix(filepath.Ext(sourceFile), ".")
targetFmt := strings.TrimPrefix(filepath.Ext(targetFile), ".")
switch sourceFmt {
case "lrc":
convertLyrics(sourceFile, targetFile, targetFmt)
case "srt":
convertSRT(sourceFile, targetFile, targetFmt)
default:
fmt.Printf("unsupported source file format: %s\n", sourceFmt)
}
}
func lrcToTxt(sourceFile, targetFile string) { func lrcToTxt(sourceFile, targetFile string) {
sourceLyrics, err := parseLyrics(sourceFile) sourceLyrics, err := parseLyrics(sourceFile)
if err != nil { if err != nil {
@ -23,3 +47,76 @@ func lrcToTxt(sourceFile, targetFile string) {
fmt.Fprintln(file, content) fmt.Fprintln(file, content)
} }
} }
func lrcToSrt(sourceFile, targetFile string) {
sourceLyrics, err := parseLyrics(sourceFile)
if err != nil {
fmt.Println("Error parsing source lyrics file:", err)
return
}
file, err := os.Create(targetFile)
if err != nil {
fmt.Println("Error creating target file:", err)
return
}
defer file.Close()
for i, content := range sourceLyrics.Content {
startTime := sourceLyrics.Timeline[i]
var endTime Timestamp
if i < len(sourceLyrics.Timeline)-1 {
endTime = sourceLyrics.Timeline[i+1]
} else {
endTime = addSeconds(startTime, 3)
}
fmt.Fprintf(file, "%d\n", i+1)
fmt.Fprintf(file, "%s --> %s\n", formatSRTTimestamp(startTime), formatSRTTimestamp(endTime))
fmt.Fprintf(file, "%s\n\n", content)
}
}
func srtToLrc(sourceFile, targetFile string) {
srtEntries, err := parseSRT(sourceFile)
if err != nil {
fmt.Println("Error parsing source SRT file:", err)
return
}
lyrics := Lyrics{
Metadata: make(map[string]string),
Timeline: make([]Timestamp, len(srtEntries)),
Content: make([]string, len(srtEntries)),
}
for i, entry := range srtEntries {
lyrics.Timeline[i] = entry.StartTime
lyrics.Content[i] = entry.Content
}
err = saveLyrics(targetFile, lyrics)
if err != nil {
fmt.Println("Error saving LRC file:", err)
return
}
}
func srtToTxt(sourceFile, targetFile string) {
srtEntries, err := parseSRT(sourceFile)
if err != nil {
fmt.Println("Error parsing source SRT file:", err)
return
}
file, err := os.Create(targetFile)
if err != nil {
fmt.Println("Error creating target file:", err)
return
}
defer file.Close()
for _, entry := range srtEntries {
fmt.Fprintln(file, entry.Content)
}
}

29
lrc.go
View file

@ -4,14 +4,13 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
) )
func parseTimestamp(timeStr string) (Timestamp, error) { func parseTimestamp(timeStr string) (Timestamp, error) {
// 移除方括号 // remove brackets
timeStr = strings.Trim(timeStr, "[]") timeStr = strings.Trim(timeStr, "[]")
parts := strings.Split(timeStr, ":") parts := strings.Split(timeStr, ":")
@ -19,7 +18,7 @@ func parseTimestamp(timeStr string) (Timestamp, error) {
var err error var err error
switch len(parts) { switch len(parts) {
case 2: // 分钟:秒.毫秒 case 2: // minutes:seconds.milliseconds
minutes, err = strconv.Atoi(parts[0]) minutes, err = strconv.Atoi(parts[0])
if err != nil { if err != nil {
return Timestamp{}, err return Timestamp{}, err
@ -34,7 +33,7 @@ func parseTimestamp(timeStr string) (Timestamp, error) {
if err != nil { if err != nil {
return Timestamp{}, err return Timestamp{}, err
} }
// 根据毫秒的位数调整 // adjust milliseconds based on the number of digits
switch len(secParts[1]) { switch len(secParts[1]) {
case 1: case 1:
milliseconds *= 100 milliseconds *= 100
@ -42,7 +41,7 @@ func parseTimestamp(timeStr string) (Timestamp, error) {
milliseconds *= 10 milliseconds *= 10
} }
} }
case 3: // 小时:分钟:秒.毫秒 case 3: // hours:minutes:seconds.milliseconds
hours, err = strconv.Atoi(parts[0]) hours, err = strconv.Atoi(parts[0])
if err != nil { if err != nil {
return Timestamp{}, err return Timestamp{}, err
@ -61,7 +60,7 @@ func parseTimestamp(timeStr string) (Timestamp, error) {
if err != nil { if err != nil {
return Timestamp{}, err return Timestamp{}, err
} }
// 根据毫秒的位数调整 // adjust milliseconds based on the number of digits
switch len(secParts[1]) { switch len(secParts[1]) {
case 1: case 1:
milliseconds *= 100 milliseconds *= 100
@ -70,7 +69,7 @@ func parseTimestamp(timeStr string) (Timestamp, error) {
} }
} }
default: default:
return Timestamp{}, fmt.Errorf("无效的时间戳格式") return Timestamp{}, fmt.Errorf("invalid timestamp format")
} }
return Timestamp{Hours: hours, Minutes: minutes, Seconds: seconds, Milliseconds: milliseconds}, nil return Timestamp{Hours: hours, Minutes: minutes, Seconds: seconds, Milliseconds: milliseconds}, nil
@ -183,22 +182,14 @@ func syncLyrics(args []string) {
} }
} }
func convertLyrics(args []string) { func convertLyrics(sourceFile, targetFile, targetFmt string) {
if len(args) < 2 {
fmt.Println(CONVERT_USAGE)
return
}
sourceFile := args[0]
targetFile := args[1]
targetFmt := strings.TrimPrefix(filepath.Ext(targetFile), ".")
switch targetFmt { switch targetFmt {
case "txt": case "txt":
lrcToTxt(sourceFile, targetFile) lrcToTxt(sourceFile, targetFile)
case "srt":
lrcToSrt(sourceFile, targetFile)
default: default:
fmt.Println("Unsupported target format:", targetFmt) fmt.Printf("unsupported target format: %s\n", targetFmt)
} }
} }

View file

@ -15,7 +15,7 @@ func main() {
case "sync": case "sync":
syncLyrics(os.Args[2:]) syncLyrics(os.Args[2:])
case "convert": case "convert":
convertLyrics(os.Args[2:]) convert(os.Args[2:])
case "fmt": case "fmt":
fmtLyrics(os.Args[2:]) fmtLyrics(os.Args[2:])
case "version": case "version":

View file

@ -13,17 +13,27 @@ type Lyrics struct {
Content []string Content []string
} }
type SRTEntry struct {
Number int
StartTime Timestamp
EndTime Timestamp
Content string
}
const ( const (
VERSION = "0.2.0" VERSION = "0.2.0"
USAGE = `Usage: lyc-cli [command] [options] USAGE = `Usage: lyc-cli [command] [options]
Commands: Commands:
sync Synchronize timeline of two lyrics files sync Synchronize timeline of two lyrics files
convert Convert lyrics file to another format convert Convert lyrics file to another format
fmt Format lyrics file
help Show help` help Show help`
SYNC_USAGE = `Usage: lyc-cli sync <source> <target>` SYNC_USAGE = `Usage: lyc-cli sync <source> <target>`
CONVERT_USAGE = `Usage: lyc-cli convert <source> <target> CONVERT_USAGE = `Usage: lyc-cli convert <source> <target>
Note: Note:
Target format is determined by file extension. Supported formats: Target format is determined by file extension. Supported formats:
.txt Plain text format(No meta/timeline tags)` .txt Plain text format(No meta/timeline tags, only support as target format)
.srt SubRip Subtitle format
.lrc LRC format`
) )

86
srt.go Normal file
View file

@ -0,0 +1,86 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func parseSRT(filePath string) ([]SRTEntry, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var entries []SRTEntry
var currentEntry SRTEntry
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
if currentEntry.Number != 0 {
entries = append(entries, currentEntry)
currentEntry = SRTEntry{}
}
continue
}
if currentEntry.Number == 0 {
currentEntry.Number, _ = strconv.Atoi(line)
} else if currentEntry.StartTime.Hours == 0 {
times := strings.Split(line, " --> ")
currentEntry.StartTime = parseSRTTimestamp(times[0])
currentEntry.EndTime = parseSRTTimestamp(times[1])
} else {
currentEntry.Content += line + "\n"
}
}
if currentEntry.Number != 0 {
entries = append(entries, currentEntry)
}
return entries, scanner.Err()
}
func convertSRT(sourceFile, targetFile, targetFmt string) {
switch targetFmt {
case "txt":
srtToTxt(sourceFile, targetFile)
case "lrc":
srtToLrc(sourceFile, targetFile)
default:
fmt.Printf("unsupported target format: %s\n", targetFmt)
}
}
func formatSRTTimestamp(ts Timestamp) string {
return fmt.Sprintf("%02d:%02d:%02d,%03d", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds)
}
func parseSRTTimestamp(timeStr string) Timestamp {
t, _ := time.Parse("15:04:05,000", timeStr)
return Timestamp{
Hours: t.Hour(),
Minutes: t.Minute(),
Seconds: t.Second(),
Milliseconds: t.Nanosecond() / 1e6,
}
}
// basically for the last line of lrc
func addSeconds(ts Timestamp, seconds int) Timestamp {
t := time.Date(0, 1, 1, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds*1e6, time.UTC)
t = t.Add(time.Duration(seconds) * time.Second)
return Timestamp{
Hours: t.Hour(),
Minutes: t.Minute(),
Seconds: t.Second(),
Milliseconds: t.Nanosecond() / 1e6,
}
}