srt.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package srt
  2. import (
  3. "github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
  4. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  5. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  6. "github.com/allanpk716/ChineseSubFinder/internal/pkg/regex_things"
  7. "github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. )
  13. type Parser struct {
  14. }
  15. func NewParser() *Parser {
  16. return &Parser{}
  17. }
  18. func (p Parser) GetParserName() string {
  19. return "srt"
  20. }
  21. /*
  22. DetermineFileTypeFromFile 确定字幕文件的类型,是双语字幕或者某一种语言等等信息
  23. 当 error 是 common.DetermineFileTypeFromFileExtNotFitSRT
  24. 需要额外的处理逻辑,比如不用报错,而是跳过后续的逻辑
  25. */
  26. func (p Parser) DetermineFileTypeFromFile(filePath string) (bool, *subparser.FileInfo, error) {
  27. nowExt := filepath.Ext(filePath)
  28. log_helper.GetLogger().Debugln("DetermineFileTypeFromFile", p.GetParserName(), filePath)
  29. fBytes, err := os.ReadFile(filePath)
  30. if err != nil {
  31. return false, nil, err
  32. }
  33. inBytes, err := language.ChangeFileCoding2UTF8(fBytes)
  34. if err != nil {
  35. return false, nil, err
  36. }
  37. return p.DetermineFileTypeFromBytes(inBytes, nowExt)
  38. }
  39. // DetermineFileTypeFromBytes 确定字幕文件的类型,是双语字幕或者某一种语言等等信息
  40. func (p Parser) DetermineFileTypeFromBytes(inBytes []byte, nowExt string) (bool, *subparser.FileInfo, error) {
  41. subFileInfo := subparser.FileInfo{}
  42. subFileInfo.Content = string(inBytes)
  43. subFileInfo.Ext = nowExt
  44. subFileInfo.Dialogues = make([]subparser.OneDialogue, 0)
  45. subFileInfo.DialoguesFilter = make([]subparser.OneDialogue, 0)
  46. orgDialogues := p.parseContent(inBytes)
  47. if len(orgDialogues) <= 0 {
  48. log_helper.GetLogger().Debugln("DetermineFileTypeFromBytes can't found DialoguesFilter, Skip")
  49. return false, nil, nil
  50. }
  51. subFileInfo.Dialogues = orgDialogues
  52. // 这里需要统计一共有几个 \N,以及这个数量在整体行数中的比例,这样就知道是不是双语字幕了
  53. countLineFeed := 0
  54. for _, oneDialogue := range orgDialogues {
  55. if len(oneDialogue.Lines) == 0 || my_util.ReplaceSpecString(oneDialogue.Lines[0], "") == "" {
  56. continue
  57. }
  58. ol := oneDialogue
  59. for i, line := range oneDialogue.Lines {
  60. fixedLine := line
  61. // 剔除 {\fn微软雅黑\fs14}C'mon, Rick. We're -- We're almost there. {} 这一段
  62. fixedLine = regex_things.ReMatchBrace.ReplaceAllString(line, "")
  63. fixedLine = regex_things.ReMatchBracket.ReplaceAllString(fixedLine, "")
  64. fixedLine = strings.ReplaceAll(fixedLine, `\N`, "")
  65. if my_util.ReplaceSpecString(fixedLine, "") == "" {
  66. ol.Lines[i] = ""
  67. break
  68. } else {
  69. if i == 1 {
  70. // 这样说明有两行字幕,也就是双语啦
  71. countLineFeed++
  72. }
  73. ol.Lines[i] = fixedLine
  74. }
  75. }
  76. if ol.Lines[0] == "" {
  77. continue
  78. }
  79. subFileInfo.DialoguesFilter = append(subFileInfo.DialoguesFilter, ol)
  80. }
  81. // 再分析
  82. // 需要判断每一个 Line 是啥语言,[语言的code]次数
  83. var langDict map[int]int
  84. langDict = make(map[int]int)
  85. // 抽取出所有的中文对话
  86. var chLines = make([]string, 0)
  87. // 抽取出所有的第二语言对话
  88. var otherLines = make([]string, 0)
  89. // 抽取出来的对话数组,为了后续用来匹配和修改时间轴
  90. var usefulDialogueExs = make([]subparser.OneDialogueEx, 0)
  91. emptyLines := 0
  92. for _, dialogue := range subFileInfo.DialoguesFilter {
  93. emptyLines += language.DetectSubLangAndStatistics(dialogue, langDict, &usefulDialogueExs, &chLines, &otherLines)
  94. }
  95. // 从统计出来的字典,找出 Top 1 或者 2 的出来,然后计算出是什么语言的字幕
  96. detectLang := language.SubLangStatistics2SubLangType(float32(countLineFeed), float32(len(subFileInfo.DialoguesFilter)-emptyLines), langDict, chLines)
  97. subFileInfo.Lang = detectLang
  98. subFileInfo.Data = inBytes
  99. subFileInfo.DialoguesFilterEx = usefulDialogueExs
  100. subFileInfo.CHLines = chLines
  101. subFileInfo.OtherLines = otherLines
  102. return true, &subFileInfo, nil
  103. }
  104. func (p Parser) parseContent(inBytes []byte) []subparser.OneDialogue {
  105. allString := string(inBytes)
  106. // 注意,需要替换掉 \r 不然正则表达式会有问题
  107. allString = strings.ReplaceAll(allString, "\r", "")
  108. lines := strings.Split(allString, "\n")
  109. // 需要把每一行如果是多余的特殊剔除掉
  110. // 这里的目标是后续的匹配更加容易,但是,后续也得注意
  111. // 因为这个样的操作,那么匹配对白内容的时候,可能是不存在的,只要是 index 和 时间匹配上了,就应该算一句话,只要在 dialogue 上是没得问题的
  112. // 而 dialogueFilter 中则可以把这样没有内容的排除,但是实际时间轴匹配的时候还是用 dialogue 而不是 dialogueFilter
  113. filterLines := make([]string, 0)
  114. for _, line := range lines {
  115. // 如果当前的这一句话,为空,或者进过正则表达式剔除特殊字符后为空,则跳过
  116. if my_util.ReplaceSpecString(line, "") == "" {
  117. continue
  118. }
  119. filterLines = append(filterLines, line)
  120. }
  121. dialogues := make([]subparser.OneDialogue, 0)
  122. /*
  123. 这里可以确定,srt 格式,开始一定是第一句话,那么首先就需要找到,第一行,一定是数字的,从这里开始算起
  124. 1. 先将 content 进行 \r 的替换为空
  125. 2. 将 content 进行 \n 来分割
  126. 3. 将分割的数组进行筛选,把空行剔除掉
  127. 4. 然后使用循环,用下面的 steps 进行解析一句对白
  128. steps:
  129. 0 找对白的 ID
  130. 1 找时间轴
  131. 2 找对白内容,可能有多行,停止的方式,一个是向后能找到 0以及2 或者 是最后一行
  132. */
  133. steps := 0
  134. nowDialogue := subparser.NewOneDialogue()
  135. newOneDialogueFun := func() {
  136. // 重新新建一个缓存对白,从新开始
  137. steps = 0
  138. nowDialogue = subparser.NewOneDialogue()
  139. }
  140. // 使用过滤后的列表
  141. for i, line := range filterLines {
  142. if steps == 0 {
  143. // 匹配对白的索引
  144. line = my_util.ReplaceSpecString(line, "")
  145. dialogueIndex, err := strconv.Atoi(line)
  146. if err != nil {
  147. newOneDialogueFun()
  148. continue
  149. }
  150. nowDialogue.Index = dialogueIndex
  151. // 继续
  152. steps = 1
  153. continue
  154. }
  155. if steps == 1 {
  156. // 匹配时间
  157. matched := regex_things.ReMatchDialogueTimeSRT.FindAllStringSubmatch(line, -1)
  158. if matched == nil || len(matched) < 1 {
  159. matched = regex_things.ReMatchDialogueTimeSRT2.FindAllStringSubmatch(line, -1)
  160. if matched == nil || len(matched) < 1 {
  161. newOneDialogueFun()
  162. continue
  163. }
  164. }
  165. nowDialogue.StartTime = matched[0][1]
  166. nowDialogue.EndTime = matched[0][2]
  167. // 是否到结尾
  168. if i+1 > len(filterLines)-1 {
  169. // 是尾部
  170. // 那么这一个对白就需要 add 到总列表中了
  171. dialogues = append(dialogues, nowDialogue)
  172. newOneDialogueFun()
  173. continue
  174. }
  175. // 如上面提到的,因为把特殊字符的行去除了,那么一个对话,如果只有 index 和 时间,也是需要添加进去的
  176. if p.needMatchNextContentLine(filterLines, i+1) == true {
  177. // 是,那么也认为当前这个对话完成了,需要 add 到总列表中了
  178. dialogues = append(dialogues, nowDialogue)
  179. newOneDialogueFun()
  180. continue
  181. }
  182. // 非上述特殊情况,继续
  183. steps = 2
  184. continue
  185. }
  186. if steps == 2 {
  187. // 在上述情况排除后,才继续
  188. // 匹配内容
  189. nowDialogue.Lines = append(nowDialogue.Lines, line)
  190. // 是否到结尾
  191. if i+1 > len(filterLines)-1 {
  192. // 是尾部
  193. // 那么这一个对白就需要 add 到总列表中了
  194. dialogues = append(dialogues, nowDialogue)
  195. newOneDialogueFun()
  196. continue
  197. }
  198. // 不是尾部,那么就需要往后看两句话,是否是下一个对白的头部(index 和 时间)
  199. if p.needMatchNextContentLine(filterLines, i+1) == true {
  200. // 是,那么也认为当前这个对话完成了,需要 add 到总列表中了
  201. dialogues = append(dialogues, nowDialogue)
  202. newOneDialogueFun()
  203. continue
  204. } else {
  205. // 如果还不是,那么就可能是这个对白有多行,有可能是同一种语言的多行,也可能是多语言的多行
  206. // 那么 step 应该不变继续是 2
  207. continue
  208. }
  209. }
  210. }
  211. return dialogues
  212. }
  213. // needMatchNextContentLine 是否需要继续匹配下一句话作为一个对白的对话内容
  214. func (p Parser) needMatchNextContentLine(lines []string, index int) bool {
  215. // 匹配到对白的 Index
  216. _, err := strconv.Atoi(lines[index])
  217. if err != nil {
  218. return false
  219. }
  220. // 匹配到字幕的时间
  221. matched := regex_things.ReMatchDialogueTimeSRT.FindAllStringSubmatch(lines[index+1], -1)
  222. if matched == nil || len(matched) < 1 {
  223. matched = regex_things.ReMatchDialogueTimeSRT2.FindAllStringSubmatch(lines[index+1], -1)
  224. if matched == nil || len(matched) < 1 {
  225. return false
  226. }
  227. }
  228. return true
  229. }