You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

451 lines
13 KiB

package app
import (
"bytes"
"errors"
"fmt"
"os"
"path"
"strconv"
"strings"
"time"
"videosrt/app/parse"
"videosrt/app/tool"
"videosrt/app/translate"
)
//字幕行结构
type SrtRows struct {
Id int //字幕自然行id
Number string //字幕序号
TimeStart string //字幕开始时间戳
TimeStartSecond float64 //字幕开始(秒)
TimeStartMilliSecond int64 //字幕开始(毫秒)
TimeEnd string //字幕结束时间戳
TimeEndSecond float64 //字幕结束(秒)
TimeEndMilliSecond int64 //字幕结束(毫秒)
Text string //字幕文本
TranslateText string //翻译字幕文本
}
//应用翻译配置
type SrtTranslateStruct struct {
TranslateSwitch bool //字幕翻译开关
Supplier int //引擎供应商
BilingualSubtitleSwitch bool //是否输出双语字幕
InputLanguage int //输入字幕语言
OutputLanguage int //输出字幕语言
OutputMainSubtitleInputLanguage bool //双语主字幕(输入语言)
BaiduTranslate translate.BaiduTranslate //百度翻译
TengxunyunTranslate translate.TengxunyunTranslate //腾讯云翻译
}
//统一翻译结果
type SrtTranslateResult struct {
From string //翻译源语言
To string //译文语言
TransResultSrc string //翻译结果(原文)
TransResultDst string //翻译结果(译文)
}
//主应用
type SrtTranslateApp struct {
AppDir string //应用根目录
SrtDir string //文件输出目录
OutputType *AppSetingsOutput //输出文件类型
OutputEncode int //输出文件编码
MaxConcurrency int //最大处理并发数
TranslateCfg *SrtTranslateStruct //翻译配置
FilterSetings *AppFilterSetings //过滤器配置
LogHandler func(s string , file string) //日志回调
SuccessHandler func(file string) //成功回调
FailHandler func(file string) //失败回调
}
//获取应用
func NewSrtTranslateApp(appDir string) *SrtTranslateApp {
app := new(SrtTranslateApp)
app.AppDir = appDir
app.OutputEncode = OUTPUT_ENCODE_UTF8 //默认输出文件编码
app.MaxConcurrency = 2
//实例应用翻译配置
app.TranslateCfg = new(SrtTranslateStruct)
return app
}
//加载翻译设置
func (app *SrtTranslateApp) InitTranslateConfig (translateSettings *SrtTranslateStruct) {
app.TranslateCfg = translateSettings
}
//加载过滤器配置
func (app *SrtTranslateApp) InitFilterConfig (filterSetings *AppFilterSetings) {
app.FilterSetings = filterSetings
}
func (app *SrtTranslateApp) SetSrtDir(dir string) {
app.SrtDir = dir
}
func (app *SrtTranslateApp) SetOutputType(output *AppSetingsOutput) {
app.OutputType = output
}
func (app *SrtTranslateApp) SetOutputEncode(encode int) {
app.OutputEncode = encode
}
func (app *SrtTranslateApp) SetMaxConcurrency(number int) {
if number == 0 {
number = 2
}
app.MaxConcurrency = number
}
func (app *SrtTranslateApp) SetSuccessHandler(callback func(file string)) {
app.SuccessHandler = callback
}
func (app *SrtTranslateApp) SetFailHandler(callback func(file string)) {
app.FailHandler = callback
}
func (app *SrtTranslateApp) SetLogHandler(callback func(s string , file string)) {
app.LogHandler = callback
}
//输出日志
func (app *SrtTranslateApp) Log(s string , file string) {
app.LogHandler(s , file)
}
//应用运行
func (app *SrtTranslateApp) Run(srtfile string) {
//致命错误捕获
defer func() {
//拦截panic
if err := recover(); err != nil {
//失败回调
app.FailHandler(srtfile)
e , ok := err.(error)
if ok {
app.Log("错误:" + e.Error() , srtfile)
} else {
panic(err)
}
}
}()
//校验文件
if tool.VaildFile(srtfile) != true {
panic("字幕文件不存在")
}
//分析、解析字幕
srt := &parse.Srt{File:srtfile}
subtitleParse := parse.NewSubtitleParse(srt)
if e := subtitleParse.Parse(); e!=nil {
panic("字幕文件解析错误:" + e.Error())
}
//获取字幕结构
srtRows := app.SrtHandleGetData(subtitleParse)
app.Log("字幕文件解析成功" , srtfile)
//字幕翻译
app.SrtTranslate(srtfile , srtRows)
//字幕过滤
app.SrtFilters(srtRows , srtfile)
//输出文件
if app.OutputType.SRT {
app.SrtOutputFile(srtfile , srtRows , OUTPUT_SRT)
}
if app.OutputType.LRC {
app.SrtOutputFile(srtfile , srtRows , OUTPUT_LRC)
}
if app.OutputType.TXT {
app.SrtOutputFile(srtfile , srtRows , OUTPUT_STRING)
}
app.Log("处理完成" , srtfile)
//成功回调
app.SuccessHandler(srtfile)
}
//字幕结构处理
func (app *SrtTranslateApp) SrtHandleGetData(srt *parse.SubtitleParse) ([] *SrtRows) {
data := make([]*SrtRows , 0)
for _,rows := range srt.Rows {
temp := new(SrtRows)
temp.Id = rows.Id
temp.Number = rows.Number
temp.TimeStart = rows.TimeStart
temp.TimeStartSecond = rows.TimeStartSecond
temp.TimeStartMilliSecond = int64(rows.TimeStartSecond * 1000)
temp.TimeEnd = rows.TimeEnd
temp.TimeEndSecond = rows.TimeEndSecond
temp.TimeEndMilliSecond = int64(rows.TimeEndSecond * 1000)
line := srt.Srt.AppointBilingualRows
if line < 0 || line > 1 {
line = 0
}
if len(rows.Text) == 0 {
continue
}
temp.Text = rows.Text[line]
data = append(data , temp)
}
return data
}
//字幕翻译处理
func (app *SrtTranslateApp) SrtTranslate(file string , srtRows []*SrtRows) {
//输出日志
if app.TranslateCfg.TranslateSwitch {
app.Log("字幕翻译处理中 ..." , file)
//百度翻译标准版
if app.TranslateCfg.Supplier == TRANSLATE_SUPPLIER_BAIDU && app.TranslateCfg.BaiduTranslate.AuthenType == translate.ACCOUNT_COMMON_AUTHEN {
app.Log("你使用的是 “百度翻译标准版” 账号,翻译速度较慢,请耐心等待 ..." , file)
}
} else {
return
}
total := len(srtRows)
var lastrv float64 = 0
//翻译处理
index := 0
for _,data := range srtRows {
transResult,e := app.RunTranslate(data.Text , file)
if e != nil {
panic(e) //终止翻译
}
data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文
index++
rv := (float64(index)/float64(total))*100
if (rv - lastrv) > 20 {
//输出比例
app.Log("字幕翻译已处理:" + fmt.Sprintf("%.2f" , rv) + "%" , file)
lastrv = rv
}
}
}
//字幕过滤处理
func (app *SrtTranslateApp) SrtFilters (srtRows []*SrtRows , file string) {
if !app.FilterSetings.DefinedFilter.Switch && !app.FilterSetings.GlobalFilter.Switch {
return
}
app.Log("字幕过滤处理中 ..." , file)
//语气词过滤
if app.FilterSetings.GlobalFilter.Switch && strings.TrimSpace(app.FilterSetings.GlobalFilter.Words) != "" {
modalWords := strings.Split(app.FilterSetings.GlobalFilter.Words , "\r\n")
for _ , row := range srtRows {
for _ , w := range modalWords {
row.Text = ModalWordsFilter(row.Text , w)
if app.TranslateCfg.TranslateSwitch {
row.TranslateText = ModalWordsFilter(row.TranslateText , w)
}
}
}
}
//自定义规则过滤
if app.FilterSetings.DefinedFilter.Switch && len(app.FilterSetings.DefinedFilter.Rule) > 0 {
rules := app.FilterSetings.DefinedFilter.Rule
for _ , row := range srtRows {
for _ , ru := range rules {
row.Text = DefinedWordRuleFilter(row.Text , ru)
if app.TranslateCfg.TranslateSwitch {
row.TranslateText = DefinedWordRuleFilter(row.TranslateText , ru)
}
}
}
}
}
//文件输出
func (app *SrtTranslateApp) SrtOutputFile(file string , srtRows []*SrtRows , outputType int) {
var subfileDir string
if app.SrtDir == "" {
subfileDir = path.Dir(file)
} else {
subfileDir = app.SrtDir
}
subfile := tool.GetFileBaseName(file)
//输出文件名
thisfile := subfileDir + "/" + subfile + "_translaste"
//文件后缀
if outputType == OUTPUT_SRT {
thisfile += ".srt"
} else if outputType == OUTPUT_STRING {
thisfile += ".txt"
} else if outputType == OUTPUT_LRC {
thisfile += ".lrc"
}
//创建文件
fd, e := os.Create(thisfile)
if e != nil {
panic("创建文件失败:" + e.Error())
}
defer fd.Close()
//文件编码分支
if app.OutputEncode == OUTPUT_ENCODE_UTF8_BOM {
if _, e := fd.Write([]byte{0xEF, 0xBB, 0xBF});e != nil {
panic(e)
}
}
//歌词头
if outputType == OUTPUT_LRC {
_,_ = fd.WriteString("[ar:]\r\n[ti:]\r\n[al:]\r\n[by:]\r\n")
}
//主字幕
bilingualAsc := app.TranslateCfg.OutputMainSubtitleInputLanguage
index := 0
//普通文本容器
var txtOutputContent bytes.Buffer
var txtTransalteOutputContent bytes.Buffer
for _ , data := range srtRows {
var linestr string
if data.Text == "" {
continue //跳过空行
}
//字幕、歌词文件处理
if outputType == OUTPUT_SRT || outputType == OUTPUT_LRC {
//拼接
if outputType == OUTPUT_SRT {
if app.TranslateCfg.TranslateSwitch {
if app.TranslateCfg.BilingualSubtitleSwitch {
linestr = MakeSubtitleText(index , data.TimeStartMilliSecond , data.TimeEndMilliSecond , data.Text , data.TranslateText , true , bilingualAsc)
} else {
linestr = MakeSubtitleText(index , data.TimeStartMilliSecond , data.TimeEndMilliSecond , data.TranslateText , "" , false , true)
}
} else {
linestr = MakeSubtitleText(index , data.TimeStartMilliSecond , data.TimeEndMilliSecond , data.Text , "" , false , true)
}
} else if outputType == OUTPUT_LRC {
if app.TranslateCfg.TranslateSwitch {
linestr = MakeMusicLrcText(index , data.TimeStartMilliSecond , data.TimeEndMilliSecond , data.TranslateText)
} else {
linestr = MakeMusicLrcText(index , data.TimeStartMilliSecond , data.TimeEndMilliSecond , data.Text)
}
}
//写入行
if _, e = fd.WriteString(linestr);e != nil {
panic("写入文件失败:" + e.Error())
}
} else if outputType == OUTPUT_STRING {
//普通文本处理
txtOutputContent.WriteString(data.Text)
txtOutputContent.WriteString("\r\n")
if app.TranslateCfg.TranslateSwitch {
txtTransalteOutputContent.WriteString(data.TranslateText)
txtTransalteOutputContent.WriteString("\r\n")
}
}
index++
}
//写入文本文件
if outputType == OUTPUT_STRING {
txtOutputContent.WriteString("\r\n\r\n\r\n\r\n\r\n")
if _, e = fd.WriteString(txtOutputContent.String());e != nil {
panic("写入文件失败:" + e.Error())
}
if _, e = fd.WriteString(txtTransalteOutputContent.String());e != nil {
panic("写入文件失败:" + e.Error())
}
}
}
//统一运行翻译结果
func (app *SrtTranslateApp) RunTranslate(s string , file string) (*SrtTranslateResult , error) {
var trys int = 0
translateResult := new(SrtTranslateResult)
if app.TranslateCfg.Supplier == TRANSLATE_SUPPLIER_BAIDU {
if app.TranslateCfg.BaiduTranslate.AuthenType == translate.ACCOUNT_COMMON_AUTHEN { //百度翻译标准版
//休眠1010毫秒
time.Sleep(time.Millisecond * 1010)
} else {
//休眠200毫秒
time.Sleep(time.Millisecond * 200)
}
from := GetLanguageChar(app.TranslateCfg.InputLanguage , TRANSLATE_SUPPLIER_BAIDU)
to := GetLanguageChar(app.TranslateCfg.OutputLanguage , TRANSLATE_SUPPLIER_BAIDU)
//发起翻译请求
baiduResult,transErr := app.TranslateCfg.BaiduTranslate.TranslateBaidu(s , from , to)
for transErr != nil && trys <= 5 {
trys++
app.Log("翻译请求失败,重试第" + strconv.Itoa(trys) + "次 ..." , file)
time.Sleep(time.Second * time.Duration(trys))
//重试
baiduResult,transErr = app.TranslateCfg.BaiduTranslate.TranslateBaidu(s , from , to)
}
if transErr != nil {
return translateResult,errors.New("翻译失败!错误信息:" + transErr.Error())
}
translateResult.TransResultDst = baiduResult.TransResultDst
translateResult.TransResultSrc = baiduResult.TransResultSrc
translateResult.From = baiduResult.From
translateResult.To = baiduResult.To
return translateResult,nil
} else if app.TranslateCfg.Supplier == TRANSLATE_SUPPLIER_TENGXUNYUN {
//休眠
t := app.TranslateCfg.TengxunyunTranslate.TranslateSleepTime(app.MaxConcurrency)
time.Sleep(t)
from := GetLanguageChar(app.TranslateCfg.InputLanguage , TRANSLATE_SUPPLIER_TENGXUNYUN)
to := GetLanguageChar(app.TranslateCfg.OutputLanguage , TRANSLATE_SUPPLIER_TENGXUNYUN)
//发起翻译请求
txResult,transErr := app.TranslateCfg.TengxunyunTranslate.TranslateTengxunyun(s , from , to)
for transErr != nil && trys <= 5 {
trys++
app.Log("翻译请求失败,重试第" + strconv.Itoa(trys) + "次 ..." , file)
time.Sleep(time.Second * time.Duration(trys))
//重试
txResult,transErr = app.TranslateCfg.TengxunyunTranslate.TranslateTengxunyun(s , from , to)
}
if transErr != nil {
return translateResult,errors.New("翻译失败!错误信息:" + transErr.Error())
}
translateResult.TransResultDst = txResult.TransResultDst
translateResult.TransResultSrc = txResult.TransResultSrc
translateResult.From = txResult.From
translateResult.To = txResult.To
return translateResult,nil
} else {
return translateResult,errors.New("翻译失败!缺少翻译引擎!")
}
}