ok trnscode swp
This commit is contained in:
parent
54668c0d93
commit
e9766de877
12 changed files with 221 additions and 243 deletions
29
Dockerfile.govod
Normal file
29
Dockerfile.govod
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
FROM golang:1.23-bookworm AS builder
|
||||
|
||||
ARG MEMORIES_COMMIT=951b79a38f5f6438c956b98d63f760fdb4cd6c50
|
||||
WORKDIR /src
|
||||
|
||||
RUN git clone https://github.com/pulsejet/memories.git . \
|
||||
&& git checkout "$MEMORIES_COMMIT"
|
||||
|
||||
WORKDIR /src/go-vod
|
||||
COPY patches/go-vod-sheepflix.patch /tmp/go-vod-sheepflix.patch
|
||||
RUN git apply /tmp/go-vod-sheepflix.patch \
|
||||
&& gofmt -w transcoder/handler.go transcoder/stream.go \
|
||||
&& CGO_ENABLED=0 go build -ldflags="-s -w" -o /go-vod .
|
||||
|
||||
# go-vod itself uses Jellyfin's FFmpeg distribution. Pin the stable Jellyfin
|
||||
# runtime so driver/codec behavior does not change unexpectedly on rebuild.
|
||||
FROM jellyfin/jellyfin:10.11.11
|
||||
USER root
|
||||
|
||||
RUN ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/local/bin/ffmpeg \
|
||||
&& ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/local/bin/ffprobe
|
||||
|
||||
COPY --from=builder /go-vod /app/go-vod
|
||||
COPY govod-config.json /app/config.json
|
||||
|
||||
EXPOSE 8080
|
||||
ENV LIBVA_DRIVER_NAME=i965
|
||||
|
||||
ENTRYPOINT ["/app/go-vod", "/app/config.json"]
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
ARG GO_TRANSCODE_COMMIT=f8137e68c7b8da88c51aec9563eec9585c10c723
|
||||
WORKDIR /src
|
||||
|
||||
RUN apk add --no-cache git
|
||||
RUN git clone https://github.com/m1k1o/go-transcode.git . \
|
||||
&& git checkout "$GO_TRANSCODE_COMMIT"
|
||||
|
||||
COPY patches/go-transcode-vaapi.patch /tmp/go-transcode-vaapi.patch
|
||||
RUN git apply /tmp/go-transcode-vaapi.patch \
|
||||
&& gofmt -w hlsvod/manager.go hlsvod/probe.go hlsvod/transcode.go \
|
||||
&& CGO_ENABLED=0 go build -o /go-transcode .
|
||||
|
||||
# Keep Alpine 3.17: upstream documents an FFmpeg 6 segmenting regression with
|
||||
# newer Alpine releases. Install both Intel and Mesa VAAPI drivers so this also
|
||||
# works if the Unraid host exposes an AMD render device.
|
||||
FROM alpine:3.17
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ffmpeg \
|
||||
intel-media-driver \
|
||||
libva-intel-driver \
|
||||
mesa-va-gallium
|
||||
|
||||
COPY --from=builder /go-transcode /app/go-transcode
|
||||
|
||||
EXPOSE 8080
|
||||
ENV TRANSCODE_BIND=:8080 \
|
||||
TRANSCODE_VAAPI_DEVICE=/dev/dri/renderD128
|
||||
|
||||
ENTRYPOINT ["/app/go-transcode"]
|
||||
CMD ["serve"]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# go-transcode VOD configuration. Hardware encoding is enabled by the
|
||||
# TRANSCODE_VAAPI_DEVICE environment variable in docker-compose.yml.
|
||||
|
||||
vod:
|
||||
media-dir: /media
|
||||
transcode-dir: /tmp/go-transcode
|
||||
cache: true
|
||||
cache-dir: /cache
|
||||
# Avoid expensive full-file keyframe scans. FFmpeg forces HLS boundaries.
|
||||
video-keyframes: false
|
||||
video-profiles:
|
||||
480p:
|
||||
width: 854
|
||||
height: 480
|
||||
bitrate: 1200
|
||||
|
||||
720p:
|
||||
width: 1280
|
||||
height: 720
|
||||
bitrate: 3000
|
||||
|
||||
1080p:
|
||||
width: 1920
|
||||
height: 1080
|
||||
bitrate: 6000
|
||||
|
||||
audio-profile:
|
||||
bitrate: 192
|
||||
|
|
@ -15,19 +15,17 @@ services:
|
|||
transcode:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.transcode
|
||||
dockerfile: Dockerfile.govod
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
environment:
|
||||
- TRANSCODE_VAAPI_DEVICE=/dev/dri/renderD128
|
||||
- LIBVA_DRIVER_NAME=i965
|
||||
volumes:
|
||||
- /mnt/user/plex:/media:ro
|
||||
- ./transcode-config.yaml:/app/config.yaml:ro
|
||||
- transcode-cache-v5:/cache
|
||||
- govod-cache:/cache
|
||||
ports:
|
||||
- "8090:8080"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
transcode-cache-v5:
|
||||
govod-cache:
|
||||
|
|
|
|||
16
govod-config.json
Normal file
16
govod-config.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"bind": ":8080",
|
||||
"tempdir": "/cache",
|
||||
"chunkSize": 4,
|
||||
"lookBehind": 3,
|
||||
"goalBufferMin": 3,
|
||||
"goalBufferMax": 8,
|
||||
"streamIdleTime": 60,
|
||||
"managerIdleTime": 300,
|
||||
"qf": 22,
|
||||
"vaapi": true,
|
||||
"vaapiLowPower": false,
|
||||
"useTranspose": true,
|
||||
"forceSwTranspose": false,
|
||||
"useGopSize": true
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -27,6 +30,7 @@ func NewRouter(lib *library.Library) http.Handler {
|
|||
r.Get("/api/search", searchHandler(lib))
|
||||
r.Get("/api/rescan", rescanHandler(lib))
|
||||
r.Get("/api/stream/{id}", streamHandler(lib))
|
||||
r.Get("/api/subtitles/{id}/{index}", subtitleHandler(lib))
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
@ -101,12 +105,15 @@ func detailHandler(lib *library.Library) http.HandlerFunc {
|
|||
probeResult, err := probe.Run(fullPath)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"item": item,
|
||||
"probe": nil,
|
||||
"item": item,
|
||||
"probe": nil,
|
||||
"probe_error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
for i := range probeResult.Subtitles {
|
||||
probeResult.Subtitles[i].URL = fmt.Sprintf("/api/subtitles/%s/%d", id, probeResult.Subtitles[i].Index)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"item": item,
|
||||
|
|
@ -142,6 +149,75 @@ func streamHandler(lib *library.Library) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func subtitleHandler(lib *library.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
index, err := strconv.Atoi(chi.URLParam(r, "index"))
|
||||
if err != nil || index < 0 {
|
||||
http.Error(w, "invalid subtitle index", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, fullPath, ok := lib.Get(id)
|
||||
if !ok {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
metadata, err := probe.Run(fullPath)
|
||||
if err != nil {
|
||||
http.Error(w, "unable to inspect media", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var subtitle *probe.SubTrack
|
||||
for i := range metadata.Subtitles {
|
||||
if metadata.Subtitles[i].Index == index {
|
||||
subtitle = &metadata.Subtitles[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if subtitle == nil {
|
||||
http.Error(w, "subtitle track not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !isTextSubtitleCodec(subtitle.Codec) {
|
||||
http.Error(w, fmt.Sprintf("subtitle codec %q requires OCR or burn-in", subtitle.Codec), http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(r.Context(), "ffmpeg",
|
||||
"-v", "error",
|
||||
"-i", fullPath,
|
||||
"-map", fmt.Sprintf("0:%d", index),
|
||||
"-c:s", "webvtt",
|
||||
"-f", "webvtt",
|
||||
"pipe:1",
|
||||
)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
http.Error(w, fmt.Sprintf("subtitle conversion failed: %s", strings.TrimSpace(stderr.String())), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/vtt; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400")
|
||||
_, _ = w.Write(stdout.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func isTextSubtitleCodec(codec string) bool {
|
||||
switch strings.ToLower(codec) {
|
||||
case "ass", "ssa", "subrip", "srt", "text", "webvtt", "mov_text":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func rescanHandler(lib *library.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
go lib.Scan()
|
||||
|
|
|
|||
17
internal/api/api_test.go
Normal file
17
internal/api/api_test.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package api
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIsTextSubtitleCodec(t *testing.T) {
|
||||
for _, codec := range []string{"ass", "ssa", "subrip", "srt", "text", "webvtt", "mov_text", "SUBRIP"} {
|
||||
if !isTextSubtitleCodec(codec) {
|
||||
t.Errorf("expected %q to be treated as text", codec)
|
||||
}
|
||||
}
|
||||
|
||||
for _, codec := range []string{"hdmv_pgs_subtitle", "dvd_subtitle", "dvb_subtitle", ""} {
|
||||
if isTextSubtitleCodec(codec) {
|
||||
t.Errorf("expected %q to require bitmap handling", codec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ func (l *Library) Scan() error {
|
|||
RelPath: f.RelPath,
|
||||
Size: f.Size,
|
||||
ModTime: f.ModTime,
|
||||
StreamURL: "/vod/" + f.RelPath + "/index.m3u8",
|
||||
StreamURL: "/vod/" + id + "/media/" + f.RelPath + "/index.m3u8",
|
||||
fullPath: f.Path,
|
||||
}
|
||||
items = append(items, item)
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import (
|
|||
)
|
||||
|
||||
type ProbeResult struct {
|
||||
Duration float64 `json:"duration"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
VideoCodec string `json:"video_codec"`
|
||||
Bitrate int64 `json:"bitrate"`
|
||||
Audio []AudioTrack `json:"audio_tracks"`
|
||||
Subtitles []SubTrack `json:"subtitle_tracks"`
|
||||
Duration float64 `json:"duration"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
VideoCodec string `json:"video_codec"`
|
||||
Bitrate int64 `json:"bitrate"`
|
||||
Audio []AudioTrack `json:"audio_tracks"`
|
||||
Subtitles []SubTrack `json:"subtitle_tracks"`
|
||||
}
|
||||
|
||||
type AudioTrack struct {
|
||||
|
|
@ -28,6 +28,7 @@ type SubTrack struct {
|
|||
Index int `json:"index"`
|
||||
Codec string `json:"codec"`
|
||||
Language string `json:"language"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type ffprobeOutput struct {
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
diff --git a/hlsvod/manager.go b/hlsvod/manager.go
|
||||
index 662c41d..51460f8 100644
|
||||
--- a/hlsvod/manager.go
|
||||
+++ b/hlsvod/manager.go
|
||||
@@ -378,12 +378,17 @@ func (m *ManagerCtx) waitForSegment(index int) (chan struct{}, bool) {
|
||||
|
||||
func (m *ManagerCtx) transcodeSegments(offset, limit int) error {
|
||||
logger := m.logger.With().Int("offset", offset).Int("limit", limit).Logger()
|
||||
+ inputCodec := ""
|
||||
+ if m.metadata.Video != nil {
|
||||
+ inputCodec = m.metadata.Video.CodecName
|
||||
+ }
|
||||
|
||||
segmentTimes := m.breakpoints[offset : offset+limit+1]
|
||||
logger.Info().Interface("segments-times", segmentTimes).Msg("transcoding segments")
|
||||
|
||||
segments, err := TranscodeSegments(m.ctx, m.config.FFmpegBinary, TranscodeConfig{
|
||||
InputFilePath: m.config.MediaPath,
|
||||
+ InputCodec: inputCodec,
|
||||
OutputDirPath: m.config.TranscodeDir,
|
||||
SegmentPrefix: m.config.SegmentPrefix, // This does not need to match.
|
||||
|
||||
diff --git a/hlsvod/probe.go b/hlsvod/probe.go
|
||||
index c39461c..58c3edd 100644
|
||||
--- a/hlsvod/probe.go
|
||||
+++ b/hlsvod/probe.go
|
||||
@@ -83,6 +83,7 @@ func ProbeMedia(ctx context.Context, ffprobeBinary string, inputFilePath string)
|
||||
}
|
||||
|
||||
data.Video = &ProbeVideoData{
|
||||
+ CodecName: stream.CodecName,
|
||||
Width: stream.Width,
|
||||
Height: stream.Height,
|
||||
Duration: duration,
|
||||
@@ -118,6 +119,7 @@ func ProbeMedia(ctx context.Context, ffprobeBinary string, inputFilePath string)
|
||||
}
|
||||
|
||||
type ProbeVideoData struct {
|
||||
+ CodecName string
|
||||
Width int
|
||||
Height int
|
||||
Duration time.Duration
|
||||
diff --git a/hlsvod/transcode.go b/hlsvod/transcode.go
|
||||
index 9bbe037..c0297fe 100644
|
||||
--- a/hlsvod/transcode.go
|
||||
+++ b/hlsvod/transcode.go
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
+ "os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
|
||||
type TranscodeConfig struct {
|
||||
InputFilePath string // Transcoded video input.
|
||||
+ InputCodec string // ffprobe codec name, e.g. h264 or hevc.
|
||||
OutputDirPath string // Segments output path.
|
||||
SegmentPrefix string // e.g. prefix-000001.ts
|
||||
SegmentOffset int // Start segment number.
|
||||
@@ -69,6 +71,21 @@ func TranscodeSegments(ctx context.Context, ffmpegBinary string, config Transcod
|
||||
}...)
|
||||
}
|
||||
|
||||
+ // Haswell can decode H.264 in hardware, but not HEVC Main 10. Use full
|
||||
+ // hardware transcoding for H.264 and software decode plus VAAPI upload for
|
||||
+ // other codecs. Scaling and H.264 encoding always remain on the GPU.
|
||||
+ vaapiDevice := os.Getenv("TRANSCODE_VAAPI_DEVICE")
|
||||
+ hardwareDecode := vaapiDevice != "" && config.InputCodec == "h264"
|
||||
+ if vaapiDevice != "" {
|
||||
+ args = append(args, []string{
|
||||
+ "-init_hw_device", "vaapi=va:" + vaapiDevice,
|
||||
+ "-filter_hw_device", "va",
|
||||
+ }...)
|
||||
+ if hardwareDecode {
|
||||
+ args = append(args, "-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi")
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Input specs
|
||||
args = append(args, []string{
|
||||
"-i", config.InputFilePath, // Input file
|
||||
@@ -89,14 +106,30 @@ func TranscodeSegments(ctx context.Context, ffmpegBinary string, config Transcod
|
||||
scale = fmt.Sprintf("scale=%d:-2", profile.Width)
|
||||
}
|
||||
|
||||
- args = append(args, []string{
|
||||
- "-vf", scale,
|
||||
- "-c:v", "libx264",
|
||||
- "-preset", "faster",
|
||||
- "-profile:v", "high",
|
||||
- "-level:v", "4.0",
|
||||
- "-b:v", fmt.Sprintf("%dk", profile.Bitrate),
|
||||
- }...)
|
||||
+ if vaapiDevice != "" {
|
||||
+ if profile.Width >= profile.Height {
|
||||
+ scale = fmt.Sprintf("scale_vaapi=w=-2:h=%d:format=nv12", profile.Height)
|
||||
+ } else {
|
||||
+ scale = fmt.Sprintf("scale_vaapi=w=%d:h=-2:format=nv12", profile.Width)
|
||||
+ }
|
||||
+ if !hardwareDecode {
|
||||
+ scale = "format=nv12,hwupload," + scale
|
||||
+ }
|
||||
+ args = append(args, []string{
|
||||
+ "-vf", scale, "-c:v", "h264_vaapi", "-profile:v", "high",
|
||||
+ "-level:v", "4.0", "-rc_mode", "VBR",
|
||||
+ "-b:v", fmt.Sprintf("%dk", profile.Bitrate),
|
||||
+ "-maxrate", fmt.Sprintf("%dk", profile.Bitrate*3/2),
|
||||
+ "-bufsize", fmt.Sprintf("%dk", profile.Bitrate*2),
|
||||
+ "-bf", "2", "-compression_level", "0",
|
||||
+ }...)
|
||||
+ } else {
|
||||
+ args = append(args, []string{
|
||||
+ "-vf", scale, "-c:v", "libx264", "-preset", "faster",
|
||||
+ "-profile:v", "high", "-level:v", "4.0",
|
||||
+ "-b:v", fmt.Sprintf("%dk", profile.Bitrate),
|
||||
+ }...)
|
||||
+ }
|
||||
}
|
||||
|
||||
// Audio specs
|
||||
@@ -106,12 +127,15 @@ func TranscodeSegments(ctx context.Context, ffmpegBinary string, config Transcod
|
||||
args = append(args, []string{
|
||||
"-c:a", "aac",
|
||||
"-b:a", fmt.Sprintf("%dk", profile.Bitrate),
|
||||
+ "-af", "aresample=async=1:min_hard_comp=0.100",
|
||||
}...)
|
||||
}
|
||||
|
||||
// Segmenting specs
|
||||
args = append(args, []string{
|
||||
"-f", "segment",
|
||||
+ "-muxdelay", "0",
|
||||
+ "-muxpreload", "0",
|
||||
"-segment_time_delta", "0.2",
|
||||
"-segment_format", "mpegts",
|
||||
"-segment_times", commaSeparatedSegTimes,
|
||||
69
patches/go-vod-sheepflix.patch
Normal file
69
patches/go-vod-sheepflix.patch
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
diff --git a/transcoder/handler.go b/transcoder/handler.go
|
||||
index 8c5bb08..0ad7458 100644
|
||||
--- a/transcoder/handler.go
|
||||
+++ b/transcoder/handler.go
|
||||
@@ -37,6 +37,14 @@ func NewHandler(c *Config) *Handler {
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
+ w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
+ w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
|
||||
+ w.Header().Set("Access-Control-Allow-Headers", "Range, Content-Type")
|
||||
+ if r.Method == http.MethodOptions {
|
||||
+ w.WriteHeader(http.StatusNoContent)
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
// Check version if monitoring is enabled
|
||||
if h.c.VersionMonitor && !h.versionOk(w, r) {
|
||||
return
|
||||
@@ -55,15 +63,15 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Serve actual file from manager
|
||||
- if len(parts) < 3 {
|
||||
+ if len(parts) < 5 || parts[0] != "vod" {
|
||||
log.Println("Invalid URL", url)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get streamid and chunk
|
||||
- streamid := parts[0]
|
||||
- path := "/" + strings.Join(parts[1:len(parts)-1], "/")
|
||||
+ streamid := parts[1]
|
||||
+ path := "/" + strings.Join(parts[2:len(parts)-1], "/")
|
||||
chunk := parts[len(parts)-1]
|
||||
|
||||
// Check if POST request to create temp file
|
||||
diff --git a/transcoder/stream.go b/transcoder/stream.go
|
||||
index cf353fc..f8694fc 100644
|
||||
--- a/transcoder/stream.go
|
||||
+++ b/transcoder/stream.go
|
||||
@@ -370,8 +370,12 @@ func (s *Stream) transcodeArgs(startAt float64, isHls bool) []string {
|
||||
// Check whether hwaccel should be used
|
||||
if s.c.VAAPI {
|
||||
CV = ENCODER_VAAPI
|
||||
- extra := "-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi -init_hw_device vaapi=memories:/dev/dri/renderD128 -filter_hw_device memories"
|
||||
+ extra := "-init_hw_device vaapi=memories:/dev/dri/renderD128 -filter_hw_device memories"
|
||||
args = append(args, strings.Split(extra, " ")...)
|
||||
+ if s.m.probe.CodecName == CODEC_H264 {
|
||||
+ extra = "-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi"
|
||||
+ args = append(args, strings.Split(extra, " ")...)
|
||||
+ }
|
||||
} else if s.c.NVENC {
|
||||
CV = ENCODER_NVENC
|
||||
extra := "-hwaccel cuda"
|
||||
@@ -397,7 +401,11 @@ func (s *Stream) transcodeArgs(startAt float64, isHls bool) []string {
|
||||
scalerArgs = append(scalerArgs, "force_original_aspect_ratio=decrease")
|
||||
|
||||
if CV == ENCODER_VAAPI {
|
||||
- format = "format=nv12|vaapi,hwupload"
|
||||
+ if s.m.probe.CodecName == CODEC_H264 {
|
||||
+ format = "format=nv12|vaapi,hwupload"
|
||||
+ } else {
|
||||
+ format = "format=nv12,hwupload"
|
||||
+ }
|
||||
scaler = "scale_vaapi"
|
||||
scalerArgs = append(scalerArgs, "format=nv12")
|
||||
} else if CV == ENCODER_NVENC {
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
cors: true
|
||||
|
||||
vod:
|
||||
media-dir: /media
|
||||
cache: true
|
||||
cache-dir: /cache
|
||||
# Full-file keyframe probing is expensive and can be OOM-killed on Unraid.
|
||||
# FFmpeg will force segment-boundary keyframes during transcoding instead.
|
||||
video-keyframes: false
|
||||
|
||||
video-profiles:
|
||||
1080p:
|
||||
width: 1920
|
||||
height: 1080
|
||||
bitrate: 6000
|
||||
720p:
|
||||
width: 1280
|
||||
height: 720
|
||||
bitrate: 3000
|
||||
480p:
|
||||
width: 854
|
||||
height: 480
|
||||
bitrate: 1200
|
||||
|
||||
audio-profile:
|
||||
bitrate: 192
|
||||
Loading…
Add table
Add a link
Reference in a new issue