2020-05-30 04:08:33 +03:00
package main
import (
"fmt"
2020-06-15 07:17:01 +03:00
"math"
2020-05-30 04:08:33 +03:00
"os"
"os/exec"
2020-06-02 02:53:31 +03:00
"path"
2020-06-13 00:14:05 +03:00
"strconv"
2020-06-09 11:52:15 +03:00
"strings"
2020-06-18 02:29:47 +03:00
log "github.com/sirupsen/logrus"
2020-05-30 04:08:33 +03:00
)
2020-06-16 02:27:58 +03:00
func showStreamOfflineState ( configuration Config ) {
fmt . Println ( "----- Stream offline! Showing offline state!" )
2020-06-02 02:53:31 +03:00
var outputDir = configuration . PublicHLSPath
2020-06-09 11:52:15 +03:00
var variantPlaylistPath = configuration . PublicHLSPath
2020-05-30 04:08:33 +03:00
2020-06-03 11:34:05 +03:00
if configuration . IPFS . Enabled || configuration . S3 . Enabled {
2020-06-02 02:53:31 +03:00
outputDir = configuration . PrivateHLSPath
2020-06-09 11:52:15 +03:00
variantPlaylistPath = configuration . PrivateHLSPath
2020-06-02 02:53:31 +03:00
}
2020-06-01 23:28:14 +03:00
2020-06-09 11:52:15 +03:00
outputDir = path . Join ( outputDir , "%v" )
var variantPlaylistName = path . Join ( variantPlaylistPath , "%v" , "stream.m3u8" )
2020-06-16 02:27:58 +03:00
var videoMaps = make ( [ ] string , 0 )
var streamMaps = make ( [ ] string , 0 )
var videoMapsString = ""
var streamMappingString = ""
if configuration . VideoSettings . EnablePassthrough || len ( configuration . VideoSettings . StreamQualities ) == 0 {
fmt . Println ( "Enabling passthrough video" )
streamMaps = append ( streamMaps , fmt . Sprintf ( "v:%d" , 0 ) )
} else {
for index , quality := range configuration . VideoSettings . StreamQualities {
maxRate := math . Floor ( float64 ( quality . Bitrate ) * 0.8 )
videoMaps = append ( videoMaps , fmt . Sprintf ( "-map v:0 -c:v:%d libx264 -b:v:%d %dk -maxrate %dk -bufsize %dk" , index , index , int ( quality . Bitrate ) , int ( maxRate ) , int ( maxRate ) ) )
streamMaps = append ( streamMaps , fmt . Sprintf ( "v:%d" , index ) )
videoMapsString = strings . Join ( videoMaps , " " )
}
}
framerate := 25
streamMappingString = "-var_stream_map \"" + strings . Join ( streamMaps , " " ) + "\""
ffmpegFlags := [ ] string {
"-hide_banner" ,
2020-06-18 01:02:50 +03:00
// "-stream_loop 100",
// "-fflags", "+genpts",
2020-06-16 02:27:58 +03:00
"-i" , configuration . VideoSettings . OfflineImage ,
2020-06-19 03:47:44 +03:00
"-i" , "webroot/thumbnail.jpg" ,
2020-06-18 01:02:50 +03:00
"-filter_complex" , "\"[0:v]scale=2640:2360[bg];[bg][1:v]overlay=200:250:enable='between(t,0,3)'\"" ,
2020-06-16 02:27:58 +03:00
videoMapsString , // All the different video variants
"-f hls" ,
2020-06-18 01:02:50 +03:00
// "-hls_list_size " + strconv.Itoa(configuration.Files.MaxNumberInPlaylist),
"-hls_time 4" , // + strconv.Itoa(configuration.VideoSettings.ChunkLengthInSeconds),
2020-06-16 02:27:58 +03:00
"-hls_playlist_type" , "event" ,
"-master_pl_name" , "stream.m3u8" ,
2020-06-18 01:02:50 +03:00
"-strftime 1" ,
2020-06-16 02:27:58 +03:00
"-use_localtime 1" ,
2020-06-18 01:02:50 +03:00
"-hls_flags temp_file" ,
2020-06-16 02:27:58 +03:00
"-tune" , "zerolatency" ,
"-g " + strconv . Itoa ( framerate * 2 ) , " -keyint_min " + strconv . Itoa ( framerate * 2 ) , // multiply your output frame rate * 2. For example, if your input is -framerate 30, then use -g 60
2020-06-18 01:02:50 +03:00
"-framerate " + strconv . Itoa ( framerate ) ,
2020-06-16 02:27:58 +03:00
"-preset " + configuration . VideoSettings . EncoderPreset ,
"-sc_threshold 0" , // don't create key frames on scene change - only according to -g
2020-06-18 01:02:50 +03:00
"-profile:v" , "main" , // Main – for standard definition (SD) to 640× 480, High – for high definition (HD) to 1920× 1080
// "-movflags +faststart",
2020-06-16 02:27:58 +03:00
"-pix_fmt yuv420p" ,
streamMappingString ,
2020-06-16 02:38:11 +03:00
"-hls_segment_filename " + path . Join ( outputDir , "offline-%s.ts" ) ,
2020-06-18 01:02:50 +03:00
// "-s", "720x480", // size
2020-06-16 02:27:58 +03:00
variantPlaylistName ,
}
ffmpegFlagsString := strings . Join ( ffmpegFlags , " " )
ffmpegCmd := configuration . FFMpegPath + " " + ffmpegFlagsString
2020-06-19 01:31:51 +03:00
// fmt.Println(ffmpegCmd)
2020-06-16 02:27:58 +03:00
_ , err := exec . Command ( "sh" , "-c" , ffmpegCmd ) . Output ( )
fmt . Println ( err )
verifyError ( err )
}
func startFfmpeg ( configuration Config ) {
var outputDir = configuration . PublicHLSPath
var variantPlaylistPath = configuration . PublicHLSPath
if configuration . IPFS . Enabled || configuration . S3 . Enabled {
outputDir = configuration . PrivateHLSPath
variantPlaylistPath = configuration . PrivateHLSPath
}
outputDir = path . Join ( outputDir , "%v" )
var variantPlaylistName = path . Join ( variantPlaylistPath , "%v" , "stream.m3u8" )
2020-06-09 11:52:15 +03:00
log . Printf ( "Starting transcoder saving to /%s." , variantPlaylistName )
2020-06-02 03:42:36 +03:00
pipePath := getTempPipePath ( )
2020-06-01 23:28:14 +03:00
2020-06-09 11:52:15 +03:00
var videoMaps = make ( [ ] string , 0 )
var streamMaps = make ( [ ] string , 0 )
var audioMaps = make ( [ ] string , 0 )
2020-06-12 22:55:50 +03:00
var videoMapsString = ""
var audioMapsString = ""
var streamMappingString = ""
2020-06-19 07:57:02 +03:00
var profileString = ""
2020-06-12 22:55:50 +03:00
if configuration . VideoSettings . EnablePassthrough || len ( configuration . VideoSettings . StreamQualities ) == 0 {
fmt . Println ( "Enabling passthrough video" )
streamMaps = append ( streamMaps , fmt . Sprintf ( "v:%d,a:%d" , 0 , 0 ) )
2020-06-19 07:57:02 +03:00
videoMaps = append ( videoMaps , "-map v:0 -c:v copy" )
videoMapsString = strings . Join ( videoMaps , " " )
audioMaps = append ( audioMaps , "-map a:0" )
audioMapsString = strings . Join ( audioMaps , " " ) + " -c:a copy" // Pass through audio for all the variants, don't reencode
2020-06-12 22:55:50 +03:00
} else {
for index , quality := range configuration . VideoSettings . StreamQualities {
2020-06-15 07:17:01 +03:00
maxRate := math . Floor ( float64 ( quality . Bitrate ) * 0.8 )
videoMaps = append ( videoMaps , fmt . Sprintf ( "-map v:0 -c:v:%d libx264 -b:v:%d %dk -maxrate %dk -bufsize %dk" , index , index , int ( quality . Bitrate ) , int ( maxRate ) , int ( maxRate ) ) )
2020-06-12 22:55:50 +03:00
streamMaps = append ( streamMaps , fmt . Sprintf ( "v:%d,a:%d" , index , index ) )
videoMapsString = strings . Join ( videoMaps , " " )
audioMaps = append ( audioMaps , "-map a:0" )
audioMapsString = strings . Join ( audioMaps , " " ) + " -c:a copy" // Pass through audio for all the variants, don't reencode
2020-06-19 07:57:02 +03:00
profileString = "-profile:v high" // Main – for standard definition (SD) to 640× 480, High – for high definition (HD) to 1920× 1080
2020-06-12 22:55:50 +03:00
}
2020-06-09 11:52:15 +03:00
}
2020-06-15 08:26:31 +03:00
framerate := 25
2020-06-15 07:17:01 +03:00
2020-06-12 22:55:50 +03:00
streamMappingString = "-var_stream_map \"" + strings . Join ( streamMaps , " " ) + "\""
2020-06-09 11:52:15 +03:00
ffmpegFlags := [ ] string {
"-hide_banner" ,
2020-06-18 01:02:50 +03:00
// "-re",
"-fflags" , "+genpts" ,
2020-06-09 11:52:15 +03:00
"-i pipe:" ,
2020-06-12 03:34:20 +03:00
// "-vf scale=900:-2", // Re-enable in the future with a config to togging resizing?
// "-sws_flags fast_bilinear",
2020-06-12 22:55:50 +03:00
videoMapsString , // All the different video variants
audioMapsString ,
2020-06-09 11:52:15 +03:00
"-master_pl_name stream.m3u8" ,
2020-06-15 07:17:01 +03:00
"-framerate " + strconv . Itoa ( framerate ) ,
"-g " + strconv . Itoa ( framerate * 2 ) , " -keyint_min " + strconv . Itoa ( framerate * 2 ) , // multiply your output frame rate * 2. For example, if your input is -framerate 30, then use -g 60
2020-06-14 04:00:26 +03:00
// "-r 25",
2020-06-09 23:15:44 +03:00
"-preset " + configuration . VideoSettings . EncoderPreset ,
2020-06-12 03:34:20 +03:00
"-sc_threshold 0" , // don't create key frames on scene change - only according to -g
2020-06-19 07:57:02 +03:00
profileString ,
2020-06-14 04:00:26 +03:00
"-movflags +faststart" ,
"-pix_fmt yuv420p" ,
2020-06-09 11:52:15 +03:00
"-f hls" ,
2020-06-13 01:02:02 +03:00
"-hls_list_size " + strconv . Itoa ( configuration . Files . MaxNumberInPlaylist ) ,
2020-06-13 00:14:05 +03:00
"-hls_time " + strconv . Itoa ( configuration . VideoSettings . ChunkLengthInSeconds ) ,
2020-06-09 11:52:15 +03:00
"-strftime 1" ,
"-use_localtime 1" ,
"-hls_playlist_type event" ,
"-hls_segment_filename " + path . Join ( outputDir , "stream-%Y%m%d-%s.ts" ) ,
"-hls_flags delete_segments+program_date_time+temp_file" ,
2020-06-09 12:56:23 +03:00
"-tune zerolatency" ,
2020-06-18 01:02:50 +03:00
// "-s", "720x480", // size
2020-06-09 12:56:23 +03:00
2020-06-12 22:55:50 +03:00
streamMappingString ,
2020-06-09 11:52:15 +03:00
variantPlaylistName ,
}
ffmpegFlagsString := strings . Join ( ffmpegFlags , " " )
ffmpegCmd := "cat " + pipePath + " | " + configuration . FFMpegPath + " " + ffmpegFlagsString
2020-06-17 05:08:34 +03:00
// fmt.Println(ffmpegCmd)
2020-06-01 23:28:14 +03:00
2020-06-10 04:28:07 +03:00
_ , err := exec . Command ( "sh" , "-c" , ffmpegCmd ) . Output ( )
2020-06-09 11:52:15 +03:00
fmt . Println ( err )
2020-05-30 04:08:33 +03:00
verifyError ( err )
}
func writePlaylist ( data string , filePath string ) {
f , err := os . Create ( filePath )
2020-06-01 22:15:07 +03:00
defer f . Close ( )
2020-05-30 04:08:33 +03:00
if err != nil {
fmt . Println ( err )
return
}
_ , err = f . WriteString ( data )
if err != nil {
fmt . Println ( err )
return
}
}