修复歌词原文和译文交错排列时会出现不对应的问题

This commit is contained in:
Hami Lemon 2022-03-05 13:02:32 +08:00
parent 452db2f7d2
commit f92ddb245b
8 changed files with 183 additions and 40 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
*.bat /example/*.lrc
/temp /temp

3
build.bat Normal file
View file

@ -0,0 +1,3 @@
echo off
@REM forceposix 表示在windows上参数也为linux风格即以“-”开头
go build -tags="forceposix" -ldflags "-s -w" -o lts.exe .

16
example/example.txt Normal file
View file

@ -0,0 +1,16 @@
encoding=utf-8
example#1 从网易云上下载歌词
lts -i 1903635166 传说的世界.srt
example#2 从qq音乐上下载歌词
lts -i 003eKeNV0t8IVi -s qq "bad guy.srt"
example#3 从网易云上下载歌词,不解析
lts -i 1903635166 -d 传说的世界.lrc
example#4 解析已有的lrc文件
lts -I 传说的世界.lrc 传说的世界2.srt
example#5 设置mode为2原文在上译文在下
lts -i 003eKeNV0t8IVi -s qq -m 2 "bad guy2.srt"

66
lts.go
View file

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -44,7 +45,7 @@ type Option struct {
const ( const (
// VERSION 当前版本 // VERSION 当前版本
VERSION = `"0.1.0" (build 2022.02.18)` VERSION = `"0.1.1" (build 2022.03.05)`
) )
var ( var (
@ -68,7 +69,7 @@ func main() {
opt.Output = args[0] opt.Output = args[0]
} }
//获取歌词 //获取歌词lyric为原文歌词tranLyric为译文歌词
var lyric, tranLyric string var lyric, tranLyric string
if opt.Id != "" { if opt.Id != "" {
if opt.Source != "163" { if opt.Source != "163" {
@ -107,7 +108,7 @@ func main() {
fmt.Println("Error: 请指定需要转换的歌词。") fmt.Println("Error: 请指定需要转换的歌词。")
os.Exit(1) os.Exit(1)
} }
//原文和译文作为两条歌词流信息分开保存但最终生成的srt文件会同时包含两个信息
lyricSRT, tranLyricSRT := Lrc2Srt(lyric), Lrc2Srt(tranLyric) lyricSRT, tranLyricSRT := Lrc2Srt(lyric), Lrc2Srt(tranLyric)
SaveSRT(lyricSRT, tranLyricSRT, opt.Output) SaveSRT(lyricSRT, tranLyricSRT, opt.Output)
} }
@ -117,6 +118,8 @@ func SaveSRT(srt *SRT, tranSrt *SRT, name string) {
if tranSrt == nil { if tranSrt == nil {
//没有译文时用一个空的对象的代替减少nil判断 //没有译文时用一个空的对象的代替减少nil判断
tranSrt = &SRT{Content: make([]*SRTContent, 0)} tranSrt = &SRT{Content: make([]*SRTContent, 0)}
//因为没有译文所以mode选项无效设为2之后后面不用做多余判断
opt.Mode = 2
} }
//处理结果文件的文件名 //处理结果文件的文件名
if name == "" { if name == "" {
@ -138,40 +141,45 @@ func SaveSRT(srt *SRT, tranSrt *SRT, name string) {
name += ".srt" name += ".srt"
} }
file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, os.ModePerm) file, err := os.Create(name)
if err != nil { if err != nil {
fmt.Printf("保存结果失败:%v\n", err) fmt.Printf("创建结果文件失败:%v\n", err)
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer func(file *os.File) {
writer := bufio.NewWriter(file) _ = file.Close()
}(file)
//6KB的缓存区大部分歌词生成的SRT文件均在4-6kb左右
writer := bufio.NewWriterSize(file, 1024*6)
/*
原文和译文歌词的排列方式因为原文歌词中可能包含一些非歌词信息
例如作词者作曲者等而在译文歌词中却可能不包含这些
*/
//srt的序号
index := 1 index := 1
switch opt.Mode { switch opt.Mode {
case 1: case 1:
//译文和原文交错排列 //将两个歌词合并成一个新的数组
_len, _lent, size := len(srt.Content), len(tranSrt.Content), 0 size := len(srt.Content) + len(tranSrt.Content)
if _len > _lent { temp := make([]*SRTContent, size, size)
size = 2 * _len i := 0
} else { for _, v := range srt.Content {
size = 2 * _lent temp[i] = v
i++
} }
//临时缓冲区,偶数索引存原文,奇数存译文 for _, v := range tranSrt.Content {
buf := make([]*SRTContent, size, size) temp[i] = v
for i, item := range srt.Content { i++
buf[2*i] = item
} }
for i, item := range tranSrt.Content { //按开始时间进行排序使用SliceStable确保一句歌词的原文在译文之前
buf[2*i+1] = item sort.SliceStable(temp, func(i, j int) bool {
} return temp[i].Start < temp[j].Start
})
//写入文件 //写入文件
for _, item := range buf { for i, v := range temp {
if item != nil { v.Index = i + 1
item.Index = index _, _ = writer.WriteString(v.String())
_, _ = writer.WriteString(item.String())
index++
}
} }
case 2: case 2:
//原文在上,译文在下 //原文在上,译文在下
@ -202,9 +210,9 @@ func SaveSRT(srt *SRT, tranSrt *SRT, name string) {
err = writer.Flush() err = writer.Flush()
if err != nil { if err != nil {
fmt.Printf("保存结果失败:%v\n", err) fmt.Printf("保存结果失败:%v\n", err)
os.Exit(1) } else {
fmt.Printf("转换文件完成,保存结果为:%s\n", name)
} }
fmt.Printf("转换文件完成,保存结果为:%s\n", name)
} }
// Lrc2Srt 将原始个LRC字符串歌词解析SRT对象 // Lrc2Srt 将原始个LRC字符串歌词解析SRT对象

25
lts_test.go Normal file
View file

@ -0,0 +1,25 @@
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

@ -9,12 +9,19 @@ import (
"os" "os"
) )
/**
从QQ音乐上获取歌词
*/
// QQLyric qq音乐获取歌词接口返回的数据结构
type QQLyric struct { type QQLyric struct {
RetCode int `json:"retcode"` RetCode int `json:"retcode"`
Code int `json:"code"` Code int `json:"code"`
SubCode int `json:"subcode"` SubCode int `json:"subcode"`
Lyric string `json:"lyric"` //Lyric 原文歌词
Trans string `json:"trans"` Lyric string `json:"lyric"`
//Trans 译文歌词
Trans string `json:"trans"`
} }
func GetQQLyric(id string) (lyric, tLyric string) { func GetQQLyric(id string) (lyric, tLyric string) {

12
util.go
View file

@ -36,7 +36,9 @@ func ReadFile(name string) string {
fmt.Printf("打开文件失败:%v\n", err) fmt.Printf("打开文件失败:%v\n", err)
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer func(file *os.File) {
_ = file.Close()
}(file)
data, err := io.ReadAll(file) data, err := io.ReadAll(file)
if err != nil { if err != nil {
fmt.Printf("读取文件失败:%v\n", err) fmt.Printf("读取文件失败:%v\n", err)
@ -46,12 +48,14 @@ func ReadFile(name string) string {
} }
func WriteFile(name string, data string) { func WriteFile(name string, data string) {
file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, os.ModePerm) file, err := os.Create(name)
if err != nil { if err != nil {
fmt.Printf("保存文件失败:%v\n", err) fmt.Printf("创建结果文件失败:%v\n", err)
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer func(file *os.File) {
_ = file.Close()
}(file)
nw, err := file.WriteString(data) nw, err := file.WriteString(data)
if err != nil || nw < len(data) { if err != nil || nw < len(data) {
fmt.Printf("保存文件失败:%v\n", err) fmt.Printf("保存文件失败:%v\n", err)

80
util_test.go Normal file
View file

@ -0,0 +1,80 @@
package main
import "testing"
func TestMillisecond2Time(t *testing.T) {
type args struct {
millisecond int
}
tests := []struct {
name string
args args
wantH int
wantM int
wantS int
wantMs int
}{
{"ms_0", args{0}, 0, 0, 0, 0},
{"ms_100", args{100}, 0, 0, 0, 100},
{"ms_1000", args{1000}, 0, 0, 1, 0},
{"ms_1100", args{1100}, 0, 0, 1, 100},
{"ms_60000", args{60000}, 0, 1, 0, 0},
{"ms_3600000", args{3600000}, 1, 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotH, gotM, gotS, gotMs := Millisecond2Time(tt.args.millisecond)
if gotH != tt.wantH {
t.Errorf("Millisecond2Time() gotH = %v, want %v", gotH, tt.wantH)
}
if gotM != tt.wantM {
t.Errorf("Millisecond2Time() gotM = %v, want %v", gotM, tt.wantM)
}
if gotS != tt.wantS {
t.Errorf("Millisecond2Time() gotS = %v, want %v", gotS, tt.wantS)
}
if gotMs != tt.wantMs {
t.Errorf("Millisecond2Time() gotMs = %v, want %v", gotMs, tt.wantMs)
}
})
}
}
func TestTime2Millisecond(t *testing.T) {
type args struct {
m int
s int
ms int
}
tests := []struct {
name string
args args
want int
}{
{"0:0.0", args{0, 0, 0}, 0},
{"0:0.1", args{0, 0, 1}, 1},
{"0:0.999", args{0, 0, 999}, 999},
{"0:1.0", args{0, 1, 0}, 1000},
{"0:1.999", args{0, 1, 999}, 1999},
{"1:0.0", args{1, 0, 0}, 60000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Time2Millisecond(tt.args.m, tt.args.s, tt.args.ms); got != tt.want {
t.Errorf("Time2Millisecond() = %v, want %v", got, tt.want)
}
})
}
}
func BenchmarkTime2Millisecond(b *testing.B) {
for i := 0; i < b.N; i++ {
Time2Millisecond(999, 999, 999)
}
}
func BenchmarkMillisecond2Time(b *testing.B) {
for i := 0; i < b.N; i++ {
Millisecond2Time(9999999999)
}
}