util.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. package my_util
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "crypto/sha1"
  6. "crypto/sha256"
  7. "crypto/tls"
  8. "encoding/binary"
  9. "encoding/hex"
  10. "fmt"
  11. "github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
  12. "github.com/allanpk716/ChineseSubFinder/internal/pkg/regex_things"
  13. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  14. "github.com/allanpk716/ChineseSubFinder/internal/types/common"
  15. browser "github.com/allanpk716/fake-useragent"
  16. "github.com/go-resty/resty/v2"
  17. "github.com/google/uuid"
  18. "github.com/sirupsen/logrus"
  19. "golang.org/x/net/context"
  20. "golang.org/x/net/proxy"
  21. "io"
  22. "math"
  23. "net"
  24. "net/http"
  25. "net/url"
  26. "os"
  27. "os/exec"
  28. "path"
  29. "path/filepath"
  30. "regexp"
  31. "runtime"
  32. "sort"
  33. "strconv"
  34. "strings"
  35. "time"
  36. )
  37. // NewHttpClient 新建一个 resty 的对象
  38. func NewHttpClient(_proxySettings ...*settings.ProxySettings) (*resty.Client, error) {
  39. //const defUserAgent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
  40. //const defUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.41"
  41. var proxySettings *settings.ProxySettings
  42. var HttpProxyAddress, socket5ProxyAddress, UserAgent, Referer string
  43. if len(_proxySettings) > 0 {
  44. proxySettings = _proxySettings[0]
  45. if proxySettings.UseHttpProxy == true && len(proxySettings.HttpProxyAddress) > 0 {
  46. HttpProxyAddress = proxySettings.HttpProxyAddress
  47. }
  48. if proxySettings.UseSocks5Proxy == true && len(proxySettings.Socks5ProxyAddress) > 0 {
  49. socket5ProxyAddress = proxySettings.Socks5ProxyAddress
  50. }
  51. }
  52. // ------------------------------------------------
  53. // 随机的 Browser
  54. UserAgent = browser.Random()
  55. // ------------------------------------------------
  56. httpClient := resty.New()
  57. httpClient.SetTimeout(common.HTMLTimeOut)
  58. httpClient.SetRetryCount(2)
  59. // ------------------------------------------------
  60. // 设置 Referer
  61. if len(_proxySettings) > 0 {
  62. if len(proxySettings.Referer) > 0 {
  63. Referer = proxySettings.Referer
  64. }
  65. if len(Referer) > 0 {
  66. httpClient.SetHeader("Referer", Referer)
  67. }
  68. }
  69. // ------------------------------------------------
  70. // 设置 Header
  71. httpClient.SetHeaders(map[string]string{
  72. "Content-Type": "application/json",
  73. "User-Agent": UserAgent,
  74. })
  75. // ------------------------------------------------
  76. // 不要求安全链接
  77. httpClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
  78. // ------------------------------------------------
  79. if len(_proxySettings) == 0 {
  80. // 无需设置代理
  81. return httpClient, nil
  82. }
  83. // ------------------------------------------------
  84. if proxySettings.UseSocks5OrHttpProxy == false {
  85. // http 代理
  86. if HttpProxyAddress != "" {
  87. httpClient.SetProxy(HttpProxyAddress)
  88. } else {
  89. httpClient.RemoveProxy()
  90. }
  91. } else {
  92. // socket5 代理
  93. dialer, err := proxy.SOCKS5("tcp", socket5ProxyAddress, nil, proxy.Direct)
  94. if err != nil {
  95. return nil, err
  96. }
  97. dialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
  98. return dialer.Dial(network, address)
  99. }
  100. transport := &http.Transport{DialContext: dialContext, DisableKeepAlives: true}
  101. httpClient.SetTransport(transport)
  102. }
  103. return httpClient, nil
  104. }
  105. func getPublicIP(inputSite string, _proxySettings ...*settings.ProxySettings) string {
  106. var client *resty.Client
  107. client, err := NewHttpClient(_proxySettings...)
  108. if err != nil {
  109. return ""
  110. }
  111. response, err := client.R().Get(inputSite)
  112. if err != nil {
  113. return ""
  114. }
  115. return response.String()
  116. }
  117. func GetPublicIP(log *logrus.Logger, queue *settings.TaskQueue, _proxySettings ...*settings.ProxySettings) string {
  118. defPublicIPSites := []string{
  119. "https://myip.biturl.top/",
  120. "https://ip4.seeip.org/",
  121. "https://ipecho.net/plain",
  122. "https://api-ipv4.ip.sb/ip",
  123. "https://api.ipify.org/",
  124. "http://myexternalip.com/raw",
  125. }
  126. customPublicIPSites := make([]string, 0)
  127. if queue.CheckPublicIPTargetSite != "" {
  128. // 自定义了公网IP查询网站
  129. tSites := strings.Split(queue.CheckPublicIPTargetSite, ";")
  130. if tSites != nil && len(tSites) > 0 {
  131. customPublicIPSites = append(customPublicIPSites, tSites...)
  132. }
  133. } else {
  134. customPublicIPSites = append(customPublicIPSites, defPublicIPSites...)
  135. }
  136. for i, publicIPSite := range customPublicIPSites {
  137. log.Debugln("[GetPublicIP]", i, publicIPSite)
  138. publicIP := getPublicIP(publicIPSite, _proxySettings...)
  139. matcheds := regex_things.ReMatchIP.FindAllString(publicIP, -1)
  140. if publicIP != "" || matcheds == nil || len(matcheds) == 0 {
  141. return publicIP
  142. }
  143. }
  144. return ""
  145. }
  146. // DownFile 从指定的 url 下载文件
  147. func DownFile(l *logrus.Logger, urlStr string, _proxySettings ...*settings.ProxySettings) ([]byte, string, error) {
  148. var err error
  149. var httpClient *resty.Client
  150. httpClient, err = NewHttpClient(_proxySettings...)
  151. if err != nil {
  152. return nil, "", err
  153. }
  154. resp, err := httpClient.R().Get(urlStr)
  155. if err != nil {
  156. return nil, "", err
  157. }
  158. filename := GetFileName(l, resp.RawResponse)
  159. if filename == "" {
  160. l.Warningln("DownFile.GetFileName is string.empty", urlStr)
  161. }
  162. return resp.Body(), filename, nil
  163. }
  164. // GetFileName 获取下载文件的文件名
  165. func GetFileName(l *logrus.Logger, resp *http.Response) string {
  166. contentDisposition := resp.Header.Get("Content-Disposition")
  167. if len(contentDisposition) == 0 {
  168. m := regexp.MustCompile(`^(.*/)?(?:$|(.+?)(?:(\.[^.]*$)|$))`).FindStringSubmatch(resp.Request.URL.String())
  169. if m == nil || len(m) < 4 {
  170. l.Warningln("GetFileName.regexp.MustCompile.FindStringSubmatch", resp.Request.URL.String())
  171. return ""
  172. }
  173. return m[2] + m[3]
  174. }
  175. re := regexp.MustCompile(`filename=["]*([^"]+)["]*`)
  176. matched := re.FindStringSubmatch(contentDisposition)
  177. if matched == nil || len(matched) == 0 || len(matched[0]) == 0 {
  178. l.Errorln("GetFileName.Content-Disposition", contentDisposition)
  179. return ""
  180. }
  181. return matched[1]
  182. }
  183. // AddBaseUrl 判断传入的 url 是否需要拼接 baseUrl
  184. func AddBaseUrl(baseUrl, url string) string {
  185. if strings.Contains(url, "://") {
  186. return url
  187. }
  188. return fmt.Sprintf("%s%s", baseUrl, url)
  189. }
  190. // IsDir 存在且是文件夹
  191. func IsDir(path string) bool {
  192. s, err := os.Stat(path)
  193. if err != nil {
  194. return false
  195. }
  196. return s.IsDir()
  197. }
  198. // IsFile 存在且是文件
  199. func IsFile(filePath string) bool {
  200. s, err := os.Stat(filePath)
  201. if err != nil {
  202. return false
  203. }
  204. return !s.IsDir()
  205. }
  206. // VideoNameSearchKeywordMaker 拼接视频搜索的 title 和 年份
  207. func VideoNameSearchKeywordMaker(l *logrus.Logger, title string, year string) string {
  208. iYear, err := strconv.Atoi(year)
  209. if err != nil {
  210. // 允许的错误
  211. l.Errorln("VideoNameSearchKeywordMaker", "year to int", err)
  212. iYear = 0
  213. }
  214. searchKeyword := title
  215. if iYear >= 2020 {
  216. searchKeyword = searchKeyword + " " + year
  217. }
  218. return searchKeyword
  219. }
  220. // SearchMatchedVideoFileFromDirs 搜索符合后缀名的视频文件
  221. func SearchMatchedVideoFileFromDirs(l *logrus.Logger, dirs []string) ([]string, error) {
  222. defer func() {
  223. l.Debugln("SearchMatchedVideoFileFromDirs End ----------------")
  224. }()
  225. l.Debugln("SearchMatchedVideoFileFromDirs Start ----------------")
  226. var fileFullPathList = make([]string, 0)
  227. for _, dir := range dirs {
  228. matchedVideoFile, err := SearchMatchedVideoFile(l, dir)
  229. if err != nil {
  230. return nil, err
  231. }
  232. fileFullPathList = append(fileFullPathList, matchedVideoFile...)
  233. }
  234. // 排序,从最新的到最早的
  235. SortByModTime(fileFullPathList)
  236. for _, s := range fileFullPathList {
  237. l.Debugln(s)
  238. }
  239. return fileFullPathList, nil
  240. }
  241. // SearchMatchedVideoFile 搜索符合后缀名的视频文件,现在也会把 BDMV 的文件搜索出来,但是这个并不是一个视频文件,需要在后续特殊处理
  242. func SearchMatchedVideoFile(l *logrus.Logger, dir string) ([]string, error) {
  243. var fileFullPathList = make([]string, 0)
  244. pathSep := string(os.PathSeparator)
  245. files, err := os.ReadDir(dir)
  246. if err != nil {
  247. return nil, err
  248. }
  249. for _, curFile := range files {
  250. fullPath := dir + pathSep + curFile.Name()
  251. if curFile.IsDir() {
  252. // 内层的错误就无视了
  253. oneList, _ := SearchMatchedVideoFile(l, fullPath)
  254. if oneList != nil {
  255. fileFullPathList = append(fileFullPathList, oneList...)
  256. }
  257. } else {
  258. // 这里就是文件了
  259. bok, fakeBDMVVideoFile := FileNameIsBDMV(fullPath)
  260. if bok == true {
  261. // 这类文件后续的扫描字幕操作需要额外的处理
  262. fileFullPathList = append(fileFullPathList, fakeBDMVVideoFile)
  263. continue
  264. }
  265. if IsWantedVideoExtDef(curFile.Name()) == false {
  266. // 不是期望的视频后缀名则跳过
  267. continue
  268. } else {
  269. // 这里还有一种情况,就是蓝光, BDMV 下面会有一个 STREAM 文件夹,里面很多 m2ts 的视频组成
  270. if filepath.Base(filepath.Dir(fullPath)) == "STREAM" {
  271. l.Debugln("SearchMatchedVideoFile, Skip BDMV.STREAM:", fullPath)
  272. continue
  273. }
  274. // 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
  275. fi, err := curFile.Info()
  276. if err != nil {
  277. l.Debugln("SearchMatchedVideoFile, file.Info:", fullPath, err)
  278. continue
  279. }
  280. if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
  281. l.Debugln("SearchMatchedVideoFile file.Size() == 4096 && Prefix Name == ._*", fullPath)
  282. continue
  283. }
  284. fileFullPathList = append(fileFullPathList, fullPath)
  285. }
  286. }
  287. }
  288. return fileFullPathList, nil
  289. }
  290. func GetFileModTime(fileFPath string) time.Time {
  291. if IsFile(fileFPath) == true {
  292. // 存在
  293. fi, err := os.Stat(fileFPath)
  294. if err != nil {
  295. return time.Time{}
  296. }
  297. return fi.ModTime()
  298. } else {
  299. // 不存在才需要考虑蓝光情况
  300. bok, idBDMVFPath, _ := decode.IsFakeBDMVWorked(fileFPath)
  301. if bok == false {
  302. // 也不是蓝光
  303. return time.Time{}
  304. }
  305. // 获取这个蓝光 ID BDMV 文件的时间
  306. fInfo, err := os.Stat(idBDMVFPath)
  307. if err != nil {
  308. return time.Time{}
  309. }
  310. return fInfo.ModTime()
  311. }
  312. }
  313. // SortByModTime 根据文件的 Mod Time 进行排序,递减
  314. func SortByModTime(fileList []string) []string {
  315. byModTime := make(ByModTime, 0)
  316. byModTime = append(byModTime, fileList...)
  317. sort.Sort(sort.Reverse(byModTime))
  318. return byModTime
  319. }
  320. type ByModTime []string
  321. func (fis ByModTime) Len() int {
  322. return len(fis)
  323. }
  324. func (fis ByModTime) Swap(i, j int) {
  325. fis[i], fis[j] = fis[j], fis[i]
  326. }
  327. func (fis ByModTime) Less(i, j int) bool {
  328. aModTime := GetFileModTime(fis[i])
  329. bModTime := GetFileModTime(fis[j])
  330. return aModTime.Before(bModTime)
  331. }
  332. // FileNameIsBDMV 是否是 BDMV 蓝光目录,符合返回 true,以及 fakseVideoFPath
  333. func FileNameIsBDMV(id_bdmv_fileFPath string) (bool, string) {
  334. /*
  335. 这类蓝光视频比较特殊,它没有具体的一个后缀名的视频文件而是由两个文件夹来存储视频数据
  336. * BDMV
  337. * CERTIFICATE
  338. 但是不管如何,都需要使用一个文件作为锚点,就选定 CERTIFICATE 中的 id.bdmv 文件
  339. 后续的下载逻辑也需要单独为这个文件进行处理,比如,从这个文件向上一层获取 nfo 文件,
  340. 以及再上一层得到视频文件夹名称等
  341. */
  342. if strings.ToLower(filepath.Base(id_bdmv_fileFPath)) == common.FileBDMV {
  343. // 这个文件是确认了,那么就需要查看这个文件父级目录是不是 CERTIFICATE 文件夹
  344. // 且 CERTIFICATE 需要和 BDMV 文件夹都存在
  345. CERDir := filepath.Dir(id_bdmv_fileFPath)
  346. BDMVDir := filepath.Join(filepath.Dir(CERDir), "BDMV")
  347. if IsDir(CERDir) == true && IsDir(BDMVDir) == true {
  348. return true, filepath.Join(filepath.Dir(CERDir), filepath.Base(filepath.Dir(CERDir))+common.VideoExtMp4)
  349. }
  350. }
  351. return false, ""
  352. }
  353. func SearchTVNfo(l *logrus.Logger, dir string) ([]string, error) {
  354. var fileFullPathList = make([]string, 0)
  355. pathSep := string(os.PathSeparator)
  356. files, err := os.ReadDir(dir)
  357. if err != nil {
  358. return nil, err
  359. }
  360. for _, curFile := range files {
  361. fullPath := dir + pathSep + curFile.Name()
  362. if curFile.IsDir() {
  363. // 内层的错误就无视了
  364. oneList, _ := SearchTVNfo(l, fullPath)
  365. if oneList != nil {
  366. fileFullPathList = append(fileFullPathList, oneList...)
  367. }
  368. } else {
  369. // 这里就是文件了
  370. if strings.ToLower(curFile.Name()) != decode.MetadateTVNfo {
  371. continue
  372. } else {
  373. // 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
  374. fi, err := curFile.Info()
  375. if err != nil {
  376. l.Debugln("SearchTVNfo, file.Info:", fullPath, err)
  377. continue
  378. }
  379. if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
  380. l.Debugln("SearchTVNfo file.Size() == 4096 && Prefix Name == ._*", fullPath)
  381. continue
  382. }
  383. fileFullPathList = append(fileFullPathList, fullPath)
  384. }
  385. }
  386. }
  387. return fileFullPathList, nil
  388. }
  389. // IsWantedVideoExtDef 后缀名是否符合规则
  390. func IsWantedVideoExtDef(fileName string) bool {
  391. if len(_wantedExtMap) < 1 {
  392. _defExtMap[common.VideoExtMp4] = common.VideoExtMp4
  393. _defExtMap[common.VideoExtMkv] = common.VideoExtMkv
  394. _defExtMap[common.VideoExtRmvb] = common.VideoExtRmvb
  395. _defExtMap[common.VideoExtIso] = common.VideoExtIso
  396. _defExtMap[common.VideoExtM2ts] = common.VideoExtM2ts
  397. _wantedExtMap[common.VideoExtMp4] = common.VideoExtMp4
  398. _wantedExtMap[common.VideoExtMkv] = common.VideoExtMkv
  399. _wantedExtMap[common.VideoExtRmvb] = common.VideoExtRmvb
  400. _wantedExtMap[common.VideoExtIso] = common.VideoExtIso
  401. _wantedExtMap[common.VideoExtM2ts] = common.VideoExtM2ts
  402. for _, videoExt := range _customVideoExts {
  403. _wantedExtMap[videoExt] = videoExt
  404. }
  405. }
  406. fileExt := strings.ToLower(filepath.Ext(fileName))
  407. _, bFound := _wantedExtMap[fileExt]
  408. return bFound
  409. }
  410. func GetEpisodeKeyName(season, eps int) string {
  411. return "S" + strconv.Itoa(season) + "E" + strconv.Itoa(eps)
  412. }
  413. // CopyFile copies a single file from src to dst
  414. func CopyFile(src, dst string) error {
  415. var err error
  416. var srcFd *os.File
  417. var dstFd *os.File
  418. var srcInfo os.FileInfo
  419. if srcFd, err = os.Open(src); err != nil {
  420. return err
  421. }
  422. defer func() {
  423. _ = srcFd.Close()
  424. }()
  425. if dstFd, err = os.Create(dst); err != nil {
  426. return err
  427. }
  428. defer func() {
  429. _ = dstFd.Close()
  430. }()
  431. if _, err = io.Copy(dstFd, srcFd); err != nil {
  432. return err
  433. }
  434. if srcInfo, err = os.Stat(src); err != nil {
  435. return err
  436. }
  437. return os.Chmod(dst, srcInfo.Mode())
  438. }
  439. // CopyDir copies a whole directory recursively
  440. func CopyDir(src string, dst string) error {
  441. var err error
  442. var fds []os.DirEntry
  443. var srcInfo os.FileInfo
  444. if srcInfo, err = os.Stat(src); err != nil {
  445. return err
  446. }
  447. if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
  448. return err
  449. }
  450. if fds, err = os.ReadDir(src); err != nil {
  451. return err
  452. }
  453. for _, fd := range fds {
  454. srcfp := filepath.Join(src, fd.Name())
  455. dstfp := filepath.Join(dst, fd.Name())
  456. if fd.IsDir() {
  457. if err = CopyDir(srcfp, dstfp); err != nil {
  458. fmt.Println(err)
  459. }
  460. } else {
  461. if err = CopyFile(srcfp, dstfp); err != nil {
  462. fmt.Println(err)
  463. }
  464. }
  465. }
  466. return nil
  467. }
  468. // CloseChrome 强行结束没有关闭的 Chrome 进程
  469. func CloseChrome(l *logrus.Logger) {
  470. cmdString := ""
  471. var command *exec.Cmd
  472. sysType := runtime.GOOS
  473. if sysType == "linux" {
  474. // LINUX系统
  475. cmdString = "pkill chrome"
  476. command = exec.Command("/bin/sh", "-c", cmdString)
  477. }
  478. if sysType == "windows" {
  479. // windows系统
  480. cmdString = "taskkill /F /im chrome.exe"
  481. command = exec.Command("cmd.exe", "/c", cmdString)
  482. }
  483. if sysType == "darwin" {
  484. // macOS
  485. // https://stackoverflow.com/questions/57079120/using-exec-command-in-golang-how-do-i-open-a-new-terminal-and-execute-a-command
  486. cmdString = `tell application "/Applications/Google Chrome.app" to quit`
  487. command = exec.Command("osascript", "-s", "h", "-e", cmdString)
  488. }
  489. if cmdString == "" || command == nil {
  490. l.Errorln("CloseChrome OS:", sysType)
  491. return
  492. }
  493. err := command.Run()
  494. if err != nil {
  495. l.Warningln("CloseChrome", err)
  496. }
  497. }
  498. // OSCheck 强制的系统支持检查
  499. func OSCheck() bool {
  500. sysType := runtime.GOOS
  501. if sysType == "linux" {
  502. return true
  503. }
  504. if sysType == "windows" {
  505. return true
  506. }
  507. if sysType == "darwin" {
  508. return true
  509. }
  510. return false
  511. }
  512. // FixWindowPathBackSlash 修复 Windows 反斜杠的梗
  513. func FixWindowPathBackSlash(path string) string {
  514. return strings.Replace(path, string(filepath.Separator), "/", -1)
  515. }
  516. func WriteStrings2File(desfilePath string, strings []string) error {
  517. dstFile, err := os.Create(desfilePath)
  518. if err != nil {
  519. return err
  520. }
  521. defer func() {
  522. _ = dstFile.Close()
  523. }()
  524. allString := ""
  525. for _, s := range strings {
  526. allString += s + "\r\n"
  527. }
  528. _, err = dstFile.WriteString(allString)
  529. if err != nil {
  530. return err
  531. }
  532. return nil
  533. }
  534. func TimeNumber2Time(inputTimeNumber float64) time.Time {
  535. newTime := time.Time{}.Add(time.Duration(inputTimeNumber * math.Pow10(9)))
  536. return newTime
  537. }
  538. func Time2SecondNumber(inTime time.Time) float64 {
  539. outSecond := 0.0
  540. outSecond += float64(inTime.Hour() * 60 * 60)
  541. outSecond += float64(inTime.Minute() * 60)
  542. outSecond += float64(inTime.Second())
  543. outSecond += float64(inTime.Nanosecond()) / 1000 / 1000 / 1000
  544. return outSecond
  545. }
  546. func Time2Duration(inTime time.Time) time.Duration {
  547. return time.Duration(Time2SecondNumber(inTime) * math.Pow10(9))
  548. }
  549. func Second2Time(sec int64) time.Time {
  550. return time.Unix(sec, 0)
  551. }
  552. // ReplaceSpecString 替换特殊的字符
  553. func ReplaceSpecString(inString string, rep string) string {
  554. return regex_things.RegMatchSpString.ReplaceAllString(inString, rep)
  555. }
  556. func Bool2Int(inBool bool) int {
  557. if inBool == true {
  558. return 1
  559. } else {
  560. return 0
  561. }
  562. }
  563. // Round 取整
  564. func Round(x float64) int64 {
  565. if x-float64(int64(x)) > 0 {
  566. return int64(x) + 1
  567. } else {
  568. return int64(x)
  569. }
  570. //return int64(math.Floor(x + 0.5))
  571. }
  572. // MakePowerOfTwo 2的整次幂数 buffer length is not a power of two
  573. func MakePowerOfTwo(x int64) int64 {
  574. power := math.Log2(float64(x))
  575. tmpRound := Round(power)
  576. return int64(math.Pow(2, float64(tmpRound)))
  577. }
  578. // MakeCeil10msMultipleFromFloat 将传入的秒,规整到 10ms 的倍数,返回依然是 秒,向上取整
  579. func MakeCeil10msMultipleFromFloat(input float64) float64 {
  580. const bb = 100
  581. // 先转到 10 ms 单位,比如传入是 1.912 - > 191.2
  582. t10ms := input * bb
  583. // 191.2 - > 192.0
  584. newT10ms := math.Ceil(t10ms)
  585. // 转换回来
  586. return newT10ms / bb
  587. }
  588. // MakeFloor10msMultipleFromFloat 将传入的秒,规整到 10ms 的倍数,返回依然是 秒,向下取整
  589. func MakeFloor10msMultipleFromFloat(input float64) float64 {
  590. const bb = 100
  591. // 先转到 10 ms 单位,比如传入是 1.912 - > 191.2
  592. t10ms := input * bb
  593. // 191.2 - > 191.0
  594. newT10ms := math.Floor(t10ms)
  595. // 转换回来
  596. return newT10ms / bb
  597. }
  598. // MakeCeil10msMultipleFromTime 向上取整,规整到 10ms 的倍数
  599. func MakeCeil10msMultipleFromTime(input time.Time) time.Time {
  600. nowTime := MakeCeil10msMultipleFromFloat(Time2SecondNumber(input))
  601. newTime := time.Time{}.Add(time.Duration(nowTime * math.Pow10(9)))
  602. return newTime
  603. }
  604. // MakeFloor10msMultipleFromTime 向下取整,规整到 10ms 的倍数
  605. func MakeFloor10msMultipleFromTime(input time.Time) time.Time {
  606. nowTime := MakeFloor10msMultipleFromFloat(Time2SecondNumber(input))
  607. newTime := time.Time{}.Add(time.Duration(nowTime * math.Pow10(9)))
  608. return newTime
  609. }
  610. // Time2SubTimeString 时间转字幕格式的时间字符串
  611. func Time2SubTimeString(inTime time.Time, timeFormat string) string {
  612. /*
  613. 这里进行时间转字符串的时候有一点比较特殊
  614. 正常来说输出的格式是类似 15:04:05.00
  615. 那么有个问题,字幕的时间格式是 0:00:12.00, 小时,是个数,除非有跨度到 20 小时的视频,不然小时就应该是个数
  616. 这就需要一个额外的函数去处理这些情况
  617. */
  618. outTimeString := inTime.Format(timeFormat)
  619. if inTime.Hour() > 9 {
  620. // 小时,两位数
  621. return outTimeString
  622. } else {
  623. // 小时,一位数
  624. items := strings.SplitN(outTimeString, ":", -1)
  625. if len(items) == 3 {
  626. outTimeString = strings.Replace(outTimeString, items[0], fmt.Sprintf("%d", inTime.Hour()), 1)
  627. return outTimeString
  628. }
  629. return outTimeString
  630. }
  631. }
  632. // IsEqual 比较 float64
  633. func IsEqual(f1, f2 float64) bool {
  634. const MIN = 0.000001
  635. if f1 > f2 {
  636. return math.Dim(f1, f2) < MIN
  637. } else {
  638. return math.Dim(f2, f1) < MIN
  639. }
  640. }
  641. // ParseTime 解析字幕时间字符串,这里可能小数点后面有 2-4 位
  642. func ParseTime(inTime string) (time.Time, error) {
  643. parseTime, err := time.Parse(common.TimeFormatPoint2, inTime)
  644. if err != nil {
  645. parseTime, err = time.Parse(common.TimeFormatPoint3, inTime)
  646. if err != nil {
  647. parseTime, err = time.Parse(common.TimeFormatPoint4, inTime)
  648. }
  649. }
  650. return parseTime, err
  651. }
  652. // GetFileSHA1 获取文件的 SHA1 值
  653. func GetFileSHA1(srcFileFPath string) (string, error) {
  654. infile, err := os.Open(srcFileFPath)
  655. if err != nil {
  656. return "", err
  657. }
  658. defer func() {
  659. _ = infile.Close()
  660. }()
  661. h := sha1.New()
  662. _, err = io.Copy(h, infile)
  663. if err != nil {
  664. return "", err
  665. }
  666. return hex.EncodeToString(h.Sum(nil)), nil
  667. }
  668. // WriteFile 写文件
  669. func WriteFile(desFileFPath string, bytes []byte) error {
  670. var err error
  671. nowDesPath := desFileFPath
  672. if filepath.IsAbs(nowDesPath) == false {
  673. nowDesPath, err = filepath.Abs(nowDesPath)
  674. if err != nil {
  675. return err
  676. }
  677. }
  678. // 创建对应的目录
  679. nowDirPath := filepath.Dir(nowDesPath)
  680. err = os.MkdirAll(nowDirPath, os.ModePerm)
  681. if err != nil {
  682. return err
  683. }
  684. file, err := os.Create(nowDesPath)
  685. if err != nil {
  686. return err
  687. }
  688. defer func() {
  689. _ = file.Close()
  690. }()
  691. _, err = file.Write(bytes)
  692. if err != nil {
  693. return err
  694. }
  695. return nil
  696. }
  697. // GetNowTimeString 获取当前的时间,没有秒
  698. func GetNowTimeString() (string, int, int, int) {
  699. nowTime := time.Now()
  700. addString := fmt.Sprintf("%d-%d-%d", nowTime.Hour(), nowTime.Minute(), nowTime.Nanosecond())
  701. return addString, nowTime.Hour(), nowTime.Minute(), nowTime.Nanosecond()
  702. }
  703. // GenerateAccessToken 生成随机的 AccessToken
  704. func GenerateAccessToken() string {
  705. u4 := uuid.New()
  706. return u4.String()
  707. }
  708. func Get2UUID() string {
  709. u4 := uuid.New()
  710. u5 := uuid.New()
  711. return u4.String() + u5.String()
  712. }
  713. func UrlJoin(hostUrl, subUrl string) (string, error) {
  714. u, err := url.Parse(hostUrl)
  715. if err != nil {
  716. return "", err
  717. }
  718. u.Path = path.Join(u.Path, subUrl)
  719. return u.String(), nil
  720. }
  721. // GetFileSHA1String 获取文件的 SHA1 字符串
  722. func GetFileSHA1String(fileFPath string) (string, error) {
  723. h := sha1.New()
  724. fp, err := os.Open(fileFPath)
  725. if err != nil {
  726. return "", err
  727. }
  728. defer func() {
  729. _ = fp.Close()
  730. }()
  731. partAll, err := io.ReadAll(fp)
  732. if err != nil {
  733. return "", err
  734. }
  735. h.Write(partAll)
  736. hashBytes := h.Sum(nil)
  737. return fmt.Sprintf("%x", md5.Sum(hashBytes)), nil
  738. }
  739. // GetFileSHA256String 获取文件的 SHA256 字符串
  740. func GetFileSHA256String(fileFPath string) (string, error) {
  741. fp, err := os.Open(fileFPath)
  742. if err != nil {
  743. return "", err
  744. }
  745. defer func() {
  746. _ = fp.Close()
  747. }()
  748. partAll, err := io.ReadAll(fp)
  749. if err != nil {
  750. return "", err
  751. }
  752. return fmt.Sprintf("%x", sha256.Sum256(partAll)), nil
  753. }
  754. func GetRestOfDaySec() time.Duration {
  755. nowTime := time.Now()
  756. todayLast := nowTime.Format("2006-01-02") + " 23:59:59"
  757. todayLastTime, _ := time.ParseInLocation("2006-01-02 15:04:05", todayLast, time.Local)
  758. // 今天剩余的时间(s)
  759. restOfDaySec := time.Duration(todayLastTime.Unix()-time.Now().Local().Unix()) * time.Second
  760. return restOfDaySec
  761. }
  762. // IntToBytes 整形转换成字节
  763. func IntToBytes(n int) ([]byte, error) {
  764. x := int32(n)
  765. bytesBuffer := bytes.NewBuffer([]byte{})
  766. err := binary.Write(bytesBuffer, binary.BigEndian, x)
  767. if err != nil {
  768. return nil, err
  769. }
  770. return bytesBuffer.Bytes(), nil
  771. }
  772. // BytesToInt 字节转换成整形
  773. func BytesToInt(b []byte) (int, error) {
  774. bytesBuffer := bytes.NewBuffer(b)
  775. var x int32
  776. err := binary.Read(bytesBuffer, binary.BigEndian, &x)
  777. if err != nil {
  778. return 0, err
  779. }
  780. return int(x), nil
  781. }
  782. var (
  783. _wantedExtMap = make(map[string]string) // 人工确认的需要监控的视频后缀名
  784. _defExtMap = make(map[string]string) // 内置支持的视频后缀名列表
  785. _customVideoExts = make([]string, 0) // 用户额外自定义的视频后缀名列表
  786. )