重构项目,调整了项目结构

This commit is contained in:
Hami Lemon 2022-03-28 18:26:40 +08:00
parent 5f49bde3ae
commit c9b1b38c03
11 changed files with 456 additions and 386 deletions

4
.gitignore vendored
View file

@ -87,6 +87,6 @@ fabric.properties
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
/example/*.srt *.srt
/example/*.lrc *.lrc
/temp /temp

View file

@ -1,11 +1,10 @@
package main package lrc2srt
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"os"
) )
type CloudLyricBase struct { type CloudLyricBase struct {
@ -38,15 +37,15 @@ func Get163Lyric(id string) (lyric, tLyric string) {
req, _ := http.NewRequest("GET", api, nil) req, _ := http.NewRequest("GET", api, nil)
//必须设置Referer,否则会请求失败 //必须设置Referer,否则会请求失败
req.Header.Add("Referer", "https://music.163.com") req.Header.Add("Referer", "https://music.163.com")
req.Header.Add("User-Agent", ChromeUA) req.Header.Add("User-Agent", CHROME_UA)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Printf("网络错误:%v\n", err) fmt.Printf("网络错误:%v\n", err)
os.Exit(1) panic("网络异常,获取失败。")
} }
if resp == nil || resp.StatusCode != http.StatusOK { if resp == nil || resp.StatusCode != http.StatusOK {
fmt.Printf("网络请求失败,状态码为:%d\n", resp.StatusCode) fmt.Printf("网络请求失败,状态码为:%d\n", resp.StatusCode)
os.Exit(1) panic("获取失败,未能正确获取到数据")
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -54,7 +53,7 @@ func Get163Lyric(id string) (lyric, tLyric string) {
err = json.NewDecoder(resp.Body).Decode(&cloudLyric) err = json.NewDecoder(resp.Body).Decode(&cloudLyric)
if cloudLyric.Sgc { if cloudLyric.Sgc {
fmt.Printf("获取歌词失败,返回的结果为:%+v请检查id是否正确\n", cloudLyric) fmt.Printf("获取歌词失败,返回的结果为:%+v请检查id是否正确\n", cloudLyric)
os.Exit(1) panic("id错误获取歌词失败")
} }
return cloudLyric.Lrc.Lyric, cloudLyric.TLyric.Lyric return cloudLyric.Lrc.Lyric, cloudLyric.TLyric.Lyric
} }

4
go.mod
View file

@ -1,6 +1,6 @@
module github.com/hami_lemon/lrc2srt module github.com/Hami-Lemon/lrc2srt
go 1.17 go 1.18
require github.com/jessevdk/go-flags v1.5.0 require github.com/jessevdk/go-flags v1.5.0

128
lrc.go Normal file
View file

@ -0,0 +1,128 @@
package lrc2srt
import (
"regexp"
"strconv"
"strings"
"github.com/Hami-Lemon/lrc2srt/glist"
)
type LRCNode struct {
//歌词出现的时间,单位毫秒
time int
//歌词内容
content string
}
type LRC struct {
//歌曲名
Title string
//歌手名
Artist string
//专辑名
Album string
//歌词作者
Author string
//歌词列表
LrcList glist.Queue[*LRCNode]
}
func ParseLRC(src string) *LRC {
if src == "" {
return nil
}
//标准的LRC文件为一行一句歌词
lyrics := strings.Split(src, "\n")
//标识标签的正则 [ar:A-SOUL]形式
infoRegx := regexp.MustCompile(`^\[([a-z]+):([\s\S]*)]`)
lrc := &LRC{LrcList: glist.NewLinkedList[*LRCNode]()}
//解析标识信息
for {
if len(lyrics) == 0 {
break
}
l := lyrics[0]
//根据正则表达式进行匹配
info := infoRegx.FindStringSubmatch(l)
//标识信息位于歌词信息前面,当出现未匹配成功时,即可退出循环
if info != nil {
//info 中为匹配成功的字符串和 子组合(正则表达式中括号包裹的部分)
//例如,对于标识信息:[ar:A-SOUL]info中的数据为[[ar:A-SOUL], ar, A-SOUL]
key := info[1]
switch key {
case "ar":
//歌手名
if len(info) == 3 {
lrc.Artist = info[2]
}
case "ti":
//歌曲名
if len(info) == 3 {
lrc.Title = info[2]
}
case "al":
//专辑名
if len(info) == 3 {
lrc.Album = info[2]
}
case "by":
//歌词作者
if len(info) == 3 {
lrc.Author = info[2]
}
}
lyrics = lyrics[1:]
} else {
break
}
}
//歌词信息的正则,"[00:10.222]超级的敏感"或“[00:10:222]超级的敏感”或“[00:10]超级的敏感”或“[00:10.22]超级的敏感”或“[00:10:22]超级的敏感”
lyricRegx := regexp.MustCompile(`\[(\d\d):(\d\d)([.:]\d{2,3})?]([\s\S]+)`)
for _, l := range lyrics {
content := lyricRegx.FindStringSubmatch(l)
if content != nil {
node := SplitLyric(content[1:])
if node != nil {
lrc.LrcList.PushBack(node)
}
}
}
return lrc
}
// SplitLyric 对分割出来的歌词信息进行解析
func SplitLyric(src []string) *LRCNode {
minute, err := strconv.Atoi(src[0])
second, err := strconv.Atoi(src[1])
if err != nil {
panic("错误的时间格式:" + strings.Join(src, " "))
return nil
}
millisecond, content := 0, ""
_len := len(src)
if _len == 3 {
//歌词信息没有毫秒值
content = src[2]
} else if _len == 4 {
content = src[3]
//毫秒字符串的第一个字符是 "." 或 ":",需要去掉
ms := src[2][1:]
millisecond, err = strconv.Atoi(ms)
//QQ音乐歌词文件中毫秒值只有两位需要特殊处理一下
if len(ms) == 2 {
millisecond *= 10
}
if err != nil {
panic("错误的时间格式:" + strings.Join(src, " "))
return nil
}
}
lrcNode := &LRCNode{
time: time2Millisecond(minute, second, millisecond),
content: content,
}
return lrcNode
}

337
lts.go
View file

@ -1,337 +0,0 @@
package main
import (
"bufio"
"fmt"
"net/http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/jessevdk/go-flags"
)
type SRTContent struct {
//序号从1开始
Index int
//开始时间,单位毫秒
Start int
//结束时间,单位毫秒
End int
//歌词内容
Text string
}
type SRT struct {
//歌曲名
Title string
//歌手名 未指定文件名是,文件名格式为:歌曲名-歌手名.srt
Artist string
Content []*SRTContent
}
// Option 运行时传入的选项
type Option struct {
Id string `short:"i" long:"id" description:"歌曲的id网易云和QQ音乐均可。"`
Input string `short:"I" long:"input" description:"需要转换的LRC文件路径。"`
Source string `short:"s" long:"source" description:"当设置id时有效指定从网易云163还是QQ音乐qq上获取歌词。" default:"163" choice:"163" choice:"qq" choice:"QQ"`
Download bool `short:"d" long:"download" description:"只下载歌词,而不进行解析。"`
Mode int `short:"m" long:"mode" default:"1" description:"原文和译文的排列模式,可选值有:[1] [2] [3]" choice:"1" choice:"2" choice:"3"`
Version bool `short:"v" long:"version" description:"获取版本信息"`
Output string `no-flag:""`
}
const (
// VERSION 当前版本
VERSION = `"0.1.1" (build 2022.03.05)`
)
var (
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"
client = http.Client{}
opt Option
)
func main() {
args, err := flags.Parse(&opt)
if err != nil {
os.Exit(0)
}
//显示版本信息
if opt.Version {
fmt.Printf("LrcToSrt(lts) version %s\n", VERSION)
os.Exit(0)
}
//获取保存的文件名
if len(args) != 0 {
opt.Output = args[0]
}
//获取歌词lyric为原文歌词tranLyric为译文歌词
var lyric, tranLyric string
if opt.Id != "" {
if opt.Source != "163" {
lyric, tranLyric = GetQQLyric(opt.Id)
} else {
lyric, tranLyric = Get163Lyric(opt.Id)
}
//下载歌词
if opt.Download {
//对文件名进行处理
o := opt.Output
if o == "" {
o = opt.Id + ".lrc"
} else if !strings.HasSuffix(o, ".lrc") {
o += ".lrc"
}
WriteFile(o, lyric)
if tranLyric != "" {
WriteFile("tran_"+o, tranLyric)
}
fmt.Println("下载歌词完成!")
return
}
} else if opt.Input != "" {
//从文件中获取歌词
if !strings.HasSuffix(opt.Input, ".lrc") {
fmt.Println("Error: 不支持的格式目前只支持lrc歌词文件。")
os.Exit(1)
}
lyric = ReadFile(opt.Input)
if lyric == "" {
fmt.Println("获取歌词失败,文件内容为空。")
os.Exit(1)
}
} else {
fmt.Println("Error: 请指定需要转换的歌词。")
os.Exit(1)
}
//原文和译文作为两条歌词流信息分开保存但最终生成的srt文件会同时包含两个信息
lyricSRT, tranLyricSRT := Lrc2Srt(lyric), Lrc2Srt(tranLyric)
SaveSRT(lyricSRT, tranLyricSRT, opt.Output)
}
// SaveSRT 保存数据为SRT文件
func SaveSRT(srt *SRT, tranSrt *SRT, name string) {
if tranSrt == nil {
//没有译文时用一个空的对象的代替减少nil判断
tranSrt = &SRT{Content: make([]*SRTContent, 0)}
//因为没有译文所以mode选项无效设为2之后后面不用做多余判断
opt.Mode = 2
}
//处理结果文件的文件名
if name == "" {
title := srt.Title
if title != "" {
//以歌曲名命名
name = fmt.Sprintf("%s.srt", title)
} else if opt.Id != "" {
//以歌曲的id命名
name = fmt.Sprintf("%s.srt", opt.Id)
} else if opt.Input != "" {
//以LRC文件的文件名命名
name = fmt.Sprintf("%s.srt", opt.Input[:len(opt.Input)-4])
} else {
//以当前时间的毫秒值命名
name = fmt.Sprintf("%d.srt", time.Now().Unix())
}
} else if !strings.HasSuffix(name, ".srt") {
name += ".srt"
}
file, err := os.Create(name)
if err != nil {
fmt.Printf("创建结果文件失败:%v\n", err)
os.Exit(1)
}
defer func(file *os.File) {
_ = file.Close()
}(file)
//6KB的缓存区大部分歌词生成的SRT文件均在4-6kb左右
writer := bufio.NewWriterSize(file, 1024*6)
/*
原文和译文歌词的排列方式因为原文歌词中可能包含一些非歌词信息
例如作词者作曲者等而在译文歌词中却可能不包含这些
*/
//srt的序号
index := 1
switch opt.Mode {
case 1:
//将两个歌词合并成一个新的数组
size := len(srt.Content) + len(tranSrt.Content)
temp := make([]*SRTContent, size, size)
i := 0
for _, v := range srt.Content {
temp[i] = v
i++
}
for _, v := range tranSrt.Content {
temp[i] = v
i++
}
//按开始时间进行排序使用SliceStable确保一句歌词的原文在译文之前
sort.SliceStable(temp, func(i, j int) bool {
return temp[i].Start < temp[j].Start
})
//写入文件
for i, v := range temp {
v.Index = i + 1
_, _ = writer.WriteString(v.String())
}
case 2:
//原文在上,译文在下
for _, item := range srt.Content {
item.Index = index
_, _ = writer.WriteString(item.String())
index++
}
for _, item := range tranSrt.Content {
item.Index = index
_, _ = writer.WriteString(item.String())
index++
}
case 3:
//译文在上,原文在下
for _, item := range tranSrt.Content {
item.Index = index
_, _ = writer.WriteString(item.String())
index++
}
for _, item := range srt.Content {
item.Index = index
_, _ = writer.WriteString(item.String())
index++
}
}
err = writer.Flush()
if err != nil {
fmt.Printf("保存结果失败:%v\n", err)
} else {
fmt.Printf("转换文件完成,保存结果为:%s\n", name)
}
}
// Lrc2Srt 将原始个LRC字符串歌词解析SRT对象
func Lrc2Srt(src string) *SRT {
if src == "" {
return nil
}
//标准的LRC文件为一行一句歌词
lyrics := strings.Split(src, "\n")
//标识标签的正则 [ar:A-SOUL]形式
infoRegx := regexp.MustCompile(`^\[([a-z]+):([\s\S]*)]`)
srt := &SRT{Content: make([]*SRTContent, 0, len(lyrics))}
//解析标识信息
for {
if len(lyrics) == 0 {
break
}
l := lyrics[0]
//根据正则表达式进行匹配
info := infoRegx.FindStringSubmatch(l)
//标识信息位于歌词信息前面,当出现未匹配成功时,即可退出循环
if info != nil {
//info 中为匹配成功的字符串和 子组合(正则表达式中括号包裹的部分)
//例如,对于标识信息:[ar:A-SOUL]info中的数据为[[ar:A-SOUL] ar A-SOUL]
key := info[1]
switch key {
case "ar":
//歌手名
if len(info) == 3 {
srt.Artist = info[2]
}
case "ti":
//歌曲名
if len(info) == 3 {
srt.Title = info[2]
}
}
lyrics = lyrics[1:]
} else {
break
}
}
//歌词信息的正则,"[00:10.222]超级的敏感"或“[00:10:222]超级的敏感”或“[00:10]超级的敏感”或“[00:10.22]超级的敏感”或“[00:10:22]超级的敏感”
lyricRegx := regexp.MustCompile(`\[(\d\d):(\d\d)([.:]\d{2,3})?]([\s\S]+)`)
index := 0
for _, l := range lyrics {
content := lyricRegx.FindStringSubmatch(l)
if content != nil {
c := SplitLyric(content[1:])
if c != nil {
if index != 0 {
//前一条字幕的结束时间为当前字幕开始的时间
srt.Content[index-1].End = c.Start
}
srt.Content = append(srt.Content, c)
index++
}
}
}
//最后一条字幕
last := srt.Content[index-1]
//最后一条字幕的结束时间为其开始时间 + 10秒
last.End = last.Start + 10000
return srt
}
// SplitLyric 对分割出来的歌词信息进行解析
func SplitLyric(src []string) *SRTContent {
minute, err := strconv.Atoi(src[0])
second, err := strconv.Atoi(src[1])
if err != nil {
fmt.Printf("错误的时间格式:%s\n", src)
return nil
}
millisecond, content := 0, ""
_len := len(src)
if _len == 3 {
//歌词信息没有毫秒值
content = src[2]
} else if _len == 4 {
content = src[3]
//字符串的第一个字符是 "." 或 ":"
ms := src[2][1:]
millisecond, err = strconv.Atoi(ms)
//QQ音乐歌词文件中毫秒值只有两位需要特殊处理一下
if len(ms) == 2 {
millisecond *= 10
}
if err != nil {
fmt.Printf("错误的时间格式:%s\n", src)
return nil
}
}
srtContent := &SRTContent{}
srtContent.Start = Time2Millisecond(minute, second, millisecond)
srtContent.Text = content
return srtContent
}
//返回SRT文件中一句字幕的字符串表示形式
/**
1
00:00:01,111 --> 00:00:10,111
字幕
*/
func (s *SRTContent) String() string {
builder := strings.Builder{}
builder.WriteString(strconv.Itoa(s.Index))
builder.WriteByte('\n')
sh, sm, ss, sms := Millisecond2Time(s.Start)
eh, em, es, ems := Millisecond2Time(s.End)
builder.WriteString(fmt.Sprintf("%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n",
sh, sm, ss, sms, eh, em, es, ems))
builder.WriteString(s.Text)
builder.WriteString("\n\n")
return builder.String()
}

124
lts/main.go Normal file
View file

@ -0,0 +1,124 @@
package main
import (
"fmt"
"os"
"strings"
"time"
l2s "github.com/Hami-Lemon/lrc2srt"
"github.com/jessevdk/go-flags"
)
type Option struct {
Id string `short:"i" long:"id" description:"歌曲的id网易云和QQ音乐均可。"`
Input string `short:"I" long:"input" description:"需要转换的LRC文件路径。"`
Source string `short:"s" long:"source" description:"当设置id时有效指定从网易云163还是QQ音乐qq上获取歌词。" default:"163" choice:"163" choice:"qq" choice:"QQ"`
Download bool `short:"d" long:"download" description:"只下载歌词,而不进行解析。"`
Mode int `short:"m" long:"mode" default:"1" description:"原文和译文的排列模式,可选值有:[1] [2] [3]" choice:"1" choice:"2" choice:"3"`
Version bool `short:"v" long:"version" description:"获取版本信息"`
Output string `no-flag:""`
}
const (
// VERSION 当前版本
VERSION = `"0.2.0" (build 2022.03.28)`
)
var (
opt Option
)
func main() {
//TODO 支持转ass文件
args, err := flags.Parse(&opt)
if err != nil {
os.Exit(0)
}
//显示版本信息
if opt.Version {
fmt.Printf("LrcToSrt(lts) version %s\n", VERSION)
return
}
//获取保存的文件名
if len(args) != 0 {
opt.Output = args[0]
}
//获取歌词lyric为原文歌词tranLyric为译文歌词
var lyric, tranLyric string
if opt.Id != "" {
if opt.Source != "163" {
lyric, tranLyric = l2s.GetQQLyric(opt.Id)
} else {
lyric, tranLyric = l2s.Get163Lyric(opt.Id)
}
//下载歌词
if opt.Download {
//对文件名进行处理
o := opt.Output
if o == "" {
o = opt.Id + ".lrc"
} else if !strings.HasSuffix(o, ".lrc") {
o += ".lrc"
}
l2s.WriteFile(o, lyric)
if tranLyric != "" {
l2s.WriteFile("tran_"+o, tranLyric)
}
fmt.Println("下载歌词完成!")
return
}
} else if opt.Input != "" {
//从文件中获取歌词
if !strings.HasSuffix(opt.Input, ".lrc") {
fmt.Println("Error: 不支持的格式目前只支持lrc歌词文件。")
os.Exit(1)
}
lyric = l2s.ReadFile(opt.Input)
if lyric == "" {
fmt.Println("获取歌词失败,文件内容为空。")
os.Exit(1)
}
} else {
fmt.Println("Error: 请指定需要转换的歌词。")
os.Exit(1)
}
lrc, lrcT := l2s.ParseLRC(lyric), l2s.ParseLRC(tranLyric)
srt, srtT := l2s.LrcToSrt(lrc), l2s.LrcToSrt(lrcT)
if srtT != nil {
var mode l2s.SRTMergeMode
switch opt.Mode {
case 1:
mode = l2s.SRT_MERGE_MODE_STACK
case 2:
mode = l2s.SRT_MERGE_MODE_UP
case 3:
mode = l2s.SRT_MERGE_MODE_BOTTOM
}
srt.Merge(srtT, mode)
}
name := opt.Output
if name == "" {
title := srt.Title
if title != "" {
//以歌曲名命名
name = fmt.Sprintf("%s.srt", title)
} else if opt.Id != "" {
//以歌曲的id命名
name = fmt.Sprintf("%s.srt", opt.Id)
} else if opt.Input != "" {
//以LRC文件的文件名命名
name = fmt.Sprintf("%s.srt", opt.Input[:len(opt.Input)-4])
} else {
//以当前时间的毫秒值命名
name = fmt.Sprintf("%d.srt", time.Now().Unix())
}
} else if !strings.HasSuffix(name, ".srt") {
name += ".srt"
}
if err = srt.WriteFile(name); err != nil {
fmt.Println("出现错误,保存失败")
panic(err.Error())
}
}

View file

@ -1,25 +0,0 @@
package main
import "testing"
func BenchmarkSRTContent_String(b *testing.B) {
srt := SRTContent{
Index: 10,
Start: 100,
End: 200,
Text: "言语 不起作用,想看到 具体行动",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = srt.String()
}
}
func BenchmarkLrc2Srt(b *testing.B) {
id := "28891491"
lyric, lyricT := Get163Lyric(id)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = Lrc2Srt(lyric), Lrc2Srt(lyricT)
}
}

View file

@ -1,4 +1,4 @@
package main package lrc2srt
import ( import (
"compress/gzip" "compress/gzip"
@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"os"
) )
/** /**
@ -43,17 +42,17 @@ func GetQQLyric(id string) (lyric, tLyric string) {
req, _ := http.NewRequest("GET", api, nil) req, _ := http.NewRequest("GET", api, nil)
//必须设置Referer,否则会请求失败 //必须设置Referer,否则会请求失败
req.Header.Add("Referer", "https://y.qq.com") req.Header.Add("Referer", "https://y.qq.com")
req.Header.Add("User-Agent", ChromeUA) req.Header.Add("User-Agent", CHROME_UA)
req.Header.Add("accept-encoding", "gzip") req.Header.Add("accept-encoding", "gzip")
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Printf("网络错误:%v\n", err) fmt.Printf("网络错误:%v\n", err)
os.Exit(1) panic("网络异常,请求失败。")
} }
if resp == nil || resp.StatusCode != http.StatusOK { if resp == nil || resp.StatusCode != http.StatusOK {
fmt.Printf("网络请求失败,状态码为:%d\n", resp.StatusCode) fmt.Printf("网络请求失败,状态码为:%d\n", resp.StatusCode)
os.Exit(1) panic("获取失败,未能正确获取到数据")
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -63,7 +62,7 @@ func GetQQLyric(id string) (lyric, tLyric string) {
err = json.NewDecoder(reader).Decode(&qqLyric) err = json.NewDecoder(reader).Decode(&qqLyric)
if qqLyric.RetCode != 0 { if qqLyric.RetCode != 0 {
fmt.Printf("获取歌词失败,返回的结果为:%+v请检查id是否正确\n", qqLyric) fmt.Printf("获取歌词失败,返回的结果为:%+v请检查id是否正确\n", qqLyric)
os.Exit(1) panic("id错误获取歌词失败。")
} }
return qqLyric.Lyric, qqLyric.Trans return qqLyric.Lyric, qqLyric.Trans
} }

173
srt.go Normal file
View file

@ -0,0 +1,173 @@
package lrc2srt
import (
"bufio"
"fmt"
"github.com/Hami-Lemon/lrc2srt/glist"
"io"
"os"
"sort"
"strconv"
"strings"
)
type SRTMergeMode int
const (
SRT_MERGE_MODE_STACK SRTMergeMode = iota
SRT_MERGE_MODE_UP
SRT_MERGE_MODE_BOTTOM
)
type SRTContent struct {
//序号从1开始
Index int
//开始时间,单位毫秒
Start int
//结束时间,单位毫秒
End int
//歌词内容
Text string
}
/**
1
00:00:01,111 --> 00:00:10,111
字幕
*/
//返回SRT文件中一句字幕的字符串表示形式
func (s *SRTContent) String() string {
builder := strings.Builder{}
builder.WriteString(strconv.Itoa(s.Index))
builder.WriteByte('\n')
sh, sm, ss, sms := millisecond2Time(s.Start)
eh, em, es, ems := millisecond2Time(s.End)
builder.WriteString(fmt.Sprintf("%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n",
sh, sm, ss, sms, eh, em, es, ems))
builder.WriteString(s.Text)
builder.WriteString("\n\n")
return builder.String()
}
type SRT struct {
//歌曲名
Title string
//歌手名 未指定文件名是,文件名格式为:歌曲名-歌手名.srt
Artist string
Content glist.Queue[*SRTContent]
}
// LrcToSrt LRC对象转换成SRT对象
func LrcToSrt(lrc *LRC) *SRT {
if lrc == nil {
return nil
}
srt := &SRT{
Title: lrc.Title,
Artist: lrc.Artist,
Content: glist.NewLinkedList[*SRTContent](),
}
index := 1
//上一条srt信息
var prevSRT *SRTContent
for it := lrc.LrcList.Iterator(); it.Has(); {
lrcNode := it.Next()
srtContent := &SRTContent{
Index: index,
Start: lrcNode.time,
Text: lrcNode.content,
}
if index != 1 {
//上一条歌词的结束时间设置为当前歌词的开始时间
prevSRT.End = srtContent.Start
}
srt.Content.PushBack(srtContent)
index++
prevSRT = srtContent
}
//最后一条歌词
if prevSRT != nil {
//结束时间是为其 开始时间+10 秒
prevSRT.End = prevSRT.Start + 1000
}
return srt
}
// Merge 将另一个srt信息合并到当前srt中,有三种合并模式
//1. SRT_MERGE_MODE_STACK: 按照开始时间对两个srt信息进行排序,交错合并
//2. SRT_MERGE_MODE_UP: 当前srt信息排列在上,另一个排列在下,即 other 追加到后面
//3. SRT_MERGE_MODE_BOTTOM: 当前srt信息排列在下,另一个排列在上,即 other 添加到前面
func (s *SRT) Merge(other *SRT, mode SRTMergeMode) {
switch mode {
case SRT_MERGE_MODE_STACK:
s.mergeStack(other)
case SRT_MERGE_MODE_UP:
s.mergeUp(other)
case SRT_MERGE_MODE_BOTTOM:
s.mergeBottom(other)
}
}
//todo 改进算法,现在的算法太慢了
func (s *SRT) mergeStack(other *SRT) {
size := s.Content.Size() + other.Content.Size()
temp := make([]*SRTContent, size, size)
index := 0
for it := s.Content.Iterator(); it.Has(); {
temp[index] = it.Next()
index++
}
for it := other.Content.Iterator(); it.Has(); {
temp[index] = it.Next()
index++
}
sort.SliceStable(temp, func(i, j int) bool {
return temp[i].Start < temp[j].Start
})
list := glist.NewLinkedList[*SRTContent]()
for _, v := range temp {
list.Append(v)
}
s.Content = list
}
func (s *SRT) mergeUp(other *SRT) {
if other.Content.IsNotEmpty() {
for it := other.Content.Iterator(); it.Has(); {
s.Content.Append(it.Next())
}
}
}
func (s *SRT) mergeBottom(other *SRT) {
oq := other.Content
if oq.IsNotEmpty() {
s.Content.PushFront(*(oq.PullBack()))
}
}
// WriteFile 将SRT格式的数据写入指定的文件中
func (s *SRT) WriteFile(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
err = s.Write(f)
err = f.Close()
return err
}
// Write 将SRT格式的数据写入dst中
func (s *SRT) Write(dst io.Writer) error {
//6KB的缓冲
bufSize := 1024 * 6
writer := bufio.NewWriterSize(dst, bufSize)
for it := s.Content.Iterator(); it.Has(); {
_, err := writer.WriteString(it.Next().String())
if err != nil {
return err
}
}
return writer.Flush()
}

15
util.go
View file

@ -1,13 +1,22 @@
package main package lrc2srt
import ( import (
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
) )
const (
CHROME_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"
)
var (
client = http.Client{}
)
// Time2Millisecond 根据分,秒,毫秒 计算出对应的毫秒值 // Time2Millisecond 根据分,秒,毫秒 计算出对应的毫秒值
func Time2Millisecond(m, s, ms int) int { func time2Millisecond(m, s, ms int) int {
t := m*60 + s t := m*60 + s
t *= 1000 t *= 1000
t += ms t += ms
@ -15,7 +24,7 @@ func Time2Millisecond(m, s, ms int) int {
} }
// Millisecond2Time 根据毫秒值计算出对应的 时,分,秒,毫秒形式的时间值 // Millisecond2Time 根据毫秒值计算出对应的 时,分,秒,毫秒形式的时间值
func Millisecond2Time(millisecond int) (h, m, s, ms int) { func millisecond2Time(millisecond int) (h, m, s, ms int) {
ms = millisecond % 1000 ms = millisecond % 1000
s = millisecond / 1000 s = millisecond / 1000

View file

@ -1,4 +1,4 @@
package main package lrc2srt
import "testing" import "testing"
@ -23,7 +23,7 @@ func TestMillisecond2Time(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotH, gotM, gotS, gotMs := Millisecond2Time(tt.args.millisecond) gotH, gotM, gotS, gotMs := millisecond2Time(tt.args.millisecond)
if gotH != tt.wantH { if gotH != tt.wantH {
t.Errorf("Millisecond2Time() gotH = %v, want %v", gotH, tt.wantH) t.Errorf("Millisecond2Time() gotH = %v, want %v", gotH, tt.wantH)
} }
@ -60,7 +60,7 @@ func TestTime2Millisecond(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := Time2Millisecond(tt.args.m, tt.args.s, tt.args.ms); got != tt.want { if got := time2Millisecond(tt.args.m, tt.args.s, tt.args.ms); got != tt.want {
t.Errorf("Time2Millisecond() = %v, want %v", got, tt.want) t.Errorf("Time2Millisecond() = %v, want %v", got, tt.want)
} }
}) })
@ -69,12 +69,12 @@ func TestTime2Millisecond(t *testing.T) {
func BenchmarkTime2Millisecond(b *testing.B) { func BenchmarkTime2Millisecond(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Time2Millisecond(999, 999, 999) time2Millisecond(999, 999, 999)
} }
} }
func BenchmarkMillisecond2Time(b *testing.B) { func BenchmarkMillisecond2Time(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Millisecond2Time(9999999999) millisecond2Time(9999999999)
} }
} }