transcoder.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package transcoder
  2. /*
  3. Transcoder.go
  4. This module handle real-time transcoding of media files
  5. that is not supported by playing on web.
  6. */
  7. import (
  8. "io"
  9. "log"
  10. "net/http"
  11. "os/exec"
  12. )
  13. type TranscodeOutputResolution string
  14. const (
  15. TranscodeResolution_360p TranscodeOutputResolution = "360p"
  16. TranscodeResolution_720p TranscodeOutputResolution = "720p"
  17. TranscodeResolution_1080p TranscodeOutputResolution = "1280p"
  18. TranscodeResolution_original TranscodeOutputResolution = ""
  19. )
  20. // Transcode and stream the given file. Make sure ffmpeg is installed before calling to transcoder.
  21. func TranscodeAndStream(w http.ResponseWriter, r *http.Request, inputFile string, resolution TranscodeOutputResolution) {
  22. // Build the FFmpeg command based on the resolution parameter
  23. var cmd *exec.Cmd
  24. switch resolution {
  25. case "360p":
  26. cmd = exec.Command("ffmpeg", "-i", inputFile, "-vf", "scale=-1:360", "-f", "mp4", "-vcodec", "libx264", "-preset", "fast", "-movflags", "frag_keyframe+empty_moov", "pipe:1")
  27. case "720p":
  28. cmd = exec.Command("ffmpeg", "-i", inputFile, "-vf", "scale=-1:720", "-f", "mp4", "-vcodec", "libx264", "-preset", "fast", "-movflags", "frag_keyframe+empty_moov", "pipe:1")
  29. case "1080p":
  30. cmd = exec.Command("ffmpeg", "-i", inputFile, "-vf", "scale=-1:1080", "-f", "mp4", "-vcodec", "libx264", "-preset", "fast", "-movflags", "frag_keyframe+empty_moov", "pipe:1")
  31. case "":
  32. // Original resolution
  33. cmd = exec.Command("ffmpeg", "-i", inputFile, "-f", "mp4", "-vcodec", "libx264", "-preset", "fast", "-movflags", "frag_keyframe+empty_moov", "pipe:1")
  34. default:
  35. http.Error(w, "Invalid resolution parameter", http.StatusBadRequest)
  36. return
  37. }
  38. // Set response headers for streaming MP4 video
  39. w.Header().Set("Content-Type", "video/mp4")
  40. w.Header().Set("Transfer-Encoding", "chunked")
  41. w.Header().Set("Cache-Control", "no-cache")
  42. // Get the command output pipe
  43. stdout, err := cmd.StdoutPipe()
  44. if err != nil {
  45. http.Error(w, "Failed to create output pipe", http.StatusInternalServerError)
  46. return
  47. }
  48. // Get the command error pipe to capture standard error
  49. stderr, err := cmd.StderrPipe()
  50. if err != nil {
  51. http.Error(w, "Failed to create error pipe", http.StatusInternalServerError)
  52. log.Printf("Failed to create error pipe: %v", err)
  53. return
  54. }
  55. // Start the command
  56. if err := cmd.Start(); err != nil {
  57. http.Error(w, "Failed to start FFmpeg", http.StatusInternalServerError)
  58. return
  59. }
  60. // Create a channel to signal when the client disconnects
  61. done := make(chan struct{})
  62. // Monitor client connection close
  63. go func() {
  64. <-r.Context().Done()
  65. cmd.Process.Kill() // Kill the FFmpeg process when client disconnects
  66. close(done)
  67. }()
  68. // Copy the command output to the HTTP response in a separate goroutine
  69. go func() {
  70. if _, err := io.Copy(w, stdout); err != nil {
  71. // End of video or client disconnected
  72. cmd.Process.Kill()
  73. return
  74. }
  75. }()
  76. // Read and log the command standard error
  77. go func() {
  78. errOutput, _ := io.ReadAll(stderr)
  79. if len(errOutput) > 0 {
  80. log.Printf("FFmpeg error output: %s", string(errOutput))
  81. }
  82. }()
  83. go func() {
  84. if err := cmd.Wait(); err != nil {
  85. log.Printf("FFmpeg process failed: %v", err)
  86. return
  87. }
  88. }()
  89. // Wait for the command to finish or client disconnect
  90. <-done
  91. log.Println("[Media Server] Transcode client disconnected")
  92. }