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

View file

@ -13,17 +13,27 @@ type Lyrics struct {
Content []string
}
type SRTEntry struct {
Number int
StartTime Timestamp
EndTime Timestamp
Content string
}
const (
VERSION = "0.2.0"
USAGE = `Usage: lyc-cli [command] [options]
Commands:
sync Synchronize timeline of two lyrics files
convert Convert lyrics file to another format
fmt Format lyrics file
help Show help`
SYNC_USAGE = `Usage: lyc-cli sync <source> <target>`
CONVERT_USAGE = `Usage: lyc-cli convert <source> <target>
Note:
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,
}
}