feat: support srt
This commit is contained in:
parent
00deeaf425
commit
7d5a8bdf54
5 changed files with 205 additions and 21 deletions
97
convert.go
97
convert.go
|
@ -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
29
lrc.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -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":
|
||||
|
|
12
model.go
12
model.go
|
@ -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
86
srt.go
Normal 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,
|
||||
}
|
||||
}
|
Reference in a new issue