Compare commits

..
26 Commits
Author SHA1 Message Date
Owncast f1da0fc30c Bundle embedded web app 2026-04-11 20:47:29 +00:00
renovate[bot]andGitHub 1160fc3e9f chore(deps): update dependency msw-storybook-addon to v2.0.7 2026-04-11 16:57:23 +00:00
Owncast 5c5f678523 Bundle embedded web app 2026-04-11 16:55:56 +00:00
renovate[bot]andGitHub 92da6edd93 chore(deps): update dependency msw to v2.13.2 2026-04-11 12:54:16 +00:00
Owncast cb73028e51 Bundle embedded web app 2026-04-11 09:28:14 +00:00
renovate[bot]andGitHub c207815b8e chore(deps): update typescript-eslint monorepo to v8.58.1 2026-04-11 05:56:21 +00:00
Owncast 875b41dd99 Bundle embedded web app 2026-04-11 05:55:12 +00:00
renovate[bot]andGitHub 1a578bab2a chore(deps): update dependency msw to v2.13.1 2026-04-11 02:02:55 +00:00
Owncast 415657298f Bundle embedded web app 2026-04-11 02:00:28 +00:00
renovate[bot]andGitHub 8c5dc5f3b2 chore(deps): update dependency cypress to v15.13.1 2026-04-10 22:15:43 +00:00
Gabe Kangas 05a3e8696d fix(ci): the auto-comment on missing checklist wasn't firing 2026-04-10 10:25:04 -07:00
renovate[bot]andGitHub 09b4179b73 fix(deps): update module golang.org/x/crypto to v0.50.0 2026-04-09 19:01:40 +00:00
Owncast 92689ffd6b Bundle embedded web app 2026-04-09 18:58:55 +00:00
renovate[bot]andGitHub 432079d660 chore(deps): update dependency msw to v2.13.0 2026-04-09 15:06:27 +00:00
renovate[bot]andGitHub 0cbf8fad1b fix(deps): update module github.com/oapi-codegen/runtime to v1.4.0 2026-04-09 01:58:48 +00:00
renovate[bot]andGitHub bf4636b148 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.42 2026-04-08 21:14:07 +00:00
renovate[bot]andGitHub 43630aaccf chore(deps): update dependency go to v1.26.2 2026-04-08 04:58:58 +00:00
renovate[bot]andGitHub 93379cbc08 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.41 2026-04-06 16:57:45 +00:00
Owncast 153f9a488f Bundle embedded web app 2026-04-06 05:37:13 +00:00
renovate[bot]andGitHub 8c9cf367ea chore(deps): update dependency sass to v1.99.0 2026-04-06 01:57:36 +00:00
Owncast a42f0ff5a5 Bundle embedded web app 2026-04-05 20:53:08 +00:00
renovate[bot]andGitHub d9d26035aa chore(deps): update dependency chromatic to v16.1.0 2026-04-05 17:31:26 +00:00
Owncast 3209695726 Bundle embedded web app 2026-04-04 21:18:10 +00:00
renovate[bot]andGitHub 97f91f1c87 chore(deps): update dependency mermaid to v11.14.0 2026-04-04 20:37:42 +00:00
Owncast 6ee9c6cd99 Bundle embedded web app 2026-04-04 20:36:08 +00:00
renovate[bot]andGitHub 20e6d5cb5e chore(deps): update dependency ts-jest to v29.4.9 2026-04-04 17:04:22 +00:00
89 changed files with 959 additions and 1581 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ jobs:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
if: steps.changed-files-yaml.outputs.testable_any_changed == 'true' if: steps.changed-files-yaml.outputs.testable_any_changed == 'true'
with: with:
go-version: '1.26.1' go-version: '1.26.2'
cache: true cache: true
- name: Install Google Chrome - name: Install Google Chrome
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version: '1.26.1' go-version: '1.26.2'
cache: true cache: true
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version: '1.26.1' go-version: '1.26.2'
cache: true cache: true
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: golangci-lint - name: golangci-lint
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
with: with:
go-version: '1.26.1' go-version: '1.26.2'
cache: true cache: true
- name: Cache node modules - name: Cache node modules
+1 -1
View File
@@ -91,7 +91,7 @@ jobs:
} }
- name: Comment on PR about missing checklist - name: Comment on PR about missing checklist
if: ${{ steps.validate.outputs.missing_checklist == 'true' }} if: ${{ always() && steps.validate.outputs.missing_checklist == 'true' }}
uses: actions/github-script@v8 uses: actions/github-script@v8
with: with:
script: | script: |
-2
View File
@@ -93,8 +93,6 @@ func GetDefaults() Defaults {
VideoBitrate: 1200, VideoBitrate: 1200,
Framerate: 24, Framerate: 24,
CPUUsageLevel: 2, CPUUsageLevel: 2,
VideoCodec: "h264",
AudioCodec: "aac",
}, },
}, },
+10 -41
View File
@@ -1,7 +1,6 @@
package core package core
import ( import (
"fmt"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -99,53 +98,23 @@ func createInitialOfflineState() error {
// offline video stream state only. No live stream HLS segments will continue to be // offline video stream state only. No live stream HLS segments will continue to be
// referenced. // referenced.
func transitionToOfflineVideoStreamContent() { func transitionToOfflineVideoStreamContent() {
log.Traceln("Placing offline fMP4 content into HLS directories") log.Traceln("Firing transcoder with offline stream state")
configRepository := configrepository.Get() _transcoder := transcoder.NewTranscoder()
_transcoder.SetIdentifier("offline")
_transcoder.SetLatencyLevel(models.GetLatencyLevel(4))
_transcoder.SetIsEvent(true)
offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk() offlineFilePath, err := saveOfflineClipToDisk("offline-v2.ts")
if err != nil { if err != nil {
log.Fatalln("unable to save offline fMP4 files:", err) log.Fatalln("unable to save offline clip:", err)
} }
variants := configRepository.GetStreamOutputVariants() _transcoder.SetInput(offlineFilePath)
if len(variants) == 0 { go _transcoder.Start(false)
// 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 // Copy the logo to be the thumbnail
configRepository := configrepository.Get()
logo := configRepository.GetLogoPath() logo := configRepository.GetLogoPath()
dst := filepath.Join(config.TempDir, "thumbnail.jpg") dst := filepath.Join(config.TempDir, "thumbnail.jpg")
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil { if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
+33 -61
View File
@@ -5,13 +5,14 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/grafov/m3u8"
"github.com/owncast/owncast/config" "github.com/owncast/owncast/config"
"github.com/owncast/owncast/static" "github.com/owncast/owncast/static"
"github.com/owncast/owncast/utils" "github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFilename string, segmentFilename string) { func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec
if err != nil { if err != nil {
log.Debugln("unable to read existing playlist file", err) log.Debugln("unable to read existing playlist file", err)
@@ -33,10 +34,9 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFile
// Manually append the offline clip to the end of the media playlist. // Manually append the offline clip to the end of the media playlist.
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-DISCONTINUITY\n") _, _ = 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 // If "offline" content gets changed then change the duration below
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n") _, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n")
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "%s\n", segmentFilename) _, _ = atomicWriteTmpPlaylistFile.WriteString("offline-v2.ts\n")
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n") _, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n")
if err := atomicWriteTmpPlaylistFile.Close(); err != nil { if err := atomicWriteTmpPlaylistFile.Close(); err != nil {
@@ -48,89 +48,61 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFile
} }
} }
func makeVariantIndexOffline(index int, offlineInitPath string, offlineSegmentPath string) { func makeVariantIndexOffline(index int, offlineFilePath string, offlineFilename string) {
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index)) playlistFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/stream.m3u8"), index)
playlistFilePath := filepath.Join(variantDir, "stream.m3u8") segmentFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/%s"), index, offlineFilename)
initDest := filepath.Join(variantDir, "offline-init.mp4") if err := utils.Copy(offlineFilePath, segmentFilePath); err != nil {
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) log.Warnln(err)
} }
if err := utils.Copy(offlineSegmentPath, segmentDest); err != nil { if _, err := _storage.Save(segmentFilePath, 0); err != nil {
log.Warnln(err)
return
}
if _, err := _storage.Save(segmentDest, 0); err != nil {
log.Warnln(err) log.Warnln(err)
} }
if utils.DoesFileExists(playlistFilePath) { if utils.DoesFileExists(playlistFilePath) {
appendOfflineToVariantPlaylist(index, playlistFilePath, "offline-init.mp4", "offline-v2.m4s") appendOfflineToVariantPlaylist(index, playlistFilePath)
} else { } else {
createEmptyOfflinePlaylist(playlistFilePath, "offline-init.mp4", "offline-v2.m4s") createEmptyOfflinePlaylist(playlistFilePath, offlineFilename)
} }
if _, err := _storage.Save(playlistFilePath, 0); err != nil { if _, err := _storage.Save(playlistFilePath, 0); err != nil {
log.Warnln(err) log.Warnln(err)
} }
} }
func createEmptyOfflinePlaylist(playlistFilePath string, initFilename string, segmentFilename string) { 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()
f, err := os.Create(playlistFilePath) //nolint:gosec f, err := os.Create(playlistFilePath) //nolint:gosec
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return
} }
defer f.Close() defer f.Close()
if _, err := f.Write(p.Encode().Bytes()); err != nil {
_, _ = f.WriteString("#EXTM3U\n") log.Errorln(err)
_, _ = 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 saveOfflineFMP4ToDisk() (initPath string, segmentPath string, err error) { func saveOfflineClipToDisk(offlineFilename string) (string, error) {
initData := static.GetOfflineInitSegment() offlineFileData := static.GetOfflineSegment()
initTmp, err := os.CreateTemp(config.TempDir, "offline-init-*.mp4") offlineTmpFile, err := os.CreateTemp(config.TempDir, offlineFilename)
if err != nil { if err != nil {
return "", "", fmt.Errorf("unable to create temp file for offline init segment: %s", err) log.Errorln("unable to create temp file for offline video segment", err)
} }
if _, err = initTmp.Write(initData); err != nil { if _, err = offlineTmpFile.Write(offlineFileData); err != nil {
return "", "", fmt.Errorf("unable to write offline init segment to disk: %s", err) return "", fmt.Errorf("unable to write offline segment to disk: %s", err)
} }
if err := initTmp.Close(); err != nil { offlineFilePath := offlineTmpFile.Name()
return "", "", fmt.Errorf("unable to close offline init segment: %s", err) return filepath.Abs(offlineFilePath)
}
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() directory = info.Name()
} }
if filepath.Ext(info.Name()) == ".m4s" { if filepath.Ext(info.Name()) == ".ts" {
files[directory] = append(files[directory], info) 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, err := s.s3Client.ListObjectsPages(allObjectsListRequest,
func(page *s3.ListObjectsOutput, lastPage bool) bool { func(page *s3.ListObjectsOutput, lastPage bool) bool {
for _, item := range page.Contents { for _, item := range page.Contents {
if strings.HasSuffix(*item.Key, ".m4s") { if strings.HasSuffix(*item.Key, ".ts") {
allObjects = append(allObjects, s3object{ allObjects = append(allObjects, s3object{
key: *item.Key, key: *item.Key,
lastModified: *item.LastModified, lastModified: *item.LastModified,
+4 -2
View File
@@ -96,7 +96,9 @@ func SetStreamAsDisconnected() {
_stats.LastConnectTime = nil _stats.LastConnectTime = nil
_broadcaster = nil _broadcaster = nil
offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk() offlineFilename := "offline-v2.ts"
offlineFilePath, err := saveOfflineClipToDisk(offlineFilename)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return return
@@ -120,7 +122,7 @@ func SetStreamAsDisconnected() {
} }
for index := range _currentBroadcast.OutputSettings { for index := range _currentBroadcast.OutputSettings {
makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath) makeVariantIndexOffline(index, offlineFilePath, offlineFilename)
} }
StartOfflineCleanupTimer() StartOfflineCleanupTimer()
-42
View File
@@ -1,42 +0,0 @@
package transcoder
const audioCodecAAC = "aac"
// aacFFmpegEncoder happens to match audioCodecAAC, but represents the ffmpeg
// encoder name, which could be "libfdk_aac" or another AAC encoder instead.
const aacFFmpegEncoder = "aac"
// AudioCodec represents an audio compression format (e.g., AAC, Opus).
type AudioCodec interface {
Name() string
DisplayName() string
FFmpegEncoderName() string
}
// AACCodec implements AudioCodec for AAC audio.
type AACCodec struct{}
func (c *AACCodec) Name() string {
return audioCodecAAC
}
func (c *AACCodec) DisplayName() string {
return "AAC"
}
// FFmpegEncoderName returns the ffmpeg encoder to use. We use the built-in
// "aac" encoder rather than "libfdk_aac" since it is universally available.
func (c *AACCodec) FFmpegEncoderName() string {
return aacFFmpegEncoder
}
var audioCodecRegistry = map[string]AudioCodec{
audioCodecAAC: &AACCodec{},
}
func getAudioCodec(name string) AudioCodec {
if codec, ok := audioCodecRegistry[name]; ok {
return codec
}
return &AACCodec{}
}
+549
View File
@@ -0,0 +1,549 @@
//nolint:goconst
package transcoder
import (
"fmt"
"os/exec"
"strings"
log "github.com/sirupsen/logrus"
)
// Codec represents a supported codec on the system.
type Codec interface {
Name() string
DisplayName() string
GlobalFlags() []string
PixelFormat() string
Scaler() string
ExtraArguments() []string
ExtraFilters() string
VariantFlags(v *HLSVariant) []string
GetPresetForLevel(l int) string
}
var supportedCodecs = map[string]string{
(&Libx264Codec{}).Name(): "libx264",
(&OmxCodec{}).Name(): "omx",
(&VaapiCodec{}).Name(): "vaapi",
(&QuicksyncCodec{}).Name(): "qsv",
(&NvencCodec{}).Name(): "NVIDIA nvenc",
(&VideoToolboxCodec{}).Name(): "videotoolbox",
}
// Libx264Codec represents an instance of the Libx264 Codec.
type Libx264Codec struct{}
// Name returns the codec name.
func (c *Libx264Codec) Name() string {
return "libx264"
}
// DisplayName returns the human readable name of the codec.
func (c *Libx264Codec) DisplayName() string {
return "x264"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *Libx264Codec) GlobalFlags() []string {
return nil
}
// PixelFormat is the pixel format required for this codec.
func (c *Libx264Codec) PixelFormat() string {
return "yuv420p" //nolint:goconst
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *Libx264Codec) Scaler() string {
return ""
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *Libx264Codec) ExtraArguments() []string {
return []string{
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
}
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *Libx264Codec) ExtraFilters() string {
return ""
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *Libx264Codec) VariantFlags(v *HLSVariant) []string {
return []string{
fmt.Sprintf("-x264-params:v:%d", v.index),
"scenecut=0:open_gop=0", // How often the encoder checks the bitrate in order to meet average/max values
fmt.Sprintf("-bufsize:v:%d", v.index), fmt.Sprintf("%dk", v.getBufferSize()),
fmt.Sprintf("-profile:v:%d", v.index), "high", // Encoding profile
}
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *Libx264Codec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for x264 preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// OmxCodec represents an instance of the Omx codec.
type OmxCodec struct{}
// Name returns the codec name.
func (c *OmxCodec) Name() string {
return "h264_omx"
}
// DisplayName returns the human readable name of the codec.
func (c *OmxCodec) DisplayName() string {
return "OpenMAX (omx)"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *OmxCodec) GlobalFlags() []string {
return nil
}
// PixelFormat is the pixel format required for this codec.
func (c *OmxCodec) PixelFormat() string {
return "yuv420p"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *OmxCodec) Scaler() string {
return ""
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *OmxCodec) ExtraArguments() []string {
return []string{
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
}
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *OmxCodec) ExtraFilters() string {
return ""
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *OmxCodec) VariantFlags(v *HLSVariant) []string {
return nil
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *OmxCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for omx preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// VaapiCodec represents an instance of the Vaapi codec.
type VaapiCodec struct{}
// Name returns the codec name.
func (c *VaapiCodec) Name() string {
return "h264_vaapi"
}
// DisplayName returns the human readable name of the codec.
func (c *VaapiCodec) DisplayName() string {
return "VA-API"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *VaapiCodec) GlobalFlags() []string {
flags := []string{
"-hwaccel", "vaapi",
"-hwaccel_output_format", "vaapi",
"-vaapi_device", "/dev/dri/renderD128",
}
return flags
}
// PixelFormat is the pixel format required for this codec.
func (c *VaapiCodec) PixelFormat() string {
return "vaapi"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *VaapiCodec) Scaler() string {
return "scale_vaapi"
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *VaapiCodec) ExtraFilters() string {
return "hwupload=extra_hw_frames=64,format=vaapi"
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *VaapiCodec) ExtraArguments() []string {
return nil
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *VaapiCodec) VariantFlags(v *HLSVariant) []string {
return nil
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *VaapiCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for vaapi preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// NvencCodec represents an instance of the Nvenc Codec.
type NvencCodec struct{}
// Name returns the codec name.
func (c *NvencCodec) Name() string {
return "h264_nvenc"
}
// DisplayName returns the human readable name of the codec.
func (c *NvencCodec) DisplayName() string {
return "nvidia nvenc"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *NvencCodec) GlobalFlags() []string {
flags := []string{
"-hwaccel", "cuda",
}
return flags
}
// PixelFormat is the pixel format required for this codec.
func (c *NvencCodec) PixelFormat() string {
return "yuv420p"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *NvencCodec) Scaler() string {
return ""
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *NvencCodec) ExtraArguments() []string {
return nil
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *NvencCodec) ExtraFilters() string {
return ""
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *NvencCodec) VariantFlags(v *HLSVariant) []string {
tuning := "ll" // low latency
return []string{
fmt.Sprintf("-tune:v:%d", v.index), tuning,
}
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *NvencCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "p1",
1: "p2",
2: "p3",
3: "p4",
4: "p5",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[2]
log.Errorf("Invalid level for nvenc preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// QuicksyncCodec represents an instance of the Intel Quicksync Codec.
type QuicksyncCodec struct{}
// Name returns the codec name.
func (c *QuicksyncCodec) Name() string {
return "h264_qsv"
}
// DisplayName returns the human readable name of the codec.
func (c *QuicksyncCodec) DisplayName() string {
return "Intel QuickSync"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *QuicksyncCodec) GlobalFlags() []string {
flags := []string{
"-init_hw_device", "qsv=hw",
"-filter_hw_device", "hw",
}
return flags
}
// PixelFormat is the pixel format required for this codec.
func (c *QuicksyncCodec) PixelFormat() string {
return "qsv"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *QuicksyncCodec) Scaler() string {
return "scale_qsv"
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *QuicksyncCodec) ExtraArguments() []string {
return nil
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *QuicksyncCodec) ExtraFilters() string {
return "hwupload=extra_hw_frames=64,format=qsv"
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) []string {
return nil
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "veryfast",
1: "fast",
2: "medium",
3: "slow",
4: "veryslow",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[2]
log.Errorf("Invalid level for quicksync preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// Video4Linux represents an instance of the V4L Codec.
type Video4Linux struct{}
// Name returns the codec name.
func (c *Video4Linux) Name() string {
return "h264_v4l2m2m"
}
// DisplayName returns the human readable name of the codec.
func (c *Video4Linux) DisplayName() string {
return "Video4Linux"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *Video4Linux) GlobalFlags() []string {
return nil
}
// PixelFormat is the pixel format required for this codec.
func (c *Video4Linux) PixelFormat() string {
return "nv21"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *Video4Linux) Scaler() string {
return ""
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *Video4Linux) ExtraArguments() []string {
return nil
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *Video4Linux) ExtraFilters() string {
return ""
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *Video4Linux) VariantFlags(v *HLSVariant) []string {
return nil
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *Video4Linux) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for v4l preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// VideoToolboxCodec represents an instance of the VideoToolbox codec.
type VideoToolboxCodec struct{}
// Name returns the codec name.
func (c *VideoToolboxCodec) Name() string {
return "h264_videotoolbox"
}
// DisplayName returns the human readable name of the codec.
func (c *VideoToolboxCodec) DisplayName() string {
return "VideoToolbox"
}
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *VideoToolboxCodec) GlobalFlags() []string {
return nil
}
// PixelFormat is the pixel format required for this codec.
func (c *VideoToolboxCodec) PixelFormat() string {
return "nv12"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *VideoToolboxCodec) Scaler() string {
return ""
}
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *VideoToolboxCodec) ExtraFilters() string {
return ""
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
func (c *VideoToolboxCodec) ExtraArguments() []string {
return nil
}
// VariantFlags returns a string representing a single variant processed by this codec.
func (c *VideoToolboxCodec) VariantFlags(v *HLSVariant) []string {
if v.cpuUsageLevel >= 3 {
return nil
}
return []string{"-realtime", "true"}
}
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *VideoToolboxCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for videotoolbox preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
// GetCodecs will return the supported codecs available on the system.
func GetCodecs(ffmpegPath string) []string {
codecs := make([]string, 0)
cmd := exec.Command(ffmpegPath, "-encoders")
out, err := cmd.CombinedOutput()
if err != nil {
log.Errorln(err)
return codecs
}
response := string(out)
lines := strings.Split(response, "\n")
for _, line := range lines {
if strings.Contains(line, "H.264") {
fields := strings.Fields(line)
codec := fields[1]
if _, supported := supportedCodecs[codec]; supported {
codecs = append(codecs, codec)
}
}
}
return codecs
}
func getCodec(name string) Codec {
switch name {
case (&NvencCodec{}).Name():
return &NvencCodec{}
case (&VaapiCodec{}).Name():
return &VaapiCodec{}
case (&QuicksyncCodec{}).Name():
return &QuicksyncCodec{}
case (&OmxCodec{}).Name():
return &OmxCodec{}
case (&Video4Linux{}).Name():
return &Video4Linux{}
case (&VideoToolboxCodec{}).Name():
return &VideoToolboxCodec{}
default:
return &Libx264Codec{}
}
}
-118
View File
@@ -1,118 +0,0 @@
package transcoder
import (
"os/exec"
"sort"
"strings"
log "github.com/sirupsen/logrus"
)
// EncoderCodecSupport describes which codecs an encoder supports on this system.
type EncoderCodecSupport struct {
EncoderType string `json:"encoderType"`
EncoderDisplayName string `json:"encoderDisplayName"`
SupportedVideoCodecs []string `json:"supportedVideoCodecs"`
}
// GetAvailableVideoEncoders probes ffmpeg to discover which encoder+codec combinations
// are available on this system. Returns only encoders that have at least one
// supported codec available.
func GetAvailableVideoEncoders(ffmpegPath string) []EncoderCodecSupport {
ffmpegEncoders := getFFmpegEncoders(ffmpegPath)
// Sort encoder names for deterministic output order.
encoderNames := make([]string, 0, len(encoderRegistry))
for name := range encoderRegistry {
encoderNames = append(encoderNames, name)
}
sort.Strings(encoderNames)
var available []EncoderCodecSupport
for _, name := range encoderNames {
enc := encoderRegistry[name]
var supportedCodecs []string
for _, codecName := range enc.SupportedVideoCodecs() {
ffmpegEncoderName := enc.FFmpegEncoderForCodec(codecName)
if _, ok := ffmpegEncoders[ffmpegEncoderName]; ok {
supportedCodecs = append(supportedCodecs, codecName)
}
}
if len(supportedCodecs) > 0 {
available = append(available, EncoderCodecSupport{
EncoderType: enc.Name(),
EncoderDisplayName: enc.DisplayName(),
SupportedVideoCodecs: supportedCodecs,
})
}
}
// Always include software encoder even if ffmpeg probe fails,
// as libx264 is almost universally available.
hasSoftware := false
for _, enc := range available {
if enc.EncoderType == "software" {
hasSoftware = true
break
}
}
if !hasSoftware {
available = append([]EncoderCodecSupport{{
EncoderType: "software",
EncoderDisplayName: "Software (CPU)",
SupportedVideoCodecs: []string{"h264"},
}}, available...)
}
return available
}
// GetAvailableAudioCodecs probes ffmpeg to discover which audio codecs are available.
func GetAvailableAudioCodecs(ffmpegPath string) []string {
ffmpegEncoders := getFFmpegEncoders(ffmpegPath)
var available []string
for _, codec := range audioCodecRegistry {
if _, ok := ffmpegEncoders[codec.FFmpegEncoderName()]; ok {
available = append(available, codec.Name())
}
}
// Always include AAC as it is built into ffmpeg.
hasAAC := false
for _, name := range available {
if name == "aac" {
hasAAC = true
break
}
}
if !hasAAC {
available = append([]string{"aac"}, available...)
}
sort.Strings(available)
return available
}
// getFFmpegEncoders runs `ffmpeg -encoders` and returns a set of encoder names.
func getFFmpegEncoders(ffmpegPath string) map[string]struct{} {
result := make(map[string]struct{})
cmd := exec.Command(ffmpegPath, "-encoders")
out, err := cmd.CombinedOutput()
if err != nil {
log.Errorln(err)
return result
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 || line[0] == '-' || line[0] == '=' {
continue
}
fields := strings.Fields(line)
if len(fields) >= 2 {
result[fields[1]] = struct{}{}
}
}
return result
}
-128
View File
@@ -1,128 +0,0 @@
package transcoder
import (
"fmt"
log "github.com/sirupsen/logrus"
)
const (
pixelFormatYUV420P = "yuv420p"
ffmpegEncoderX264 = "libx264"
)
// CodecProfile holds codec-dependent encoding parameters for an encoder.
type CodecProfile struct {
PixelFormat string
Scaler string
ExtraFilters string
}
// Encoder represents a hardware/software video encoding backend (e.g., software CPU, NVENC, VA-API).
type Encoder interface {
Name() string
DisplayName() string
GlobalFlags() []string
ExtraArguments() []string
ProfileForCodec(codec string) CodecProfile
VariantFlags(v *HLSVariant) []string
SupportedVideoCodecs() []string
FFmpegEncoderForCodec(codec string) string
GetPresetForLevel(l int) string
}
// SoftwareEncoder uses CPU-based encoding via libx264/libx265.
type SoftwareEncoder struct{}
func (e *SoftwareEncoder) Name() string {
return "software"
}
func (e *SoftwareEncoder) DisplayName() string {
return "Software (CPU)"
}
func (e *SoftwareEncoder) GlobalFlags() []string {
return nil
}
func (e *SoftwareEncoder) ExtraArguments() []string {
return []string{"-tune", "zerolatency"}
}
func (e *SoftwareEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: pixelFormatYUV420P,
}
}
func (e *SoftwareEncoder) VariantFlags(v *HLSVariant) []string {
codec := v.videoCodec.Name()
var flags []string
if codec == videoCodecH264 {
flags = append(flags,
fmt.Sprintf("-x264-params:v:%d", v.index), "scenecut=0:open_gop=0",
)
}
flags = append(flags,
fmt.Sprintf("-bufsize:v:%d", v.index), fmt.Sprintf("%dk", v.getBufferSize()),
)
if codec == videoCodecH264 {
flags = append(flags,
fmt.Sprintf("-profile:v:%d", v.index), "high",
)
}
return flags
}
func (e *SoftwareEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *SoftwareEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return ffmpegEncoderX264
default:
log.Warnf("software encoder does not support codec %q, falling back to x264", codec)
return ffmpegEncoderX264
}
}
func (e *SoftwareEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for software preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
var encoderRegistry = map[string]Encoder{
"software": &SoftwareEncoder{},
"nvenc": &NvencEncoder{},
"vaapi": &VaapiEncoder{},
"qsv": &QuicksyncEncoder{},
"omx": &OmxEncoder{},
"v4l2m2m": &V4l2m2mEncoder{},
"videotoolbox": &VideoToolboxEncoder{},
}
func getEncoder(encoderType string) Encoder {
if enc, ok := encoderRegistry[encoderType]; ok {
return enc
}
return &SoftwareEncoder{}
}
-71
View File
@@ -1,71 +0,0 @@
package transcoder
import (
"fmt"
log "github.com/sirupsen/logrus"
)
// NvencEncoder uses NVIDIA GPU hardware encoding.
type NvencEncoder struct{}
func (e *NvencEncoder) Name() string {
return "nvenc"
}
func (e *NvencEncoder) DisplayName() string {
return "NVIDIA NVENC"
}
func (e *NvencEncoder) GlobalFlags() []string {
return []string{
"-hwaccel", "cuda",
}
}
func (e *NvencEncoder) ExtraArguments() []string {
return nil
}
func (e *NvencEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: pixelFormatYUV420P,
}
}
func (e *NvencEncoder) VariantFlags(v *HLSVariant) []string {
return []string{
fmt.Sprintf("-tune:v:%d", v.index), "ll",
}
}
func (e *NvencEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *NvencEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_nvenc"
default:
log.Warnf("nvenc encoder does not support codec %q, falling back to h264_nvenc", codec)
return "h264_nvenc"
}
}
func (e *NvencEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "p1",
1: "p2",
2: "p3",
3: "p4",
4: "p5",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[2]
log.Errorf("Invalid level for nvenc preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
-63
View File
@@ -1,63 +0,0 @@
package transcoder
import log "github.com/sirupsen/logrus"
// OmxEncoder uses OpenMAX hardware encoding (Raspberry Pi / ARM).
type OmxEncoder struct{}
func (e *OmxEncoder) Name() string {
return "omx"
}
func (e *OmxEncoder) DisplayName() string {
return "OpenMAX (omx)"
}
func (e *OmxEncoder) GlobalFlags() []string {
return nil
}
func (e *OmxEncoder) ExtraArguments() []string {
return []string{"-tune", "zerolatency"}
}
func (e *OmxEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: pixelFormatYUV420P,
}
}
func (e *OmxEncoder) VariantFlags(v *HLSVariant) []string {
return nil
}
func (e *OmxEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *OmxEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_omx"
default:
log.Warnf("omx encoder does not support codec %q, falling back to h264_omx", codec)
return "h264_omx"
}
}
func (e *OmxEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for omx preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
-68
View File
@@ -1,68 +0,0 @@
package transcoder
import log "github.com/sirupsen/logrus"
// QuicksyncEncoder uses Intel Quick Sync Video hardware encoding.
type QuicksyncEncoder struct{}
func (e *QuicksyncEncoder) Name() string {
return "qsv"
}
func (e *QuicksyncEncoder) DisplayName() string {
return "Intel QuickSync"
}
func (e *QuicksyncEncoder) GlobalFlags() []string {
return []string{
"-init_hw_device", "qsv=hw",
"-filter_hw_device", "hw",
}
}
func (e *QuicksyncEncoder) ExtraArguments() []string {
return nil
}
func (e *QuicksyncEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: "qsv",
Scaler: "scale_qsv",
ExtraFilters: "hwupload=extra_hw_frames=64,format=qsv",
}
}
func (e *QuicksyncEncoder) VariantFlags(v *HLSVariant) []string {
return nil
}
func (e *QuicksyncEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *QuicksyncEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_qsv"
default:
log.Warnf("quicksync encoder does not support codec %q, falling back to h264_qsv", codec)
return "h264_qsv"
}
}
func (e *QuicksyncEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "veryfast",
1: "fast",
2: "medium",
3: "slow",
4: "veryslow",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[2]
log.Errorf("Invalid level for quicksync preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
-63
View File
@@ -1,63 +0,0 @@
package transcoder
import log "github.com/sirupsen/logrus"
// V4l2m2mEncoder uses Video4Linux memory-to-memory hardware encoding.
type V4l2m2mEncoder struct{}
func (e *V4l2m2mEncoder) Name() string {
return "v4l2m2m"
}
func (e *V4l2m2mEncoder) DisplayName() string {
return "Video4Linux"
}
func (e *V4l2m2mEncoder) GlobalFlags() []string {
return nil
}
func (e *V4l2m2mEncoder) ExtraArguments() []string {
return nil
}
func (e *V4l2m2mEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: "nv21",
}
}
func (e *V4l2m2mEncoder) VariantFlags(v *HLSVariant) []string {
return nil
}
func (e *V4l2m2mEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *V4l2m2mEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_v4l2m2m"
default:
log.Warnf("v4l2m2m encoder does not support codec %q, falling back to h264_v4l2m2m", codec)
return "h264_v4l2m2m"
}
}
func (e *V4l2m2mEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for v4l preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
-69
View File
@@ -1,69 +0,0 @@
package transcoder
import log "github.com/sirupsen/logrus"
// VaapiEncoder uses VA-API hardware encoding (Intel/AMD GPUs).
type VaapiEncoder struct{}
func (e *VaapiEncoder) Name() string {
return "vaapi"
}
func (e *VaapiEncoder) DisplayName() string {
return "VA-API"
}
func (e *VaapiEncoder) GlobalFlags() []string {
return []string{
"-hwaccel", "vaapi",
"-hwaccel_output_format", "vaapi",
"-vaapi_device", "/dev/dri/renderD128",
}
}
func (e *VaapiEncoder) ExtraArguments() []string {
return nil
}
func (e *VaapiEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: "vaapi",
Scaler: "scale_vaapi",
ExtraFilters: "hwupload=extra_hw_frames=64,format=vaapi",
}
}
func (e *VaapiEncoder) VariantFlags(v *HLSVariant) []string {
return nil
}
func (e *VaapiEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *VaapiEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_vaapi"
default:
log.Warnf("vaapi encoder does not support codec %q, falling back to h264_vaapi", codec)
return "h264_vaapi"
}
}
func (e *VaapiEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for vaapi preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
-66
View File
@@ -1,66 +0,0 @@
package transcoder
import log "github.com/sirupsen/logrus"
// VideoToolboxEncoder uses Apple VideoToolbox hardware encoding (macOS).
type VideoToolboxEncoder struct{}
func (e *VideoToolboxEncoder) Name() string {
return "videotoolbox"
}
func (e *VideoToolboxEncoder) DisplayName() string {
return "VideoToolbox"
}
func (e *VideoToolboxEncoder) GlobalFlags() []string {
return nil
}
func (e *VideoToolboxEncoder) ExtraArguments() []string {
return nil
}
func (e *VideoToolboxEncoder) ProfileForCodec(codec string) CodecProfile {
return CodecProfile{
PixelFormat: "nv12",
}
}
func (e *VideoToolboxEncoder) VariantFlags(v *HLSVariant) []string {
if v.cpuUsageLevel >= 3 {
return nil
}
return []string{"-realtime", "true"}
}
func (e *VideoToolboxEncoder) SupportedVideoCodecs() []string {
return []string{videoCodecH264}
}
func (e *VideoToolboxEncoder) FFmpegEncoderForCodec(codec string) string {
switch codec {
case videoCodecH264:
return "h264_videotoolbox"
default:
log.Warnf("videotoolbox encoder does not support codec %q, falling back to h264_videotoolbox", codec)
return "h264_videotoolbox"
}
}
func (e *VideoToolboxEncoder) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
log.Errorf("Invalid level for videotoolbox preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
return preset
}
+1 -4
View File
@@ -18,7 +18,6 @@ type FileWriterReceiverServiceCallback interface {
SegmentWritten(localFilePath string) SegmentWritten(localFilePath string)
VariantPlaylistWritten(localFilePath string) VariantPlaylistWritten(localFilePath string)
MasterPlaylistWritten(localFilePath string) MasterPlaylistWritten(localFilePath string)
InitSegmentWritten(localFilePath string)
} }
// FileWriterReceiverService accepts transcoder responses via HTTP and fires the callbacks. // FileWriterReceiverService accepts transcoder responses via HTTP and fires the callbacks.
@@ -82,10 +81,8 @@ func (s *FileWriterReceiverService) uploadHandler(w http.ResponseWriter, r *http
func (s *FileWriterReceiverService) fileWritten(path string) { func (s *FileWriterReceiverService) fileWritten(path string) {
if utils.GetRelativePathFromAbsolutePath(path) == "hls/stream.m3u8" { if utils.GetRelativePathFromAbsolutePath(path) == "hls/stream.m3u8" {
s.callbacks.MasterPlaylistWritten(path) s.callbacks.MasterPlaylistWritten(path)
} else if strings.HasSuffix(path, ".m4s") { } else if strings.HasSuffix(path, ".ts") {
s.callbacks.SegmentWritten(path) s.callbacks.SegmentWritten(path)
} else if strings.HasSuffix(path, "init.mp4") {
s.callbacks.InitSegmentWritten(path)
} else if strings.HasSuffix(path, ".m3u8") { } else if strings.HasSuffix(path, ".m3u8") {
s.callbacks.VariantPlaylistWritten(path) s.callbacks.VariantPlaylistWritten(path)
} }
-5
View File
@@ -23,8 +23,3 @@ func (h *HLSHandler) VariantPlaylistWritten(localFilePath string) {
func (h *HLSHandler) MasterPlaylistWritten(localFilePath string) { func (h *HLSHandler) MasterPlaylistWritten(localFilePath string) {
h.Storage.MasterPlaylistWritten(localFilePath) 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)
}
+3 -18
View File
@@ -1,7 +1,6 @@
package transcoder package transcoder
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -65,7 +64,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
var modTime time.Time var modTime time.Time
var names []string var names []string
for _, f := range files { for _, f := range files {
if path.Ext(f.Name()) != ".m4s" { if path.Ext(f.Name()) != ".ts" {
continue continue
} }
@@ -89,21 +88,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
return nil return nil
} }
configRepository := configrepository.Get() 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]) 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()) ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg") outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg")
@@ -111,7 +96,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
"-y", // Overwrite file "-y", // Overwrite file
"-threads", "1", // Low priority processing "-threads", "1", // Low priority processing
"-t", "1", // Pull from frame 1 "-t", "1", // Pull from frame 1
"-i", inputArg, // Input (init segment + media segment) "-i", mostRecentFile, // Input
"-f", "image2", // format "-f", "image2", // format
"-vframes", "1", // Single frame "-vframes", "1", // Single frame
outputFileTemp, outputFileTemp,
@@ -126,7 +111,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
log.Errorln(err) log.Errorln(err)
} }
makeAnimatedGifPreview(inputArg, previewGifFile) makeAnimatedGifPreview(mostRecentFile, previewGifFile)
return nil return nil
} }
+23 -37
View File
@@ -33,7 +33,7 @@ func (e *execInfo) String() string {
// Transcoder is a single instance of a video transcoder. // Transcoder is a single instance of a video transcoder.
type Transcoder struct { type Transcoder struct {
encoder Encoder codec Codec
stdin *io.PipeReader stdin *io.PipeReader
@@ -66,9 +66,6 @@ type HLSVariant struct {
isVideoPassthrough bool // Override all settings and just copy the video stream isVideoPassthrough bool // Override all settings and just copy the video stream
isAudioPassthrough bool // Override all settings and just copy the audio stream isAudioPassthrough bool // Override all settings and just copy the audio stream
videoCodec VideoCodec
audioCodec AudioCodec
} }
// VideoSize is the scaled size of the video output. // VideoSize is the scaled size of the video output.
@@ -130,7 +127,7 @@ func (t *Transcoder) Start(shouldLog bool) {
flags := t.getFlags() flags := t.getFlags()
if shouldLog { if shouldLog {
log.Infof("Processing video using encoder %s with %d output qualities configured.", t.encoder.DisplayName(), len(t.variants)) log.Infof("Processing video using codec %s with %d output qualities configured.", t.codec.DisplayName(), len(t.variants))
} }
createVariantDirectories() createVariantDirectories()
command := flags.String() command := flags.String()
@@ -170,7 +167,7 @@ func (t *Transcoder) Start(shouldLog bool) {
} }
if err != nil { if err != nil {
log.Errorln("transcoding error. look at", logging.GetTranscoderLogFilePath(), "to help debug. your copy of ffmpeg may not support your selected encoder of", t.encoder.Name(), "https://owncast.online/docs/codecs/") log.Errorln("transcoding error. look at", logging.GetTranscoderLogFilePath(), "to help debug. your copy of ffmpeg may not support your selected codec of", t.codec.Name(), "https://owncast.online/docs/codecs/")
} }
} }
@@ -230,7 +227,7 @@ func (t *Transcoder) getFlags() *execInfo {
} }
ffmpegFlags := make([]string, 0, 64) ffmpegFlags := make([]string, 0, 64)
ffmpegFlags = append(ffmpegFlags, "-hide_banner", "-loglevel", "warning") ffmpegFlags = append(ffmpegFlags, "-hide_banner", "-loglevel", "warning")
ffmpegFlags = append(ffmpegFlags, t.encoder.GlobalFlags()...) ffmpegFlags = append(ffmpegFlags, t.codec.GlobalFlags()...)
ffmpegFlags = append(ffmpegFlags, []string{ ffmpegFlags = append(ffmpegFlags, []string{
"-fflags", "+genpts", // Generate presentation time stamp if missing "-fflags", "+genpts", // Generate presentation time stamp if missing
"-flags", "+cgop", // Force closed GOPs "-flags", "+cgop", // Force closed GOPs
@@ -248,20 +245,19 @@ func (t *Transcoder) getFlags() *execInfo {
ffmpegFlags = append(ffmpegFlags, hlsOptionsString...) ffmpegFlags = append(ffmpegFlags, hlsOptionsString...)
ffmpegFlags = append(ffmpegFlags, hlsEventString...) ffmpegFlags = append(ffmpegFlags, hlsEventString...)
ffmpegFlags = append(ffmpegFlags, []string{ ffmpegFlags = append(ffmpegFlags, []string{
"-hls_segment_type", "fmp4", "-segment_format_options", "mpegts_flags=mpegts_copyts=1",
"-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()...) ffmpegFlags = append(ffmpegFlags, t.codec.ExtraArguments()...)
ffmpegFlags = append(ffmpegFlags, []string{ ffmpegFlags = append(ffmpegFlags, []string{
// Video settings // Video settings
"-pix_fmt", t.codec.PixelFormat(),
"-sc_threshold", "0", // Disable scene change detection for creating segments "-sc_threshold", "0", // Disable scene change detection for creating segments
// Filenames // Filenames
"-master_pl_name", "stream.m3u8", "-master_pl_name", "stream.m3u8",
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.m4s", // Send HLS segments back to us over HTTP "-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.ts", // 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 "-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 "-method", "PUT", // HLS results sent back to us will be over PUTs
@@ -282,9 +278,6 @@ func getVariantFromConfigQuality(quality models.StreamOutputVariant, index int)
variant.isAudioPassthrough = quality.IsAudioPassthrough variant.isAudioPassthrough = quality.IsAudioPassthrough
variant.isVideoPassthrough = quality.IsVideoPassthrough variant.isVideoPassthrough = quality.IsVideoPassthrough
variant.videoCodec = getVideoCodec(quality.VideoCodec)
variant.audioCodec = getAudioCodec(quality.AudioCodec)
// If no audio bitrate is specified then we pass through original audio // If no audio bitrate is specified then we pass through original audio
if quality.AudioBitrate == 0 { if quality.AudioBitrate == 0 {
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
@@ -325,7 +318,7 @@ func NewTranscoder() *Transcoder {
transcoder.currentStreamOutputSettings = configRepository.GetStreamOutputVariants() transcoder.currentStreamOutputSettings = configRepository.GetStreamOutputVariants()
transcoder.currentLatencyLevel = configRepository.GetStreamLatencyLevel() transcoder.currentLatencyLevel = configRepository.GetStreamLatencyLevel()
transcoder.encoder = getEncoder(configRepository.GetVideoEncoder()) transcoder.codec = getCodec(configRepository.GetVideoCodec())
transcoder.segmentOutputPath = config.HLSStoragePath transcoder.segmentOutputPath = config.HLSStoragePath
transcoder.playlistOutputPath = config.HLSStoragePath transcoder.playlistOutputPath = config.HLSStoragePath
@@ -344,28 +337,26 @@ func (v *HLSVariant) getVariantString(t *Transcoder) []string {
variantEncoderCommands := v.getVideoQualityString(t) variantEncoderCommands := v.getVideoQualityString(t)
variantEncoderCommands = append(variantEncoderCommands, v.getAudioQualityString()...) variantEncoderCommands = append(variantEncoderCommands, v.getAudioQualityString()...)
profile := t.encoder.ProfileForCodec(v.videoCodec.Name())
if (v.videoSize.Width != 0 || v.videoSize.Height != 0) && !v.isVideoPassthrough { if (v.videoSize.Width != 0 || v.videoSize.Height != 0) && !v.isVideoPassthrough {
// Order here matters, you must scale before changing hardware formats // Order here matters, you must scale before changing hardware formats
filters := []string{ filters := []string{
v.getScalingString(profile.Scaler), v.getScalingString(t.codec.Scaler()),
} }
if profile.ExtraFilters != "" { if t.codec.ExtraFilters() != "" {
filters = append(filters, profile.ExtraFilters) filters = append(filters, t.codec.ExtraFilters())
} }
scalingAlgorithm := "bilinear" scalingAlgorithm := "bilinear"
variantEncoderCommands = append(variantEncoderCommands, []string{ variantEncoderCommands = append(variantEncoderCommands, []string{
"-sws_flags", scalingAlgorithm, "-sws_flags", scalingAlgorithm,
"-filter:v:" + strconv.Itoa(v.index), strings.Join(filters, ","), "-filter:v:" + strconv.Itoa(v.index), strings.Join(filters, ","),
}...) }...)
} else if profile.ExtraFilters != "" && !v.isVideoPassthrough { } else if t.codec.ExtraFilters() != "" && !v.isVideoPassthrough {
variantEncoderCommands = append(variantEncoderCommands, []string{ variantEncoderCommands = append(variantEncoderCommands, []string{
"-filter:v:" + strconv.Itoa(v.index), profile.ExtraFilters, "-filter:v:" + strconv.Itoa(v.index), t.codec.ExtraFilters(),
}...) }...)
} }
preset := t.encoder.GetPresetForLevel(v.cpuUsageLevel) preset := t.codec.GetPresetForLevel(v.cpuUsageLevel)
if preset != "" { if preset != "" {
variantEncoderCommands = append(variantEncoderCommands, []string{ variantEncoderCommands = append(variantEncoderCommands, []string{
"-preset", preset, "-preset", preset,
@@ -424,25 +415,18 @@ func (v *HLSVariant) getVideoQualityString(t *Transcoder) []string {
} }
} }
codec := v.videoCodec.Name()
ffmpegEncoder := t.encoder.FFmpegEncoderForCodec(codec)
profile := t.encoder.ProfileForCodec(codec)
gop := v.framerate * t.currentLatencyLevel.SecondsPerSegment // force an i-frame every segment gop := v.framerate * t.currentLatencyLevel.SecondsPerSegment // force an i-frame every segment
cmd := make([]string, 0, 20) cmd := make([]string, 0, 20)
cmd = append(cmd, cmd = append(cmd,
"-map", "v:0", "-map", "v:0",
fmt.Sprintf("-c:v:%d", v.index), ffmpegEncoder, // Video encoder used for this variant fmt.Sprintf("-c:v:%d", v.index), t.codec.Name(), // Video codec used for this variant
fmt.Sprintf("-pix_fmt:v:%d", v.index), profile.PixelFormat, // Pixel format for this variant
fmt.Sprintf("-b:v:%d", v.index), fmt.Sprintf("%dk", v.getAllocatedVideoBitrate()), // The average bitrate for this variant allowing space for audio fmt.Sprintf("-b:v:%d", v.index), fmt.Sprintf("%dk", v.getAllocatedVideoBitrate()), // The average bitrate for this variant allowing space for audio
fmt.Sprintf("-maxrate:v:%d", v.index), fmt.Sprintf("%dk", v.getMaxVideoBitrate()), // The max bitrate allowed for this variant fmt.Sprintf("-maxrate:v:%d", v.index), fmt.Sprintf("%dk", v.getMaxVideoBitrate()), // The max bitrate allowed for this variant
fmt.Sprintf("-g:v:%d", v.index), fmt.Sprintf("%d", gop), // Suggested interval where i-frames are encoded into the segments fmt.Sprintf("-g:v:%d", v.index), fmt.Sprintf("%d", gop), // Suggested interval where i-frames are encoded into the segments
fmt.Sprintf("-keyint_min:v:%d", v.index), fmt.Sprintf("%d", gop), // minimum i-keyframe interval fmt.Sprintf("-keyint_min:v:%d", v.index), fmt.Sprintf("%d", gop), // minimum i-keyframe interval
fmt.Sprintf("-r:v:%d", v.index), fmt.Sprintf("%d", v.framerate), fmt.Sprintf("-r:v:%d", v.index), fmt.Sprintf("%d", v.framerate),
) )
cmd = append(cmd, t.codec.VariantFlags(v)...)
// Add encoder-specific variant flags (e.g., x264-params for software, NVENC low-latency tuning).
cmd = append(cmd, t.encoder.VariantFlags(v)...)
return cmd return cmd
} }
@@ -471,9 +455,11 @@ func (v *HLSVariant) getAudioQualityString() []string {
} }
} }
// libfdk_aac is not a part of every ffmpeg install, so use "aac" instead
encoderCodec := "aac"
return []string{ return []string{
"-map", "a:0?", "-map", "a:0?",
fmt.Sprintf("-c:a:%d", v.index), v.audioCodec.FFmpegEncoderName(), fmt.Sprintf("-c:a:%d", v.index), encoderCodec,
fmt.Sprintf("-b:a:%d", v.index), v.audioBitrate, fmt.Sprintf("-b:a:%d", v.index), v.audioBitrate,
} }
} }
@@ -509,7 +495,7 @@ func (t *Transcoder) SetInternalHTTPPort(port string) {
t.internalListenerPort = port t.internalListenerPort = port
} }
// SetEncoder will set the encoder to be used for the transcoder. // SetCodec will set the codec to be used for the transocder.
func (t *Transcoder) SetEncoder(encoderType string) { func (t *Transcoder) SetCodec(codecName string) {
t.encoder = getEncoder(encoderType) t.codec = getCodec(codecName)
} }
+3 -8
View File
@@ -9,6 +9,7 @@ import (
func TestFFmpegNvencCommand(t *testing.T) { func TestFFmpegNvencCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := NvencCodec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegNvencCommand(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdoieGg") transcoder.SetIdentifier("jdoieGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("nvenc") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegNvencCommand(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegNvencCommand(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt yuv420p -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+3 -8
View File
@@ -9,6 +9,7 @@ import (
func TestFFmpegOmxCommand(t *testing.T) { func TestFFmpegOmxCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := OmxCodec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegOmxCommand(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdFsdfzGg") transcoder.SetIdentifier("jdFsdfzGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("omx") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegOmxCommand(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegOmxCommand(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt yuv420p -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+3 -8
View File
@@ -9,6 +9,7 @@ import (
func TestFFmpegQuicksyncCommand(t *testing.T) { func TestFFmpegQuicksyncCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := QuicksyncCodec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdofFGg") transcoder.SetIdentifier("jdofFGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("qsv") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt qsv -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+3 -8
View File
@@ -9,6 +9,7 @@ import (
func TestFFmpegVaapiCommand(t *testing.T) { func TestFFmpegVaapiCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := VaapiCodec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegVaapiCommand(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdofFGg") transcoder.SetIdentifier("jdofFGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("vaapi") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegVaapiCommand(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegVaapiCommand(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt vaapi -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
@@ -9,6 +9,7 @@ import (
func TestFFmpegVideoToolboxCommand(t *testing.T) { func TestFFmpegVideoToolboxCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := VideoToolboxCodec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdFsdfzGg") transcoder.SetIdentifier("jdFsdfzGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("videotoolbox") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt nv12 -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+3 -8
View File
@@ -9,6 +9,7 @@ import (
func TestFFmpegx264Command(t *testing.T) { func TestFFmpegx264Command(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2) latencyLevel := models.GetLatencyLevel(2)
codec := Libx264Codec{}
transcoder := new(Transcoder) transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
@@ -16,14 +17,12 @@ func TestFFmpegx264Command(t *testing.T) {
transcoder.SetOutputPath("fakeOutput") transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdofFGg") transcoder.SetIdentifier("jdofFGg")
transcoder.SetInternalHTTPPort("8123") transcoder.SetInternalHTTPPort("8123")
transcoder.SetEncoder("software") transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{} variant := HLSVariant{}
variant.videoBitrate = 1200 variant.videoBitrate = 1200
variant.isAudioPassthrough = true variant.isAudioPassthrough = true
variant.videoCodec = &H264Codec{}
variant.audioCodec = &AACCodec{}
variant.SetVideoFramerate(30) variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2) variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant) transcoder.AddVariant(variant)
@@ -31,8 +30,6 @@ func TestFFmpegx264Command(t *testing.T) {
variant2 := HLSVariant{} variant2 := HLSVariant{}
variant2.videoBitrate = 3500 variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true variant2.isAudioPassthrough = true
variant2.videoCodec = &H264Codec{}
variant2.audioCodec = &AACCodec{}
variant2.SetVideoFramerate(24) variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4) variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2) transcoder.AddVariant(variant2)
@@ -40,14 +37,12 @@ func TestFFmpegx264Command(t *testing.T) {
variant3 := HLSVariant{} variant3 := HLSVariant{}
variant3.isAudioPassthrough = true variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true variant3.isVideoPassthrough = true
variant3.videoCodec = &H264Codec{}
variant3.audioCodec = &AACCodec{}
transcoder.AddVariant(variant3) transcoder.AddVariant(variant3)
cmd := transcoder.GetString() cmd := transcoder.GetString()
expectedLogPath := "data/logs/transcoder.log" 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 -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` 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 -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 -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 -pix_fmt yuv420p -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`
if cmd != expected { if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
-31
View File
@@ -1,31 +0,0 @@
package transcoder
const videoCodecH264 = "h264"
// VideoCodec represents a video compression format (e.g., H.264, H.265).
type VideoCodec interface {
Name() string
DisplayName() string
}
// H264Codec implements VideoCodec for the H.264 format.
type H264Codec struct{}
func (c *H264Codec) Name() string {
return videoCodecH264
}
func (c *H264Codec) DisplayName() string {
return "H.264"
}
var videoCodecRegistry = map[string]VideoCodec{
videoCodecH264: &H264Codec{},
}
func getVideoCodec(name string) VideoCodec {
if codec, ok := videoCodecRegistry[name]; ok {
return codec
}
return &H264Codec{}
}
+10 -14
View File
@@ -16,26 +16,26 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-retryablehttp v0.7.8
github.com/jellydator/ttlcache/v3 v3.4.0 github.com/jellydator/ttlcache/v3 v3.4.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/mattn/go-sqlite3 v1.14.40 github.com/mattn/go-sqlite3 v1.14.42
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/mssola/user_agent v0.6.0 github.com/mssola/user_agent v0.6.0
github.com/nakabonne/tstorage v0.3.6 github.com/nakabonne/tstorage v0.3.6
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590 github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590
github.com/oapi-codegen/runtime v1.3.1 github.com/oapi-codegen/runtime v1.4.0
github.com/oschwald/geoip2-golang v1.13.0 github.com/oschwald/geoip2-golang v1.13.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/schollz/sqlite3dump v1.3.1 github.com/schollz/sqlite3dump v1.3.1
github.com/shirou/gopsutil/v4 v4.26.2 github.com/shirou/gopsutil/v4 v4.25.8
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-emoji v1.0.6 github.com/yuin/goldmark-emoji v1.0.6
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.50.0
golang.org/x/mod v0.34.0 golang.org/x/mod v0.34.0
golang.org/x/net v0.51.0 golang.org/x/net v0.52.0
golang.org/x/time v0.15.0 golang.org/x/time v0.15.0
gopkg.in/evanphx/json-patch.v5 v5.9.11 gopkg.in/evanphx/json-patch.v5 v5.9.11
mvdan.cc/xurls/v2 v2.6.0 mvdan.cc/xurls/v2 v2.6.0
@@ -48,8 +48,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@@ -60,7 +59,6 @@ require (
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lestrrat-go/strftime v1.1.1 // indirect github.com/lestrrat-go/strftime v1.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -68,16 +66,14 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.36.0 // indirect
google.golang.org/protobuf v1.36.8 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+31 -16
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= 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-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= 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/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 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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-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= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -100,6 +100,10 @@ github.com/mattn/go-sqlite3 v1.14.39 h1:sIwSjlJGOaRJjw44/HXaeTblZMjseqr6OOio1tz/
github.com/mattn/go-sqlite3 v1.14.39/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.39/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.40 h1:f7+saIsbq4EF86mUqe0uiecQOJYMOdfi5uATADmUG94= github.com/mattn/go-sqlite3 v1.14.40 h1:f7+saIsbq4EF86mUqe0uiecQOJYMOdfi5uATADmUG94=
github.com/mattn/go-sqlite3 v1.14.40/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mattn/go-sqlite3 v1.14.40/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA=
github.com/mattn/go-sqlite3 v1.14.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4= github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
@@ -114,6 +118,8 @@ github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/
github.com/oapi-codegen/runtime v1.3.0/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= github.com/oapi-codegen/runtime v1.3.0/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY=
github.com/oapi-codegen/runtime v1.3.1 h1:RgDY6J4OGQLbRXhG/Xpt3vSVqYpHQS7hN4m85+5xB9g= github.com/oapi-codegen/runtime v1.3.1 h1:RgDY6J4OGQLbRXhG/Xpt3vSVqYpHQS7hN4m85+5xB9g=
github.com/oapi-codegen/runtime v1.3.1/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= github.com/oapi-codegen/runtime v1.3.1/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY=
github.com/oapi-codegen/runtime v1.4.0 h1:KLOSFOp7UzkbS7Cs1ms6NBEKYr0WmH2wZG0KKbd2er4=
github.com/oapi-codegen/runtime v1.4.0/go.mod h1:5sw5fxCDmnOzKNYmkVNF8d34kyUeejJEY8HNT2WaPec=
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI= github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
@@ -138,12 +144,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/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 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA= github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI= github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= 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= github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -161,10 +167,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/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 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI= github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 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 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
@@ -189,11 +195,15 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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.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.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.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 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= 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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -209,6 +219,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -235,6 +247,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -257,6 +271,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -274,9 +290,8 @@ 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/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 h1:OMPeiLomOQwe8+Ku4nwXsdOmrRw2vGUpP3XgLj3ojNw=
gopkg.in/evanphx/json-patch.v5 v5.9.11/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= 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.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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-5
View File
@@ -28,11 +28,6 @@ type StreamOutputVariant struct {
Framerate int `yaml:"framerate" json:"framerate"` Framerate int `yaml:"framerate" json:"framerate"`
// CPUUsageLevel represents a codec preset to configure CPU usage. // CPUUsageLevel represents a codec preset to configure CPU usage.
CPUUsageLevel int `json:"cpuUsageLevel"` CPUUsageLevel int `json:"cpuUsageLevel"`
// VideoCodec is the video compression format for this variant (e.g., "h264").
VideoCodec string `yaml:"videoCodec" json:"videoCodec,omitempty"`
// AudioCodec is the audio compression format for this variant (e.g., "aac").
AudioCodec string `yaml:"audioCodec" json:"audioCodec,omitempty"`
} }
// GetFramerate returns the framerate or default. // GetFramerate returns the framerate or default.
+4 -25
View File
@@ -1545,8 +1545,7 @@ paths:
$ref: '#/components/responses/204' $ref: '#/components/responses/204'
/admin/config/video/codec: /admin/config/video/codec:
post: post:
summary: Set video encoder type summary: Set video codec
description: "Set the global video encoder backend (e.g., software, nvenc, vaapi)."
operationId: SetVideoCodec operationId: SetVideoCodec
tags: ['Internal', 'Admin', 'Video'] tags: ['Internal', 'Admin', 'Video']
security: security:
@@ -1555,7 +1554,7 @@ paths:
$ref: '#/components/requestBodies/AdminConfigValue' $ref: '#/components/requestBodies/AdminConfigValue'
responses: responses:
'200': '200':
description: Video encoder updated description: Video codec updated
content: content:
application/json: application/json:
schema: schema:
@@ -3919,12 +3918,6 @@ components:
type: integer type: integer
cpuUsageLevel: cpuUsageLevel:
type: integer type: integer
videoCodec:
type: string
description: "Video compression format (e.g., h264). Defaults to h264."
audioCodec:
type: string
description: "Audio compression format (e.g., aac). Defaults to aac."
LatencyLevel: LatencyLevel:
type: object type: object
properties: properties:
@@ -4211,29 +4204,15 @@ components:
type: string type: string
webServerIP: webServerIP:
type: string type: string
videoEncoder: videoCodec:
type: string type: string
description: "The global video encoder type (e.g., software, nvenc, vaapi)"
videoServingEndpoint: videoServingEndpoint:
type: string type: string
s3: s3:
$ref: '#/components/schemas/S3Info' $ref: '#/components/schemas/S3Info'
federation: federation:
$ref: '#/components/schemas/AdminFederationConfig' $ref: '#/components/schemas/AdminFederationConfig'
supportedVideoEncoders: supportedCodecs:
type: array
items:
type: object
properties:
encoderType:
type: string
encoderDisplayName:
type: string
supportedVideoCodecs:
type: array
items:
type: string
supportedAudioCodecs:
type: array type: array
items: items:
type: string type: string
@@ -9,7 +9,7 @@ import (
) )
const ( const (
datastoreValuesVersion = 5 datastoreValuesVersion = 4
datastoreValueVersionKey = "DATA_STORE_VERSION" datastoreValueVersionKey = "DATA_STORE_VERSION"
) )
@@ -30,8 +30,6 @@ func migrateDatastoreValues(datastore *data.Datastore, configRepository ConfigRe
migrateToDatastoreValues3ServingEndpoint3(configRepository) migrateToDatastoreValues3ServingEndpoint3(configRepository)
case 3: case 3:
migrateToDatastoreValues4(datastore, configRepository) migrateToDatastoreValues4(datastore, configRepository)
case 4:
migrateToDatastoreValues5(datastore, configRepository)
default: default:
log.Fatalln("missing datastore values migration step") log.Fatalln("missing datastore values migration step")
} }
@@ -88,44 +86,3 @@ func migrateToDatastoreValues4(datastore *data.Datastore, configRepository Confi
log.Fatalln("error migrating admin password:", err) log.Fatalln("error migrating admin password:", err)
} }
} }
func migrateToDatastoreValues5(datastore *data.Datastore, configRepository ConfigRepository) {
// Migrate the old video_codec value (which was really an encoder name like "libx264",
// "h264_nvenc") to the new video_encoder key with the encoder type ("software", "nvenc").
// Inlined from transcoder.OldCodecNameToEncoderType to avoid an import cycle
// (core/transcoder imports persistence/configrepository).
oldCodecToEncoderType := map[string]string{
"libx264": "software",
"h264_nvenc": "nvenc",
"h264_vaapi": "vaapi",
"h264_qsv": "qsv",
"h264_omx": "omx",
"h264_v4l2m2m": "v4l2m2m",
"h264_videotoolbox": "videotoolbox",
}
oldCodecValue, _ := datastore.GetString("video_codec")
if oldCodecValue != "" {
encoderType, ok := oldCodecToEncoderType[oldCodecValue]
if !ok {
encoderType = "software"
}
if err := configRepository.SetVideoEncoder(encoderType); err != nil {
log.Errorln("error migrating video encoder setting:", err)
}
}
// Add default video and audio codec values to existing stream output variants.
variants := configRepository.GetStreamOutputVariants()
for i := range variants {
if variants[i].VideoCodec == "" {
variants[i].VideoCodec = "h264"
}
if variants[i].AudioCodec == "" {
variants[i].AudioCodec = "aac"
}
}
if err := configRepository.SetStreamOutputVariants(variants); err != nil {
log.Errorln("error migrating stream output variants with codec defaults:", err)
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ const (
externalActionsKey = "external_actions" externalActionsKey = "external_actions"
customStylesKey = "custom_styles" customStylesKey = "custom_styles"
customJavascriptKey = "custom_javascript" customJavascriptKey = "custom_javascript"
videoEncoderKey = "video_encoder" videoCodecKey = "video_codec"
blockedUsernamesKey = "blocked_usernames" blockedUsernamesKey = "blocked_usernames"
publicKeyKey = "public_key" publicKeyKey = "public_key"
privateKeyKey = "private_key" privateKeyKey = "private_key"
@@ -75,8 +75,8 @@ type ConfigRepository interface {
GetCustomStyles() string GetCustomStyles() string
SetCustomJavascript(styles string) error SetCustomJavascript(styles string) error
GetCustomJavascript() string GetCustomJavascript() string
SetVideoEncoder(encoder string) error SetVideoCodec(codec string) error
GetVideoEncoder() string GetVideoCodec() string
VerifySettings() error VerifySettings() error
FindHighestVideoQualityIndex(qualities []models.StreamOutputVariant) (int, bool) FindHighestVideoQualityIndex(qualities []models.StreamOutputVariant) (int, bool)
GetForbiddenUsernameList() []string GetForbiddenUsernameList() []string
@@ -623,19 +623,19 @@ func (r *SqlConfigRepository) GetCustomJavascript() string {
return style return style
} }
// SetVideoEncoder will set the encoder used for video encoding. // SetVideoCodec will set the codec used for video encoding.
func (r *SqlConfigRepository) SetVideoEncoder(encoder string) error { func (r *SqlConfigRepository) SetVideoCodec(codec string) error {
return r.datastore.SetString(videoEncoderKey, encoder) return r.datastore.SetString(videoCodecKey, codec)
} }
// GetVideoEncoder returns the encoder to use for transcoding video. // GetVideoCodec returns the codec to use for transcoding video.
func (r *SqlConfigRepository) GetVideoEncoder() string { func (r *SqlConfigRepository) GetVideoCodec() string {
encoder, err := r.datastore.GetString(videoEncoderKey) codec, err := r.datastore.GetString(videoCodecKey)
if encoder == "" || err != nil { if codec == "" || err != nil {
return "software" return "libx264" // Default value
} }
return encoder return codec
} }
// VerifySettings will perform a sanity check for specific settings values. // VerifySettings will perform a sanity check for specific settings values.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+5 -13
View File
@@ -51,20 +51,12 @@ func GetWebIndexTemplate() (*template.Template, error) {
return tmpl, err return tmpl, err
} }
//go:embed offline-init.mp4 //go:embed offline-v2.ts
var offlineInitSegment []byte var offlineVideoSegment []byte
// GetOfflineInitSegment will return the fMP4 init segment for the offline clip. // GetOfflineSegment will return the offline video segment data.
func GetOfflineInitSegment() []byte { func GetOfflineSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-init.mp4", offlineInitSegment) return getFileSystemStaticFileOrDefault("offline-v2.ts", offlineVideoSegment)
}
//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 //go:embed img/logo.png
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
* - Please do NOT modify this file. * - Please do NOT modify this file.
*/ */
const PACKAGE_VERSION = '2.12.14' const PACKAGE_VERSION = '2.13.2'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set() const activeClientIds = new Set()
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -20
View File
@@ -11,40 +11,24 @@ function install_ffmpeg() {
fi fi
FFMPEG_VER="8.0" FFMPEG_VER="8.0"
FFMPEG_BUILD_VERSION="20260223192056" FFMPEG_BUILD_VERSION="20251223233512"
FFMPEG_PATH="$(pwd)" FFMPEG_PATH="$(pwd)"
PATH=$FFMPEG_PATH:$PATH 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 if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}') ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
if [[ "$ffmpeg_version" == "${FFMPEG_VER}.1" ]]; then if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
return 0 return 0
else else
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg" mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
fi fi
fi fi
echo "Downloading ffmpeg v${FFMPEG_VER} release ${FFMPEG_BUILD_VERSION} (${FFMPEG_ARCHIVE})" echo "Downloading ffmpeg v${FFMPEG_VER} release ${FFMPEG_BUILD_VERSION}"
rm -rf ffmpeg.tar.gz rm -rf ffmpeg.tar.gz
curl -sL --fail "https://github.com/owncast/ffmpeg-builds/releases/download/${FFMPEG_BUILD_VERSION}/${FFMPEG_ARCHIVE}" --output ffmpeg.tar.gz >/dev/null 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
tar -xzf ffmpeg.tar.gz tar -xzf ffmpeg.tar.gz
rm -f ffmpeg.tar.gz rm -f ffmpeg.tar.gz
chmod +x ffmpeg chmod +x ffmpeg
+5 -5
View File
@@ -9,8 +9,8 @@
# Example: ./test/ocTestStream.sh ~/Downloads/*.mp4 rtmp://127.0.0.1/live/abc123 # Example: ./test/ocTestStream.sh ~/Downloads/*.mp4 rtmp://127.0.0.1/live/abc123
ffmpeg_execs=( 'ffmpeg' ) ffmpeg_execs=( 'ffmpeg' 'ffmpeg.exe' )
ffmpeg_paths=( '' './' ) ffmpeg_paths=( './' '../' '' )
for _ffmpeg_exec in "${ffmpeg_execs[@]}"; do for _ffmpeg_exec in "${ffmpeg_execs[@]}"; do
for _ffmpeg_path in "${ffmpeg_paths[@]}"; do for _ffmpeg_path in "${ffmpeg_paths[@]}"; do
@@ -43,14 +43,14 @@ if [[ -z "$ffmpeg_exec" ]]; then
else else
ffmpeg_version=$("$ffmpeg_exec" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}') 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 executable: $ffmpeg_exec ($ffmpeg_version)"
echo "ffmpeg path: $(readlink -f "$(which "$ffmpeg_exec")")" echo "ffmpeg path: $(readlink -f "$(which "$ffmpeg_exec")")"
fi fi
if [[ ${FILE_COUNT} -eq 0 ]]; then if [[ ${FILE_COUNT} -eq 0 ]]; then
echo "Streaming internal test video loop to $DESTINATION_HOST" echo "Streaming internal test video loop to $DESTINATION_HOST"
echo "...press ctrl+c to exit" echo "...press ctrl+c to exit"
command "${ffmpeg_exec}" -hide_banner -loglevel warning -nostdin -re -f lavfi \ command "${ffmpeg_exec}" -hide_banner -loglevel panic -nostdin -re -f lavfi \
-i "testsrc=size=1280x720:rate=60[out0];sine=frequency=400:sample_rate=48000[out1]" \ -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]" \ -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 \ -nal-hrd cbr \
@@ -91,7 +91,7 @@ else
echo "ERROR: File not found: $file" echo "ERROR: File not found: $file"
exit 1 exit 1
fi fi
done done
function finish { function finish {
+10 -12
View File
@@ -215,25 +215,23 @@ func GetCacheDurationSecondsForPath(filePath string) int {
defaultDaysCached := 30 defaultDaysCached := 30
if filename == "thumbnail.jpg" || filename == "preview.gif" || filename == "init.mp4" { if filename == "thumbnail.jpg" || filename == "preview.gif" {
// Thumbnails & preview gif re-generate during live // Thumbnails & preview gif re-generate during live
return 20 return 20
} } else if fileExtension == ".js" || fileExtension == ".css" {
// Cache javascript & CSS
if filename == "/" || fileExtension == "" { return 60 * 60 * 24 * defaultDaysCached
return 0 } else if fileExtension == ".ts" || fileExtension == ".woff2" {
}
switch fileExtension {
case ".m3u8", ".html":
return 0
case ".m4s", ".mp4", ".woff2":
// Cache video segments as long as you want. They can't change. // Cache video segments as long as you want. They can't change.
// This matters most for local hosting of segments for recordings // This matters most for local hosting of segments for recordings
// and not for live or 3rd party storage. // and not for live or 3rd party storage.
return 31557600 return 31557600
case ".js", ".css", ".jpg", ".png", ".gif", ".svg": } else if fileExtension == ".m3u8" {
return 0
} else if fileExtension == ".jpg" || fileExtension == ".png" || fileExtension == ".gif" || fileExtension == ".svg" {
return 60 * 60 * 24 * defaultDaysCached return 60 * 60 * 24 * defaultDaysCached
} else if fileExtension == ".html" || filename == "/" || fileExtension == "" {
return 0
} }
// Default cache length in seconds // Default cache length in seconds
+44 -24
View File
@@ -23,20 +23,20 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { serverConfig, setFieldInConfigState } = serverStatusData || {};
const { videoEncoder, supportedVideoEncoders } = serverConfig || {}; const { videoCodec, supportedCodecs } = serverConfig || {};
const { Title } = Typography; const { Title } = Typography;
const { Option } = Select; const { Option } = Select;
const [submitStatus, setSubmitStatus] = useState<StatusState>(null); const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
const { setMessage } = useContext(AlertMessageContext); const { setMessage } = useContext(AlertMessageContext);
const [selectedCodec, setSelectedCodec] = useState(videoEncoder); const [selectedCodec, setSelectedCodec] = useState(videoCodec);
const [pendingSaveCodec, setPendingSaveCodec] = useState(videoEncoder); const [pendingSaveCodec, setPendingSavecodec] = useState(videoCodec);
const [confirmPopupOpen, setConfirmPopupOpen] = React.useState(false); const [confirmPopupOpen, setConfirmPopupOpen] = React.useState(false);
let resetTimer = null; let resetTimer = null;
useEffect(() => { useEffect(() => {
setSelectedCodec(videoEncoder); setSelectedCodec(videoCodec);
}, [videoEncoder]); }, [videoCodec]);
const resetStates = () => { const resetStates = () => {
setSubmitStatus(null); setSubmitStatus(null);
@@ -45,13 +45,13 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
}; };
function handleChange(value) { function handleChange(value) {
setPendingSaveCodec(value); setPendingSavecodec(value);
setConfirmPopupOpen(true); setConfirmPopupOpen(true);
} }
async function save() { async function save() {
setSelectedCodec(pendingSaveCodec); setSelectedCodec(pendingSaveCodec);
setPendingSaveCodec(''); setPendingSavecodec('');
setConfirmPopupOpen(false); setConfirmPopupOpen(false);
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
@@ -59,8 +59,9 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
data: { value: pendingSaveCodec }, data: { value: pendingSaveCodec },
onSuccess: () => { onSuccess: () => {
setFieldInConfigState({ setFieldInConfigState({
fieldName: 'videoEncoder', fieldName: 'videoCodec',
value: pendingSaveCodec, value: pendingSaveCodec,
path: 'videoSettings',
}); });
setSubmitStatus( setSubmitStatus(
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.videoCodecUpdated)), createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.videoCodecUpdated)),
@@ -69,7 +70,7 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
if (serverStatusData.online) { if (serverStatusData.online) {
setMessage( setMessage(
'Your encoder setting will take effect the next time you begin a live stream.', 'Your latency buffer setting will take effect the next time you begin a live stream.',
); );
} }
}, },
@@ -81,31 +82,50 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
}); });
} }
const items = (supportedVideoEncoders || []).map(encoder => ( const items = supportedCodecs.map(codec => {
<Option key={encoder.encoderType} value={encoder.encoderType}> let title = codec;
{encoder.encoderDisplayName} if (title === 'libx264') {
</Option> title = 'Default (libx264)';
)); } else if (title === 'h264_nvenc') {
title = 'NVIDIA GPU acceleration';
} else if (title === 'h264_vaapi') {
title = 'VA-API hardware encoding';
} else if (title === 'h264_qsv') {
title = 'Intel QuickSync';
} else if (title === 'h264_v4l2m2m') {
title = 'Video4Linux hardware encoding';
} else if (title === 'h264_omx') {
title = 'OpenMax (omx) for Raspberry Pi';
} else if (title === 'h264_videotoolbox') {
title = 'Apple VideoToolbox (hardware)';
}
return (
<Option key={codec} value={codec}>
{title}
</Option>
);
});
let description = ''; let description = '';
if (selectedCodec === 'software') { if (selectedCodec === 'libx264') {
description = description =
'Software encoding uses your CPU. This is the default and generally the only working choice for shared VPS environments. This is likely what you should be using unless you know you have set up other options.'; 'libx264 is the default codec and generally the only working choice for shared VPS environments. This is likely what you should be using unless you know you have set up other options.';
} else if (selectedCodec === 'nvenc') { } else if (selectedCodec === 'h264_nvenc') {
description = description =
'You can use your NVIDIA GPU for encoding if you have a modern NVIDIA card with encoding cores.'; 'You can use your NVIDIA GPU for encoding if you have a modern NVIDIA card with encoding cores.';
} else if (selectedCodec === 'vaapi') { } else if (selectedCodec === 'h264_vaapi') {
description = description =
'VA-API may be supported by your NVIDIA proprietary drivers, Mesa open-source drivers for AMD or Intel graphics.'; 'VA-API may be supported by your NVIDIA proprietary drivers, Mesa open-source drivers for AMD or Intel graphics.';
} else if (selectedCodec === 'qsv') { } else if (selectedCodec === 'h264_qsv') {
description = description =
"Quick Sync Video is Intel's brand for its dedicated video encoding and decoding hardware. It may be an option if you have a modern Intel CPU with integrated graphics."; "Quick Sync Video is Intel's brand for its dedicated video encoding and decoding hardware. It may be an option if you have a modern Intel CPU with integrated graphics.";
} else if (selectedCodec === 'v4l2m2m') { } else if (selectedCodec === 'h264_v4l2m2m') {
description = description =
'Video4Linux is an interface to multiple different hardware encoding platforms such as Intel and AMD.'; 'Video4Linux is an interface to multiple different hardware encoding platforms such as Intel and AMD.';
} else if (selectedCodec === 'omx') { } else if (selectedCodec === 'h264_omx') {
description = 'OpenMax is an encoder most often used with a Raspberry Pi.'; description = 'OpenMax is a codec most often used with a Raspberry Pi.';
} else if (selectedCodec === 'videotoolbox') { } else if (selectedCodec === 'h264_videotoolbox') {
description = description =
'Apple VideoToolbox is a low-level framework that provides direct access to hardware encoders and decoders.'; 'Apple VideoToolbox is a low-level framework that provides direct access to hardware encoders and decoders.';
} }
@@ -131,7 +151,7 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
</div> </div>
<div className="segment-slider-container"> <div className="segment-slider-container">
<Popconfirm <Popconfirm
title={`Are you sure you want to change your video encoder to ${pendingSaveCodec} and understand what this means?`} title={`Are you sure you want to change your video codec to ${pendingSaveCodec} and understand what this means?`}
open={confirmPopupOpen} open={confirmPopupOpen}
placement="leftBottom" placement="leftBottom"
onConfirm={save} onConfirm={save}
+93 -93
View File
@@ -87,10 +87,10 @@
"@types/sanitize-html": "^2.9.0", "@types/sanitize-html": "^2.9.0",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/video.js": "^7.3.50", "@types/video.js": "^7.3.50",
"@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/eslint-plugin": "8.58.1",
"@typescript-eslint/parser": "8.58.0", "@typescript-eslint/parser": "8.58.1",
"babel-loader": "10.1.1", "babel-loader": "10.1.1",
"chromatic": "16.0.0", "chromatic": "16.1.0",
"css-loader": "7.1.4", "css-loader": "7.1.4",
"cypress": "^15.0.0", "cypress": "^15.0.0",
"eslint": "8.57.1", "eslint": "8.57.1",
@@ -117,7 +117,7 @@
"npm": "^11.0.0", "npm": "^11.0.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"prettier": "3.8.1", "prettier": "3.8.1",
"sass": "1.98.0", "sass": "1.99.0",
"storybook": "^9.1.13", "storybook": "^9.1.13",
"style-dictionary": "3.9.2", "style-dictionary": "3.9.2",
"style-loader": "4.0.0", "style-loader": "4.0.0",
@@ -5868,9 +5868,9 @@
} }
}, },
"node_modules/@mermaid-js/parser": { "node_modules/@mermaid-js/parser": {
"version": "1.0.1", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz",
"integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -9004,17 +9004,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz",
"integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/scope-manager": "8.58.1",
"@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/type-utils": "8.58.1",
"@typescript-eslint/utils": "8.58.0", "@typescript-eslint/utils": "8.58.1",
"@typescript-eslint/visitor-keys": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.1",
"ignore": "^7.0.5", "ignore": "^7.0.5",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0"
@@ -9027,22 +9027,22 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.58.0", "@typescript-eslint/parser": "^8.58.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0" "typescript": ">=4.8.4 <6.1.0"
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz",
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/scope-manager": "8.58.1",
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.1",
"@typescript-eslint/visitor-keys": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.1",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@@ -9058,14 +9058,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz",
"integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/tsconfig-utils": "^8.58.1",
"@typescript-eslint/types": "^8.58.0", "@typescript-eslint/types": "^8.58.1",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@@ -9080,14 +9080,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz",
"integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"@typescript-eslint/visitor-keys": "8.58.0" "@typescript-eslint/visitor-keys": "8.58.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9098,9 +9098,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz",
"integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -9115,15 +9115,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz",
"integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.1",
"@typescript-eslint/utils": "8.58.0", "@typescript-eslint/utils": "8.58.1",
"debug": "^4.4.3", "debug": "^4.4.3",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0"
}, },
@@ -9140,9 +9140,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz",
"integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -9154,16 +9154,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz",
"integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/project-service": "8.58.1",
"@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.1",
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"@typescript-eslint/visitor-keys": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.1",
"debug": "^4.4.3", "debug": "^4.4.3",
"minimatch": "^10.2.2", "minimatch": "^10.2.2",
"semver": "^7.7.3", "semver": "^7.7.3",
@@ -9182,16 +9182,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz",
"integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.9.1", "@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/scope-manager": "8.58.1",
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"@typescript-eslint/typescript-estree": "8.58.0" "@typescript-eslint/typescript-estree": "8.58.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9206,13 +9206,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.58.0", "version": "8.58.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz",
"integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.1",
"eslint-visitor-keys": "^5.0.0" "eslint-visitor-keys": "^5.0.0"
}, },
"engines": { "engines": {
@@ -12047,9 +12047,9 @@
} }
}, },
"node_modules/chromatic": { "node_modules/chromatic": {
"version": "16.0.0", "version": "16.1.0",
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-16.0.0.tgz", "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-16.1.0.tgz",
"integrity": "sha512-O81RVGDXXoreNeG894hjaUx08xep+C/BA6aJNMZkwSjH7Lln8IweZekBpBEoQPNNpjmHyZvcTIwN/aGitdK53Q==", "integrity": "sha512-7xyOUXe8jfY6c+g9S/U/cbvzVXMjdhh9cVZYvDp8dDGonjiKSbWO1dzWApdYHIiF2xj29pvWQB7N5LT6967IDw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -13029,9 +13029,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/cypress": { "node_modules/cypress": {
"version": "15.13.0", "version": "15.13.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-15.13.0.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.13.1.tgz",
"integrity": "sha512-hJ9sY++TUC/HlUzHVJpIrDyqKMjlhx5PTXl/A7eA91JNEtUWkJAqefQR5mo9AtLra/9+m+JJaMg2U5Qd0a74Fw==", "integrity": "sha512-jLkgo75zlwo7PhXp0XJot+zIfFSDzN1SvTml6Xf3ETM1XHRWnH3Q4LAR3orCo/BsnxPnhjG3m5HYSvn9DAtwBg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -22497,15 +22497,15 @@
} }
}, },
"node_modules/mermaid": { "node_modules/mermaid": {
"version": "11.13.0", "version": "11.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
"integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.1", "@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.2", "@iconify/utils": "^3.0.2",
"@mermaid-js/parser": "^1.0.1", "@mermaid-js/parser": "^1.1.0",
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@upsetjs/venn.js": "^2.0.0", "@upsetjs/venn.js": "^2.0.0",
"cytoscape": "^3.33.1", "cytoscape": "^3.33.1",
@@ -23455,9 +23455,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/msw": { "node_modules/msw": {
"version": "2.12.14", "version": "2.13.2",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.12.14.tgz", "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz",
"integrity": "sha512-4KXa4nVBIBjbDbd7vfQNuQ25eFxug0aropCQFoI0JdOBuJWamkT1yLVIWReFI8SiTRc+H1hKzaNk+cLk2N9rtQ==", "integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -23500,9 +23500,9 @@
} }
}, },
"node_modules/msw-storybook-addon": { "node_modules/msw-storybook-addon": {
"version": "2.0.6", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.6.tgz", "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.7.tgz",
"integrity": "sha512-ExCwDbcJoM2V3iQU+fZNp+axVfNc7DWMRh4lyTXebDO8IbpUNYKGFUrA8UqaeWiRGKVuS7+fU+KXEa9b0OP6uA==", "integrity": "sha512-TGmlxXy2TsaB6QcClVKRxqvay5f93xoLguHOihRFQ+gIEIyiyvcoQjkEeuOe7Y9qvddzGB1LyFomzPo9/EpnuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -23513,22 +23513,22 @@
} }
}, },
"node_modules/msw/node_modules/tldts": { "node_modules/msw/node_modules/tldts": {
"version": "7.0.27", "version": "7.0.28",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz",
"integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tldts-core": "^7.0.27" "tldts-core": "^7.0.28"
}, },
"bin": { "bin": {
"tldts": "bin/cli.js" "tldts": "bin/cli.js"
} }
}, },
"node_modules/msw/node_modules/tldts-core": { "node_modules/msw/node_modules/tldts-core": {
"version": "7.0.27", "version": "7.0.28",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz",
"integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -30108,9 +30108,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.98.0", "version": "1.99.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz",
"integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -32733,19 +32733,19 @@
} }
}, },
"node_modules/ts-jest": { "node_modules/ts-jest": {
"version": "29.4.6", "version": "29.4.9",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz",
"integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bs-logger": "^0.2.6", "bs-logger": "^0.2.6",
"fast-json-stable-stringify": "^2.1.0", "fast-json-stable-stringify": "^2.1.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.9",
"json5": "^2.2.3", "json5": "^2.2.3",
"lodash.memoize": "^4.1.2", "lodash.memoize": "^4.1.2",
"make-error": "^1.3.6", "make-error": "^1.3.6",
"semver": "^7.7.3", "semver": "^7.7.4",
"type-fest": "^4.41.0", "type-fest": "^4.41.0",
"yargs-parser": "^21.1.1" "yargs-parser": "^21.1.1"
}, },
@@ -32762,7 +32762,7 @@
"babel-jest": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0",
"jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0",
"jest-util": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0",
"typescript": ">=4.3 <6" "typescript": ">=4.3 <7"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@babel/core": { "@babel/core": {
+4 -4
View File
@@ -105,10 +105,10 @@
"@types/sanitize-html": "^2.9.0", "@types/sanitize-html": "^2.9.0",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/video.js": "^7.3.50", "@types/video.js": "^7.3.50",
"@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/eslint-plugin": "8.58.1",
"@typescript-eslint/parser": "8.58.0", "@typescript-eslint/parser": "8.58.1",
"babel-loader": "10.1.1", "babel-loader": "10.1.1",
"chromatic": "16.0.0", "chromatic": "16.1.0",
"css-loader": "7.1.4", "css-loader": "7.1.4",
"cypress": "^15.0.0", "cypress": "^15.0.0",
"eslint": "8.57.1", "eslint": "8.57.1",
@@ -135,7 +135,7 @@
"npm": "^11.0.0", "npm": "^11.0.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"prettier": "3.8.1", "prettier": "3.8.1",
"sass": "1.98.0", "sass": "1.99.0",
"storybook": "^9.1.13", "storybook": "^9.1.13",
"style-dictionary": "3.9.2", "style-dictionary": "3.9.2",
"style-loader": "4.0.0", "style-loader": "4.0.0",
+2 -12
View File
@@ -66,15 +66,6 @@ export interface VideoVariant {
scaledHeight: number; scaledHeight: number;
name: string; name: string;
videoCodec: string;
audioCodec: string;
}
export interface EncoderCodecSupport {
encoderType: string;
encoderDisplayName: string;
supportedVideoCodecs: string[];
} }
export interface VideoSettingsFields { export interface VideoSettingsFields {
latencyLevel: number; latencyLevel: number;
@@ -156,9 +147,8 @@ export interface ConfigDetails {
socketHostOverride: string; socketHostOverride: string;
videoServingEndpoint: string; videoServingEndpoint: string;
yp: ConfigDirectoryFields; yp: ConfigDirectoryFields;
supportedVideoEncoders: EncoderCodecSupport[]; supportedCodecs: string[];
supportedAudioCodecs: string[]; videoCodec: string;
videoEncoder: string;
forbiddenUsernames: string[]; forbiddenUsernames: string[];
suggestedUsernames: string[]; suggestedUsernames: string[];
chatDisabled: boolean; chatDisabled: boolean;
-2
View File
@@ -251,8 +251,6 @@ export const DEFAULT_VARIANT_STATE: VideoVariant = {
scaledHeight: null, scaledHeight: null,
scaledWidth: null, scaledWidth: null,
name: '', name: '',
videoCodec: '',
audioCodec: '',
}; };
export const FIELD_PROPS_DISABLE_CHAT = { export const FIELD_PROPS_DISABLE_CHAT = {
+2 -3
View File
@@ -64,9 +64,8 @@ const initialServerConfigState: ConfigDetails = {
discord: { enabled: false, webhook: '', goLiveMessage: '' }, discord: { enabled: false, webhook: '', goLiveMessage: '' },
}, },
externalActions: [], externalActions: [],
supportedVideoEncoders: [], supportedCodecs: [],
supportedAudioCodecs: [], videoCodec: '',
videoEncoder: '',
forbiddenUsernames: [], forbiddenUsernames: [],
suggestedUsernames: [], suggestedUsernames: [],
chatDisabled: false, chatDisabled: false,
+5 -7
View File
@@ -740,9 +740,7 @@ func SetChatDisabled(w http.ResponseWriter, r *http.Request) {
webutils.WriteSimpleResponse(w, true, "chat disabled status updated") webutils.WriteSimpleResponse(w, true, "chat disabled status updated")
} }
// SetVideoCodec will change the encoder used for video encoding. // SetVideoCodec will change the codec used for video encoding.
// The endpoint name is kept for backward compatibility but the value
// is now an encoder type (e.g., "software", "nvenc") not an ffmpeg codec name.
func SetVideoCodec(w http.ResponseWriter, r *http.Request) { func SetVideoCodec(w http.ResponseWriter, r *http.Request) {
if !requirePOST(w, r) { if !requirePOST(w, r) {
return return
@@ -750,17 +748,17 @@ func SetVideoCodec(w http.ResponseWriter, r *http.Request) {
configValue, success := getValueFromRequest(w, r) configValue, success := getValueFromRequest(w, r)
if !success { if !success {
webutils.WriteSimpleResponse(w, false, "unable to change video encoder") webutils.WriteSimpleResponse(w, false, "unable to change video codec")
return return
} }
configRepository := configrepository.Get() configRepository := configrepository.Get()
if err := configRepository.SetVideoEncoder(configValue.Value.(string)); err != nil { if err := configRepository.SetVideoCodec(configValue.Value.(string)); err != nil {
webutils.WriteSimpleResponse(w, false, "unable to update video encoder") webutils.WriteSimpleResponse(w, false, "unable to update codec")
return return
} }
webutils.WriteSimpleResponse(w, true, "video encoder updated") webutils.WriteSimpleResponse(w, true, "video codec updated")
} }
// SetExternalActions will set the 3rd party actions for the web interface. // SetExternalActions will set the 3rd party actions for the web interface.
+34 -38
View File
@@ -34,8 +34,6 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
CPUUsageLevel: variant.CPUUsageLevel, CPUUsageLevel: variant.CPUUsageLevel,
ScaledWidth: variant.ScaledWidth, ScaledWidth: variant.ScaledWidth,
ScaledHeight: variant.ScaledHeight, ScaledHeight: variant.ScaledHeight,
VideoCodec: variant.VideoCodec,
AudioCodec: variant.AudioCodec,
}) })
} }
response := serverConfigAdminResponse{ response := serverConfigAdminResponse{
@@ -79,13 +77,12 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
Enabled: configRepository.GetDirectoryEnabled(), Enabled: configRepository.GetDirectoryEnabled(),
InstanceURL: configRepository.GetServerURL(), InstanceURL: configRepository.GetServerURL(),
}, },
S3: configRepository.GetS3Config(), S3: configRepository.GetS3Config(),
ExternalActions: configRepository.GetExternalActions(), ExternalActions: configRepository.GetExternalActions(),
SupportedVideoEncoders: transcoder.GetAvailableVideoEncoders(ffmpeg), SupportedCodecs: transcoder.GetCodecs(ffmpeg),
SupportedAudioCodecs: transcoder.GetAvailableAudioCodecs(ffmpeg), VideoCodec: configRepository.GetVideoCodec(),
VideoEncoder: configRepository.GetVideoEncoder(), ForbiddenUsernames: usernameBlocklist,
ForbiddenUsernames: usernameBlocklist, SuggestedUsernames: usernameSuggestions,
SuggestedUsernames: usernameSuggestions,
Federation: federationConfigResponse{ Federation: federationConfigResponse{
Enabled: configRepository.GetFederationEnabled(), Enabled: configRepository.GetFederationEnabled(),
IsPrivate: configRepository.GetFederationIsPrivate(), IsPrivate: configRepository.GetFederationIsPrivate(),
@@ -109,35 +106,34 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
} }
type serverConfigAdminResponse struct { type serverConfigAdminResponse struct {
InstanceDetails webConfigResponse `json:"instanceDetails"` InstanceDetails webConfigResponse `json:"instanceDetails"`
Notifications notificationsConfigResponse `json:"notifications"` Notifications notificationsConfigResponse `json:"notifications"`
YP yp `json:"yp"` YP yp `json:"yp"`
FFmpegPath string `json:"ffmpegPath"` FFmpegPath string `json:"ffmpegPath"`
AdminPassword string `json:"adminPassword"` AdminPassword string `json:"adminPassword"`
SocketHostOverride string `json:"socketHostOverride,omitempty"` SocketHostOverride string `json:"socketHostOverride,omitempty"`
WebServerIP string `json:"webServerIP"` WebServerIP string `json:"webServerIP"`
VideoEncoder string `json:"videoEncoder"` VideoCodec string `json:"videoCodec"`
VideoServingEndpoint string `json:"videoServingEndpoint"` VideoServingEndpoint string `json:"videoServingEndpoint"`
S3 models.S3 `json:"s3"` S3 models.S3 `json:"s3"`
Federation federationConfigResponse `json:"federation"` Federation federationConfigResponse `json:"federation"`
SupportedVideoEncoders []transcoder.EncoderCodecSupport `json:"supportedVideoEncoders"` SupportedCodecs []string `json:"supportedCodecs"`
SupportedAudioCodecs []string `json:"supportedAudioCodecs"` ExternalActions []models.ExternalAction `json:"externalActions"`
ExternalActions []models.ExternalAction `json:"externalActions"` ForbiddenUsernames []string `json:"forbiddenUsernames"`
ForbiddenUsernames []string `json:"forbiddenUsernames"` SuggestedUsernames []string `json:"suggestedUsernames"`
SuggestedUsernames []string `json:"suggestedUsernames"` StreamKeys []generated.StreamKey `json:"streamKeys"`
StreamKeys []generated.StreamKey `json:"streamKeys"` VideoSettings videoSettings `json:"videoSettings"`
VideoSettings videoSettings `json:"videoSettings"` RTMPServerPort int `json:"rtmpServerPort"`
RTMPServerPort int `json:"rtmpServerPort"` WebServerPort int `json:"webServerPort"`
WebServerPort int `json:"webServerPort"` ChatDisabled bool `json:"chatDisabled"`
ChatDisabled bool `json:"chatDisabled"` ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"`
ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"` ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"` ChatSpamProtectionEnabled bool `json:"chatSpamProtectionEnabled"`
ChatSpamProtectionEnabled bool `json:"chatSpamProtectionEnabled"` ChatSlurFilterEnabled bool `json:"chatSlurFilterEnabled"`
ChatSlurFilterEnabled bool `json:"chatSlurFilterEnabled"` ChatRequireAuthentication bool `json:"chatRequireAuthentication"`
ChatRequireAuthentication bool `json:"chatRequireAuthentication"` DisableSearchIndexing bool `json:"disableSearchIndexing"`
DisableSearchIndexing bool `json:"disableSearchIndexing"` StreamKeyOverridden bool `json:"streamKeyOverridden"`
StreamKeyOverridden bool `json:"streamKeyOverridden"` HideViewerCount bool `json:"hideViewerCount"`
HideViewerCount bool `json:"hideViewerCount"`
} }
type videoSettings struct { type videoSettings struct {
@@ -105,20 +105,13 @@ type AdminServerConfig struct {
StreamKeyOverridden *bool `json:"streamKeyOverridden,omitempty"` StreamKeyOverridden *bool `json:"streamKeyOverridden,omitempty"`
StreamKeys *[]StreamKey `json:"streamKeys,omitempty"` StreamKeys *[]StreamKey `json:"streamKeys,omitempty"`
SuggestedUsernames *[]string `json:"suggestedUsernames,omitempty"` SuggestedUsernames *[]string `json:"suggestedUsernames,omitempty"`
SupportedAudioCodecs *[]string `json:"supportedAudioCodecs,omitempty"` SupportedCodecs *[]string `json:"supportedCodecs,omitempty"`
SupportedVideoEncoders *[]struct { VideoCodec *string `json:"videoCodec,omitempty"`
EncoderDisplayName *string `json:"encoderDisplayName,omitempty"` VideoServingEndpoint *string `json:"videoServingEndpoint,omitempty"`
EncoderType *string `json:"encoderType,omitempty"` VideoSettings *AdminVideoSettings `json:"videoSettings,omitempty"`
SupportedVideoCodecs *[]string `json:"supportedVideoCodecs,omitempty"` WebServerIP *string `json:"webServerIP,omitempty"`
} `json:"supportedVideoEncoders,omitempty"` WebServerPort *int `json:"webServerPort,omitempty"`
Yp *AdminYPInfo `json:"yp,omitempty"`
// VideoEncoder The global video encoder type (e.g., software, nvenc, vaapi)
VideoEncoder *string `json:"videoEncoder,omitempty"`
VideoServingEndpoint *string `json:"videoServingEndpoint,omitempty"`
VideoSettings *AdminVideoSettings `json:"videoSettings,omitempty"`
WebServerIP *string `json:"webServerIP,omitempty"`
WebServerPort *int `json:"webServerPort,omitempty"`
Yp *AdminYPInfo `json:"yp,omitempty"`
} }
// AdminStatus defines model for AdminStatus. // AdminStatus defines model for AdminStatus.
@@ -500,10 +493,7 @@ type StreamKey struct {
// StreamOutputVariant defines model for StreamOutputVariant. // StreamOutputVariant defines model for StreamOutputVariant.
type StreamOutputVariant struct { type StreamOutputVariant struct {
AudioBitrate *int `json:"audioBitrate,omitempty"` AudioBitrate *int `json:"audioBitrate,omitempty"`
// AudioCodec Audio compression format (e.g., aac). Defaults to aac.
AudioCodec *string `json:"audioCodec,omitempty"`
AudioPassthrough *bool `json:"audioPassthrough,omitempty"` AudioPassthrough *bool `json:"audioPassthrough,omitempty"`
CpuUsageLevel *int `json:"cpuUsageLevel,omitempty"` CpuUsageLevel *int `json:"cpuUsageLevel,omitempty"`
Framerate *int `json:"framerate,omitempty"` Framerate *int `json:"framerate,omitempty"`
@@ -511,9 +501,6 @@ type StreamOutputVariant struct {
ScaledHeight *int `json:"scaledHeight,omitempty"` ScaledHeight *int `json:"scaledHeight,omitempty"`
ScaledWidth *int `json:"scaledWidth,omitempty"` ScaledWidth *int `json:"scaledWidth,omitempty"`
VideoBitrate *int `json:"videoBitrate,omitempty"` VideoBitrate *int `json:"videoBitrate,omitempty"`
// VideoCodec Video compression format (e.g., h264). Defaults to h264.
VideoCodec *string `json:"videoCodec,omitempty"`
VideoPassthrough *bool `json:"videoPassthrough,omitempty"` VideoPassthrough *bool `json:"videoPassthrough,omitempty"`
} }
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -18,8 +18,7 @@ import (
// HandleHLSRequest will manage all requests to HLS content. // HandleHLSRequest will manage all requests to HLS content.
func HandleHLSRequest(w http.ResponseWriter, r *http.Request) { func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
// Sanity check to limit requests to HLS file types. // Sanity check to limit requests to HLS file types.
ext := filepath.Ext(r.URL.Path) if filepath.Ext(r.URL.Path) != ".m3u8" && filepath.Ext(r.URL.Path) != ".ts" {
if ext != ".m3u8" && ext != ".m4s" && ext != ".mp4" {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }