Browse Source

新增可参考项目

master
VIVIMAN 3 years ago
parent
commit
05f8469074
  1. 47
      gui_learn/.github/workflows/go.yml
  2. 6
      gui_learn/.gitignore
  3. 339
      gui_learn/LICENSE
  4. 116
      gui_learn/README.md
  5. 268
      gui_learn/app/aliyun/engine.go
  6. 102
      gui_learn/app/aliyun/oss.go
  7. 416
      gui_learn/app/aliyun/tool.go
  8. 97
      gui_learn/app/app_tool.go
  9. 665
      gui_learn/app/data.go
  10. 53
      gui_learn/app/datacache/cache.go
  11. 31
      gui_learn/app/datacache/json.go
  12. 1128
      gui_learn/app/event.go
  13. 57
      gui_learn/app/ffmpeg/ffmpeg.go
  14. 27
      gui_learn/app/log.go
  15. 148
      gui_learn/app/parse/srt.go
  16. 88
      gui_learn/app/parse/tool.go
  17. 451
      gui_learn/app/srt.go
  18. 92
      gui_learn/app/task.go
  19. 89
      gui_learn/app/task_tanslate.go
  20. 122
      gui_learn/app/tool/chinese_simple.go
  21. 248
      gui_learn/app/tool/tool.go
  22. 110
      gui_learn/app/translate/baidu.go
  23. 91
      gui_learn/app/translate/tengxunyun.go
  24. 83
      gui_learn/app/version.go
  25. 836
      gui_learn/app/video.go
  26. 5
      gui_learn/build.bat
  27. BIN
      gui_learn/data/img/about.png
  28. BIN
      gui_learn/data/img/app-setings.png
  29. BIN
      gui_learn/data/img/app.ico
  30. BIN
      gui_learn/data/img/gitee.png
  31. BIN
      gui_learn/data/img/github.png
  32. BIN
      gui_learn/data/img/index.png
  33. BIN
      gui_learn/data/img/media.png
  34. BIN
      gui_learn/data/img/muyan.png
  35. BIN
      gui_learn/data/img/new.png
  36. BIN
      gui_learn/data/img/open.png
  37. BIN
      gui_learn/data/img/oss.png
  38. BIN
      gui_learn/data/img/setings.png
  39. BIN
      gui_learn/data/img/translate.png
  40. BIN
      gui_learn/data/img/version.png
  41. BIN
      gui_learn/data/img/voice.png
  42. 20
      gui_learn/go.mod
  43. 1065
      gui_learn/main.go
  44. 15
      gui_learn/main.manifest
  45. BIN
      gui_learn/rsrc.syso

47
gui_learn/.github/workflows/go.yml

@ -0,0 +1,47 @@
name: Build&test video-srt-windows
on:
pull_request:
branches:
- master
- test
- i18n
push:
branches:
- master
- test
- i18n
jobs:
Build:
runs-on: windows-latest
strategy:
matrix:
ver: [ 'test','build' ]
go: [ '1.15','1.14', '1.13' ]
name: ${{ matrix.ver }} video-srt-windows With Go${{ matrix.go }}
steps:
- uses: actions/checkout@v2
- name: Setup go ${{ matrix.go }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: ${{ matrix.ver }} video-srt-windows in go ${{ matrix.go }}
run: go ${{ matrix.ver }}
- name: Cache local go package
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Artifact Pack
uses: actions/upload-artifact@v2
with:
name: videosrt-${{ matrix.ver }}-${{ matrix.go }}.exe
path: videosrt.exe

6
gui_learn/.gitignore

@ -0,0 +1,6 @@
tests
data/json*
go.sum
*.zip
*.exe
.idea

339
gui_learn/LICENSE

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

116
gui_learn/README.md

@ -0,0 +1,116 @@
# VideoSrt
[![GitHub license](https://img.shields.io/github/license/wxbool/video-srt-windows?logo=Apache&style=flat-square)](https://github.com/wxbool/video-srt-windows/blob/master/LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/wxbool/video-srt-windows?style=flat-square)](https://github.com/wxbool/video-srt-windows/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/wxbool/video-srt-windows?logo=github&style=flat-square)](https://github.com/wxbool/video-srt-windows/network)
![GitHub Action](https://github.com/wxbool/video-srt-windows/workflows/Go/badge.svg?branch=master)
## 简介
`VideoSrt` 是用 `Golang`语言,基于 [lxn/walk](https://github.com/lxn/walk) Windows-GUI 工具包开发。
这是一个可以识别视频语音自动生成字幕SRT文件的开源软件工具。<br />适用于快速、批量的为媒体(视频/音频)生成中/英文字幕、文本文件的业务场景。
0.3.3 版本将会使用以下接口:
- 阿里云 [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)
- 腾讯云 [翻译API](https://cloud.tencent.com/product/tmt)
软件帮助文档/使用教程看这个:[https://www.yuque.com/viggo-t7cdi/videosrt](https://www.yuque.com/viggo-t7cdi/videosrt)
B站Up主自制教程:[https://search.bilibili.com/all?keyword=videosrt](https://search.bilibili.com/all?keyword=videosrt)
线上“字幕生成/字幕翻译”解决方案:[字幕酱(付费)](https://www.zimujiang.com/aff?code=aannv4os)
线上“文本配音/字幕配音/文章转视频”解决方案:[幕言](https://www.mu-yan.com/?videosrt)
<a name="0b884e4f"></a>
## 界面预览
![](https://pic.downk.cc/item/5f28c7ea14195aa594369a59.gif)
## 应用场景
- 识别**视频/音频**的语音生成字幕文件(支持中英互译,双语字幕)
- 提取**视频/音频**的语音文本
- 批量翻译、过滤处理/编码SRT字幕文件
<a name="b89d37d3"></a>
## 软件优势
- 使用阿里云语音识别接口,准确度高,标准普通话/英语识别率95%以上
- 视频识别无需上传原视频,方便快速且节省时间
- 支持多任务多文件批量处理
- 支持视频、音频常见多种格式文件
- 支持同时输出字幕SRT文件、LRC文件、普通文本3种类型
- 支持语气词过滤、自定义文本过滤、正则过滤等,使软件生成的字幕更加精准
- 支持字幕中英互译、双语字幕输出,及日语、韩语、法语、德语、西班牙语、俄语、意大利语、泰语等
- 支持多翻译引擎(百度翻译、腾讯云翻译)
- 支持批量翻译、编码SRT字幕文件
<a name="Download"></a>
## Download
<a name="e66a66f1"></a>
##### 下载地址:
- (v0.3.3)(含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.3.3/video-srt-gui-ffmpeg-0.3.3-x64.zip)
- (v0.3.3)(不含ffmpeg依赖) [点我下载](http://file.viggo.site/video-srt/0.3.3/video-srt-gui-0.3.3-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)
你也可以到 [release](https://github.com/wxbool/video-srt-windows/releases) 页面下载其他版本
<a name="1bbbb204"></a>
## 注意事项
- 软件目录下的 `data`目录为数据存储目录,请勿删除。否则可能会导致配置丢失
- 项目使用了 [ffmpeg](http://ffmpeg.org/) 依赖,除非您的电脑已经安装了`ffmpeg`环境,否则请下载包含`ffmpeg`依赖的软件包
<a name="9a751511"></a>
## 升级说明
- 先下载最新版本的软件包
- 然后用旧版本软件的 `data` 文件夹覆盖新版软件的 `data` 文件夹
- 0.2.6 升级至 0.2.9 以上的版本时,由于翻译设置无法直接兼容低版本,可能需要重新在软件创建翻译引擎才能继续使用翻译功能
## 其他
今日,有小伙伴在淘宝、闲鱼上发现一些界面相似的盗版且额外收费的软件。由于`VideoSrt` 是开源软件,所以不排除个别不良用户通过微调软件源代码,然后进行二次售卖甚至传播病毒。
目前已知的有:`大象字幕`
因此,软件的安装包不要在第三方平台渠道下载,尽量在 github/码云/帮助文档 等官方渠道的页面进行下载。
小伙伴们要擦亮眼睛,打击盗版,望周知!
## FAQ
##### 1.为什么Linux和Mac不能用?
因为`VideoSrt`的GUI是使用[lxn/walk](https://github.com/lxn/walk)开发的,仅支持Windows的GUI,如果您想在Linux上使用,可以体验[CLI版本](https://github.com/wxbool/video-srt)
##### 2.使用此软件会产生费用吗?
如果您适量使用本软件(各个API的免费使用额度可以自行查询),将不会产生费用。
如果您大量使用,建议根据自己的情况购买各个平台的资源包,以满足需求。
##### 3.难受,为什么我一直报错?
报错的原因有很多,软件配置错误、阿里云、腾讯云等账户权限问题都可能会导致软件显示错误。如果您遇到麻烦,建议加入`QQ群`与我们交流。
<a name="f3dc992e"></a>
## 交流&联系
- QQ交流群3:[742310571](https://jq.qq.com/?_wv=1027&k=OPKLP16H) (已满)
- QQ交流群2:[1035527812](https://jq.qq.com/?_wv=1027&k=lzbkMgzJ) (980/1000)
- QQ交流群1:[109695078](https://jq.qq.com/?_wv=1027&k=5Eco2hO) (已满)
<a name="AyJ3E"></a>
## 捐赠&支持
![](https://pic2.superbed.cn/item/5e00b93476085c3289dd2dc0.png)
#### 每日领支付宝红包(2021.8 ~ 2021.11)
<div style="text-align: center;">
<img src="https://cdn.nlark.com/yuque/0/2021/png/695280/1633671491324-b3375ad6-027b-4297-bd7f-2f42784dccbf.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_VmlkZW9TcnQ%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10%2Fresize%2Cw_794%2Climit_0" width=220 />
</div>

268
gui_learn/app/aliyun/engine.go

@ -0,0 +1,268 @@
package aliyun
import (
"encoding/json"
"errors"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"strconv"
"time"
)
const (
ALIYUN_CLOUND_REGION_CHA = 1 //中国
ALIYUN_CLOUND_REGION_INT = 2 //海外
)
//阿里云语音识别引擎
type AliyunClound struct {
AccessKeyId string
AccessKeySecret string
AppKey string
Region int
}
//阿里云录音文件识别结果集
type AliyunAudioRecognitionResult struct {
Text string //文本结果
TranslateText string //翻译文本结果
ChannelId int64 //音轨ID
BeginTime int64 //该句的起始时间偏移,单位为毫秒
EndTime int64 //该句的结束时间偏移,单位为毫秒
SilenceDuration int64 //本句与上一句之间的静音时长,单位为秒
SpeechRate int64 //本句的平均语速,单位为每分钟字数
EmotionValue int64 //情绪能量值1-10,值越高情绪越强烈
}
//阿里云识别词语数据集
type AliyunAudioWord struct {
Word string
ChannelId int64
BeginTime int64
EndTime int64
}
// 定义常量
const REGION_ID_REGION_INT string = "ap-southeast-1"
const ENDPOINT_NAME_REGION_INT string = "ap-southeast-1"
const PRODUCT_REGION_INT string = "nls-filetrans"
const DOMAIN_REGION_INT string = "filetrans.ap-southeast-1.aliyuncs.com"
const API_VERSION_REGION_INT string = "2019-08-23"
const REGION_ID_REGION_CHA string = "cn-shanghai"
const ENDPOINT_NAME_REGION_CHA string = "cn-shanghai"
const PRODUCT_REGION_CHA string = "nls-filetrans"
const DOMAIN_REGION_CHA string = "filetrans.cn-shanghai.aliyuncs.com"
const API_VERSION_REGION_CHA string = "2018-08-17"
const POST_REQUEST_ACTION string = "SubmitTask"
const GET_REQUEST_ACTION string = "GetTaskResult"
// 请求参数key
const KEY_APP_KEY string = "appkey"
const KEY_FILE_LINK string = "file_link"
const KEY_VERSION string = "version"
const KEY_ENABLE_WORDS string = "enable_words"
//是否打开ITN,中文数字将转为阿拉伯数字输出,默认值为false
const KEY_ENABLE_INVERSE_TEXT_NORMAL = "enable_inverse_text_normalization"
//是否启⽤语义断句,取值:true/false,默认值false
const KEY_ENABLE_SEMANTIC_SENTENCE_DETECTION = "enable_semantic_sentence_detection"
//是否启用时间戳校准功能,取值:true/false,默认值false
const KEY_ENABLE_TIMESTAMP_ALIGNMENT = "enable_timestamp_alignment"
// 响应参数key
const KEY_TASK string = "Task"
const KEY_TASK_ID string = "TaskId"
const KEY_STATUS_TEXT string = "StatusText"
const KEY_RESULT string = "Result"
// 状态值
const STATUS_SUCCESS string = "SUCCESS"
const STATUS_RUNNING string = "RUNNING"
const STATUS_QUEUEING string = "QUEUEING"
//发起录音文件识别
//接口文档 https://help.aliyun.com/document_detail/90727.html?spm=a2c4g.11186623.6.581.691af6ebYsUkd1
func (c AliyunClound) NewAudioFile(fileLink string) (string , *sdk.Client , error) {
regionId, domain, apiVersion, product := c.GetApiVariable()
client, err := sdk.NewClientWithAccessKey(regionId, c.AccessKeyId, c.AccessKeySecret)
if err != nil {
return "" , client , err
}
client.SetConnectTimeout(time.Second * 20)
postRequest := requests.NewCommonRequest()
postRequest.Domain = domain
postRequest.Version = apiVersion
postRequest.Product = product
postRequest.ApiName = POST_REQUEST_ACTION
postRequest.Method = "POST"
mapTask := make(map[string]string)
mapTask[KEY_APP_KEY] = c.AppKey
mapTask[KEY_FILE_LINK] = fileLink
// 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置
mapTask[KEY_VERSION] = "4.0"
// 设置是否输出词信息,默认为false,开启时需要设置version为4.0
mapTask[KEY_ENABLE_WORDS] = "true"
//统一后处理
mapTask[KEY_ENABLE_INVERSE_TEXT_NORMAL] = "true"
mapTask[KEY_ENABLE_SEMANTIC_SENTENCE_DETECTION] = "true"
mapTask[KEY_ENABLE_TIMESTAMP_ALIGNMENT] = "true"
// to json
task, err := json.Marshal(mapTask)
if err != nil {
return "" , client , errors.New("to json error .")
}
postRequest.FormParams[KEY_TASK] = string(task)
// 发起请求
postResponse, err := client.ProcessCommonRequest(postRequest)
if err != nil {
return "" , client , err
}
postResponseContent := postResponse.GetHttpContentString()
//校验请求
if (postResponse.GetHttpStatus() != 200) {
return "" , client , errors.New("录音文件识别请求失败 , Http错误码 : " + strconv.Itoa(postResponse.GetHttpStatus()))
}
//解析数据
var postMapResult map[string]interface{}
err = json.Unmarshal([]byte(postResponseContent), &postMapResult)
if err != nil {
return "" , client , errors.New("to map struct error .")
}
var taskId = ""
var statusText = ""
statusText = postMapResult[KEY_STATUS_TEXT].(string)
//检验结果
if statusText == STATUS_SUCCESS {
taskId = postMapResult[KEY_TASK_ID].(string)
return taskId , client , nil
}
return "" , client , errors.New("录音文件识别失败 , (" + c.GetErrorStatusTextMessage(statusText) + ")")
}
//获取录音文件识别结果
//接口文档 https://help.aliyun.com/document_detail/90727.html?spm=a2c4g.11186623.6.581.691af6ebYsUkd1
func (c AliyunClound) GetAudioFileResult(taskId string , client *sdk.Client , logOutput func(text string) , callback func(result []byte)) (err error) {
_, domain, apiVersion, product := c.GetApiVariable()
getRequest := requests.NewCommonRequest()
getRequest.Domain = domain
getRequest.Version = apiVersion
getRequest.Product = product
getRequest.ApiName = GET_REQUEST_ACTION
getRequest.Method = "GET"
getRequest.QueryParams[KEY_TASK_ID] = taskId
statusText := ""
var (
trys = 0
getResponse *responses.CommonResponse
getResponseContent string
)
//遍历获取识别结果
for trys < 10 {
if trys != 0 {
logOutput("尝试重新查询识别结果,第" + strconv.Itoa(trys) + "次")
}
getResponse, err = client.ProcessCommonRequest(getRequest)
if err != nil {
logOutput("查询识别结果失败:" + err.Error())
trys++
time.Sleep(time.Second * time.Duration(trys))
continue
}
getResponseContent = getResponse.GetHttpContentString()
if (getResponse.GetHttpStatus() != 200) {
logOutput("查询识别结果失败,Http错误码:" + strconv.Itoa(getResponse.GetHttpStatus()))
trys++
time.Sleep(time.Second * time.Duration(trys))
continue
}
var getMapResult map[string]interface{}
err = json.Unmarshal([]byte(getResponseContent), &getMapResult)
if err != nil {
trys++
logOutput("查询识别结果失败,解析结果失败:" + err.Error())
continue
}
//校验遍历条件
statusText = getMapResult[KEY_STATUS_TEXT].(string)
if statusText == STATUS_RUNNING || statusText == STATUS_QUEUEING {
time.Sleep(3 * time.Second)
} else {
break
}
}
if statusText == STATUS_SUCCESS && getResponse != nil {
//调用回调函数
callback(getResponse.GetHttpContentBytes())
} else {
err = errors.New("录音文件识别失败 , (" + c.GetErrorStatusTextMessage(statusText) + ")")
return
}
return
}
//获取API常量
func (c AliyunClound) GetApiVariable () (regionId string , domain string , apiVersion string , product string) {
if c.Region == 0 || c.Region == ALIYUN_CLOUND_REGION_CHA {
regionId = REGION_ID_REGION_CHA
domain = DOMAIN_REGION_CHA
apiVersion = API_VERSION_REGION_CHA
product = PRODUCT_REGION_CHA
} else if c.Region == ALIYUN_CLOUND_REGION_INT {
regionId = REGION_ID_REGION_INT
domain = DOMAIN_REGION_INT
apiVersion = API_VERSION_REGION_INT
product = PRODUCT_REGION_INT
}
return
}
//获取错误信息
func (c AliyunClound) GetErrorStatusTextMessage (statusText string) string {
var code map[string]string = map[string]string{
"REQUEST_APPKEY_UNREGISTERED":"阿里云智能语音项目未创建/无访问权限。请检查语音引擎Appkey是否填写错误;如果是海外地区,在软件创建语音引擎时,服务区域需要选择“海外”",
"USER_BIZDURATION_QUOTA_EXCEED":"单日2小时识别免费额度超出限制",
"FILE_DOWNLOAD_FAILED":"文件访问失败,请检查OSS存储空间访问权限。请将OSS存储空间设置为“公共读”",
"FILE_TOO_LARGE":"音频文件超出512MB",
"FILE_PARSE_FAILED":"音频文件解析失败,请检查音频文件是否有损坏",
"UNSUPPORTED_SAMPLE_RATE":"采样率不匹配",
"FILE_TRANS_TASK_EXPIRED":"音频文件识别任务过期,请重试",
"REQUEST_INVALID_FILE_URL_VALUE":"音频文件访问失败,请检查OSS存储空间访问权限",
"FILE_404_NOT_FOUND":"音频文件访问失败,请检查OSS存储空间访问权限",
"FILE_403_FORBIDDEN":"音频文件访问失败,请检查OSS存储空间访问权限",
"FILE_SERVER_ERROR":"音频文件访问失败,请检查请求的文件所在的服务是否可用",
"INTERNAL_ERROR":"识别内部通用错误,请稍候重试",
}
if _, ok := code[statusText]; ok {
return code[statusText]
} else {
return statusText
}
}

102
gui_learn/app/aliyun/oss.go

@ -0,0 +1,102 @@
package aliyun
import (
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"strconv"
"strings"
"time"
)
type AliyunOss struct {
Endpoint string
AccessKeyId string
AccessKeySecret string
BucketName string //yourBucketName
BucketDomain string //Bucket 域名
}
//获取Buckets列表
func (c AliyunOss) GetListBuckets() ([]string , error) {
client, err := oss.New(c.Endpoint , c.AccessKeyId , c.AccessKeySecret)
if err != nil {
return nil,err
}
lsRes, err := client.ListBuckets()
if err != nil {
return nil,err
}
result := []string{}
for _, bucket := range lsRes.Buckets {
result = append(result , bucket.Name)
}
return result,nil
}
//上传本地文件
//localFileName:本地文件
//objectName:oss文件名称
func (c AliyunOss) UploadFile(localFileName string , objectName string) (string , error) {
// 创建OSSClient实例
client, err := oss.New(c.Endpoint , c.AccessKeyId , c.AccessKeySecret)
if err != nil {
return "",err
}
// 获取存储空间
bucket, err := client.Bucket(c.BucketName)
if err != nil {
return "",err
}
//分日期存储
date := time.Now()
year := date.Year()
month := date.Month()
day := date.Day()
objectName = strconv.Itoa(year) + "/" + strconv.Itoa(int(month)) + "/" + strconv.Itoa(day) + "/" + objectName
// 上传文件
err = bucket.PutObjectFromFile(objectName , localFileName)
if err != nil {
return "",err
}
return objectName , nil
}
//删除oss文件
func (c AliyunOss) DeleteFile(objectName string) error {
// 创建OSSClient实例
client, err := oss.New(c.Endpoint , c.AccessKeyId , c.AccessKeySecret)
if err != nil {
return err
}
// 获取存储空间
bucket, err := client.Bucket(c.BucketName)
if err != nil {
return err
}
// 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
// 如需删除文件夹,请将objectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
err = bucket.DeleteObject(objectName)
if err != nil {
return err
}
return nil
}
//获取文件 url link
func (c AliyunOss) GetObjectFileUrl(objectFile string) string {
if strings.Index(c.BucketDomain, "http://") == -1 && strings.Index(c.BucketDomain, "https://") == -1 {
return "http://" + c.BucketDomain + "/" + objectFile
} else {
return c.BucketDomain + "/" + objectFile
}
}

416
gui_learn/app/aliyun/tool.go

@ -0,0 +1,416 @@
package aliyun
import (
"github.com/buger/jsonparser"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"videosrt/app/tool"
)
type AliyunAudioRecognitionResultBlock struct {
AliyunAudioRecognitionResult
Blocks []int
BlockEmptyTag bool
BlockEmptyHandle bool
}
//阿里云录音录音文件识别 - 智能分段处理
func AliyunAudioResultWordHandle(result [] byte , callback func (vresult *AliyunAudioRecognitionResult)) {
var audioResult = make(map[int64][] *AliyunAudioRecognitionResultBlock)
var wordResult = make(map[int64][]*AliyunAudioWord)
var err error
//获取录音识别数据集
_, err = jsonparser.ArrayEach(result, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
text , _ := jsonparser.GetString(value, "Text")
channelId , _ := jsonparser.GetInt(value, "ChannelId")
beginTime , _ := jsonparser.GetInt(value, "BeginTime")
endTime , _ := jsonparser.GetInt(value, "EndTime")
silenceDuration , _ := jsonparser.GetInt(value, "SilenceDuration")
speechRate , _ := jsonparser.GetInt(value, "SpeechRate")
emotionValue , _ := jsonparser.GetInt(value, "EmotionValue")
vresult := &AliyunAudioRecognitionResultBlock {}
vresult.Text = text
vresult.ChannelId = channelId
vresult.BeginTime = beginTime
vresult.EndTime = endTime
vresult.SilenceDuration = silenceDuration
vresult.SpeechRate = speechRate
vresult.EmotionValue = emotionValue
_ , isPresent := audioResult[channelId]
if isPresent {
//追加
audioResult[channelId] = append(audioResult[channelId] , vresult)
} else {
//初始
audioResult[channelId] = []*AliyunAudioRecognitionResultBlock{}
audioResult[channelId] = append(audioResult[channelId] , vresult)
}
} , "Result", "Sentences")
if err != nil {
panic(err)
}
//获取词语数据集
_, err = jsonparser.ArrayEach(result , func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
word , _ := jsonparser.GetString(value, "Word")
channelId , _ := jsonparser.GetInt(value, "ChannelId")
beginTime , _ := jsonparser.GetInt(value, "BeginTime")
endTime , _ := jsonparser.GetInt(value, "EndTime")
vresult := &AliyunAudioWord{
Word:word,
ChannelId:channelId,
BeginTime:beginTime,
EndTime:endTime,
}
_, isPresent := wordResult[channelId]
if isPresent {
//追加
wordResult[channelId] = append(wordResult[channelId] , vresult)
} else {
//初始
wordResult[channelId] = []*AliyunAudioWord{}
wordResult[channelId] = append(wordResult[channelId] , vresult)
}
} , "Result" , "Words")
if err != nil {
panic(err)
}
var symbol = []string{"?","。",",","!",";","、","?",".",",","!"}
//数据集处理
for _ , value := range audioResult {
for _ , data := range value {
// filter
data.Text = FilterText(data.Text)
data.Blocks = GetTextBlock(data.Text)
data.Text = ReplaceStrs(data.Text , symbol , "")
if len(data.Blocks) == 0 {
data.BlockEmptyTag = true
}
}
}
//遍历输出
for _,value := range wordResult {
var block string = ""
var blockRune int = 0
var lastBlock int = 0
var beginTime int64 = 0
var blockBool = false
var ischinese = IsChineseWords(value) //校验中文
var chineseNumberWordIndexs []int
var chineseNumberDiffLength int = 0
for i , word := range value {
if blockBool || i == 0 {
beginTime = word.BeginTime
blockBool = false
}
if ischinese && block == "" {
chineseNumberWordIndexs = []int{}
chineseNumberDiffLength = 0
}
if ischinese {
block += word.Word
if tool.CheckChineseNumber(word.Word) && FindSliceIntCount(chineseNumberWordIndexs , i) == 0 {
cl := tool.ChineseNumberToLowercaseLength(word.Word) - utf8.RuneCountInString(word.Word)
if cl > 0 {
chineseNumberDiffLength += cl
chineseNumberWordIndexs = append(chineseNumberWordIndexs , i)
} else {
//例外
if i != 0 {
newWord := value[i-1].Word + word.Word
cl := tool.ChineseNumberToLowercaseLength(newWord) - utf8.RuneCountInString(newWord)
if cl > 0 {
chineseNumberDiffLength += cl
chineseNumberWordIndexs = append(chineseNumberWordIndexs , i)
}
}
}
}
} else {
block += CompleSpace(word.Word) //补全空格
}
blockRune = utf8.RuneCountInString(block)
//fmt.Println("chineseNumberDiffLength : " , chineseNumberWordIndexs , chineseNumberDiffLength , word.Word)
for channel , p := range audioResult {
if word.ChannelId != channel {
continue
}
for windex , w := range p {
if (word.BeginTime >= w.BeginTime && word.EndTime <= w.EndTime) || ((word.BeginTime < w.EndTime && word.EndTime > w.EndTime) && (FindSliceIntCount(w.Blocks , -1) != len(w.Blocks))) {
flag := false
early := false
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")
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{
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;
}
}
} 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: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 )
callback(vresult) //回调传参
//覆盖下一段落的时间戳
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
}
}
}
}
}
}
}
}
func FindSliceIntCount(slice []int , target int) int {
c := 0
for _ , v := range slice {
if target == v {
c++
}
}
return c
}
//批量替换多个关键词文本
func ReplaceStrs(strs string , olds []string , s string) string {
for _ , word := range olds {
strs = strings.Replace(strs , word , s , -1)
}
return strs
}
func StringIndex(strs string , word rune) int {
strsRune := []rune(strs)
for i,v := range strsRune {
if v == word {
return i
}
}
return -1
}
//补全右边空格
func CompleSpace(s string) string {
s = strings.TrimLeft(s , " ");
s = strings.TrimRight(s , " ");
return s + " ";
}
func IsChineseWords(words []*AliyunAudioWord) bool {
for _,v := range words {
if (IsChineseChar(v.Word)){
return true
}
}
return false
}
func IsChineseChar(str string) bool {
for _, r := range str {
if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
return true
}
}
return false
}
func IndexRunes(strs string , olds []rune) int {
min := -1
for i , word := range olds {
index := StringIndex(strs , word)
//println( "ts : " , index)
if i == 0 {
min = index
} else {
if min == -1 {
min = index
} else {
if index < min && index != -1 {
min = index
}
}
}
}
return min
}
func GetTextBlock(strs string) ([]int) {
var symbol_zhcn = []rune{'?','。',',','!',';','、','?','.',',','!'}
//var symbol_en = []rune{'?','.',',','!'}
strsRune := []rune(strs)
blocks := []int{}
for {
index := IndexRunes(strs , symbol_zhcn)
if index == -1 {
break
}
strs = string(strsRune[0:index]) + string(strsRune[(index + 1):])
strsRune = []rune(strs)
blocks = append(blocks , index)
}
return blocks
}
func SubString(str string , begin int ,length int) (substr string) {
// 将字符串的转换成[]rune
rs := []rune(str)
lth := len(rs)
// 简单的越界判断
if begin < 0 {
begin = 0
}
if begin >= lth {
begin = lth
}
if length < 0 {
length = 0
}
end := begin + length
if end > lth {
end = lth
}
// 返回子串
return string(rs[begin:end])
}
//过滤文本
func FilterText(text string) string {
//去除换行符
re, _ := regexp.Compile("[\n|\r|\r\n]+")
text = re.ReplaceAllString(text, "")
return text
}

97
gui_learn/app/app_tool.go

@ -0,0 +1,97 @@
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
content.WriteString(strconv.Itoa(index))
content.WriteString("\r\n")
content.WriteString(tool.SubtitleTimeMillisecond(startTime , true))
content.WriteString(" --> ")
content.WriteString(tool.SubtitleTimeMillisecond(endTime , true))
content.WriteString("\r\n")
//输出双语字幕
if bilingualSubtitleSwitch {
if bilingualAsc {
content.WriteString(text)
content.WriteString("\r\n")
content.WriteString(translateText)
} else {
content.WriteString(translateText)
content.WriteString("\r\n")
content.WriteString(text)
}
} else {
content.WriteString(text)
}
content.WriteString("\r\n")
content.WriteString("\r\n")
return content.String()
}
//拼接文本格式
func MakeText(index int , startTime int64 , endTime int64 , text string) string {
var content bytes.Buffer
content.WriteString(text)
content.WriteString("\r\n")
content.WriteString("\r\n")
return content.String()
}
//拼接歌词文本
func MakeMusicLrcText(index int , startTime int64 , endTime int64 , text string) string {
var content bytes.Buffer
content.WriteString("[")
content.WriteString(tool.MusicLrcTextMillisecond(startTime))
content.WriteString("]")
content.WriteString(text)
content.WriteString("\r\n")
return content.String()
}

665
gui_learn/app/data.go

@ -0,0 +1,665 @@
package app
import (
"videosrt/app/aliyun"
"videosrt/app/datacache"
"videosrt/app/translate"
)
//输出文件类型
const(
OUTPUT_SRT = 1 //字幕SRT文件
OUTPUT_STRING = 2 //普通文本
OUTPUT_LRC = 3 //LRC文本
)
//输出文件编码
const(
OUTPUT_ENCODE_UTF8 = 1 //文件编码 utf-8
OUTPUT_ENCODE_UTF8_BOM = 2 //文件编码 utf-8 带 BOM
)
//翻译语言
const (
LANGUAGE_ZH = 1 //中文
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
)
//缓存结构
type OssAppStruct struct {
Data *datacache.AppCache
}
type TranslateEngineAppStruct struct {
Data *datacache.AppCache
}
type SpeechEngineAppStruct struct {
Data *datacache.AppCache
}
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() {
RootDir = GetAppRootDir()
if RootDir == "" {
panic("应用根目录获取失败")
}
Oss = new(OssAppStruct)
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")
}
//表单结构
type OperateFrom struct {
EngineId int //当前语音引擎
TranslateEngineId int //当前翻译引擎
TranslateSwitch bool //字幕翻译开关
BilingualSubtitleSwitch bool //是否输出双语字幕
InputLanguage int //输入字幕语言
OutputLanguage int //输出字幕语言
OutputMainSubtitleInputLanguage bool //双语主字幕(输入语言)
OutputSrt bool
OutputLrc bool
OutputTxt bool
OutputType *AppSetingsOutput //输出文件类型
OutputEncode int //输出文件编码
SoundTrack int //输出音轨
}
//输出类型选项
type OutputSelects struct {
Id int
Name string
}
//输出音轨类型选项
type SoundTrackSelects struct {
Id int
Name string
}
//字幕翻译语言选项列表
type LanguageSelects struct {
Id int
Name string
}
type AppSetingsOutput struct {
SRT bool
LRC bool
TXT bool
}
//应用配置结构
type AppSetings struct {
CurrentEngineId int //目前语音引擎Id
CurrentTranslateEngineId int //目前翻译引擎Id
MaxConcurrency int //任务最大处理并发数
OutputType *AppSetingsOutput //输出文件类型
OutputEncode int //输出文件编码
SrtFileDir string //Srt文件输出目录
SoundTrack int //输出音轨
TranslateSwitch bool //字幕翻译开关
BilingualSubtitleSwitch bool //是否输出双语字幕
InputLanguage int //输入字幕语言
OutputLanguage int //输出字幕语言
OutputMainSubtitleInputLanguage bool //双语主字幕(输入语言)
CloseIntelligentBlockSwitch bool //关闭智能分段
CloseNewVersionMessage bool //关闭软件新版本提醒(默认开启)[false开启 true关闭]
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
}
//根据配置初始化表单
func (from *OperateFrom) Init(setings *AppSetings) {
from.OutputType = new(AppSetingsOutput)
if setings.CurrentEngineId != 0 {
from.EngineId = setings.CurrentEngineId
}
if setings.CurrentTranslateEngineId != 0 {
from.TranslateEngineId = setings.CurrentTranslateEngineId
}
if !setings.OutputType.LRC && !setings.OutputType.SRT && !setings.OutputType.TXT {
from.OutputType.SRT = true
from.OutputSrt = true
} else {
from.OutputType = setings.OutputType
if setings.OutputType.SRT {
from.OutputSrt = true
}
if setings.OutputType.TXT {
from.OutputTxt = true
}
if setings.OutputType.LRC {
from.OutputLrc = true
}
}
if setings.OutputEncode == 0 {
from.OutputEncode = OUTPUT_ENCODE_UTF8 //默认编码
} else {
from.OutputEncode = setings.OutputEncode
}
from.OutputMainSubtitleInputLanguage = setings.OutputMainSubtitleInputLanguage
if setings.SoundTrack == 0 {
from.SoundTrack = 1 //默认输出音轨一
} else {
from.SoundTrack = setings.SoundTrack
}
//默认翻译设置
if setings.InputLanguage == 0 {
from.InputLanguage = LANGUAGE_ZH
} else {
from.InputLanguage = setings.InputLanguage
}
if setings.OutputLanguage == 0 {
from.OutputLanguage = LANGUAGE_ZH
} else {
from.OutputLanguage = setings.OutputLanguage
}
from.TranslateSwitch = setings.TranslateSwitch
from.BilingualSubtitleSwitch = setings.BilingualSubtitleSwitch
}
//获取 输出文件选项列表
func GetOutputOptionsSelects() []*OutputSelects {
return []*OutputSelects{
&OutputSelects{Id:OUTPUT_SRT , Name:"字幕文件"},
&OutputSelects{Id:OUTPUT_STRING , Name:"普通文本"},
}
}
//获取 输出文件编码选项列表
func GetOutputEncodeOptionsSelects() []*OutputSelects {
return []*OutputSelects{
&OutputSelects{Id:OUTPUT_ENCODE_UTF8 , Name:"UTF-8"},
&OutputSelects{Id:OUTPUT_ENCODE_UTF8_BOM , Name:"UTF-8-BOM"},
}
}
//获取 输出音轨选项列表
func GetSoundTrackSelects() []*SoundTrackSelects {
return []*SoundTrackSelects{
&SoundTrackSelects{Id:3 , Name:"全部"},
&SoundTrackSelects{Id:1 , Name:"音轨一"},
&SoundTrackSelects{Id:2 , Name:"音轨二"},
}
}
//获取 允许翻译[输入字幕语言]列表
func GetTranslateInputLanguageOptionsSelects() []*LanguageSelects {
return []*LanguageSelects{
&LanguageSelects{Id:LANGUAGE_ZH , Name:"中文"},
&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:"泰语"},
}
}
//获取 允许翻译[输出字幕语言]列表
func GetTranslateOutputLanguageOptionsSelects() []*LanguageSelects {
return []*LanguageSelects{
&LanguageSelects{Id:LANGUAGE_ZH , Name:"中文"},
&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:"泰语"},
}
}
//获取 应用配置
func (setings *AppSetingsAppStruct) GetCacheAppSetingsData() *AppSetings {
data := new(AppSetings)
data.OutputType = new(AppSetingsOutput)
vdata := setings.Data.Get(data)
if v, ok := vdata.(*AppSetings); ok {
return v
}
return data
}
//设置 应用配置
func (setings *AppSetingsAppStruct) SetCacheAppSetingsData(data *AppSetings) {
setings.Data.Set(data)
}
//获取 应用过滤器配置
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:"正则替换"},
}
}
//阿里云OSS - 缓存结构
type AliyunOssCache struct {
aliyun.AliyunOss
}
//设置 阿里云OSS 缓存
func (oss *OssAppStruct) SetCacheAliyunOssData(data *AliyunOssCache) {
oss.Data.Set(data)
}
//获取 阿里云OSS 缓存数据
func (oss *OssAppStruct) GetCacheAliyunOssData() *AliyunOssCache {
data := new(AliyunOssCache)
vdata := oss.Data.Get(data)
if v, ok := vdata.(*AliyunOssCache); ok {
return v
}
return data
}
//阿里云语音识别引擎 - 缓存结构
type AliyunEngineCache struct {
aliyun.AliyunClound
Id int //Id
Alias string //别名
}
//阿里云语音识别引擎 - 列表缓存结构
type AliyunEngineListCache struct {
Engine [] *AliyunEngineCache
}
//语音引擎选项
type EngineSelects struct {
Id int
Name string
}
//阿里云语音引擎区域选项
type AliyunEngineRegionSelects struct {
Id int
Name string
}
//获取 阿里云语音引擎区域选项列表
func GetAliyunEngineRegionOptionSelects() []*BaiduAuthTypeSelects {
return []*BaiduAuthTypeSelects{
&BaiduAuthTypeSelects{Id:aliyun.ALIYUN_CLOUND_REGION_CHA , Name:"中国"},
&BaiduAuthTypeSelects{Id:aliyun.ALIYUN_CLOUND_REGION_INT , Name:"海外"},
}
}
//获取 引擎选项列表
func (speechEng *SpeechEngineAppStruct) GetEngineOptionsSelects() []*EngineSelects {
engines := make([]*EngineSelects , 0)
//获取数据
data := speechEng.GetCacheAliyunEngineListData()
for _,v := range data.Engine {
engines = append(engines , &EngineSelects{
Id:v.Id,
Name:v.Alias,
})
}
return engines
}
//根据选择引擎id 获取 引擎数据
func (speechEng *SpeechEngineAppStruct) GetEngineById(id int) (*AliyunEngineCache , bool) {
//获取数据
data := speechEng.GetCacheAliyunEngineListData()
for _,v := range data.Engine {
if id == v.Id {
return v , true
}
}
return nil , false
}
//获取 当前引擎id 下标
func (speechEng *SpeechEngineAppStruct) GetCurrentIndex(data []*EngineSelects , id int) int {
for index,v := range data {
if v.Id == id {
return index
}
}
return -1
}
//获取 阿里云语音识别引擎 缓存数据
func (speechEng *SpeechEngineAppStruct) GetCacheAliyunEngineListData() *AliyunEngineListCache {
data := new(AliyunEngineListCache)
vdata := speechEng.Data.Get(data)
if v, ok := vdata.(*AliyunEngineListCache); ok {
return v
}
return data
}
//设置 阿里云语音识别引擎 缓存
func (speechEng *SpeechEngineAppStruct) SetCacheAliyunEngineListData(data *AliyunEngineListCache) {
speechEng.Data.Set(data)
}
//根据id 删除 阿里云语音识别引擎 缓存数据
func (speechEng *SpeechEngineAppStruct) RemoveCacheAliyunEngineData(id int) (bool) {
var ok = false
var newEngine = make([]*AliyunEngineCache , 0)
origin := speechEng.GetCacheAliyunEngineListData()
total := len(origin.Engine)
for i,engine := range origin.Engine {
if engine.Id == id {
if i == (total - 1) {
newEngine = origin.Engine[:i]
} else {
newEngine = append(origin.Engine[:i] , origin.Engine[i+1:]...)
}
ok = true
break
}
}
if ok {
origin.Engine = newEngine
//更新缓存数据
speechEng.SetCacheAliyunEngineListData(origin)
}
return ok
}
//声明引擎供应商
const (
TRANSLATE_SUPPLIER_BAIDU = 1 //百度翻译
TRANSLATE_SUPPLIER_TENGXUNYUN = 2 //腾讯云翻译
)
//翻译引擎 - 数据结构
type TranslateEngineStruct struct {
TengxunyunEngine translate.TengxunyunTranslate
BaiduEngine translate.BaiduTranslate
Supplier int //引擎供应商
Id int //Id
Alias string //别名
}
//翻译引擎 - 列表缓存结构
type TranslateEngineListCacheStruct struct {
Engine [] *TranslateEngineStruct
}
//翻译引擎选项 - 数据结构
type TranslateEngineSelects struct {
Id int
Name string
}
//获取 翻译引擎 选项列表
func (translateEng *TranslateEngineAppStruct) GetTranslateEngineOptionsSelects() []*TranslateEngineSelects {
engines := make([]*TranslateEngineSelects , 0)
//获取数据
data := translateEng.GetCacheTranslateEngineListData()
for _,v := range data.Engine {
engines = append(engines , &TranslateEngineSelects{
Id:v.Id,
Name:v.Alias,
})
}
return engines
}
//根据 选择翻译引擎id 获取数据
func (translateEng *TranslateEngineAppStruct) GetTranslateEngineById(id int) (*TranslateEngineStruct , bool) {
//获取数据
data := translateEng.GetCacheTranslateEngineListData()
for _,v := range data.Engine {
if id == v.Id {
return v , true
}
}
return nil , false
}
//获取 当前翻译引擎id 下标
func (translateEng *TranslateEngineAppStruct) GetCurrentTranslateEngineIndex(data []*TranslateEngineSelects , id int) int {
for index,v := range data {
if v.Id == id {
return index
}
}
return -1
}
//设置 翻译引擎 数据缓存
func (translateEng *TranslateEngineAppStruct) SetCacheTranslateEngineListData(data *TranslateEngineListCacheStruct) {
translateEng.Data.Set(data)
}
//根据id 删除 翻译引擎 数据缓存
func (translateEng *TranslateEngineAppStruct) RemoveCacheTranslateEngineData(id int) (bool) {
var ok = false
var newEngine = make([]*TranslateEngineStruct , 0)
origin := translateEng.GetCacheTranslateEngineListData()
total := len(origin.Engine)
for i,engine := range origin.Engine {
if engine.Id == id {
if i == (total - 1) {
newEngine = origin.Engine[:i]
} else {
newEngine = append(origin.Engine[:i] , origin.Engine[i+1:]...)
}
ok = true
break
}
}
if ok {
origin.Engine = newEngine
//更新缓存数据
translateEng.SetCacheTranslateEngineListData(origin)
}
return ok
}
//获取 翻译引擎 数据缓存
func (translateEng *TranslateEngineAppStruct) GetCacheTranslateEngineListData() *TranslateEngineListCacheStruct {
data := new(TranslateEngineListCacheStruct)
vdata := translateEng.Data.Get(data)
if v, ok := vdata.(*TranslateEngineListCacheStruct); ok {
return v
}
return data
}
//获取不同翻译引擎的语音字符标识
func GetLanguageChar(Language int , Supplier int) string {
if Supplier == TRANSLATE_SUPPLIER_BAIDU {
switch Language {
case LANGUAGE_ZH:
return "zh"
case LANGUAGE_EN:
return "en"
case LANGUAGE_JP:
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 {
switch Language {
case LANGUAGE_ZH:
return "zh"
case LANGUAGE_EN:
return "en"
case LANGUAGE_JP:
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 ""
}
//百度翻译账号认证类型选项
type BaiduAuthTypeSelects struct {
Id int
Name string
}
//获取 百度翻译账号认证类型
func GetBaiduTranslateAuthenTypeOptionsSelects() []*BaiduAuthTypeSelects {
return []*BaiduAuthTypeSelects{
&BaiduAuthTypeSelects{Id:translate.ACCOUNT_COMMON_AUTHEN , Name:"标准版"},
&BaiduAuthTypeSelects{Id:translate.ACCOUNT_SENIOR_AUTHEN , Name:"高级版"},
}
}

53
gui_learn/app/datacache/cache.go

@ -0,0 +1,53 @@
package datacache
import (
"os"
"videosrt/app/tool"
)
type AppCache struct {
RootDir string
Dir string
File string
}
func NewAppCahce(rootdir string , file string) *AppCache {
c := new(AppCache)
c.Dir = "data/json"
c.RootDir = rootdir
c.File = c.RootDir + "/" + c.Dir + "/" + file + ".json"
c.initDir()
return c
}
func (app *AppCache) Set(data interface{}) {
file := app.File
if err := SavetoJson(data , file); err != nil {
panic(err)
}
}
func (app *AppCache) Get(structs interface{}) interface{} {
file := app.File
err, data := GettoJson(file, structs)
if err != nil {
if os.IsNotExist(err) {
return structs
}
return data
}
return data
}
func (app *AppCache) initDir() {
fileDir := app.RootDir + "/" + app.Dir
if !tool.DirExists(fileDir) {
//创建目录
if err := tool.CreateDir(fileDir , false); err != nil {
panic(err)
}
}
}

31
gui_learn/app/datacache/json.go

@ -0,0 +1,31 @@
package datacache
import (
"encoding/json"
"io/ioutil"
"os"
)
func SavetoJson(data interface{} , path string) error {
string , err := json.Marshal(data)
if err != nil{
return err
}
err = ioutil.WriteFile(path , string , os.ModePerm)
if err != nil{
return err
}
return nil
}
func GettoJson(path string , structs interface{}) (error , interface{}) {
data , err := ioutil.ReadFile(path)
if err != nil{
return err , structs
}
err = json.Unmarshal(data , structs)
if err != nil{
return err , structs
}
return nil , structs
}

1128
gui_learn/app/event.go

File diff suppressed because it is too large

57
gui_learn/app/ffmpeg/ffmpeg.go

@ -0,0 +1,57 @@
package ffmpeg
import (
"errors"
"os"
"os/exec"
"strings"
"syscall"
"videosrt/app/tool"
)
type Ffmpeg struct {
Os string //ffmpeg 文件目录
}
//提取视频音频
func ExtractAudio (video string , tmpAudio string) (error) {
//校验
if e := VailFfmpegLibrary();e != nil {
return e
}
cmd := exec.Command("ffmpeg" , "-i" , video , "-ar" , "16000" , tmpAudio)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
err := cmd.Run()
if err != nil {
return err
}
return nil
}
//校验依赖库
func VailFfmpegLibrary() error {
ts := exec.Command("ffmpeg" , "-version")
ts.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := ts.Run() ; err != nil {
return errors.New("请先安装 ffmpeg 依赖 ,并设置环境变量")
}
return nil
}
//校验ffmpeg并加入临时环境遍历
func VailTempFfmpegLibrary(rootDir string) {
ffmpegDir := tool.WinDir(rootDir + "/ffmpeg")
if tool.DirExists(ffmpegDir) {
//临时加入用户环境变量
path := os.Getenv("PATH")
ps := strings.Split(path , ";")
ps = append(ps , ffmpegDir)
path = strings.Join(ps , ";")
_ = os.Setenv("PATH", path)
}
}

27
gui_learn/app/log.go

@ -0,0 +1,27 @@
package app
import (
"github.com/lxn/walk"
)
type TaskLog struct {
TextEdit *walk.TextEdit
}
func NewTasklog(textEdit *walk.TextEdit) *TaskLog {
task := new(TaskLog)
task.TextEdit = textEdit
return task
}
func (t *TaskLog) SetTextEdit(textEdit *walk.TextEdit) {
t.TextEdit = textEdit
}
func (t *TaskLog) AppendLogText(s string) {
t.TextEdit.AppendText(s + "\r\n")
}
func (t *TaskLog) ClearLogText() {
t.TextEdit.SetText("")
}

148
gui_learn/app/parse/srt.go

@ -0,0 +1,148 @@
package parse
import (
"bufio"
"errors"
"os"
"strings"
"videosrt/app/tool"
)
//允许解析的文件类型
const (
PARSE_FILE_SRT = 1
)
const (
PARSE_SRT_NUMBER = 1
PARSE_SRT_TIME_RANGE = 2
PARSE_SRT_SUBTITLE_TEXT = 3
PARSE_SRT_TRIM = 4
)
//字幕行结构
type SrtRows struct {
Id int //字幕自然行id
Number string //字幕序号
TimeStart string //字幕开始时间戳
TimeStartSecond float64 //字幕开始(秒)
TimeEnd string //字幕结束时间戳
TimeEndSecond float64 //字幕结束(秒)
Text []string //字幕文本
}
//字幕文件属性
type Srt struct {
File string
FileType int
AppointBilingualRows int //指定使用N行字幕文本[双语字幕参数]
}
//字幕解析器
type SubtitleParse struct {
Srt *Srt
Rows []*SrtRows
}
//获取解析字幕实例
func NewSubtitleParse(srt *Srt) (*SubtitleParse) {
parse := new(SubtitleParse)
if srt.FileType == 0 {
srt.FileType = PARSE_FILE_SRT //默认文件类型
}
parse.Srt = srt
return parse
}
//解析文本
func (parse *SubtitleParse) Parse() error {
srtfile := parse.Srt.File
//校验文件
if tool.VaildFile(srtfile) == false {
return errors.New("字幕文件不存在")
}
if file, err := os.Open(srtfile); err!=nil {
return errors.New("打开字幕文件失败")
} else {
lineRows := 0
lineStart := false
lineRowSrt := new(SrtRows)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lineText := scanner.Text()
//逐行读取
if strResult, rowsType , err := ParseSrtRows(lineText , lineStart); err != nil {
return err
} else {
//分支处理
switch rowsType {
case PARSE_SRT_NUMBER:
lineRowSrt = new(SrtRows)
lineRowSrt.Id = lineRows
lineRowSrt.Number = strResult
lineStart = true
break
case PARSE_SRT_TIME_RANGE:
srtTimes := strings.Split(strResult , "-")
lineRowSrt.TimeStart = srtTimes[0]
lineRowSrt.TimeEnd = srtTimes[1]
break
case PARSE_SRT_SUBTITLE_TEXT:
lineRowSrt.Text = append(lineRowSrt.Text , strResult)
break
case PARSE_SRT_TRIM:
//结束上一段字幕输入
if lineStart == true {
if lineRowSrt.TimeStart != "" && lineRowSrt.TimeEnd != "" && len(lineRowSrt.Text) > 0 {
if fsecond, err := SrtTimeFormatToSecond(lineRowSrt.TimeStart);err != nil {
return err
} else {
lineRowSrt.TimeStartSecond = fsecond
}
if fsecond, err := SrtTimeFormatToSecond(lineRowSrt.TimeEnd);err != nil {
return err
} else {
lineRowSrt.TimeEndSecond = fsecond
}
lineRows++;
//追加行
parse.Rows = append(parse.Rows , lineRowSrt)
}
lineStart = false
}
break
}
}
}
//未结束的最后一行
if lineStart == true {
if lineRowSrt.TimeStart != "" && lineRowSrt.TimeEnd != "" && len(lineRowSrt.Text) > 0 {
if fsecond, err := SrtTimeFormatToSecond(lineRowSrt.TimeStart);err != nil {
return err
} else {
lineRowSrt.TimeStartSecond = fsecond
}
if fsecond, err := SrtTimeFormatToSecond(lineRowSrt.TimeEnd);err != nil {
return err
} else {
lineRowSrt.TimeEndSecond = fsecond
}
lineRows++;
//追加行
parse.Rows = append(parse.Rows , lineRowSrt)
}
lineStart = false
}
}
return nil
}

88
gui_learn/app/parse/tool.go

@ -0,0 +1,88 @@
package parse
import (
"errors"
"regexp"
"strconv"
"strings"
)
//解析SRT行文本
func ParseSrtRows(rowText string , lineStart bool) (string , int , error) {
rowText = strings.TrimSpace(rowText)
if _,e := strconv.Atoi(rowText);e == nil && lineStart == false { //识别为字幕序号
return rowText , PARSE_SRT_NUMBER , nil
}
r := regexp.MustCompile(`(\d{1,2}:\d{1,2}:\d{1,2}[,|\.]\d{2,4})\s*-->\s*(\d{1,2}:\d{1,2}:\d{1,2}[,|\.]\d{2,4})`)
if r.Match([]byte(rowText)) == true {
timeResult := r.FindSubmatch([]byte(rowText))
if len(timeResult) != 3 {
return "" , 0 , errors.New("解析字幕时间错误")
}
times := strings.Join([]string{string(timeResult[1]) , string(timeResult[2])} , "-")
return times , PARSE_SRT_TIME_RANGE , nil
}
if rowText != "" {
return rowText , PARSE_SRT_SUBTITLE_TEXT , nil
} else {
return rowText , PARSE_SRT_TRIM , nil
}
}
//校验srt字幕时间格式
func VaildateSrtTimeFormat(time string) bool {
r := regexp.MustCompile(`^(\d{1,2}:\d{1,2}:\d{1,2}[,|\.]\d{2,4})$`)
return r.Match([]byte(time))
}
//srt字幕时间文本 -> 秒
func SrtTimeFormatToSecond(time string) (float64 , error) {
if VaildateSrtTimeFormat(time) == false {
return 0 , errors.New("字幕时间格式不正确")
}
time = strings.Replace(time , "," , "." , 1)
timeSplit := strings.Split(time , ":")
if len(timeSplit) != 3 {
return 0 , errors.New("字幕时间格式不正确")
}
var second float64 = 0
for k,v := range timeSplit {
if vn, err := strconv.ParseFloat(v , 64); err != nil {
return 0 , errors.New("字幕时间解析失败")
} else {
if k == 0 {
second += vn * 3600
} else if k == 1 {
second += vn * 60
} else if k == 2 {
second += vn
}
}
}
return second , nil
}
func SubString(str string , begin int ,length int) (substr string) {
// 将字符串的转换成[]rune
rs := []rune(str)
lth := len(rs)
// 简单的越界判断
if begin < 0 {
begin = 0
}
if begin >= lth {
begin = lth
}
end := begin + length
if end > lth {
end = lth
}
// 返回子串
return string(rs[begin:end])
}

451
gui_learn/app/srt.go

@ -0,0 +1,451 @@
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("翻译失败!缺少翻译引擎!")
}
}

92
gui_learn/app/task.go

@ -0,0 +1,92 @@
package app
type VideoMultitask struct {
MaxConcurrencyNumber int //最大运行并发数
Total int //任务总数
QueueFile []string //任务队列
CurrentIndex int //已处理的下标
FinishNumber int //已完成的任务数量
VideoSrt *VideoSrt
}
func NewVideoMultitask(concurrencyNumber int) (*VideoMultitask) {
app := new(VideoMultitask)
if concurrencyNumber == 0 {
//默认并发数
app.MaxConcurrencyNumber = 2
} else {
app.MaxConcurrencyNumber = concurrencyNumber
}
return app
}
//设置 任务队列
func (task *VideoMultitask) SetQueueFile(queue []string) {
task.QueueFile = queue
task.Total = len(queue)
}
//设置 任务队列
func (task *VideoMultitask) SetVideoSrt(v *VideoSrt) {
task.VideoSrt = v
}
//设置 并发数量
func (task *VideoMultitask) SetMaxConcurrencyNumber(n int) {
task.MaxConcurrencyNumber = n
}
//并发运行
func (task *VideoMultitask) Run() {
//初始参数
task.CurrentIndex = -1
task.FinishNumber = 0
number := 1
//并发调用
for number <= task.MaxConcurrencyNumber && task.CurrentIndex < (task.Total - 1){
if task.CurrentIndex == -1 {
task.CurrentIndex = 0;
path := task.QueueFile[task.CurrentIndex]
go func() {
task.VideoSrt.Run(path)
}()
} else {
task.CurrentIndex++
path := task.QueueFile[task.CurrentIndex]
go func() {
task.VideoSrt.Run(path)
}()
}
number++
}
}
func (task *VideoMultitask) RunOver() bool {
//fmt.Println("RunOver:" , task.CurrentIndex)
if task.CurrentIndex >= (task.Total - 1) {
//任务队列处理完成
return true
}
//执行
task.CurrentIndex++
path := task.QueueFile[task.CurrentIndex]
go func() {
task.VideoSrt.Run(path)
}()
return false
}
//标记已完成
func (task *VideoMultitask) FinishTask() bool {
task.FinishNumber++
//fmt.Println("FinishTask:" , task.FinishNumber)
if (task.FinishNumber == task.Total) {
return true
}
return false
}

89
gui_learn/app/task_tanslate.go

@ -0,0 +1,89 @@
package app
type TranslateMultitask struct {
MaxConcurrencyNumber int //最大运行并发数
Total int //任务总数
QueueFile []string //任务队列
CurrentIndex int //已处理的下标
FinishNumber int //已完成的任务数量
SrtTranslateApp *SrtTranslateApp
}
func NewTranslateMultitask(concurrencyNumber int) (*TranslateMultitask) {
app := new(TranslateMultitask)
if concurrencyNumber == 0 {
//默认并发数
app.MaxConcurrencyNumber = 2
} else {
app.MaxConcurrencyNumber = concurrencyNumber
}
return app
}
//设置 任务队列
func (task *TranslateMultitask) SetQueueFile(queue []string) {
task.QueueFile = queue
task.Total = len(queue)
}
//设置 任务队列
func (task *TranslateMultitask) SetSrtTranslateApp(app *SrtTranslateApp) {
task.SrtTranslateApp = app
}
//设置 并发数量
func (task *TranslateMultitask) SetMaxConcurrencyNumber(n int) {
task.MaxConcurrencyNumber = n
}
//并发运行
func (task *TranslateMultitask) Run() {
//初始参数
task.CurrentIndex = -1
task.FinishNumber = 0
number := 1
//并发调用
for number <= task.MaxConcurrencyNumber && task.CurrentIndex < (task.Total - 1){
if task.CurrentIndex == -1 {
task.CurrentIndex = 0;
path := task.QueueFile[task.CurrentIndex]
go func() {
task.SrtTranslateApp.Run(path)
}()
} else {
task.CurrentIndex++
path := task.QueueFile[task.CurrentIndex]
go func() {
task.SrtTranslateApp.Run(path)
}()
}
number++
}
}
func (task *TranslateMultitask) RunOver() bool {
if task.CurrentIndex >= (task.Total - 1) {
//任务队列处理完成
return true
}
//执行
task.CurrentIndex++
path := task.QueueFile[task.CurrentIndex]
go func() {
task.SrtTranslateApp.Run(path)
}()
return false
}
//标记已完成
func (task *TranslateMultitask) FinishTask() bool {
task.FinishNumber++
if (task.FinishNumber == task.Total) {
return true
}
return false
}

122
gui_learn/app/tool/chinese_simple.go

@ -0,0 +1,122 @@
package tool
import (
"regexp"
"strings"
"unicode/utf8"
)
func CheckChineseNumber(s string) bool {
regx := regexp.MustCompile("(.*)([一|二|两|三|四|五|六|七|八|九|十|百|千|万|亿]+)(.*)")
return regx.MatchString(s)
}
func ChineseNumberToLowercaseLength(s string) int {
st := GetStringUtf8Length(s)
regx := regexp.MustCompile("([一|二|两|三|四|五|六|七|八|九|十|百|千|万|亿]+)(.*)")
s = regx.ReplaceAllString(s , "$1")
if s == "" || !IsChineseNumber(s) {
return st
}
rst := GetStringUtf8Length(s)
cha_t := 0
if st > rst {
cha_t = st - rst
}
s = strings.TrimSpace(s)
zhTexts := strings.Split(s , "")
zhTextsLens := len(zhTexts)
numberPosi := true
maxBaseNumber := 1
if s == "十" {
maxBaseNumber = 2
} else {
for i:=0; i<zhTextsLens; i++ {
if numberPosi == false {
switch zhTexts[i] {
case "十":
maxBaseNumber = 2
break
case "百":
maxBaseNumber = 3
break
case "千":
maxBaseNumber = 4
break
case "万":
maxBaseNumber = 5
break
}
break
}
numberPosi = !numberPosi
}
}
return maxBaseNumber+cha_t
}
func IsChineseNumber(text string) bool {
text = strings.TrimSpace(text)
if text == "" {
return false
}
zhTexts := strings.Split(text , "")
zhTextsLens := len(zhTexts)
numberPosi := true
for i:=0; i<zhTextsLens; i++ {
if !ValiChineseNumberChar(zhTexts[i] , !numberPosi) {
return false
}
numberPosi = !numberPosi
}
return true
}
func ValiChineseNumberChar(s string , unit bool) bool {
zh_number := []string{"一","二","两","三","四","五","六","七","八","九","十","百","千","万"}
zh_unit := []string{"十","百","千","万","亿"}
if unit {
for _,v := range zh_unit {
if v == s {
return true
}
}
return false
}
for _,v := range zh_number {
if v == s {
return true
}
}
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
}
}

248
gui_learn/app/tool/tool.go

@ -0,0 +1,248 @@
package tool
import (
"crypto/md5"
"encoding/hex"
"math/rand"
"os"
"os/exec"
"path"
"runtime"
"strconv"
"strings"
"syscall"
"time"
)
//验证 s 是否存在 slice 中
func InSliceString(s string , slices []string) bool {
for _,v := range slices {
if v == s {
return true
}
}
return false
}
//Windows下Dir路径转换
func WinDir(dir string) string {
return strings.Replace(dir , "\\" , "/" , -1)
}
//获取文件名称(不带后缀)
func GetFileBaseName(filepath string) string {
basefile := path.Base(filepath)
ext := path.Ext(filepath)
return strings.Replace(basefile , ext , "" , 1)
}
//检验目录是否存在
func DirExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
//创建目录
func CreateDir(path string , all bool) error {
var err error
if all {
err = os.Mkdir(path, os.ModePerm)
} else {
err = os.MkdirAll(path, os.ModePerm)
}
if err != nil {
return err
}
return nil
}
// open opens the specified URL in the default browser of the user.
func OpenUrl(url string) error {
var cmd string
var args []string
switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
cmd = "xdg-open"
}
args = append(args, url)
c := exec.Command(cmd, args...)
c.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
return c.Start()
}
//获取随机字符串
func GetRandomCodeString(len int) string {
seed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
seedArr := strings.Split(seed , "")
result := []string{}
index := 0
for index < len {
s := GetIntRandomNumber(0 , 61)
result = append(result , seedArr[s])
index++
}
return strings.Join(result , "")
}
//设置随机种子
func SetRandomSeed() {
rand.Seed(time.Now().Unix()) //设置随机种子
}
//获取某范围的随机整数
func GetIntRandomNumber(min int64 , max int64) int64 {
return rand.Int63n(max - min) + min
}
//字幕时间戳转换
func SubtitleTimeMillisecond(time int64 , showMillisecond bool) string {
var miao int64 = 0
var min int64 = 0
var hours int64 = 0
var millisecond int64 = 0
millisecond = (time % 1000)
miao = (time / 1000)
if miao > 59 {
min = (time / 1000) / 60
miao = miao % 60
}
if min > 59 {
hours = (time / 1000) / 3600
min = min % 60
}
//00:00:06,770
var miaoText = RepeatStr(strconv.FormatInt(miao , 10) , "0" , 2 , true)
var minText = RepeatStr(strconv.FormatInt(min , 10) , "0" , 2 , true)
var hoursText = RepeatStr(strconv.FormatInt(hours , 10) , "0" , 2 , true)
var millisecondText = RepeatStr(strconv.FormatInt(millisecond , 10) , "0" , 3 , true)
if showMillisecond {
return hoursText + ":" + minText + ":" + miaoText + "," + millisecondText
}
return hoursText + ":" + minText + ":" + miaoText
}
//歌词时间戳转换
func MusicLrcTextMillisecond(time int64) string {
var miao int64 = 0
var min int64 = 0
var millisecond int64 = 0
millisecond = (time % 1000)
miao = (time / 1000)
if miao > 59 {
min = (time / 1000) / 60
miao = miao % 60
}
millisecond = millisecond / 10
//00:00:06,770
var miaoText = RepeatStr(strconv.FormatInt(miao , 10) , "0" , 2 , true)
var minText = RepeatStr(strconv.FormatInt(min , 10) , "0" , 2 , true)
var millisecondText = RepeatStr(strconv.FormatInt(millisecond , 10) , "0" , 2 , true)
return minText + ":" + miaoText + "." + millisecondText
}
func RepeatStr(str string , s string , length int , before bool) string {
ln := len(str)
if ln >= length {
return str
}
if before {
return strings.Repeat(s , (length - ln)) + str
} else {
return str + strings.Repeat(s , (length - ln))
}
}
//校验文件是否存在
func VaildFile (file string) bool {
_, err := os.Stat(file) //os.Stat获取文件信息
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
//文本md5
func Md5String(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
//版本号比较
func CompareVersion(version1 string, version2 string) int {
v1 := strings.Split(version1, ".")
v2 := strings.Split(version2, ".")
var limit int
if len(v1) > len(v2) {
limit = len(v1)
} else {
limit = len(v2)
}
for {
if len(v1) >= limit {
break
}
v1 = append(v1, "0")
}
for {
if len(v2) >= limit {
break
}
v2 = append(v2, "0")
}
for i := 0; i < limit; i++ {
num1, _ := strconv.Atoi(v1[i])
num2, _ := strconv.Atoi(v2[i])
if num1 > num2 {
return 1
}
if num1 < num2 {
return -1
}
}
return 0
}

110
gui_learn/app/translate/baidu.go

@ -0,0 +1,110 @@
package translate
import (
"errors"
"github.com/buger/jsonparser"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
"videosrt/app/tool"
)
//百度翻译
type BaiduTranslate struct {
AppId string //appid
AppSecret string //appsecret
AuthenType int //账号认证类型
}
//百度翻译结果集
type BaiduTranslateResult struct {
From string //翻译源语言
To string //译文语言
TransResultSrc string //翻译结果(原文)
TransResultDst string //翻译结果(译文)
ErrorCode int64 //错误码(仅当出现错误时存在)
ErrorMsg string //错误消息(仅当出现错误时存在)
}
//常量
const (
TRANS_API string = "https://fanyi-api.baidu.com/api/trans/vip/translate"
//账号认证类型
ACCOUNT_COMMON_AUTHEN int = 1 //标准版
ACCOUNT_SENIOR_AUTHEN int = 2 //高级版
)
//百度api文档
//http://api.fanyi.baidu.com/api/trans/product/apidoc
//支持语言列表 http://api.fanyi.baidu.com/api/trans/product/apidoc#languageList
func (trans *BaiduTranslate) TranslateBaidu (strings string , from string , to string) (*BaiduTranslateResult , error) {
params := &url.Values{}
params.Add("q" , strings)
params.Add("appid" , trans.AppId)
params.Add("salt" , strconv.FormatInt(tool.GetIntRandomNumber(10000 , 99999) , 10))
params.Add("from" , from)
params.Add("to" , to)
params.Add("sign" , trans.BuildSign(strings , params.Get("salt")))
return trans.CallRequest(params)
}
//生成加密sign
func (trans *BaiduTranslate) BuildSign (strings string , salt string) string {
str := trans.AppId + strings + salt + trans.AppSecret
return tool.Md5String(str)
}
//发起请求
func (trans *BaiduTranslate) CallRequest (params *url.Values ) (*BaiduTranslateResult , error) {
url := TRANS_API + "?" + params.Encode()
request, e := http.NewRequest(http.MethodGet, url , nil)
if e != nil {
return nil,e
}
http.DefaultClient.Timeout = time.Second * 60
//do request
response, e := http.DefaultClient.Do(request)
if e != nil {
return nil,e
}
//content
content, e := ioutil.ReadAll(response.Body)
if e != nil {
return nil,e
}
//解析数据
errorCode , _ := jsonparser.GetString(content , "error_code")
errorMsg , _ := jsonparser.GetString(content , "error_msg")
from , _ := jsonparser.GetString(content , "from")
to , _ := jsonparser.GetString(content , "to")
errorCodeInt , _ := strconv.Atoi(errorCode)
result := &BaiduTranslateResult{
ErrorCode:int64(errorCodeInt),
ErrorMsg:errorMsg,
From:from,
To:to,
}
_, _ = jsonparser.ArrayEach(content, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
result.TransResultSrc, _ = jsonparser.GetString(value, "src")
result.TransResultDst, _ = jsonparser.GetString(value, "dst")
}, "trans_result")
//翻译错误校验
if result.ErrorCode != 0 {
return nil , errors.New(result.ErrorMsg)
}
return result,nil
}

91
gui_learn/app/translate/tengxunyun.go

@ -0,0 +1,91 @@
package translate
import (
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
v20180321 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tmt/v20180321"
"time"
)
//腾讯云机器翻译
type TengxunyunTranslate struct {
SecretId string //secretId
SecretKey string //secretKey
}
//腾讯云翻译结果集
type TengxunyunTranslateResult struct {
From string //翻译源语言
To string //译文语言
TransResultSrc string //翻译结果(原文)
TransResultDst string //翻译结果(译文)
}
//调起腾讯云机器文本翻译
func (trans *TengxunyunTranslate) TranslateTengxunyun (strings string , from string , to string) (*TengxunyunTranslateResult , error) {
//fmt.Println("TranslateTengxunyun : " , strings , from , to)
credential := common.NewCredential(
trans.SecretId ,
trans.SecretKey ,
)
cpf := profile.NewClientProfile()
client, _ := v20180321.NewClient(credential, regions.Guangzhou, cpf)
request := v20180321.NewTextTranslateRequest()
var SourceText *string = new(string)
var Source *string = new(string)
var Target *string = new(string)
var ProjectId *int64 = new(int64)
//*SourceText = "这个需求做不了"
//*Source = "zh"
//*Target = "en"
//*ProjectId = 0
*SourceText = strings
*Source = from
*Target = to
*ProjectId = 0
request.SourceText = SourceText
request.Source = Source
request.Target = Target
request.ProjectId = ProjectId
result := new(TengxunyunTranslateResult)
// 通过client对象调用想要访问的接口,需要传入请求对象
response, err := client.TextTranslate(request)
// 处理异常
if errorObj, ok := err.(*errors.TencentCloudSDKError); ok {
return result , errorObj
}
// 非SDK异常,直接失败。实际代码中可以加入其他的处理。
if err != nil {
return result , err
}
result.From = *response.Response.Source
result.To = *response.Response.Target
result.TransResultSrc = strings
result.TransResultDst = *response.Response.TargetText
return result,nil
}
//获取并发请求停顿的时间
func (trans *TengxunyunTranslate) TranslateSleepTime (maxConcurrency int) (time.Duration) {
if maxConcurrency == 1 {
return time.Millisecond * 250
} else if maxConcurrency == 2 {
return time.Millisecond * 600
} else {
return time.Millisecond * time.Duration(maxConcurrency) * 900
}
}

83
gui_learn/app/version.go

@ -0,0 +1,83 @@
package app
import (
"errors"
"github.com/PuerkitoBio/goquery"
"github.com/lxn/walk"
"net/http"
"strconv"
"strings"
"time"
)
const (
VERSION_SOURCE string = "https://gitee.com/641453620/video-srt-windows/tags"
)
type AppVersion struct {
}
//根据码云查询新版本
func (v *AppVersion) GetVersion () (string , error) {
timeout := time.Duration(2 * time.Second)
client := &http.Client{
Timeout:timeout,
}
//获取版本来源html
response, err := client.Get(VERSION_SOURCE)
if err != nil {
return "" , err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return "" , errors.New("status code error : " + strconv.Itoa(response.StatusCode))
}
//加载html
html, err := goquery.NewDocumentFromReader(response.Body)
if err != nil {
return "" , err
}
//查找节点
vs := ""
html.Find("#git-tags-container .releases-tags-wrap .releases-tag-content .tag-list .tag-item").Each(func(i int, s *goquery.Selection) {
if vs != "" {
return
}
tag , is := s.Find(".tag-name a").Attr("title")
if is && tag != "" {
vs = strings.TrimSpace(tag)
}
})
return vs,nil
}
//显示更新提醒
func (v *AppVersion) ShowVersionNotifyInfo (version string , own *MyMainWindow) error {
mw, err := walk.NewMainWindow()
if err != nil {
return err
}
ni, err := walk.NewNotifyIcon(mw)
if err != nil {
return err
}
defer func() {
time.Sleep(time.Second * 15)
_ = ni.Dispose()
}()
if err := ni.SetVisible(true); err != nil {
return err
}
if err := ni.ShowMessage("更新提醒" , "检测到VideoSrt的新版本(v"+version+"),请及时下载更新哦") ; err != nil {
return err
}
return nil
}

836
gui_learn/app/video.go

@ -0,0 +1,836 @@
package app
import (
"bytes"
"errors"
"fmt"
"github.com/buger/jsonparser"
"os"
"path"
"strconv"
"strings"
"time"
"videosrt/app/aliyun"
"videosrt/app/ffmpeg"
"videosrt/app/tool"
"videosrt/app/translate"
)
//应用翻译配置
type VideoSrtTranslateStruct struct {
TranslateSwitch bool //字幕翻译开关
Supplier int //引擎供应商
BilingualSubtitleSwitch bool //是否输出双语字幕
InputLanguage int //输入字幕语言
OutputLanguage int //输出字幕语言
OutputMainSubtitleInputLanguage bool //双语主字幕(输入语言)
BaiduTranslate translate.BaiduTranslate //百度翻译
TengxunyunTranslate translate.TengxunyunTranslate //腾讯云翻译
}
//统一翻译结果
type VideoSrtTranslateResult struct {
From string //翻译源语言
To string //译文语言
TransResultSrc string //翻译结果(原文)
TransResultDst string //翻译结果(译文)
}
//主应用
type VideoSrt struct {
Ffmpeg ffmpeg.Ffmpeg
AliyunOss aliyun.AliyunOss //oss
AliyunClound aliyun.AliyunClound //语音识别引擎
CloseAutoDeleteOssTempFile bool //关闭自动删除临时音频文件(默认开启)[false开启 true关闭]
CloseIntelligentBlockSwitch bool //关闭智能分段处理(默认开启)
TempDir string //临时文件目录
AppDir string //应用根目录
SrtDir string //字幕文件输出目录
OutputType *AppSetingsOutput //输出文件类型
OutputEncode int //输出文件编码
SoundTrack int //输出音轨(0输出全部音轨)
MaxConcurrency int //最大处理并发数
TranslateCfg *VideoSrtTranslateStruct //翻译配置
FilterSetings *AppFilterSetings //过滤器配置
LogHandler func(s string , video string) //日志回调
SuccessHandler func(video string) //成功回调
FailHandler func(video string) //失败回调
}
//获取应用
func NewApp(appDir string) *VideoSrt {
app := new(VideoSrt)
app.TempDir = "temp/audio"
app.AppDir = appDir
app.OutputEncode = OUTPUT_ENCODE_UTF8 //默认输出文件编码
app.MaxConcurrency = 2
//实例应用翻译配置
app.TranslateCfg = new(VideoSrtTranslateStruct)
return app
}
//应用加载配置
func (app *VideoSrt) InitAppConfig(oss *AliyunOssCache , engine *AliyunEngineCache) {
//oss
app.AliyunOss.Endpoint = oss.Endpoint
app.AliyunOss.AccessKeyId = oss.AccessKeyId
app.AliyunOss.AccessKeySecret = oss.AccessKeySecret
app.AliyunOss.BucketName = oss.BucketName
app.AliyunOss.BucketDomain = oss.BucketDomain
//engine
app.AliyunClound.AppKey = engine.AppKey
app.AliyunClound.AccessKeyId = engine.AccessKeyId
app.AliyunClound.AccessKeySecret = engine.AccessKeySecret
app.AliyunClound.Region = engine.Region
}
//加载翻译设置
func (app *VideoSrt) InitTranslateConfig (translateSettings *VideoSrtTranslateStruct) {
app.TranslateCfg = translateSettings
}
//加载过滤器配置
func (app *VideoSrt) InitFilterConfig (filterSetings *AppFilterSetings) {
app.FilterSetings = filterSetings
}
func (app *VideoSrt) SetCloseAutoDeleteOssTempFile(state bool) {
app.CloseAutoDeleteOssTempFile = state
}
func (app *VideoSrt) SetCloseIntelligentBlockSwitch(state bool) {
app.CloseIntelligentBlockSwitch = state
}
func (app *VideoSrt) SetSrtDir(dir string) {
app.SrtDir = dir
}
func (app *VideoSrt) SetOutputType(output *AppSetingsOutput) {
app.OutputType = output
}
func (app *VideoSrt) SetOutputEncode(encode int) {
app.OutputEncode = encode
}
func (app *VideoSrt) SetSoundTrack(track int) {
app.SoundTrack = track
}
func (app *VideoSrt) SetMaxConcurrency(number int) {
if number == 0 {
number = 2
}
app.MaxConcurrency = number
}
func (app *VideoSrt) SetSuccessHandler(callback func(video string)) {
app.SuccessHandler = callback
}
func (app *VideoSrt) SetFailHandler(callback func(video string)) {
app.FailHandler = callback
}
func (app *VideoSrt) SetLogHandler(callback func(s string , video string)) {
app.LogHandler = callback
}
//输出日志
func (app *VideoSrt) Log(s string , v string) {
app.LogHandler(s , v)
}
//清空临时目录
func (app *VideoSrt) ClearTempDir() {
//临时目录
tmpAudioDir := app.AppDir + "/" + app.TempDir
if tool.DirExists(tmpAudioDir) {
//清空
if remove := os.RemoveAll(tmpAudioDir); remove != nil {
app.Log("清空临时文件夹失败,请手动操作" , "警告")
}
}
}
//应用运行
func (app *VideoSrt) Run(video string) {
var tmpAudio string = ""
//致命错误捕获
defer func() {
//拦截panic
if err := recover(); err != nil {
//失败回调
app.FailHandler(video)
//fmt.Println( err )
e , ok := err.(error)
if ok {
app.Log("错误:" + e.Error() , video)
} else {
panic(err)
}
}
}()
//校验媒体文件
if tool.VaildFile(video) != true {
panic("媒体文件不存在")
}
tmpAudioDir := app.AppDir + "/" + app.TempDir
if !tool.DirExists(tmpAudioDir) {
//创建目录
if err := tool.CreateDir(tmpAudioDir , false); err != nil {
panic(err)
}
}
tmpAudioFile := tool.GetRandomCodeString(15) + ".mp3"
tmpAudio = tmpAudioDir + "/" + tmpAudioFile //临时音频文件
app.Log("提取音频文件 ..." , video)
//分离/转换媒体音频
ExtractVideoAudio(video , tmpAudio)
app.Log("上传音频文件 ..." , video)
//上传音频至OSS
tempfile := UploadAudioToClound(app.AliyunOss , tmpAudio)
//获取完整链接
filelink := app.AliyunOss.GetObjectFileUrl(tempfile)
app.Log("上传文件成功 , 识别中 ..." , video)
defer func() {
//清理oss音频文件
if app.CloseAutoDeleteOssTempFile == false {
if e := DeleteOssCloundTempAudio(app.AliyunOss , tempfile); e!=nil {
app.Log("OSS临时音频清理失败,建议手动删除" , video)
} else {
app.Log("OSS临时音频清理成功" , video)
}
}
}()
//关闭软件智能分段
if app.CloseIntelligentBlockSwitch {
app.Log("您已关闭了软件智能分段处理,将输出原样结果" , video)
}
//阿里云录音文件识别
AudioResult , IntelligentBlockResult := AliyunAudioRecognition(app , video , app.AliyunClound, filelink)
app.Log("文件识别成功 , 字幕处理中 ..." , video)
//翻译字幕块
if e := AliyunAudioResultTranslate(app , video , AudioResult , IntelligentBlockResult); e != nil {
app.Log("字幕翻译失败:" + e.Error() , video)
app.Log("已强制关闭翻译,仅输出原始字幕文件" , video)
app.TranslateCfg.TranslateSwitch = false
}
//字幕过滤
AliyunResultFilter(app , video , AudioResult , IntelligentBlockResult)
//输出文件
if app.OutputType.SRT {
AliyunAudioResultMakeSubtitleFile(app , video , OUTPUT_SRT , AudioResult , IntelligentBlockResult)
}
if app.OutputType.LRC {
AliyunAudioResultMakeSubtitleFile(app , video , OUTPUT_LRC , AudioResult , IntelligentBlockResult)
}
if app.OutputType.TXT {
AliyunAudioResultMakeSubtitleFile(app , video , OUTPUT_STRING , AudioResult , IntelligentBlockResult)
}
//校验字幕段落是否为空
if CheckEmptyResult(AudioResult) {
app.Log("检测到识别结果为空,生成字幕失败(检查:媒体文件人声是否清晰?语音引擎与媒体语言是否匹配?)" , video)
}
app.Log("处理完成" , video)
//删除临时文件
if tmpAudio != "" {
if remove := os.Remove(tmpAudio); remove != nil {
app.Log("删除临时文件失败,请手动删除" , video)
}
}
//成功回调
app.SuccessHandler(video)
}
//统一运行翻译结果
func (app *VideoSrt) RunTranslate(s string , file string) (*VideoSrtTranslateResult , error) {
var trys int = 0
translateResult := new(VideoSrtTranslateResult)
if app.TranslateCfg.Supplier == TRANSLATE_SUPPLIER_BAIDU {
if app.TranslateCfg.BaiduTranslate.AuthenType == translate.ACCOUNT_COMMON_AUTHEN { //百度翻译标准版
//休眠1010毫秒
time.Sleep(time.Millisecond * 1050)
} 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("翻译失败!缺少翻译引擎!")
}
}
//提取视频音频文件
func ExtractVideoAudio(video string , tmpAudio string) {
if err := ffmpeg.ExtractAudio(video , tmpAudio); err != nil {
panic(err)
}
}
//上传音频至oss
func UploadAudioToClound(target aliyun.AliyunOss , audioFile string) string {
name := ""
//提取文件名称
if fileInfo, e := os.Stat(audioFile);e != nil {
panic(e)
} else {
name = fileInfo.Name()
}
//上传
if file , e := target.UploadFile(audioFile , name); e != nil {
panic(e)
} else {
return file
}
}
//清理临时音频文件
func DeleteOssCloundTempAudio (target aliyun.AliyunOss , objectName string) error {
//删除
if e := target.DeleteFile(objectName); e != nil {
return e
}
return nil
}
//检查是否为空输出
func CheckEmptyResult(AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult) bool {
empty := true
for _,v := range AudioResult {
for range v {
empty = false
break
}
}
return empty
}
//阿里云录音文件识别
func AliyunAudioRecognition(app *VideoSrt , video string , engine aliyun.AliyunClound , filelink string) (AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) {
//创建识别请求
taskid, client, e := engine.NewAudioFile(filelink)
if e != nil {
panic(e)
}
AudioResult = make(map[int64][] *aliyun.AliyunAudioRecognitionResult)
IntelligentBlockResult = make(map[int64][] *aliyun.AliyunAudioRecognitionResult)
//遍历获取识别结果
resultError := engine.GetAudioFileResult(taskid , client , func(text string) {
//日志输出
app.Log(text , video)
} , func(result []byte) {
//结果处理
statusText, _ := jsonparser.GetString(result, "StatusText") //结果状态
if statusText == aliyun.STATUS_SUCCESS {
if !app.CloseIntelligentBlockSwitch {
//获取智能分段结果
aliyun.AliyunAudioResultWordHandle(result , func(vresult *aliyun.AliyunAudioRecognitionResult) {
channelId := vresult.ChannelId
_ , isPresent := IntelligentBlockResult[channelId]
if isPresent {
//追加
IntelligentBlockResult[channelId] = append(IntelligentBlockResult[channelId] , vresult)
} else {
//初始
IntelligentBlockResult[channelId] = []*aliyun.AliyunAudioRecognitionResult{}
IntelligentBlockResult[channelId] = append(IntelligentBlockResult[channelId] , vresult)
}
})
}
//获取原始结果
_, err := jsonparser.ArrayEach(result, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
text , _ := jsonparser.GetString(value, "Text")
channelId , _ := jsonparser.GetInt(value, "ChannelId")
beginTime , _ := jsonparser.GetInt(value, "BeginTime")
endTime , _ := jsonparser.GetInt(value, "EndTime")
silenceDuration , _ := jsonparser.GetInt(value, "SilenceDuration")
speechRate , _ := jsonparser.GetInt(value, "SpeechRate")
emotionValue , _ := jsonparser.GetInt(value, "EmotionValue")
vresult := &aliyun.AliyunAudioRecognitionResult {
Text:text,
ChannelId:channelId,
BeginTime:beginTime,
EndTime:endTime,
SilenceDuration:silenceDuration,
SpeechRate:speechRate,
EmotionValue:emotionValue,
}
_ , isPresent := AudioResult[channelId]
if isPresent {
//追加
AudioResult[channelId] = append(AudioResult[channelId] , vresult)
} else {
//初始
AudioResult[channelId] = []*aliyun.AliyunAudioRecognitionResult{}
AudioResult[channelId] = append(AudioResult[channelId] , vresult)
}
} , "Result", "Sentences")
if err != nil {
panic(err)
}
}
})
if (resultError != nil) {
panic(resultError)
}
return
}
//阿里云识别字幕块翻译处理
func AliyunAudioResultTranslate(app *VideoSrt , video string , AudioResult map[int64][] *aliyun.AliyunAudioRecognitionResult , IntelligentBlockResult map[int64][] *aliyun.AliyunAudioRecognitionResult) error {
//输出日志
if app.TranslateCfg.TranslateSwitch {
app.Log("字幕翻译处理中 ..." , video)
//百度翻译标准版
if app.TranslateCfg.Supplier == TRANSLATE_SUPPLIER_BAIDU && app.TranslateCfg.BaiduTranslate.AuthenType == translate.ACCOUNT_COMMON_AUTHEN {
app.Log("你使用的是 “百度翻译标准版” 账号,翻译速度较慢,请耐心等待 ..." , video)
}
} else {
return nil
}
//输出音轨
outputSoundTrack := app.SoundTrack
//计算总任务行数
totalRow := 0 //总处理行数
var lastrv float64 = 0 //最后进度(%)
//开启智能分段
if !app.CloseIntelligentBlockSwitch {
if app.OutputType.SRT || app.OutputType.LRC {
for channel , result := range IntelligentBlockResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
totalRow += len(result)
}
}
if app.OutputType.TXT {
for channel,result := range AudioResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
totalRow += len(result)
}
}
index := 0
//翻译任务
if app.OutputType.SRT || app.OutputType.LRC {
for channel , result := range IntelligentBlockResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
for _ , data := range result {
transResult,e := app.RunTranslate(data.Text , video)
if e != nil {
return e
//panic(e) //终止翻译
}
data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文
index++
rv := (float64(index)/float64(totalRow))*100
if (rv - lastrv) > 20 {
//输出比例
app.Log("字幕翻译已处理:" + fmt.Sprintf("%.2f" , rv) + "%" , video)
lastrv = rv
}
}
}
}
if app.OutputType.TXT {
for channel,result := range AudioResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
for _ , data := range result {
transResult,e := app.RunTranslate(data.Text , video)
if e != nil {
return e
//panic(e) //终止翻译
}
data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文
index++
rv := (float64(index)/float64(totalRow))*100
if (rv - lastrv) > 20 {
//输出比例
app.Log("字幕翻译已处理:" + fmt.Sprintf("%.2f" , rv) + "%" , video)
lastrv = rv
}
}
}
}
} else { //关闭智能分段
for channel,result := range AudioResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
totalRow += len(result)
}
index := 0
//翻译任务
for channel,result := range AudioResult {
soundChannel := channel + 1
if outputSoundTrack != 3 && outputSoundTrack != 0 {
if outputSoundTrack != int(soundChannel) {
continue //跳出非输出音轨转换
}
}
for _ , data := range result {
transResult,e := app.RunTranslate(data.Text , video)
if e != nil {
return e
//panic(e) //终止翻译
}
data.TranslateText = strings.TrimSpace(transResult.TransResultDst) //译文
index++
rv := (float64(index)/float64(totalRow))*100
if (rv - lastrv) > 20 {
//输出比例
app.Log("字幕翻译已处理:" + fmt.Sprintf("%.2f" , rv) + "%" , video)
lastrv = rv
}
}
}
}
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
if app.SrtDir == "" {
subfileDir = path.Dir(video)
} else {
subfileDir = app.SrtDir
}
subfile := tool.GetFileBaseName(video)
//注入合适的数据块
thisAudioResult := make(map[int64][] *aliyun.AliyunAudioRecognitionResult)
//开启智能分段
if !app.CloseIntelligentBlockSwitch {
if outputType == OUTPUT_SRT || outputType == OUTPUT_LRC {
thisAudioResult = IntelligentBlockResult
} else if outputType == OUTPUT_STRING {
thisAudioResult = AudioResult
}
} else {
thisAudioResult = AudioResult
}
oneSoundChannel := false //是否输出单条音轨
//根据音轨,输出文件
for channel,result := range thisAudioResult {
soundChannel := channel + 1
if app.SoundTrack != 3 && app.SoundTrack != 0 {
oneSoundChannel = true
if app.SoundTrack != int(soundChannel) {
//跳过此音轨
continue
}
}
var thisfile string
//文件名称
if oneSoundChannel {
thisfile = subfileDir + "/" + subfile
} else {
thisfile = subfileDir + "/" + subfile + "_channel_" + strconv.FormatInt(soundChannel , 10)
}
//输出文件类型
if outputType == OUTPUT_SRT {
thisfile += ".srt"
} else if outputType == OUTPUT_STRING {
thisfile += ".txt"
} else if outputType == OUTPUT_LRC {
thisfile += ".lrc"
}
//创建文件
file, e := os.Create(thisfile)
if e != nil {
panic(e)
}
defer file.Close()
//文件编码分支
if app.OutputEncode == OUTPUT_ENCODE_UTF8_BOM {
if _, e := file.Write([]byte{0xEF, 0xBB, 0xBF});e != nil {
panic(e)
}
}
//歌词头
if outputType == OUTPUT_LRC {
_,_ = file.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 result {
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.BeginTime , data.EndTime , data.Text , data.TranslateText , true , bilingualAsc)
} else {
linestr = MakeSubtitleText(index , data.BeginTime , data.EndTime , data.TranslateText , "" , false , true)
}
} else {
linestr = MakeSubtitleText(index , data.BeginTime , data.EndTime , data.Text , "" , false , true)
}
} else if outputType == OUTPUT_LRC {
if app.TranslateCfg.TranslateSwitch {
linestr = MakeMusicLrcText(index , data.BeginTime , data.EndTime , data.TranslateText)
} else {
linestr = MakeMusicLrcText(index , data.BeginTime , data.EndTime , data.Text)
}
}
//写入行
if _, e = file.WriteString(linestr);e != nil {
panic(e)
}
} 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 = file.WriteString(txtOutputContent.String());e != nil {
panic(e)
}
if _, e = file.WriteString(txtTransalteOutputContent.String());e != nil {
panic(e)
}
}
}
}

5
gui_learn/build.bat

@ -0,0 +1,5 @@
set GO111MODULE=on
set GOARCH=386
rsrc -manifest main.manifest -ico serviceMonitor.ico -o rsrc.syso
go build -ldflags="-s -w -H=windowsgui"
upx -9 ./serviceMonitor.exe

BIN
gui_learn/data/img/about.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

BIN
gui_learn/data/img/app-setings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

BIN
gui_learn/data/img/app.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
gui_learn/data/img/gitee.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

BIN
gui_learn/data/img/github.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

BIN
gui_learn/data/img/index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

BIN
gui_learn/data/img/media.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

BIN
gui_learn/data/img/muyan.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

BIN
gui_learn/data/img/new.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

BIN
gui_learn/data/img/open.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

BIN
gui_learn/data/img/oss.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

BIN
gui_learn/data/img/setings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

BIN
gui_learn/data/img/translate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

BIN
gui_learn/data/img/version.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

BIN
gui_learn/data/img/voice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

20
gui_learn/go.mod

@ -0,0 +1,20 @@
module videosrt
go 1.12
require (
github.com/PuerkitoBio/goquery v1.5.0
github.com/aliyun/alibaba-cloud-sdk-go v1.60.268
github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e
github.com/lxn/walk v0.0.0-20191121152919-b7c43041fb1b
github.com/lxn/win v0.0.0-20191106123917-121afc750dd3 // indirect
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
)

1065
gui_learn/main.go

File diff suppressed because it is too large

15
gui_learn/main.manifest

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
</windowsSettings>
</application>
</assembly>

BIN
gui_learn/rsrc.syso

Binary file not shown.
Loading…
Cancel
Save