forked from go/golangs_learn
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
451 lines
13 KiB
3 years ago
|
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("翻译失败!缺少翻译引擎!")
|
||
|
}
|
||
|
}
|