center.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package hls_center
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "github.com/ChineseSubFinder/ChineseSubFinder/pkg"
  7. "github.com/ChineseSubFinder/ChineseSubFinder/pkg/ffmpeg_helper"
  8. "github.com/ChineseSubFinder/ChineseSubFinder/pkg/hls_center/worker"
  9. "github.com/sirupsen/logrus"
  10. "io"
  11. "path/filepath"
  12. "text/template"
  13. )
  14. type Center struct {
  15. logger *logrus.Logger
  16. ffmpegHelper *ffmpeg_helper.FFMPEGHelper
  17. encodeWorker *worker.WorkerServer
  18. }
  19. func NewCenter(logger *logrus.Logger) *Center {
  20. cacheRootDir, err := pkg.GetVideoAndSubPreviewCacheFolder()
  21. if err != nil {
  22. panic(err)
  23. }
  24. encodeWorker := worker.NewWorkerServer(worker.WorkerServerConf{
  25. NumWorkers: 2,
  26. CacheDir: filepath.Join(cacheRootDir, "segments"),
  27. Worker: worker.NewCommandWorker("ffmpeg"),
  28. })
  29. return &Center{
  30. logger: logger,
  31. ffmpegHelper: ffmpeg_helper.NewFFMPEGHelper(logger),
  32. encodeWorker: encodeWorker,
  33. }
  34. }
  35. // WritePlaylist 构建 m3u8 文件
  36. func (c *Center) WritePlaylist(urlTemplate string, videoFileFPath string, w io.Writer) error {
  37. t := template.Must(template.New("urlTemplate").Parse(urlTemplate))
  38. if pkg.IsFile(videoFileFPath) == false {
  39. return errors.New("WritePlaylist video file not exist or it's blu-ray, not support yet, file = " + videoFileFPath)
  40. }
  41. duration := c.ffmpegHelper.GetVideoDuration(videoFileFPath)
  42. getUrl := func(segmentIndex int) string {
  43. buf := new(bytes.Buffer)
  44. t.Execute(buf, struct {
  45. Resolution int64
  46. Segment int
  47. }{
  48. 720,
  49. segmentIndex,
  50. })
  51. return buf.String()
  52. }
  53. fmt.Fprint(w, "#EXTM3U\n")
  54. fmt.Fprint(w, "#EXT-X-VERSION:3\n")
  55. fmt.Fprint(w, "#EXT-X-MEDIA-SEQUENCE:0\n")
  56. fmt.Fprint(w, "#EXT-X-ALLOW-CACHE:YES\n")
  57. fmt.Fprint(w, "#EXT-X-TARGETDURATION:"+fmt.Sprintf("%.f", hlsSegmentLength)+"\n")
  58. fmt.Fprint(w, "#EXT-X-PLAYLIST-TYPE:VOD\n")
  59. leftover := duration
  60. segmentIndex := 0
  61. for leftover > 0 {
  62. if leftover > hlsSegmentLength {
  63. fmt.Fprintf(w, "#EXTINF: %f,\n", hlsSegmentLength)
  64. } else {
  65. fmt.Fprintf(w, "#EXTINF: %f,\n", leftover)
  66. }
  67. fmt.Fprintf(w, getUrl(segmentIndex)+"\n")
  68. segmentIndex++
  69. leftover = leftover - hlsSegmentLength
  70. }
  71. fmt.Fprint(w, "#EXT-X-ENDLIST\n")
  72. return nil
  73. }
  74. // WriteSegment 构建 ts 文件
  75. func (c *Center) WriteSegment(videoFileFPath string, segmentIndex int64, resolution int64, w io.Writer) error {
  76. if pkg.IsFile(videoFileFPath) == false {
  77. return errors.New("WriteSegment video file not exist, file = " + videoFileFPath)
  78. }
  79. args := encodingArgs(videoFileFPath, segmentIndex, resolution)
  80. return c.encodeWorker.Serve(args, w)
  81. }
  82. func encodingArgs(videoFile string, segment int64, resolution int64) []string {
  83. startTime := segment * hlsSegmentLength
  84. // see http://superuser.com/questions/908280/what-is-the-correct-way-to-fix-keyframes-in-ffmpeg-for-dash
  85. return []string{
  86. // Prevent encoding to run longer than 30 seonds
  87. "-timelimit", "45",
  88. // TODO: Some stuff to investigate
  89. // "-probesize", "524288",
  90. // "-fpsprobesize", "10",
  91. // "-analyzeduration", "2147483647",
  92. // "-hwaccel:0", "vda",
  93. // The start time
  94. // important: needs to be before -i to do input seeking
  95. "-ss", fmt.Sprintf("%v.00", startTime),
  96. // The source file
  97. "-i", videoFile,
  98. // Put all streams to output
  99. // "-map", "0",
  100. // The duration
  101. "-t", fmt.Sprintf("%v.00", hlsSegmentLength),
  102. // TODO: Find out what it does
  103. //"-strict", "-2",
  104. // Synchronize audio
  105. "-async", "1",
  106. // 720p
  107. "-vf", fmt.Sprintf("scale=-2:%v", resolution),
  108. // x264 video codec
  109. "-vcodec", "libx264",
  110. // x264 preset
  111. "-preset", "veryfast",
  112. // aac audio codec
  113. "-c:a", "aac",
  114. "-b:a", "128k",
  115. "-ac", "2",
  116. // TODO
  117. "-pix_fmt", "yuv420p",
  118. //"-r", "25", // fixed framerate
  119. "-force_key_frames", "expr:gte(t,n_forced*5.000)",
  120. //"-force_key_frames", "00:00:00.00",
  121. //"-x264opts", "keyint=25:min-keyint=25:scenecut=-1",
  122. //"-f", "mpegts",
  123. "-f", "ssegment",
  124. "-segment_time", fmt.Sprintf("%v.00", hlsSegmentLength),
  125. "-initial_offset", fmt.Sprintf("%v.00", startTime),
  126. "pipe:out%03d.ts",
  127. }
  128. }
  129. const hlsSegmentLength = 5.0 // Seconds