Compare commits

...
Author SHA1 Message Date
Claude 0cd3f81cb8 Merged codec and encoder separation with conflict resolution for fMP4 test expectations. 2026-04-04 19:05:19 -04:00
Claude 5841b64fd4 Merged move to fMP4 segments with associated fixes. 2026-04-04 19:00:09 -04:00
LogalDeveloper 764e41f2b7 Fixed thumbnail generator using wrong init segment filename for multi-variant streams.
Automated API tests / test (push) Failing after 6s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 15:49:45 -04:00
LogalDeveloper 84cd880753 Fixed offline master playlist not being processed by the storage provider.
Automated API tests / test (push) Failing after 6s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 14:22:25 -04:00
LogalDeveloper 20c52a51ae Replaced per-request map allocations with a switch statement in cache duration function.
Automated API tests / test (push) Failing after 5s
Browser Tests / cypress-run (push) Failing after 4s
Lint / Go linter (push) Failing after 3s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 13:39:52 -04:00
LogalDeveloper 6b93ac0d78 Fixed segment cleanup deleting init.mp4 and reduced its cache duration to 20 seconds.
Automated API tests / test (push) Failing after 5s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 09:52:15 -04:00
LogalDeveloper e0673b084b Fixed linter errors in offline state and cache duration functions. 2026-04-02 09:21:55 -04:00
LogalDeveloper f0aa545d32 Fixed offline video state to use fMP4 segments instead of launching ffmpeg with the old TS file. 2026-04-02 09:11:00 -04:00
Gabe Kangas 9c290fa0d7 feat(video): move from ts to fMP4 segments 2026-03-24 11:14:36 -07:00
24 changed files with 222 additions and 108 deletions
+41 -10
View File
@@ -1,6 +1,7 @@
package core
import (
"fmt"
"os"
"path"
"path/filepath"
@@ -98,23 +99,53 @@ func createInitialOfflineState() error {
// offline video stream state only. No live stream HLS segments will continue to be
// referenced.
func transitionToOfflineVideoStreamContent() {
log.Traceln("Firing transcoder with offline stream state")
log.Traceln("Placing offline fMP4 content into HLS directories")
_transcoder := transcoder.NewTranscoder()
_transcoder.SetIdentifier("offline")
_transcoder.SetLatencyLevel(models.GetLatencyLevel(4))
_transcoder.SetIsEvent(true)
configRepository := configrepository.Get()
offlineFilePath, err := saveOfflineClipToDisk("offline-v2.ts")
offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk()
if err != nil {
log.Fatalln("unable to save offline clip:", err)
log.Fatalln("unable to save offline fMP4 files:", err)
}
_transcoder.SetInput(offlineFilePath)
go _transcoder.Start(false)
variants := configRepository.GetStreamOutputVariants()
if len(variants) == 0 {
// Ensure at least one variant directory exists.
variants = make([]models.StreamOutputVariant, 1)
}
for index := range variants {
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
if err := os.MkdirAll(variantDir, 0o750); err != nil {
log.Errorln("unable to create variant directory:", err)
continue
}
makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath)
}
// Write the master playlist that points to each variant's playlist.
masterPlaylistPath := filepath.Join(config.HLSStoragePath, "stream.m3u8")
masterFile, err := os.Create(masterPlaylistPath) //nolint:gosec
if err != nil {
log.Errorln("unable to create master playlist:", err)
} else {
_, _ = masterFile.WriteString("#EXTM3U\n")
_, _ = masterFile.WriteString("#EXT-X-VERSION:7\n")
_, _ = masterFile.WriteString("#EXT-X-INDEPENDENT-SEGMENTS\n")
for index := range variants {
_, _ = fmt.Fprintf(masterFile, "#EXT-X-STREAM-INF:BANDWIDTH=0\n")
_, _ = fmt.Fprintf(masterFile, "%d/stream.m3u8\n", index)
}
if err := masterFile.Close(); err != nil {
log.Errorln("unable to close master playlist:", err)
}
// Notify the storage provider so it can rewrite variant URLs
// to absolute paths for S3 or remote serving endpoints.
_storage.MasterPlaylistWritten(masterPlaylistPath)
}
// Copy the logo to be the thumbnail
configRepository := configrepository.Get()
logo := configRepository.GetLogoPath()
dst := filepath.Join(config.TempDir, "thumbnail.jpg")
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
+61 -33
View File
@@ -5,14 +5,13 @@ import (
"os"
"path/filepath"
"github.com/grafov/m3u8"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/static"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
)
func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFilename string, segmentFilename string) {
existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec
if err != nil {
log.Debugln("unable to read existing playlist file", err)
@@ -34,9 +33,10 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
// Manually append the offline clip to the end of the media playlist.
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-DISCONTINUITY\n")
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
// If "offline" content gets changed then change the duration below
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n")
_, _ = atomicWriteTmpPlaylistFile.WriteString("offline-v2.ts\n")
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "%s\n", segmentFilename)
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n")
if err := atomicWriteTmpPlaylistFile.Close(); err != nil {
@@ -48,61 +48,89 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
}
}
func makeVariantIndexOffline(index int, offlineFilePath string, offlineFilename string) {
playlistFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/stream.m3u8"), index)
segmentFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/%s"), index, offlineFilename)
func makeVariantIndexOffline(index int, offlineInitPath string, offlineSegmentPath string) {
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
playlistFilePath := filepath.Join(variantDir, "stream.m3u8")
if err := utils.Copy(offlineFilePath, segmentFilePath); err != nil {
initDest := filepath.Join(variantDir, "offline-init.mp4")
segmentDest := filepath.Join(variantDir, "offline-v2.m4s")
if err := utils.Copy(offlineInitPath, initDest); err != nil {
log.Warnln(err)
return
}
if _, err := _storage.Save(initDest, 0); err != nil {
log.Warnln(err)
}
if _, err := _storage.Save(segmentFilePath, 0); err != nil {
if err := utils.Copy(offlineSegmentPath, segmentDest); err != nil {
log.Warnln(err)
return
}
if _, err := _storage.Save(segmentDest, 0); err != nil {
log.Warnln(err)
}
if utils.DoesFileExists(playlistFilePath) {
appendOfflineToVariantPlaylist(index, playlistFilePath)
appendOfflineToVariantPlaylist(index, playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
} else {
createEmptyOfflinePlaylist(playlistFilePath, offlineFilename)
createEmptyOfflinePlaylist(playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
}
if _, err := _storage.Save(playlistFilePath, 0); err != nil {
log.Warnln(err)
}
}
func createEmptyOfflinePlaylist(playlistFilePath string, offlineFilename string) {
p, err := m3u8.NewMediaPlaylist(1, 1)
if err != nil {
log.Errorln(err)
}
// If "offline" content gets changed then change the duration below
if err := p.Append(offlineFilename, 8.0, ""); err != nil {
log.Errorln(err)
}
p.Close()
func createEmptyOfflinePlaylist(playlistFilePath string, initFilename string, segmentFilename string) {
f, err := os.Create(playlistFilePath) //nolint:gosec
if err != nil {
log.Errorln(err)
return
}
defer f.Close()
if _, err := f.Write(p.Encode().Bytes()); err != nil {
log.Errorln(err)
}
_, _ = f.WriteString("#EXTM3U\n")
_, _ = f.WriteString("#EXT-X-VERSION:7\n")
_, _ = f.WriteString("#EXT-X-TARGETDURATION:8\n")
_, _ = fmt.Fprintf(f, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
// If "offline" content gets changed then change the duration below
_, _ = f.WriteString("#EXTINF:8.000000,\n")
_, _ = fmt.Fprintf(f, "%s\n", segmentFilename)
_, _ = f.WriteString("#EXT-X-ENDLIST\n")
}
func saveOfflineClipToDisk(offlineFilename string) (string, error) {
offlineFileData := static.GetOfflineSegment()
offlineTmpFile, err := os.CreateTemp(config.TempDir, offlineFilename)
func saveOfflineFMP4ToDisk() (initPath string, segmentPath string, err error) {
initData := static.GetOfflineInitSegment()
initTmp, err := os.CreateTemp(config.TempDir, "offline-init-*.mp4")
if err != nil {
log.Errorln("unable to create temp file for offline video segment", err)
return "", "", fmt.Errorf("unable to create temp file for offline init segment: %s", err)
}
if _, err = offlineTmpFile.Write(offlineFileData); err != nil {
return "", fmt.Errorf("unable to write offline segment to disk: %s", err)
if _, err = initTmp.Write(initData); err != nil {
return "", "", fmt.Errorf("unable to write offline init segment to disk: %s", err)
}
offlineFilePath := offlineTmpFile.Name()
return filepath.Abs(offlineFilePath)
if err := initTmp.Close(); err != nil {
return "", "", fmt.Errorf("unable to close offline init segment: %s", err)
}
initPath, _ = filepath.Abs(initTmp.Name())
segData := static.GetOfflineMediaSegment()
segTmp, err := os.CreateTemp(config.TempDir, "offline-v2-*.m4s")
if err != nil {
return "", "", fmt.Errorf("unable to create temp file for offline media segment: %s", err)
}
if _, err = segTmp.Write(segData); err != nil {
return "", "", fmt.Errorf("unable to write offline media segment to disk: %s", err)
}
if err := segTmp.Close(); err != nil {
return "", "", fmt.Errorf("unable to close offline media segment: %s", err)
}
segmentPath, _ = filepath.Abs(segTmp.Name())
return initPath, segmentPath, nil
}
+1 -1
View File
@@ -82,7 +82,7 @@ func getAllFilesRecursive(baseDirectory string) (map[string][]os.FileInfo, error
directory = info.Name()
}
if filepath.Ext(info.Name()) == ".ts" {
if filepath.Ext(info.Name()) == ".m4s" {
files[directory] = append(files[directory], info)
}
+1 -1
View File
@@ -317,7 +317,7 @@ func (s *S3Storage) retrieveAllVideoSegments() ([]s3object, error) {
err := s.s3Client.ListObjectsPages(allObjectsListRequest,
func(page *s3.ListObjectsOutput, lastPage bool) bool {
for _, item := range page.Contents {
if strings.HasSuffix(*item.Key, ".ts") {
if strings.HasSuffix(*item.Key, ".m4s") {
allObjects = append(allObjects, s3object{
key: *item.Key,
lastModified: *item.LastModified,
+2 -4
View File
@@ -96,9 +96,7 @@ func SetStreamAsDisconnected() {
_stats.LastConnectTime = nil
_broadcaster = nil
offlineFilename := "offline-v2.ts"
offlineFilePath, err := saveOfflineClipToDisk(offlineFilename)
offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk()
if err != nil {
log.Errorln(err)
return
@@ -122,7 +120,7 @@ func SetStreamAsDisconnected() {
}
for index := range _currentBroadcast.OutputSettings {
makeVariantIndexOffline(index, offlineFilePath, offlineFilename)
makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath)
}
StartOfflineCleanupTimer()
+4 -1
View File
@@ -18,6 +18,7 @@ type FileWriterReceiverServiceCallback interface {
SegmentWritten(localFilePath string)
VariantPlaylistWritten(localFilePath string)
MasterPlaylistWritten(localFilePath string)
InitSegmentWritten(localFilePath string)
}
// FileWriterReceiverService accepts transcoder responses via HTTP and fires the callbacks.
@@ -81,8 +82,10 @@ func (s *FileWriterReceiverService) uploadHandler(w http.ResponseWriter, r *http
func (s *FileWriterReceiverService) fileWritten(path string) {
if utils.GetRelativePathFromAbsolutePath(path) == "hls/stream.m3u8" {
s.callbacks.MasterPlaylistWritten(path)
} else if strings.HasSuffix(path, ".ts") {
} else if strings.HasSuffix(path, ".m4s") {
s.callbacks.SegmentWritten(path)
} else if strings.HasSuffix(path, "init.mp4") {
s.callbacks.InitSegmentWritten(path)
} else if strings.HasSuffix(path, ".m3u8") {
s.callbacks.VariantPlaylistWritten(path)
}
+5
View File
@@ -23,3 +23,8 @@ func (h *HLSHandler) VariantPlaylistWritten(localFilePath string) {
func (h *HLSHandler) MasterPlaylistWritten(localFilePath string) {
h.Storage.MasterPlaylistWritten(localFilePath)
}
// InitSegmentWritten is fired when an fMP4 init segment is written to disk.
func (h *HLSHandler) InitSegmentWritten(localFilePath string) {
h.Storage.SegmentWritten(localFilePath)
}
+18 -3
View File
@@ -1,6 +1,7 @@
package transcoder
import (
"fmt"
"os"
"os/exec"
"path"
@@ -64,7 +65,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
var modTime time.Time
var names []string
for _, f := range files {
if path.Ext(f.Name()) != ".ts" {
if path.Ext(f.Name()) != ".m4s" {
continue
}
@@ -88,7 +89,21 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
return nil
}
configRepository := configrepository.Get()
// With multiple variants ffmpeg names init segments init_0.mp4, init_1.mp4, etc.
// With a single variant it uses init.mp4.
initSegment := path.Join(framePath, fmt.Sprintf("init_%d.mp4", variantIndex))
if !utils.DoesFileExists(initSegment) {
initSegment = path.Join(framePath, "init.mp4")
}
mostRecentFile := path.Join(framePath, names[0])
// The segment may have been cleaned up between ReadDir and now.
if !utils.DoesFileExists(mostRecentFile) {
return nil
}
// fMP4 segments require the init segment to be readable by ffmpeg
inputArg := fmt.Sprintf("concat:%s|%s", initSegment, mostRecentFile)
ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg")
@@ -96,7 +111,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
"-y", // Overwrite file
"-threads", "1", // Low priority processing
"-t", "1", // Pull from frame 1
"-i", mostRecentFile, // Input
"-i", inputArg, // Input (init segment + media segment)
"-f", "image2", // format
"-vframes", "1", // Single frame
outputFileTemp,
@@ -111,7 +126,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
log.Errorln(err)
}
makeAnimatedGifPreview(mostRecentFile, previewGifFile)
makeAnimatedGifPreview(inputArg, previewGifFile)
return nil
}
+4 -2
View File
@@ -248,7 +248,9 @@ func (t *Transcoder) getFlags() *execInfo {
ffmpegFlags = append(ffmpegFlags, hlsOptionsString...)
ffmpegFlags = append(ffmpegFlags, hlsEventString...)
ffmpegFlags = append(ffmpegFlags, []string{
"-segment_format_options", "mpegts_flags=mpegts_copyts=1",
"-hls_segment_type", "fmp4",
"-hls_fmp4_init_filename", "init.mp4",
"-bsf:a", "aac_adtstoasc", // Convert AAC ADTS headers to ASC for fMP4 container compatibility
}...)
ffmpegFlags = append(ffmpegFlags, t.encoder.ExtraArguments()...)
@@ -259,7 +261,7 @@ func (t *Transcoder) getFlags() *execInfo {
// Filenames
"-master_pl_name", "stream.m3u8",
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.ts", // Send HLS segments back to us over HTTP
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.m4s", // Send HLS segments back to us over HTTP
"-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0
"-method", "PUT", // HLS results sent back to us will be over PUTs
+1 -1
View File
@@ -47,7 +47,7 @@ func TestFFmpegNvencCommand(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -47,7 +47,7 @@ func TestFFmpegOmxCommand(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_omx -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_omx -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -47,7 +47,7 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -init_hw_device qsv=hw -filter_hw_device hw -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_qsv -pix_fmt:v:0 qsv -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=qsv -preset medium -map v:0 -c:v:1 h264_qsv -pix_fmt:v:1 qsv -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=qsv -preset veryslow -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset veryfast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -init_hw_device qsv=hw -filter_hw_device hw -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_qsv -pix_fmt:v:0 qsv -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=qsv -preset medium -map v:0 -c:v:1 h264_qsv -pix_fmt:v:1 qsv -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=qsv -preset veryslow -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset veryfast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -47,7 +47,7 @@ func TestFFmpegVaapiCommand(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -pix_fmt:v:0 vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=vaapi -preset veryfast -map v:0 -c:v:1 h264_vaapi -pix_fmt:v:1 vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=vaapi -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -pix_fmt:v:0 vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=vaapi -preset veryfast -map v:0 -c:v:1 h264_vaapi -pix_fmt:v:1 vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=vaapi -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
@@ -47,7 +47,7 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -pix_fmt:v:0 nv12 -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -pix_fmt:v:1 nv12 -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -pix_fmt:v:0 nv12 -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -pix_fmt:v:1 nv12 -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -47,7 +47,7 @@ func TestFFmpegx264Command(t *testing.T) {
cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log"
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 libx264 -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -x264-params:v:0 scenecut=0:open_gop=0 -bufsize:v:0 1088k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -x264-params:v:1 scenecut=0:open_gop=0 -bufsize:v:1 3572k -profile:v:1 high -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 libx264 -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -x264-params:v:0 scenecut=0:open_gop=0 -bufsize:v:0 1088k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -x264-params:v:1 scenecut=0:open_gop=0 -bufsize:v:1 3572k -profile:v:1 high -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+9 -5
View File
@@ -27,14 +27,14 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/schollz/sqlite3dump v1.3.1
github.com/shirou/gopsutil/v4 v4.25.8
github.com/shirou/gopsutil/v4 v4.26.2
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-emoji v1.0.6
golang.org/x/crypto v0.49.0
golang.org/x/mod v0.33.0
golang.org/x/mod v0.34.0
golang.org/x/net v0.51.0
golang.org/x/time v0.15.0
gopkg.in/evanphx/json-patch.v5 v5.9.11
@@ -48,7 +48,8 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -59,6 +60,7 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/lestrrat-go/strftime v1.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -66,14 +68,16 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+18 -17
View File
@@ -24,10 +24,10 @@ github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
@@ -87,8 +87,8 @@ github.com/lestrrat-go/strftime v1.1.1 h1:zgf8QCsgj27GlKBy3SU9/8MMgegZ8UCzlCyHYr
github.com/lestrrat-go/strftime v1.1.1/go.mod h1:YDrzHJAODYQ+xxvrn5SG01uFIQAeDTzpxNVppCz7Nmw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -138,12 +138,12 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -161,10 +161,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
@@ -194,8 +194,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -274,8 +274,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v5 v5.9.11 h1:OMPeiLomOQwe8+Ku4nwXsdOmrRw2vGUpP3XgLj3ojNw=
gopkg.in/evanphx/json-patch.v5 v5.9.11/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+13 -5
View File
@@ -51,12 +51,20 @@ func GetWebIndexTemplate() (*template.Template, error) {
return tmpl, err
}
//go:embed offline-v2.ts
var offlineVideoSegment []byte
//go:embed offline-init.mp4
var offlineInitSegment []byte
// GetOfflineSegment will return the offline video segment data.
func GetOfflineSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-v2.ts", offlineVideoSegment)
// GetOfflineInitSegment will return the fMP4 init segment for the offline clip.
func GetOfflineInitSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-init.mp4", offlineInitSegment)
}
//go:embed offline-v2.m4s
var offlineMediaSegment []byte
// GetOfflineMediaSegment will return the fMP4 media segment for the offline clip.
func GetOfflineMediaSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-v2.m4s", offlineMediaSegment)
}
//go:embed img/logo.png
+20 -4
View File
@@ -11,24 +11,40 @@ function install_ffmpeg() {
fi
FFMPEG_VER="8.0"
FFMPEG_BUILD_VERSION="20251223233512"
FFMPEG_BUILD_VERSION="20260223192056"
FFMPEG_PATH="$(pwd)"
PATH=$FFMPEG_PATH:$PATH
# Determine the platform-specific archive name
ARCH="$(uname -m)"
if [[ "$OSTYPE" == "darwin"* ]]; then
if [[ "$ARCH" == "arm64" ]]; then
FFMPEG_ARCHIVE="ffmpeg${FFMPEG_VER}-darwin-arm64.tar.gz"
else
FFMPEG_ARCHIVE="ffmpeg${FFMPEG_VER}-darwin-amd64.tar.gz"
fi
else
if [[ "$ARCH" == "aarch64" ]]; then
FFMPEG_ARCHIVE="ffmpeg${FFMPEG_VER}-linux-arm64-static.tar.gz"
else
FFMPEG_ARCHIVE="ffmpeg${FFMPEG_VER}-linux-amd64-static.tar.gz"
fi
fi
if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
if [[ "$ffmpeg_version" == "${FFMPEG_VER}.1" ]]; then
return 0
else
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
fi
fi
echo "Downloading ffmpeg v${FFMPEG_VER} release ${FFMPEG_BUILD_VERSION}"
echo "Downloading ffmpeg v${FFMPEG_VER} release ${FFMPEG_BUILD_VERSION} (${FFMPEG_ARCHIVE})"
rm -rf ffmpeg.tar.gz
curl -sL --fail "https://github.com/owncast/ffmpeg-builds/releases/download/${FFMPEG_BUILD_VERSION}/ffmpeg${FFMPEG_VER}-amd64-static.tar.gz" --output ffmpeg.tar.gz >/dev/null
curl -sL --fail "https://github.com/owncast/ffmpeg-builds/releases/download/${FFMPEG_BUILD_VERSION}/${FFMPEG_ARCHIVE}" --output ffmpeg.tar.gz >/dev/null
tar -xzf ffmpeg.tar.gz
rm -f ffmpeg.tar.gz
chmod +x ffmpeg
+5 -5
View File
@@ -9,8 +9,8 @@
# Example: ./test/ocTestStream.sh ~/Downloads/*.mp4 rtmp://127.0.0.1/live/abc123
ffmpeg_execs=( 'ffmpeg' 'ffmpeg.exe' )
ffmpeg_paths=( './' '../' '' )
ffmpeg_execs=( 'ffmpeg' )
ffmpeg_paths=( '' './' )
for _ffmpeg_exec in "${ffmpeg_execs[@]}"; do
for _ffmpeg_path in "${ffmpeg_paths[@]}"; do
@@ -43,14 +43,14 @@ if [[ -z "$ffmpeg_exec" ]]; then
else
ffmpeg_version=$("$ffmpeg_exec" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
echo "ffmpeg executable: $ffmpeg_exec ($ffmpeg_version)"
echo "ffmpeg path: $(readlink -f "$(which "$ffmpeg_exec")")"
echo "ffmpeg path: $(readlink -f "$(which "$ffmpeg_exec")")"
fi
if [[ ${FILE_COUNT} -eq 0 ]]; then
echo "Streaming internal test video loop to $DESTINATION_HOST"
echo "...press ctrl+c to exit"
command "${ffmpeg_exec}" -hide_banner -loglevel panic -nostdin -re -f lavfi \
command "${ffmpeg_exec}" -hide_banner -loglevel warning -nostdin -re -f lavfi \
-i "testsrc=size=1280x720:rate=60[out0];sine=frequency=400:sample_rate=48000[out1]" \
-vf "[in]drawtext=fontsize=96: box=1: boxcolor=black@0.75: boxborderw=5: fontcolor=white: x=(w-text_w)/2: y=((h-text_h)/2)+((h-text_h)/-2): text='Owncast Test Stream', drawtext=fontsize=96: box=1: boxcolor=black@0.75: boxborderw=5: fontcolor=white: x=(w-text_w)/2: y=((h-text_h)/2)+((h-text_h)/2): text='%{gmtime\:%H\\\\\:%M\\\\\:%S} UTC'[out]" \
-nal-hrd cbr \
@@ -91,7 +91,7 @@ else
echo "ERROR: File not found: $file"
exit 1
fi
done
function finish {
+12 -10
View File
@@ -215,23 +215,25 @@ func GetCacheDurationSecondsForPath(filePath string) int {
defaultDaysCached := 30
if filename == "thumbnail.jpg" || filename == "preview.gif" {
if filename == "thumbnail.jpg" || filename == "preview.gif" || filename == "init.mp4" {
// Thumbnails & preview gif re-generate during live
return 20
} else if fileExtension == ".js" || fileExtension == ".css" {
// Cache javascript & CSS
return 60 * 60 * 24 * defaultDaysCached
} else if fileExtension == ".ts" || fileExtension == ".woff2" {
}
if filename == "/" || fileExtension == "" {
return 0
}
switch fileExtension {
case ".m3u8", ".html":
return 0
case ".m4s", ".mp4", ".woff2":
// Cache video segments as long as you want. They can't change.
// This matters most for local hosting of segments for recordings
// and not for live or 3rd party storage.
return 31557600
} else if fileExtension == ".m3u8" {
return 0
} else if fileExtension == ".jpg" || fileExtension == ".png" || fileExtension == ".gif" || fileExtension == ".svg" {
case ".js", ".css", ".jpg", ".png", ".gif", ".svg":
return 60 * 60 * 24 * defaultDaysCached
} else if fileExtension == ".html" || filename == "/" || fileExtension == "" {
return 0
}
// Default cache length in seconds
+2 -1
View File
@@ -18,7 +18,8 @@ import (
// HandleHLSRequest will manage all requests to HLS content.
func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
// Sanity check to limit requests to HLS file types.
if filepath.Ext(r.URL.Path) != ".m3u8" && filepath.Ext(r.URL.Path) != ".ts" {
ext := filepath.Ext(r.URL.Path)
if ext != ".m3u8" && ext != ".m4s" && ext != ".mp4" {
w.WriteHeader(http.StatusNotFound)
return
}