重构项目,调整了项目结构
This commit is contained in:
parent
5f49bde3ae
commit
c9b1b38c03
11 changed files with 456 additions and 386 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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
4
go.mod
|
@ -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
128
lrc.go
Normal 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
337
lts.go
|
@ -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
124
lts/main.go
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
25
lts_test.go
25
lts_test.go
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
11
qqlyric.go
11
qqlyric.go
|
@ -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
173
srt.go
Normal 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
15
util.go
|
@ -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
|
||||||
|
|
10
util_test.go
10
util_test.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue