diff --git a/README.md b/README.md index cd8e97f..5bb32f3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 这是一个可以识别视频语音自动生成字幕SRT文件的开源软件工具。
适用于快速、批量的为媒体(视频/音频)生成中/英文字幕、文本文件的业务场景。 -0.2.9 版本将会使用以下接口: +0.3.0 版本将会使用以下接口: - 阿里云 [OSS对象存储](https://www.aliyun.com/product/oss?spm=5176.12825654.eofdhaal5.13.e9392c4aGfj5vj&aly_as=K11FcpO8) - 阿里云 [录音文件识别](https://ai.aliyun.com/nls/filetrans?spm=5176.12061031.1228726.1.47fe3cb43I34mn) - 百度翻译开放平台 [翻译API](http://api.fanyi.baidu.com/api/trans/product/index) @@ -25,7 +25,7 @@ CLI(命令行)版本:[https://github.com/wxbool/video-srt](https://github. - 识别**视频/音频**的语音生成字幕文件(支持中英互译,双语字幕) - 提取**视频/音频**的语音文本 -- 批量翻译、编码SRT字幕文件 +- 批量翻译、过滤处理/编码SRT字幕文件 @@ -36,7 +36,8 @@ CLI(命令行)版本:[https://github.com/wxbool/video-srt](https://github. - 支持多任务多文件批量处理 - 支持视频、音频常见多种格式文件 - 支持同时输出字幕SRT文件、LRC文件、普通文本3种类型 -- 支持字幕中英互译、双语字幕输出 +- 支持语气词过滤、自定义文本过滤、正则过滤等,使软件生成的字幕更加精准 +- 支持字幕中英互译、双语字幕输出,及日语、韩语、法语、德语、西班牙语、俄语、意大利语、泰语等 - 支持多翻译引擎(百度翻译、腾讯云翻译) - 支持批量翻译、编码SRT字幕文件 @@ -45,8 +46,8 @@ CLI(命令行)版本:[https://github.com/wxbool/video-srt](https://github. ##### 下载地址: -- (v0.2.9.5)(含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.2.9.5/video-srt-gui-ffmpeg-0.2.9.5-x64.zip) -- (v0.2.9.5)(不含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.2.9.5/video-srt-gui-0.2.9.5-x64.zip) +- (v0.3.0)(含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.3.0/video-srt-gui-ffmpeg-0.3.0-x64.zip) +- (v0.3.0)(不含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.3.0/video-srt-gui-0.3.0-x64.zip) - (v0.2.6)(含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.2.6/video-srt-gui-ffmpeg-0.2.6-x64.zip) - (v0.2.6)(不含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.2.6/video-srt-gui-0.2.6-x64.zip) @@ -63,7 +64,7 @@ CLI(命令行)版本:[https://github.com/wxbool/video-srt](https://github. - 先下载最新版本的软件包 - 然后用旧版本软件的 `data` 文件夹覆盖新版软件的 `data` 文件夹 -- 0.2.6 升级至 0.2.9时,翻译设置无法直接兼容低版本,需要重新手动创建翻译引擎才能继续使用翻译功能 +- 0.2.6 升级至 0.2.9 以上的版本时,由于翻译设置无法直接兼容低版本,可能需要重新在软件创建翻译引擎才能继续使用翻译功能 ## FAQ ##### 1.为什么Linux和Mac不能用? diff --git a/app/aliyun/tool.go b/app/aliyun/tool.go index 05f60a6..5910d92 100644 --- a/app/aliyun/tool.go +++ b/app/aliyun/tool.go @@ -13,6 +13,8 @@ import ( type AliyunAudioRecognitionResultBlock struct { AliyunAudioRecognitionResult Blocks []int + BlockEmptyTag bool + BlockEmptyHandle bool } //阿里云录音录音文件识别 - 智能分段处理 @@ -87,6 +89,10 @@ func AliyunAudioResultWordHandle(result [] byte , callback func (vresult *Aliyun for _ , data := range value { data.Blocks = GetTextBlock(data.Text) data.Text = ReplaceStrs(data.Text , symbol , "") + + if len(data.Blocks) == 0 { + data.BlockEmptyTag = true + } } } @@ -144,101 +150,124 @@ func AliyunAudioResultWordHandle(result [] byte , callback func (vresult *Aliyun flag := false early := false - for t , B := range w.Blocks{ - //fmt.Println("blockRune : " , blockRune , B , word.Word) - if ((blockRune >= B) || (blockRune + chineseNumberDiffLength >= B)) && B != -1 { - flag = true - - //fmt.Println( w.Blocks ) - //fmt.Println(B , lastBlock , (B - lastBlock) , word.Word) - //fmt.Println(w.Text) - //fmt.Println( block ) - //fmt.Println("\n\n\n") - - var thisText = "" - //容错机制 - if t == (len(w.Blocks) - 1) { - thisText = SubString(w.Text , lastBlock , 10000) - } else { - //下个词提前结束 - if i < len(value)-1 && value[i+1].BeginTime >= w.EndTime{ + if !w.BlockEmptyTag { + for t , B := range w.Blocks{ + //fmt.Println("blockRune : " , blockRune , B , word.Word) + if ((blockRune >= B) || (blockRune + chineseNumberDiffLength >= B)) && B != -1 { + flag = true + + //fmt.Println( w.Blocks ) + //fmt.Println(B , lastBlock , (B - lastBlock) , word.Word) + //fmt.Println(w.Text) + //fmt.Println( block ) + //fmt.Println("\n\n\n") + + var thisText = "" + //容错机制 + if t == (len(w.Blocks) - 1) { thisText = SubString(w.Text , lastBlock , 10000) - early = true } else { - thisText = SubString(w.Text , lastBlock , (B - lastBlock)) + //下个词提前结束 + if i < len(value)-1 && value[i+1].BeginTime >= w.EndTime{ + thisText = SubString(w.Text , lastBlock , 10000) + early = true + } else { + thisText = SubString(w.Text , lastBlock , (B - lastBlock)) + } } - } - lastBlock = B - if early == true { - //全部设置为-1 - for vt,vb := range w.Blocks{ - if vb != -1 { - w.Blocks[vt] = -1; + lastBlock = B + if early == true { + //全部设置为-1 + for vt,vb := range w.Blocks{ + if vb != -1 { + w.Blocks[vt] = -1; + } } + } else { + w.Blocks[t] = -1 } - } else { - w.Blocks[t] = -1 + + vresult := &AliyunAudioRecognitionResult{ + Text:thisText, + ChannelId:channel, + BeginTime:beginTime, + EndTime:word.EndTime, + SilenceDuration:w.SilenceDuration, + SpeechRate:w.SpeechRate, + EmotionValue:w.EmotionValue, + } + callback(vresult) //回调传参 + + blockBool = true + break } + } + + //fmt.Println("word.Word : " , word.Word) + //fmt.Println(block) + + if FindSliceIntCount(w.Blocks , -1) == len(w.Blocks) { + //全部截取完成 + block = "" + lastBlock = 0 + } + //容错机制 + if FindSliceIntCount(w.Blocks , -1) == (len(w.Blocks)-1) && flag == false { + var thisText = SubString(w.Text , lastBlock , 10000) + w.Blocks[len(w.Blocks) - 1] = -1 + //vresult vresult := &AliyunAudioRecognitionResult{ Text:thisText, ChannelId:channel, BeginTime:beginTime, - EndTime:word.EndTime, + EndTime:w.EndTime, SilenceDuration:w.SilenceDuration, SpeechRate:w.SpeechRate, EmotionValue:w.EmotionValue, } - callback(vresult) //回调传参 - - blockBool = true - break - } - } - //fmt.Println("word.Word : " , word.Word) - //fmt.Println(block) - - if FindSliceIntCount(w.Blocks , -1) == len(w.Blocks) { - //全部截取完成 - block = "" - lastBlock = 0 - } - - //容错机制 - if FindSliceIntCount(w.Blocks , -1) == (len(w.Blocks)-1) && flag == false { - var thisText = SubString(w.Text , lastBlock , 10000) - - w.Blocks[len(w.Blocks) - 1] = -1 - //vresult - vresult := &AliyunAudioRecognitionResult{ - Text:thisText, - ChannelId:channel, - BeginTime:beginTime, - EndTime:w.EndTime, - SilenceDuration:w.SilenceDuration, - SpeechRate:w.SpeechRate, - EmotionValue:w.EmotionValue, - } + //fmt.Println( thisText ) + //fmt.Println( block ) + //fmt.Println( word.Word , beginTime, w.EndTime , flag , word.EndTime ) - //fmt.Println( thisText ) - //fmt.Println( block ) - //fmt.Println( word.Word , beginTime, w.EndTime , flag , word.EndTime ) + callback(vresult) //回调传参 - callback(vresult) //回调传参 + //覆盖下一段落的时间戳 + if windex < (len(p)-1) { + beginTime = p[windex+1].BeginTime + } else { + beginTime = w.EndTime + } - //覆盖下一段落的时间戳 - if windex < (len(p)-1) { - beginTime = p[windex+1].BeginTime - } else { - beginTime = w.EndTime + //清除参数 + block = "" + lastBlock = 0 } + } else { //清除参数 block = "" lastBlock = 0 + blockBool = true + + if w.BlockEmptyHandle == false { + vresult := &AliyunAudioRecognitionResult{ + Text:w.Text, + ChannelId:w.ChannelId, + BeginTime:w.BeginTime, + EndTime:w.EndTime, + SilenceDuration:w.SilenceDuration, + SpeechRate:w.SpeechRate, + EmotionValue:w.EmotionValue, + } + callback(vresult) //回调传参 + w.BlockEmptyHandle = true + } + } + } } } diff --git a/app/app_tool.go b/app/app_tool.go index 7f67478..6a27cc7 100644 --- a/app/app_tool.go +++ b/app/app_tool.go @@ -2,10 +2,48 @@ package app import ( "bytes" + "regexp" "strconv" + "strings" "videosrt/app/tool" ) + +//语气词过滤 +func ModalWordsFilter(s string , w string) string { + tmpText := strings.ReplaceAll(s , w , "") + if strings.TrimSpace(tmpText) == "" || tool.CheckOnlySymbolText(strings.TrimSpace(tmpText)) { + return "" + } else { + //尝试过滤重复语气词 + compile, e := regexp.Compile(w + "{2,}") + if e != nil { + return s + } + return compile.ReplaceAllString(s , "") + } +} + +//自定义规则过滤 +func DefinedWordRuleFilter(s string , rule *AppDefinedFilterRule) string { + if rule.Way == FILTER_TYPE_STRING { + //文本过滤 + s = strings.ReplaceAll(s , rule.Target , rule.Replace) + } else if rule.Way == FILTER_TYPE_REGX { + //正则过滤 + compile, e := regexp.Compile(rule.Target) + if e != nil { + return s + } + s = compile.ReplaceAllString(s , rule.Replace) + } + if strings.TrimSpace(s) == "" || tool.CheckOnlySymbolText(strings.TrimSpace(s)) { + return "" + } + return s +} + + //拼接字幕字符串 func MakeSubtitleText(index int , startTime int64 , endTime int64 , text string , translateText string , bilingualSubtitleSwitch bool , bilingualAsc bool) string { var content bytes.Buffer diff --git a/app/data.go b/app/data.go index e00cceb..ee01859 100644 --- a/app/data.go +++ b/app/data.go @@ -25,6 +25,12 @@ const ( LANGUAGE_EN = 2 //英文 LANGUAGE_JP = 3 //日语 LANGUAGE_KOR = 4 //韩语 + LANGUAGE_FRA = 5 //法语 fra + LANGUAGE_DE = 6 //德语 de + LANGUAGE_SPA = 7 //西班牙语 spa + LANGUAGE_RU = 8 //俄语 ru + LANGUAGE_IT = 9 //意大利语 it + LANGUAGE_TH = 10 //泰语 th ) //缓存结构 @@ -40,12 +46,16 @@ type SpeechEngineAppStruct struct { type AppSetingsAppStruct struct { Data *datacache.AppCache } +type AppFilterAppStruct struct { + Data *datacache.AppCache +} var RootDir string var Oss *OssAppStruct var Translate *TranslateEngineAppStruct var Engine *SpeechEngineAppStruct var Setings *AppSetingsAppStruct +var Filter *AppFilterAppStruct func init() { @@ -58,11 +68,13 @@ func init() { Translate = new(TranslateEngineAppStruct) Engine = new(SpeechEngineAppStruct) Setings = new(AppSetingsAppStruct) + Filter = new(AppFilterAppStruct) Oss.Data = datacache.NewAppCahce(RootDir , "oss") Translate.Data = datacache.NewAppCahce(RootDir , "translate_engine") Engine.Data = datacache.NewAppCahce(RootDir , "engine") Setings.Data = datacache.NewAppCahce(RootDir , "setings") + Filter.Data = datacache.NewAppCahce(RootDir , "filter") } @@ -110,7 +122,7 @@ type AppSetingsOutput struct { TXT bool } -//应用配置 - 缓存结构 +//应用配置结构 type AppSetings struct { CurrentEngineId int //目前语音引擎Id CurrentTranslateEngineId int //目前翻译引擎Id @@ -130,6 +142,32 @@ type AppSetings struct { CloseAutoDeleteOssTempFile bool //关闭自动删除临时音频文件(默认开启)[false开启 true关闭] } + +const ( + FILTER_TYPE_STRING = 1 //文本过滤 + FILTER_TYPE_REGX = 2 //正则过滤 +) +//自定义过滤器规则 +type AppDefinedFilterRule struct { + Target string //目标规则 + Replace string //替换规则 + Way int //规则类型 +} +//应用字幕过滤器结构 +type AppFilterSetings struct { + //通用过滤器 + GlobalFilter struct{ + Switch bool + Words string //过滤词组 + } + //自定义过滤器 + DefinedFilter struct{ + Switch bool + Rule [] *AppDefinedFilterRule + } +} + + //任务文件列表 - 结构 type TaskHandleFile struct { Files [] string @@ -224,6 +262,12 @@ func GetTranslateInputLanguageOptionsSelects() []*LanguageSelects { &LanguageSelects{Id:LANGUAGE_EN , Name:"英文"}, &LanguageSelects{Id:LANGUAGE_JP , Name:"日语"}, &LanguageSelects{Id:LANGUAGE_KOR , Name:"韩语"}, + &LanguageSelects{Id:LANGUAGE_FRA , Name:"法语"}, + &LanguageSelects{Id:LANGUAGE_DE , Name:"德语"}, + &LanguageSelects{Id:LANGUAGE_SPA , Name:"西班牙语"}, + &LanguageSelects{Id:LANGUAGE_RU , Name:"俄语"}, + &LanguageSelects{Id:LANGUAGE_IT , Name:"意大利语"}, + &LanguageSelects{Id:LANGUAGE_TH , Name:"泰语"}, } } @@ -234,6 +278,12 @@ func GetTranslateOutputLanguageOptionsSelects() []*LanguageSelects { &LanguageSelects{Id:LANGUAGE_EN , Name:"英文"}, &LanguageSelects{Id:LANGUAGE_JP , Name:"日语"}, &LanguageSelects{Id:LANGUAGE_KOR , Name:"韩语"}, + &LanguageSelects{Id:LANGUAGE_FRA , Name:"法语"}, + &LanguageSelects{Id:LANGUAGE_DE , Name:"德语"}, + &LanguageSelects{Id:LANGUAGE_SPA , Name:"西班牙语"}, + &LanguageSelects{Id:LANGUAGE_RU , Name:"俄语"}, + &LanguageSelects{Id:LANGUAGE_IT , Name:"意大利语"}, + &LanguageSelects{Id:LANGUAGE_TH , Name:"泰语"}, } } @@ -259,6 +309,32 @@ func (setings *AppSetingsAppStruct) SetCacheAppSetingsData(data *AppSetings) { +//获取 应用过滤器配置 +func (setings *AppFilterAppStruct) GetCacheAppFilterData() *AppFilterSetings { + data := new(AppFilterSetings) + vdata := setings.Data.Get(data) + if v, ok := vdata.(*AppFilterSetings); ok { + return v + } + return data +} +//设置 应用过滤器配置 +func (setings *AppFilterAppStruct) SetCacheAppFilterData(data *AppFilterSetings) { + setings.Data.Set(data) +} + +//过滤类型选项结构 +type FilterTypeSelects struct { + Id int + Name string +} +//获取 过滤类型选项列表 +func GetFilterTypeOptionsSelects() []*FilterTypeSelects { + return []*FilterTypeSelects{ + &FilterTypeSelects{Id:FILTER_TYPE_STRING , Name:"文本替换"}, + &FilterTypeSelects{Id:FILTER_TYPE_REGX , Name:"正则替换"}, + } +} @@ -291,6 +367,7 @@ func (oss *OssAppStruct) GetCacheAliyunOssData() *AliyunOssCache { + //阿里云语音识别引擎 - 缓存结构 type AliyunEngineCache struct { aliyun.AliyunClound @@ -530,6 +607,18 @@ func GetLanguageChar(Language int , Supplier int) string { return "jp" case LANGUAGE_KOR: return "kor" + case LANGUAGE_FRA: + return "fra" + case LANGUAGE_DE: + return "de" + case LANGUAGE_SPA: + return "spa" + case LANGUAGE_RU: + return "ru" + case LANGUAGE_IT: + return "it" + case LANGUAGE_TH: + return "th" } } if Supplier == TRANSLATE_SUPPLIER_TENGXUNYUN { @@ -542,6 +631,18 @@ func GetLanguageChar(Language int , Supplier int) string { return "jp" case LANGUAGE_KOR: return "kr" + case LANGUAGE_FRA: + return "fr" + case LANGUAGE_DE: + return "de" + case LANGUAGE_SPA: + return "es" + case LANGUAGE_RU: + return "ru" + case LANGUAGE_IT: + return "it" + case LANGUAGE_TH: + return "th" } } return "" diff --git a/app/event.go b/app/event.go index e96510f..016703e 100644 --- a/app/event.go +++ b/app/event.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "regexp" "strings" "videosrt/app/aliyun" "videosrt/app/tool" @@ -241,6 +242,11 @@ func(mw *MyMainWindow) RunSpeechEngineSetingDialog(owner walk.Form , confirmCall return } + //去空格 + engine.AppKey = strings.TrimSpace(engine.AppKey) + engine.AccessKeyId = strings.TrimSpace(engine.AccessKeyId) + engine.AccessKeySecret = strings.TrimSpace(engine.AccessKeySecret) + //获取缓存数据 localData := Engine.GetCacheAliyunEngineListData() //生成id @@ -273,8 +279,6 @@ func(mw *MyMainWindow) RunSpeechEngineSetingDialog(owner walk.Form , confirmCall } - - // 运行 新建[百度]翻译引擎 Dialog func(mw *MyMainWindow) RunBaiduTranslateEngineSetingDialog(owner walk.Form , confirmCall func()) { var engine *TranslateEngineStruct @@ -370,6 +374,10 @@ func(mw *MyMainWindow) RunBaiduTranslateEngineSetingDialog(owner walk.Form , con return } + //去空格 + engine.BaiduEngine.AppId = strings.TrimSpace(engine.BaiduEngine.AppId) + engine.BaiduEngine.AppSecret = strings.TrimSpace(engine.BaiduEngine.AppSecret) + //获取缓存数据 localData := Translate.GetCacheTranslateEngineListData() //生成id @@ -485,6 +493,10 @@ func(mw *MyMainWindow) RunTengxunyunTranslateEngineSetingDialog(owner walk.Form return } + //去空格 + engine.TengxunyunEngine.SecretId = strings.TrimSpace(engine.TengxunyunEngine.SecretId) + engine.TengxunyunEngine.SecretKey = strings.TrimSpace(engine.TengxunyunEngine.SecretKey) + //获取缓存数据 localData := Translate.GetCacheTranslateEngineListData() //生成id @@ -646,6 +658,396 @@ func (mw *MyMainWindow) RunObjectStorageSetingDialog(owner walk.Form) { } +//运行 语气词过滤设置 Dialog +func (mw *MyMainWindow) RunGlobalFilterSetingDialog (owner walk.Form , historyWords string , confirmCall func(words string)) { + var dlg *walk.Dialog + var db *walk.DataBinder + var acceptPB, cancelPB *walk.PushButton + + var tmpData = new(AppFilterSetings) + tmpData.GlobalFilter.Words = historyWords + + Dialog{ + AssignTo: &dlg, + Title: "全局语气词过滤设置", + DefaultButton: &acceptPB, + CancelButton: &cancelPB, + DataBinder: DataBinder{ + AssignTo: &db, + Name: "filter", + DataSource: tmpData, + }, + MinSize: Size{500, 300}, + Layout: VBox{}, + Children: []Widget{ + Composite{ + Layout: Grid{Columns: 2}, + Children: []Widget{ + Label{ + ColumnSpan: 1, + Text: "过滤语气词:", + }, + TextEdit{ + ColumnSpan: 1, + MinSize: Size{150, 80}, + Text: Bind("GlobalFilter.Words"), + VScroll: true, + }, + Label{ + ColumnSpan: 2, + Text: "说明:\r\n“过滤语气词” 支持设置多个,请保持每个词语都单独一行", + TextColor:walk.RGB(190 , 190 , 190), + }, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + HSpacer{}, + PushButton{ + AssignTo: &acceptPB, + Text: "保存", + OnClicked: func() { + if err := db.Submit(); err != nil { + log.Fatal(err) + return + } + confirmCall(tmpData.GlobalFilter.Words) + //参数验证 + dlg.Accept() + }, + }, + PushButton{ + AssignTo: &cancelPB, + Text: "取消", + OnClicked: func() { dlg.Cancel() }, + }, + }, + }, + }, + }.Run( owner ) +} + + + +type DefinedRuleTableRows struct { + Id int + Target string //目标规则 + Replace string //替换规则 + Way int //规则类型 +} +type DefinedRuleTableModel struct { + walk.SortedReflectTableModelBase + maxIndex int + items []*DefinedRuleTableRows +} +func NewDefinedRuleTableModel () *DefinedRuleTableModel { + t := new(DefinedRuleTableModel) + return t +} +func (m *DefinedRuleTableModel) Items() interface{} { + return m.items +} +func (m *DefinedRuleTableModel) AddRow (row *DefinedRuleTableRows) { + m.maxIndex++ + row.Id = m.maxIndex; + m.items = append(m.items , row) +} +func (m *DefinedRuleTableModel) BatchDelRow (indexs []int) { + id := make([]int , 0) + for row_i , row_v := range m.items { + for _ , op_v := range indexs { + if row_i == op_v { + id = append(id , row_v.Id) + } + } + } + for _ , vid := range id { + m.DelRow(vid) + } +} +func (m *DefinedRuleTableModel) GetRowIndex (index int) *DefinedRuleTableRows { + tmp := new(DefinedRuleTableRows) + for row_i , row_v := range m.items { + if row_i == index { + tmp = row_v + break + } + } + return tmp +} +func (m *DefinedRuleTableModel) DelRow (id int) { + t := len(m.items) + for row_i , row_v := range m.items { + if row_v.Id == id { + if row_i == 0 { + if t <= 1 { + m.items = make([]*DefinedRuleTableRows , 0) + } else { + m.items = m.items[row_i+1:] + } + } else if row_i+1 >= t { + m.items = m.items[:row_i] + } else { + m.items = append(m.items[:row_i] , m.items[row_i+1:]...) + } + break + } + } +} +func (tv *DefinedRuleTableModel) SetAndInitFilterRules (rules []*AppDefinedFilterRule) { + //初始化 + tv.maxIndex = 0 + tv.items = make([]*DefinedRuleTableRows , 0) + + for _ , v := range rules { + tv.maxIndex++ + tv.items = append(tv.items , &DefinedRuleTableRows{ + Id:tv.maxIndex, + Target:v.Target, + Replace:v.Replace, + Way:v.Way, + }) + } +} +func (tv *DefinedRuleTableModel) GetFilterRuleResult () []*AppDefinedFilterRule { + result := make([]*AppDefinedFilterRule , 0) + for _ , v := range tv.items { + result = append(result , &AppDefinedFilterRule{ + Target:v.Target, + Replace:v.Replace, + Way:v.Way, + }) + } + return result +} + + +//运行 自定义过滤设置 Dialog +func (mw *MyMainWindow) RunDefinedFilterSetingDialog (owner walk.Form , historyRule []*AppDefinedFilterRule , confirmCall func(rule []*AppDefinedFilterRule)) { + var dlg *walk.Dialog + var acceptPB, cancelPB *walk.PushButton + var tv *walk.TableView + + tableModel := NewDefinedRuleTableModel() + tableModel.SetAndInitFilterRules(historyRule) + + var currentIndexs []int = make([]int , 0) //选择的项 + + Dialog{ + AssignTo: &dlg, + Title: "自定义过滤设置", + DefaultButton: &acceptPB, + CancelButton: &cancelPB, + MinSize: Size{600, 500}, + Layout: VBox{}, + Children: []Widget{ + Composite{ + Layout: Grid{Columns: 2}, + Children: []Widget{ + PushButton{ + Text: "新增规则", + OnClicked: func() { + copyRow := new(DefinedRuleTableRows) + if len(currentIndexs) == 1 { + copyRow = tableModel.GetRowIndex(currentIndexs[0]) + } + + mw.RunNewDefinedFilterRuleDialog(mw , copyRow , func(rule *DefinedRuleTableRows) { + tableModel.AddRow(rule) + tv.SetModel(tableModel) + }) + }, + }, + PushButton{ + Text: "删除规则", + OnClicked: func() { + if len(currentIndexs) < 1 { + mw.NewErrormationTips("错误" , "请选择操作的对象") + return + } + tableModel.BatchDelRow(currentIndexs) + tv.SetModel(tableModel) + }, + }, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + TableView{ + Name:"tableView", + AssignTo: &tv, + AlternatingRowBG: true, + NotSortableByHeaderClick: true, + MultiSelection:true, + Columns: []TableViewColumn{ + {Title: "编号", DataMember: "Id" , Width:90}, + {Title: "类型", DataMember: "Way", Width:90 , FormatFunc: func(value interface{}) string { + switch v := value.(type) { + case int: + if v == FILTER_TYPE_STRING { + return "文本过滤" + } + if v == FILTER_TYPE_REGX { + return "正则过滤" + } + return "" + default: + return "" + } + }}, + {Title: "目标规则", DataMember: "Target", Width:165}, + {Title: "替换规则", DataMember: "Replace", Width:185}, + }, + Model: tableModel, + OnSelectedIndexesChanged: func() { + indexs := tv.SelectedIndexes() + if (len(indexs) > 0) { + currentIndexs = indexs + } else { + currentIndexs = []int{} + } + }, + }, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + HSpacer{}, + PushButton{ + AssignTo: &acceptPB, + Text: "保存", + OnClicked: func() { + confirmCall(tableModel.GetFilterRuleResult()) + + //参数验证 + dlg.Accept() + }, + }, + PushButton{ + AssignTo: &cancelPB, + Text: "取消", + OnClicked: func() { dlg.Cancel() }, + }, + }, + }, + }, + }.Run(owner) +} + +//新建自定义过滤规则 Dialog +func (mw *MyMainWindow) RunNewDefinedFilterRuleDialog (owner walk.Form , copyRows *DefinedRuleTableRows , confirmCall func(rule *DefinedRuleTableRows)) { + var dlg *walk.Dialog + var db *walk.DataBinder + var acceptPB, cancelPB *walk.PushButton + + var tmpData = new(DefinedRuleTableRows) + if copyRows.Id != 0 { + tmpData.Target = copyRows.Target + tmpData.Replace = copyRows.Replace + tmpData.Way = copyRows.Way + } else { + tmpData.Way = 1 //默认 + } + + Dialog{ + AssignTo: &dlg, + Title: "新增自定义过滤规则", + DefaultButton: &acceptPB, + CancelButton: &cancelPB, + DataBinder: DataBinder{ + AssignTo: &db, + Name: "defined", + DataSource: tmpData, + }, + MinSize: Size{500, 300}, + Layout: VBox{}, + Children: []Widget{ + Composite{ + Layout: Grid{Columns: 2}, + Children: []Widget{ + Label{ + ColumnSpan: 1, + Text: "目标规则:", + }, + LineEdit{ + ColumnSpan: 1, + MinSize: Size{Width:150}, + Text: Bind("Target"), + }, + Label{ + ColumnSpan: 1, + Text: "替换规则:", + }, + LineEdit{ + ColumnSpan: 1, + MinSize: Size{Width:150}, + Text: Bind("Replace"), + }, + Label{ + Text: "过滤类型:", + }, + ComboBox{ + Value: Bind("Way", SelRequired{}), + BindingMember: "Id", + DisplayMember: "Name", + Model: GetFilterTypeOptionsSelects(), + }, + + Label{ + ColumnSpan: 2, + Text: "说明:\r\n1.“目标规则” 填写查找的文本/正则, “替换规则” 填写替换的文本/正则\r\n2.过滤类型为正则时,“替换规则” 允许使用 $1...$9 进行反向引用", + TextColor:walk.RGB(190 , 190 , 190), + }, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + HSpacer{}, + PushButton{ + AssignTo: &acceptPB, + Text: "保存", + OnClicked: func() { + if err := db.Submit(); err != nil { + log.Fatal(err) + return + } + if strings.TrimSpace(tmpData.Target) == "" { + mw.NewErrormationTips("错误" , "必须填写目标规则噢") + return + } + if tmpData.Way == FILTER_TYPE_REGX { + //正则规则 + //校验规则 + _, e := regexp.Compile(tmpData.Target) + if e != nil { + mw.NewErrormationTips("错误" , "目标正则规则格式校验不通过,请检查是否正确") + return + } + } + + confirmCall(tmpData) + //参数验证 + dlg.Accept() + }, + }, + PushButton{ + AssignTo: &cancelPB, + Text: "取消", + OnClicked: func() { dlg.Cancel() }, + }, + }, + }, + }, + }.Run( owner ) +} + + + + //打开 Github func (mw *MyMainWindow) OpenAboutGithub() { tool.OpenUrl("https://github.com/wxbool/video-srt-windows") diff --git a/app/srt.go b/app/srt.go index 27d8a7a..767a67a 100644 --- a/app/srt.go +++ b/app/srt.go @@ -57,6 +57,7 @@ type SrtTranslateApp struct { OutputEncode int //输出文件编码 MaxConcurrency int //最大处理并发数 TranslateCfg *SrtTranslateStruct //翻译配置 + FilterSetings *AppFilterSetings //过滤器配置 LogHandler func(s string , file string) //日志回调 SuccessHandler func(file string) //成功回调 @@ -79,6 +80,11 @@ func NewSrtTranslateApp(appDir string) *SrtTranslateApp { 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 @@ -154,6 +160,9 @@ func (app *SrtTranslateApp) Run(srtfile string) { //字幕翻译 app.SrtTranslate(srtfile , srtRows) + //字幕过滤 + app.SrtFilters(srtRows , srtfile) + //输出文件 if app.OutputType.SRT { app.SrtOutputFile(srtfile , srtRows , OUTPUT_SRT) @@ -236,6 +245,40 @@ func (app *SrtTranslateApp) SrtTranslate(file string , srtRows []*SrtRows) { } +//字幕过滤处理 +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 @@ -283,6 +326,10 @@ func (app *SrtTranslateApp) SrtOutputFile(file string , srtRows []*SrtRows , out for _ , data := range srtRows { var linestr string + if data.Text == "" { + continue //跳过空行 + } + //字幕、歌词文件处理 if outputType == OUTPUT_SRT || outputType == OUTPUT_LRC { //拼接 diff --git a/app/tool/chinese_simple.go b/app/tool/chinese_simple.go index eb9c590..2d544f8 100644 --- a/app/tool/chinese_simple.go +++ b/app/tool/chinese_simple.go @@ -1,7 +1,9 @@ package tool import ( + "regexp" "strings" + "unicode/utf8" ) @@ -81,3 +83,23 @@ func ValiChineseNumberChar(s string , unit bool) bool { } return false } + + +//获取utf8字符长度 +func GetStringUtf8Length(s string) int { + return utf8.RuneCountInString(s) +} + + +//检测文本是否仅符号 +func CheckOnlySymbolText(s string) bool { + if GetStringUtf8Length(s) > 6 { + return false + } + regx := regexp.MustCompile(`^(\\|\{|\}|\[|\]|(|)|\(|\)|\*|/|~|<|>|_|\-|\+|=|&|%|\$|@|#|—|」|「|!|,|。|。|‍|、|?|;|:|‘|’|”|“|"|'|,|\.|\?|;|:|!|\s)+$`) + if regx.Match([]byte(s)) { + return true + } else { + return false + } +} \ No newline at end of file diff --git a/app/video.go b/app/video.go index 4d784b7..10854d4 100644 --- a/app/video.go +++ b/app/video.go @@ -54,6 +54,7 @@ type VideoSrt struct { MaxConcurrency int //最大处理并发数 TranslateCfg *VideoSrtTranslateStruct //翻译配置 + FilterSetings *AppFilterSetings //过滤器配置 LogHandler func(s string , video string) //日志回调 SuccessHandler func(video string) //成功回调 @@ -97,6 +98,10 @@ func (app *VideoSrt) InitAppConfig(oss *AliyunOssCache , engine *AliyunEngineCac func (app *VideoSrt) InitTranslateConfig (translateSettings *VideoSrtTranslateStruct) { app.TranslateCfg = translateSettings } +//加载过滤器配置 +func (app *VideoSrt) InitFilterConfig (filterSetings *AppFilterSetings) { + app.FilterSetings = filterSetings +} func (app *VideoSrt) SetCloseAutoDeleteOssTempFile(state bool) { @@ -224,7 +229,13 @@ func (app *VideoSrt) Run(video string) { app.Log("文件识别成功 , 字幕处理中 ..." , video) //翻译字幕块 - AliyunAudioResultTranslate(app , video , AudioResult , IntelligentBlockResult) + if e := AliyunAudioResultTranslate(app , video , AudioResult , IntelligentBlockResult); e != nil { + app.Log("字幕翻译失败,已强制关闭翻译,仅保留原始文件" , video) + app.TranslateCfg.TranslateSwitch = false + } + + //字幕过滤 + AliyunResultFilter(app , video , AudioResult , IntelligentBlockResult) //输出文件 if app.OutputType.SRT { @@ -371,7 +382,7 @@ func AliyunAudioRecognition(engine aliyun.AliyunClound , filelink string) (Audio //遍历获取识别结果 resultError := engine.GetAudioFileResult(taskid , client , func(result []byte) { - //mylog.WriteLog( string( result ) ) + //mylog.WriteLog(string(result)) //结果处理 statusText, _ := jsonparser.GetString(result, "StatusText") //结果状态 @@ -438,7 +449,7 @@ func AliyunAudioRecognition(engine aliyun.AliyunClound , filelink string) (Audio //阿里云识别字幕块翻译处理 -func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) { +func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) error { //输出日志 if app.TranslateCfg.TranslateSwitch { app.Log("字幕翻译处理中 ..." , video) @@ -448,7 +459,7 @@ func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[i app.Log("你使用的是 “百度翻译标准版” 账号,翻译速度较慢,请耐心等待 ..." , video) } } else { - return + return nil } if app.OutputType.SRT || app.OutputType.LRC { @@ -456,7 +467,8 @@ func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[i for _ , data := range result { transResult,e := app.RunTranslate(data.Text , video) if e != nil { - panic(e) //终止翻译 + return e + //panic(e) //终止翻译 } data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文 } @@ -468,14 +480,88 @@ func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[i for _ , data := range result { transResult,e := app.RunTranslate(data.Text , video) if e != nil { - panic(e) //终止翻译 + return e + //panic(e) //终止翻译 } data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文 } } } + + return nil } +//阿里云识别字幕过滤 +func AliyunResultFilter(app *VideoSrt , video string , AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) { + if !app.FilterSetings.DefinedFilter.Switch && !app.FilterSetings.GlobalFilter.Switch { + return + } + + app.Log("字幕过滤处理中 ..." , video) + + //语气词过滤 + if app.FilterSetings.GlobalFilter.Switch && strings.TrimSpace(app.FilterSetings.GlobalFilter.Words) != "" { + modalWords := strings.Split(app.FilterSetings.GlobalFilter.Words , "\r\n") + + for _,result := range IntelligentBlockResult { + for _ , data := range result { + for _ , w := range modalWords { + + data.Text = ModalWordsFilter(data.Text , w) + if app.TranslateCfg.TranslateSwitch { + data.TranslateText = ModalWordsFilter(data.TranslateText , w) + } + + } + } + } + for _,result := range AudioResult { + for _ , data := range result { + for _ , w := range modalWords { + + data.Text = ModalWordsFilter(data.Text , w) + if app.TranslateCfg.TranslateSwitch { + data.TranslateText = ModalWordsFilter(data.TranslateText , w) + } + + } + } + } + } + + //自定义规则过滤 + if app.FilterSetings.DefinedFilter.Switch && len(app.FilterSetings.DefinedFilter.Rule) > 0 { + rules := app.FilterSetings.DefinedFilter.Rule + + for _,result := range IntelligentBlockResult { + for _ , data := range result { + for _ , rule := range rules { + + data.Text = DefinedWordRuleFilter(data.Text , rule) + if app.TranslateCfg.TranslateSwitch { + data.TranslateText = DefinedWordRuleFilter(data.TranslateText , rule) + } + + } + } + } + for _,result := range AudioResult { + for _ , data := range result { + for _ , rule := range rules { + + data.Text = DefinedWordRuleFilter(data.Text , rule) + if app.TranslateCfg.TranslateSwitch { + data.TranslateText = DefinedWordRuleFilter(data.TranslateText , rule) + } + + } + } + } + } +} + + + //阿里云录音识别结果集生成字幕文件 func AliyunAudioResultMakeSubtitleFile(app *VideoSrt , video string , outputType int , AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) { var subfileDir string @@ -552,6 +638,10 @@ func AliyunAudioResultMakeSubtitleFile(app *VideoSrt , video string , outputType for _ , data := range result { var linestr string + if data.Text == "" { + continue //跳过空行 + } + //字幕、歌词文件处理 if outputType == OUTPUT_SRT || outputType == OUTPUT_LRC { //拼接 diff --git a/data/img/muyan.png b/data/img/muyan.png new file mode 100644 index 0000000..0837d28 Binary files /dev/null and b/data/img/muyan.png differ diff --git a/go.mod b/go.mod index 14a44ce..6fab679 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,23 @@ module videosrt go 1.12 require ( - github.com/CodyGuo/win v0.0.0-20170113125346-08e6b7208274 github.com/PuerkitoBio/goquery v1.5.0 - github.com/TencentCloud/tencentcloud-sdk-go v3.0.128+incompatible github.com/aliyun/alibaba-cloud-sdk-go v1.60.268 github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible - github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect + github.com/bitly/go-simplejson v0.5.0 + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e + github.com/dreamCodeMan/xfyun_go_sdk v0.0.0-20200227025001-249b66fa8600 + github.com/kr/pretty v0.2.0 // indirect github.com/lxn/walk v0.0.0-20191121152919-b7c43041fb1b - github.com/lxn/win v0.0.0-20191106123917-121afc750dd3 - github.com/russross/blackfriday v2.0.0+incompatible - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/lxn/win v0.0.0-20191106123917-121afc750dd3 // indirect + github.com/mewkiz/flac v1.0.6 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/satori/go.uuid v1.2.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go v3.0.128+incompatible golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/main.go b/main.go index bdf3724..12d5dbc 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ import ( ) //应用版本号 -const APP_VERSION = "0.2.9.5" +const APP_VERSION = "0.3.0" var AppRootDir string var mw *MyMainWindow @@ -22,6 +22,9 @@ var ( outputSrtChecked *walk.CheckBox outputLrcChecked *walk.CheckBox outputTxtChecked *walk.CheckBox + + globalFilterChecked *walk.CheckBox + definedFilterChecked *walk.CheckBox ) @@ -53,6 +56,8 @@ func main() { var operateTranslateEngineDb *walk.DataBinder var operateTranslateDb *walk.DataBinder var operateDb *walk.DataBinder + var operateFilter *walk.DataBinder + var operateFrom = new(OperateFrom) var startBtn *walk.PushButton //生成字幕Btn @@ -62,6 +67,7 @@ func main() { var dropFilesEdit *walk.TextEdit var appSetings = Setings.GetCacheAppSetingsData() + var appFilter = Filter.GetCacheAppFilterData() //初始化展示配置 operateFrom.Init(appSetings) @@ -272,7 +278,7 @@ func main() { OnTriggered: mw.OpenAboutGitee, }, Action{ - Text: "帮助文本", + Text: "帮助文档", Image: "./data/img/version.png", OnTriggered: func() { _ = tool.OpenUrl("https://www.yuque.com/viggo-t7cdi/videosrt") @@ -286,12 +292,22 @@ func main() { }, Action{ Text: "QQ交流群", + Checked:false, + Visible:false, + Checkable:false, OnTriggered: func() { _ = tool.OpenUrl("https://gitee.com/641453620/video-srt-windows#%E4%BA%A4%E6%B5%81%E8%81%94%E7%B3%BB") }, }, }, }, + Menu{ + Text: "语音合成配音/文章转视频", + Image: "./data/img/muyan.png", + OnTriggered: func() { + _ = tool.OpenUrl("http://www.mu-yan.net/") + }, + }, }, }, Size: Size{800, 600}, @@ -527,6 +543,69 @@ func main() { }, }, + + /*过滤器设置*/ + HSplitter{ + Children:[]Widget{ + Composite{ + DataBinder: DataBinder{ + AssignTo: &operateFilter, + DataSource: appFilter, + }, + Layout: Grid{Columns: 5}, + Children: []Widget{ + Label{ + Text: "过滤设置:", + }, + CheckBox{ + AssignTo:&globalFilterChecked, + Text:"语气词过滤 ", + Checked: Bind("GlobalFilter.Switch"), + OnClicked: func() { + _ = operateFilter.Submit() + //更新缓存 + Filter.SetCacheAppFilterData(appFilter) + }, + }, + CheckBox{ + AssignTo:&definedFilterChecked, + Text:"自定义过滤 ", + Checked: Bind("DefinedFilter.Switch"), + OnClicked: func() { + _ = operateFilter.Submit() + //更新缓存 + Filter.SetCacheAppFilterData(appFilter) + }, + }, + + PushButton{ + Text: "语气词过滤设置", + MaxSize:Size{95 , 55}, + OnClicked: func() { + mw.RunGlobalFilterSetingDialog(mw , appFilter.GlobalFilter.Words , func(words string) { + appFilter.GlobalFilter.Words = words + //更新缓存 + Filter.SetCacheAppFilterData(appFilter) + }) + }, + }, + PushButton{ + Text: "自定义过滤设置", + MaxSize:Size{95 , 55}, + OnClicked: func() { + mw.RunDefinedFilterSetingDialog(mw , appFilter.DefinedFilter.Rule , func(rule []*AppDefinedFilterRule) { + appFilter.DefinedFilter.Rule = rule + //更新缓存 + Filter.SetCacheAppFilterData(appFilter) + }) + }, + }, + }, + }, + }, + }, + + /*输出设置*/ HSplitter{ Children:[]Widget{ @@ -674,8 +753,11 @@ func main() { return } //校验输入语言 - if tempAppSetting.InputLanguage == LANGUAGE_KOR { - mw.NewErrormationTips("错误" , "由于语音提供商的限制,生成字幕允许的输入语言目前不支持韩语") + if tempAppSetting.InputLanguage != LANGUAGE_ZH && + tempAppSetting.InputLanguage != LANGUAGE_EN && + tempAppSetting.InputLanguage != LANGUAGE_JP && + tempAppSetting.InputLanguage != LANGUAGE_SPA { + mw.NewErrormationTips("错误" , "由于语音提供商的限制,生成字幕仅支持中文、英文、日语、西班牙语") return } @@ -721,6 +803,7 @@ func main() { //加载配置 videosrt.InitAppConfig(ossData , currentEngine) videosrt.InitTranslateConfig(tempTranslateCfg) + videosrt.InitFilterConfig(appFilter) videosrt.SetSrtDir(appSetings.SrtFileDir) videosrt.SetSoundTrack(appSetings.SoundTrack) videosrt.SetMaxConcurrency(appSetings.MaxConcurrency) @@ -806,7 +889,7 @@ func main() { PushButton{ AssignTo: &startTranslateBtn, - Text: "字幕翻译", + Text: "字幕处理", MinSize:Size{Height:50}, OnClicked: func() { //待处理的文件 @@ -863,6 +946,7 @@ func main() { //加载配置 srtTranslateApp.InitTranslateConfig(tempTranslateCfg) + srtTranslateApp.InitFilterConfig(appFilter) srtTranslateApp.SetSrtDir(appSetings.SrtFileDir) srtTranslateApp.SetMaxConcurrency(appSetings.MaxConcurrency)