Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d7a47293c
|
||
|
|
46b9c8bd2e
|
||
|
|
50e14adac2
|
@@ -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.2'
|
go-version: '1.26.1'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install Google Chrome
|
- name: Install Google Chrome
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.26.2'
|
go-version: '1.26.1'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.26.2'
|
go-version: '1.26.1'
|
||||||
cache: true
|
cache: true
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
|
|||||||
@@ -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.2'
|
go-version: '1.26.1'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
- name: Comment on PR about missing checklist
|
- name: Comment on PR about missing checklist
|
||||||
if: ${{ always() && steps.validate.outputs.missing_checklist == 'true' }}
|
if: ${{ steps.validate.outputs.missing_checklist == 'true' }}
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ func GetDefaults() Defaults {
|
|||||||
VideoBitrate: 1200,
|
VideoBitrate: 1200,
|
||||||
Framerate: 24,
|
Framerate: 24,
|
||||||
CPUUsageLevel: 2,
|
CPUUsageLevel: 2,
|
||||||
|
VideoCodec: "h264",
|
||||||
|
AudioCodec: "aac",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
BitstreamFilters() []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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AACCodec) BitstreamFilters() []string {
|
||||||
|
return []string{
|
||||||
|
// "aac_adtstoasc", // Required for fMP4 segments, not needed yet.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var audioCodecRegistry = map[string]AudioCodec{
|
||||||
|
audioCodecAAC: &AACCodec{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAudioCodec(name string) AudioCodec {
|
||||||
|
if codec, ok := audioCodecRegistry[name]; ok {
|
||||||
|
return codec
|
||||||
|
}
|
||||||
|
return &AACCodec{}
|
||||||
|
}
|
||||||
@@ -1,549 +0,0 @@
|
|||||||
//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{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
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{}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
codec Codec
|
encoder Encoder
|
||||||
|
|
||||||
stdin *io.PipeReader
|
stdin *io.PipeReader
|
||||||
|
|
||||||
@@ -66,6 +66,9 @@ 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.
|
||||||
@@ -127,7 +130,7 @@ func (t *Transcoder) Start(shouldLog bool) {
|
|||||||
|
|
||||||
flags := t.getFlags()
|
flags := t.getFlags()
|
||||||
if shouldLog {
|
if shouldLog {
|
||||||
log.Infof("Processing video using codec %s with %d output qualities configured.", t.codec.DisplayName(), len(t.variants))
|
log.Infof("Processing video using encoder %s with %d output qualities configured.", t.encoder.DisplayName(), len(t.variants))
|
||||||
}
|
}
|
||||||
createVariantDirectories()
|
createVariantDirectories()
|
||||||
command := flags.String()
|
command := flags.String()
|
||||||
@@ -167,7 +170,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 codec of", t.codec.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 encoder of", t.encoder.Name(), "https://owncast.online/docs/codecs/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +230,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.codec.GlobalFlags()...)
|
ffmpegFlags = append(ffmpegFlags, t.encoder.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
|
||||||
@@ -247,11 +250,10 @@ func (t *Transcoder) getFlags() *execInfo {
|
|||||||
ffmpegFlags = append(ffmpegFlags, []string{
|
ffmpegFlags = append(ffmpegFlags, []string{
|
||||||
"-segment_format_options", "mpegts_flags=mpegts_copyts=1",
|
"-segment_format_options", "mpegts_flags=mpegts_copyts=1",
|
||||||
}...)
|
}...)
|
||||||
ffmpegFlags = append(ffmpegFlags, t.codec.ExtraArguments()...)
|
ffmpegFlags = append(ffmpegFlags, t.encoder.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
|
||||||
@@ -278,6 +280,9 @@ 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
|
||||||
@@ -318,7 +323,7 @@ func NewTranscoder() *Transcoder {
|
|||||||
|
|
||||||
transcoder.currentStreamOutputSettings = configRepository.GetStreamOutputVariants()
|
transcoder.currentStreamOutputSettings = configRepository.GetStreamOutputVariants()
|
||||||
transcoder.currentLatencyLevel = configRepository.GetStreamLatencyLevel()
|
transcoder.currentLatencyLevel = configRepository.GetStreamLatencyLevel()
|
||||||
transcoder.codec = getCodec(configRepository.GetVideoCodec())
|
transcoder.encoder = getEncoder(configRepository.GetVideoEncoder())
|
||||||
transcoder.segmentOutputPath = config.HLSStoragePath
|
transcoder.segmentOutputPath = config.HLSStoragePath
|
||||||
transcoder.playlistOutputPath = config.HLSStoragePath
|
transcoder.playlistOutputPath = config.HLSStoragePath
|
||||||
|
|
||||||
@@ -337,26 +342,28 @@ 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(t.codec.Scaler()),
|
v.getScalingString(profile.Scaler),
|
||||||
}
|
}
|
||||||
if t.codec.ExtraFilters() != "" {
|
if profile.ExtraFilters != "" {
|
||||||
filters = append(filters, t.codec.ExtraFilters())
|
filters = append(filters, profile.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 t.codec.ExtraFilters() != "" && !v.isVideoPassthrough {
|
} else if profile.ExtraFilters != "" && !v.isVideoPassthrough {
|
||||||
variantEncoderCommands = append(variantEncoderCommands, []string{
|
variantEncoderCommands = append(variantEncoderCommands, []string{
|
||||||
"-filter:v:" + strconv.Itoa(v.index), t.codec.ExtraFilters(),
|
"-filter:v:" + strconv.Itoa(v.index), profile.ExtraFilters,
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
preset := t.codec.GetPresetForLevel(v.cpuUsageLevel)
|
preset := t.encoder.GetPresetForLevel(v.cpuUsageLevel)
|
||||||
if preset != "" {
|
if preset != "" {
|
||||||
variantEncoderCommands = append(variantEncoderCommands, []string{
|
variantEncoderCommands = append(variantEncoderCommands, []string{
|
||||||
"-preset", preset,
|
"-preset", preset,
|
||||||
@@ -415,18 +422,25 @@ 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), t.codec.Name(), // Video codec used for this variant
|
fmt.Sprintf("-c:v:%d", v.index), ffmpegEncoder, // Video encoder 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
|
||||||
}
|
}
|
||||||
@@ -455,13 +469,17 @@ func (v *HLSVariant) getAudioQualityString() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// libfdk_aac is not a part of every ffmpeg install, so use "aac" instead
|
flags := []string{
|
||||||
encoderCodec := "aac"
|
|
||||||
return []string{
|
|
||||||
"-map", "a:0?",
|
"-map", "a:0?",
|
||||||
fmt.Sprintf("-c:a:%d", v.index), encoderCodec,
|
fmt.Sprintf("-c:a:%d", v.index), v.audioCodec.FFmpegEncoderName(),
|
||||||
fmt.Sprintf("-b:a:%d", v.index), v.audioBitrate,
|
fmt.Sprintf("-b:a:%d", v.index), v.audioBitrate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, bsf := range v.audioCodec.BitstreamFilters() {
|
||||||
|
flags = append(flags, fmt.Sprintf("-bsf:a:%d", v.index), bsf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddVariant adds a new HLS variant to include in the output.
|
// AddVariant adds a new HLS variant to include in the output.
|
||||||
@@ -495,7 +513,7 @@ func (t *Transcoder) SetInternalHTTPPort(port string) {
|
|||||||
t.internalListenerPort = port
|
t.internalListenerPort = port
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCodec will set the codec to be used for the transocder.
|
// SetEncoder will set the encoder to be used for the transcoder.
|
||||||
func (t *Transcoder) SetCodec(codecName string) {
|
func (t *Transcoder) SetEncoder(encoderType string) {
|
||||||
t.codec = getCodec(codecName)
|
t.encoder = getEncoder(encoderType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegNvencCommand(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdoieGg")
|
transcoder.SetIdentifier("jdoieGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("nvenc")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegOmxCommand(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdFsdfzGg")
|
transcoder.SetIdentifier("jdFsdfzGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("omx")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_omx -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdofFGg")
|
transcoder.SetIdentifier("jdofFGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("qsv")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -init_hw_device qsv=hw -filter_hw_device hw -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_qsv -pix_fmt:v:0 qsv -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=qsv -preset medium -map v:0 -c:v:1 h264_qsv -pix_fmt:v:1 qsv -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=qsv -preset veryslow -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset veryfast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegVaapiCommand(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdofFGg")
|
transcoder.SetIdentifier("jdofFGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("vaapi")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -pix_fmt:v:0 vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 hwupload=extra_hw_frames=64,format=vaapi -preset veryfast -map v:0 -c:v:1 h264_vaapi -pix_fmt:v:1 vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 hwupload=extra_hw_frames=64,format=vaapi -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdFsdfzGg")
|
transcoder.SetIdentifier("jdFsdfzGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("videotoolbox")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -pix_fmt:v:0 nv12 -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -pix_fmt:v:1 nv12 -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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,7 +9,6 @@ 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")
|
||||||
@@ -17,12 +16,14 @@ func TestFFmpegx264Command(t *testing.T) {
|
|||||||
transcoder.SetOutputPath("fakeOutput")
|
transcoder.SetOutputPath("fakeOutput")
|
||||||
transcoder.SetIdentifier("jdofFGg")
|
transcoder.SetIdentifier("jdofFGg")
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
transcoder.SetInternalHTTPPort("8123")
|
||||||
transcoder.SetCodec(codec.Name())
|
transcoder.SetEncoder("software")
|
||||||
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)
|
||||||
@@ -30,6 +31,8 @@ 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)
|
||||||
@@ -37,12 +40,14 @@ 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 -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`
|
expected := `FFREPORT=file=` + expectedLogPath + `:level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 libx264 -pix_fmt:v:0 yuv420p -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -x264-params:v:0 scenecut=0:open_gop=0 -bufsize:v:0 1088k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -pix_fmt:v:1 yuv420p -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -x264-params:v:1 scenecut=0:open_gop=0 -bufsize:v:1 3572k -profile:v:1 high -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map v:0,a:0 v:1,a:1 v:2,a:2 -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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{}
|
||||||
|
}
|
||||||
@@ -16,12 +16,12 @@ 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.42
|
github.com/mattn/go-sqlite3 v1.14.40
|
||||||
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.4.0
|
github.com/oapi-codegen/runtime v1.3.1
|
||||||
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
|
||||||
@@ -33,9 +33,9 @@ require (
|
|||||||
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.50.0
|
golang.org/x/crypto v0.49.0
|
||||||
golang.org/x/mod v0.34.0
|
golang.org/x/mod v0.33.0
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.51.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
|
||||||
@@ -71,8 +71,8 @@ require (
|
|||||||
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.43.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.8 // indirect
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -100,10 +100,6 @@ 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=
|
||||||
@@ -118,8 +114,6 @@ 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=
|
||||||
@@ -195,8 +189,6 @@ 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=
|
||||||
@@ -204,8 +196,6 @@ 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 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -219,8 +209,6 @@ 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=
|
||||||
@@ -247,8 +235,6 @@ 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=
|
||||||
@@ -271,8 +257,6 @@ 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=
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ 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.
|
||||||
|
|||||||
+25
-4
@@ -1545,7 +1545,8 @@ paths:
|
|||||||
$ref: '#/components/responses/204'
|
$ref: '#/components/responses/204'
|
||||||
/admin/config/video/codec:
|
/admin/config/video/codec:
|
||||||
post:
|
post:
|
||||||
summary: Set video codec
|
summary: Set video encoder type
|
||||||
|
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:
|
||||||
@@ -1554,7 +1555,7 @@ paths:
|
|||||||
$ref: '#/components/requestBodies/AdminConfigValue'
|
$ref: '#/components/requestBodies/AdminConfigValue'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Video codec updated
|
description: Video encoder updated
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@@ -3918,6 +3919,12 @@ 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:
|
||||||
@@ -4204,15 +4211,29 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
webServerIP:
|
webServerIP:
|
||||||
type: string
|
type: string
|
||||||
videoCodec:
|
videoEncoder:
|
||||||
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'
|
||||||
supportedCodecs:
|
supportedVideoEncoders:
|
||||||
|
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 = 4
|
datastoreValuesVersion = 5
|
||||||
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -86,3 +88,44 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const (
|
|||||||
externalActionsKey = "external_actions"
|
externalActionsKey = "external_actions"
|
||||||
customStylesKey = "custom_styles"
|
customStylesKey = "custom_styles"
|
||||||
customJavascriptKey = "custom_javascript"
|
customJavascriptKey = "custom_javascript"
|
||||||
videoCodecKey = "video_codec"
|
videoEncoderKey = "video_encoder"
|
||||||
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
|
||||||
SetVideoCodec(codec string) error
|
SetVideoEncoder(encoder string) error
|
||||||
GetVideoCodec() string
|
GetVideoEncoder() 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVideoCodec will set the codec used for video encoding.
|
// SetVideoEncoder will set the encoder used for video encoding.
|
||||||
func (r *SqlConfigRepository) SetVideoCodec(codec string) error {
|
func (r *SqlConfigRepository) SetVideoEncoder(encoder string) error {
|
||||||
return r.datastore.SetString(videoCodecKey, codec)
|
return r.datastore.SetString(videoEncoderKey, encoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVideoCodec returns the codec to use for transcoding video.
|
// GetVideoEncoder returns the encoder to use for transcoding video.
|
||||||
func (r *SqlConfigRepository) GetVideoCodec() string {
|
func (r *SqlConfigRepository) GetVideoEncoder() string {
|
||||||
codec, err := r.datastore.GetString(videoCodecKey)
|
encoder, err := r.datastore.GetString(videoEncoderKey)
|
||||||
if codec == "" || err != nil {
|
if encoder == "" || err != nil {
|
||||||
return "libx264" // Default value
|
return "software"
|
||||||
}
|
}
|
||||||
|
|
||||||
return codec
|
return encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifySettings will perform a sanity check for specific settings values.
|
// VerifySettings will perform a sanity check for specific settings values.
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
* - Please do NOT modify this file.
|
* - Please do NOT modify this file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const PACKAGE_VERSION = '2.13.2'
|
const PACKAGE_VERSION = '2.12.14'
|
||||||
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()
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -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 { videoCodec, supportedCodecs } = serverConfig || {};
|
const { videoEncoder, supportedVideoEncoders } = 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(videoCodec);
|
const [selectedCodec, setSelectedCodec] = useState(videoEncoder);
|
||||||
const [pendingSaveCodec, setPendingSavecodec] = useState(videoCodec);
|
const [pendingSaveCodec, setPendingSaveCodec] = useState(videoEncoder);
|
||||||
const [confirmPopupOpen, setConfirmPopupOpen] = React.useState(false);
|
const [confirmPopupOpen, setConfirmPopupOpen] = React.useState(false);
|
||||||
|
|
||||||
let resetTimer = null;
|
let resetTimer = null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedCodec(videoCodec);
|
setSelectedCodec(videoEncoder);
|
||||||
}, [videoCodec]);
|
}, [videoEncoder]);
|
||||||
|
|
||||||
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,9 +59,8 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
|
|||||||
data: { value: pendingSaveCodec },
|
data: { value: pendingSaveCodec },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setFieldInConfigState({
|
setFieldInConfigState({
|
||||||
fieldName: 'videoCodec',
|
fieldName: 'videoEncoder',
|
||||||
value: pendingSaveCodec,
|
value: pendingSaveCodec,
|
||||||
path: 'videoSettings',
|
|
||||||
});
|
});
|
||||||
setSubmitStatus(
|
setSubmitStatus(
|
||||||
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.videoCodecUpdated)),
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.videoCodecUpdated)),
|
||||||
@@ -70,7 +69,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 latency buffer setting will take effect the next time you begin a live stream.',
|
'Your encoder setting will take effect the next time you begin a live stream.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -82,50 +81,31 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = supportedCodecs.map(codec => {
|
const items = (supportedVideoEncoders || []).map(encoder => (
|
||||||
let title = codec;
|
<Option key={encoder.encoderType} value={encoder.encoderType}>
|
||||||
if (title === 'libx264') {
|
{encoder.encoderDisplayName}
|
||||||
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>
|
</Option>
|
||||||
);
|
));
|
||||||
});
|
|
||||||
|
|
||||||
let description = '';
|
let description = '';
|
||||||
if (selectedCodec === 'libx264') {
|
if (selectedCodec === 'software') {
|
||||||
description =
|
description =
|
||||||
'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.';
|
'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.';
|
||||||
} else if (selectedCodec === 'h264_nvenc') {
|
} else if (selectedCodec === '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 === 'h264_vaapi') {
|
} else if (selectedCodec === '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 === 'h264_qsv') {
|
} else if (selectedCodec === '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 === 'h264_v4l2m2m') {
|
} else if (selectedCodec === '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 === 'h264_omx') {
|
} else if (selectedCodec === 'omx') {
|
||||||
description = 'OpenMax is a codec most often used with a Raspberry Pi.';
|
description = 'OpenMax is an encoder most often used with a Raspberry Pi.';
|
||||||
} else if (selectedCodec === 'h264_videotoolbox') {
|
} else if (selectedCodec === '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.';
|
||||||
}
|
}
|
||||||
@@ -151,7 +131,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 codec to ${pendingSaveCodec} and understand what this means?`}
|
title={`Are you sure you want to change your video encoder to ${pendingSaveCodec} and understand what this means?`}
|
||||||
open={confirmPopupOpen}
|
open={confirmPopupOpen}
|
||||||
placement="leftBottom"
|
placement="leftBottom"
|
||||||
onConfirm={save}
|
onConfirm={save}
|
||||||
|
|||||||
Generated
+93
-93
@@ -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.1",
|
"@typescript-eslint/eslint-plugin": "8.58.0",
|
||||||
"@typescript-eslint/parser": "8.58.1",
|
"@typescript-eslint/parser": "8.58.0",
|
||||||
"babel-loader": "10.1.1",
|
"babel-loader": "10.1.1",
|
||||||
"chromatic": "16.1.0",
|
"chromatic": "16.0.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.99.0",
|
"sass": "1.98.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.1.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz",
|
||||||
"integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==",
|
"integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
|
||||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
"integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==",
|
||||||
"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.1",
|
"@typescript-eslint/scope-manager": "8.58.0",
|
||||||
"@typescript-eslint/type-utils": "8.58.1",
|
"@typescript-eslint/type-utils": "8.58.0",
|
||||||
"@typescript-eslint/utils": "8.58.1",
|
"@typescript-eslint/utils": "8.58.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||||
"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.1",
|
"@typescript-eslint/parser": "^8.58.0",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz",
|
||||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.0",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz",
|
||||||
"integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==",
|
"integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.58.1",
|
"@typescript-eslint/tsconfig-utils": "^8.58.0",
|
||||||
"@typescript-eslint/types": "^8.58.1",
|
"@typescript-eslint/types": "^8.58.0",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz",
|
||||||
"integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==",
|
"integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.58.1"
|
"@typescript-eslint/visitor-keys": "8.58.0"
|
||||||
},
|
},
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz",
|
||||||
"integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==",
|
"integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz",
|
||||||
"integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==",
|
"integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||||
"@typescript-eslint/utils": "8.58.1",
|
"@typescript-eslint/utils": "8.58.0",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
|
||||||
"integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==",
|
"integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz",
|
||||||
"integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==",
|
"integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.58.1",
|
"@typescript-eslint/project-service": "8.58.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.58.1",
|
"@typescript-eslint/tsconfig-utils": "8.58.0",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz",
|
||||||
"integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==",
|
"integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==",
|
||||||
"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.1",
|
"@typescript-eslint/scope-manager": "8.58.0",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.58.1"
|
"@typescript-eslint/typescript-estree": "8.58.0"
|
||||||
},
|
},
|
||||||
"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.1",
|
"version": "8.58.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz",
|
||||||
"integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==",
|
"integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.0",
|
||||||
"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.1.0",
|
"version": "16.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-16.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-16.0.0.tgz",
|
||||||
"integrity": "sha512-7xyOUXe8jfY6c+g9S/U/cbvzVXMjdhh9cVZYvDp8dDGonjiKSbWO1dzWApdYHIiF2xj29pvWQB7N5LT6967IDw==",
|
"integrity": "sha512-O81RVGDXXoreNeG894hjaUx08xep+C/BA6aJNMZkwSjH7Lln8IweZekBpBEoQPNNpjmHyZvcTIwN/aGitdK53Q==",
|
||||||
"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.1",
|
"version": "15.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-15.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-15.13.0.tgz",
|
||||||
"integrity": "sha512-jLkgo75zlwo7PhXp0XJot+zIfFSDzN1SvTml6Xf3ETM1XHRWnH3Q4LAR3orCo/BsnxPnhjG3m5HYSvn9DAtwBg==",
|
"integrity": "sha512-hJ9sY++TUC/HlUzHVJpIrDyqKMjlhx5PTXl/A7eA91JNEtUWkJAqefQR5mo9AtLra/9+m+JJaMg2U5Qd0a74Fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -22497,15 +22497,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mermaid": {
|
"node_modules/mermaid": {
|
||||||
"version": "11.14.0",
|
"version": "11.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz",
|
||||||
"integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
|
"integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==",
|
||||||
"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.1.0",
|
"@mermaid-js/parser": "^1.0.1",
|
||||||
"@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.13.2",
|
"version": "2.12.14",
|
||||||
"resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/msw/-/msw-2.12.14.tgz",
|
||||||
"integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==",
|
"integrity": "sha512-4KXa4nVBIBjbDbd7vfQNuQ25eFxug0aropCQFoI0JdOBuJWamkT1yLVIWReFI8SiTRc+H1hKzaNk+cLk2N9rtQ==",
|
||||||
"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.7",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.6.tgz",
|
||||||
"integrity": "sha512-TGmlxXy2TsaB6QcClVKRxqvay5f93xoLguHOihRFQ+gIEIyiyvcoQjkEeuOe7Y9qvddzGB1LyFomzPo9/EpnuQ==",
|
"integrity": "sha512-ExCwDbcJoM2V3iQU+fZNp+axVfNc7DWMRh4lyTXebDO8IbpUNYKGFUrA8UqaeWiRGKVuS7+fU+KXEa9b0OP6uA==",
|
||||||
"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.28",
|
"version": "7.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz",
|
||||||
"integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==",
|
"integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tldts-core": "^7.0.28"
|
"tldts-core": "^7.0.27"
|
||||||
},
|
},
|
||||||
"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.28",
|
"version": "7.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz",
|
||||||
"integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==",
|
"integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -30108,9 +30108,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.99.0",
|
"version": "1.98.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz",
|
||||||
"integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==",
|
"integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==",
|
||||||
"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.9",
|
"version": "29.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
|
||||||
"integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==",
|
"integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
|
||||||
"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.9",
|
"handlebars": "^4.7.8",
|
||||||
"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.4",
|
"semver": "^7.7.3",
|
||||||
"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 <7"
|
"typescript": ">=4.3 <6"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
|
|||||||
+4
-4
@@ -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.1",
|
"@typescript-eslint/eslint-plugin": "8.58.0",
|
||||||
"@typescript-eslint/parser": "8.58.1",
|
"@typescript-eslint/parser": "8.58.0",
|
||||||
"babel-loader": "10.1.1",
|
"babel-loader": "10.1.1",
|
||||||
"chromatic": "16.1.0",
|
"chromatic": "16.0.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.99.0",
|
"sass": "1.98.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",
|
||||||
|
|||||||
@@ -66,6 +66,15 @@ 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;
|
||||||
@@ -147,8 +156,9 @@ export interface ConfigDetails {
|
|||||||
socketHostOverride: string;
|
socketHostOverride: string;
|
||||||
videoServingEndpoint: string;
|
videoServingEndpoint: string;
|
||||||
yp: ConfigDirectoryFields;
|
yp: ConfigDirectoryFields;
|
||||||
supportedCodecs: string[];
|
supportedVideoEncoders: EncoderCodecSupport[];
|
||||||
videoCodec: string;
|
supportedAudioCodecs: string[];
|
||||||
|
videoEncoder: string;
|
||||||
forbiddenUsernames: string[];
|
forbiddenUsernames: string[];
|
||||||
suggestedUsernames: string[];
|
suggestedUsernames: string[];
|
||||||
chatDisabled: boolean;
|
chatDisabled: boolean;
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ 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 = {
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ const initialServerConfigState: ConfigDetails = {
|
|||||||
discord: { enabled: false, webhook: '', goLiveMessage: '' },
|
discord: { enabled: false, webhook: '', goLiveMessage: '' },
|
||||||
},
|
},
|
||||||
externalActions: [],
|
externalActions: [],
|
||||||
supportedCodecs: [],
|
supportedVideoEncoders: [],
|
||||||
videoCodec: '',
|
supportedAudioCodecs: [],
|
||||||
|
videoEncoder: '',
|
||||||
forbiddenUsernames: [],
|
forbiddenUsernames: [],
|
||||||
suggestedUsernames: [],
|
suggestedUsernames: [],
|
||||||
chatDisabled: false,
|
chatDisabled: false,
|
||||||
|
|||||||
@@ -740,7 +740,9 @@ 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 codec used for video encoding.
|
// SetVideoCodec will change the encoder 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
|
||||||
@@ -748,17 +750,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 codec")
|
webutils.WriteSimpleResponse(w, false, "unable to change video encoder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
configRepository := configrepository.Get()
|
||||||
if err := configRepository.SetVideoCodec(configValue.Value.(string)); err != nil {
|
if err := configRepository.SetVideoEncoder(configValue.Value.(string)); err != nil {
|
||||||
webutils.WriteSimpleResponse(w, false, "unable to update codec")
|
webutils.WriteSimpleResponse(w, false, "unable to update video encoder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
webutils.WriteSimpleResponse(w, true, "video codec updated")
|
webutils.WriteSimpleResponse(w, true, "video encoder updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExternalActions will set the 3rd party actions for the web interface.
|
// SetExternalActions will set the 3rd party actions for the web interface.
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ 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,8 +81,9 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
},
|
},
|
||||||
S3: configRepository.GetS3Config(),
|
S3: configRepository.GetS3Config(),
|
||||||
ExternalActions: configRepository.GetExternalActions(),
|
ExternalActions: configRepository.GetExternalActions(),
|
||||||
SupportedCodecs: transcoder.GetCodecs(ffmpeg),
|
SupportedVideoEncoders: transcoder.GetAvailableVideoEncoders(ffmpeg),
|
||||||
VideoCodec: configRepository.GetVideoCodec(),
|
SupportedAudioCodecs: transcoder.GetAvailableAudioCodecs(ffmpeg),
|
||||||
|
VideoEncoder: configRepository.GetVideoEncoder(),
|
||||||
ForbiddenUsernames: usernameBlocklist,
|
ForbiddenUsernames: usernameBlocklist,
|
||||||
SuggestedUsernames: usernameSuggestions,
|
SuggestedUsernames: usernameSuggestions,
|
||||||
Federation: federationConfigResponse{
|
Federation: federationConfigResponse{
|
||||||
@@ -113,11 +116,12 @@ type serverConfigAdminResponse struct {
|
|||||||
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"`
|
||||||
VideoCodec string `json:"videoCodec"`
|
VideoEncoder string `json:"videoEncoder"`
|
||||||
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"`
|
||||||
SupportedCodecs []string `json:"supportedCodecs"`
|
SupportedVideoEncoders []transcoder.EncoderCodecSupport `json:"supportedVideoEncoders"`
|
||||||
|
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"`
|
||||||
|
|||||||
@@ -105,8 +105,15 @@ 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"`
|
||||||
SupportedCodecs *[]string `json:"supportedCodecs,omitempty"`
|
SupportedAudioCodecs *[]string `json:"supportedAudioCodecs,omitempty"`
|
||||||
VideoCodec *string `json:"videoCodec,omitempty"`
|
SupportedVideoEncoders *[]struct {
|
||||||
|
EncoderDisplayName *string `json:"encoderDisplayName,omitempty"`
|
||||||
|
EncoderType *string `json:"encoderType,omitempty"`
|
||||||
|
SupportedVideoCodecs *[]string `json:"supportedVideoCodecs,omitempty"`
|
||||||
|
} `json:"supportedVideoEncoders,omitempty"`
|
||||||
|
|
||||||
|
// VideoEncoder The global video encoder type (e.g., software, nvenc, vaapi)
|
||||||
|
VideoEncoder *string `json:"videoEncoder,omitempty"`
|
||||||
VideoServingEndpoint *string `json:"videoServingEndpoint,omitempty"`
|
VideoServingEndpoint *string `json:"videoServingEndpoint,omitempty"`
|
||||||
VideoSettings *AdminVideoSettings `json:"videoSettings,omitempty"`
|
VideoSettings *AdminVideoSettings `json:"videoSettings,omitempty"`
|
||||||
WebServerIP *string `json:"webServerIP,omitempty"`
|
WebServerIP *string `json:"webServerIP,omitempty"`
|
||||||
@@ -494,6 +501,9 @@ 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"`
|
||||||
@@ -501,6 +511,9 @@ 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
Reference in New Issue
Block a user