Add DASH streams for VP9 transcoding (#3275)

This commit is contained in:
DingDongSoLong4 2023-03-07 03:57:27 +02:00 committed by GitHub
parent 71e1451c94
commit 2d4384169a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2118 additions and 34 deletions

1
go.mod
View File

@ -59,6 +59,7 @@ require (
github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser/v2 v2.4.2
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
github.com/zencoder/go-dash/v3 v3.0.2
gopkg.in/guregu/null.v4 v4.0.0
)

2
go.sum
View File

@ -781,6 +781,8 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE=
github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=

View File

@ -61,6 +61,9 @@ func (rs sceneRoutes) Routes() chi.Router {
r.Get("/stream.mkv", rs.StreamMKV)
r.Get("/stream.m3u8", rs.StreamHLS)
r.Get("/stream.m3u8/{segment}.ts", rs.StreamHLSSegment)
r.Get("/stream.mpd", rs.StreamDASH)
r.Get("/stream.mpd/{segment}_v.webm", rs.StreamDASHVideoSegment)
r.Get("/stream.mpd/{segment}_a.webm", rs.StreamDASHAudioSegment)
r.Get("/screenshot", rs.Screenshot)
r.Get("/preview", rs.Preview)
@ -168,6 +171,10 @@ func (rs sceneRoutes) StreamHLS(w http.ResponseWriter, r *http.Request) {
rs.streamManifest(w, r, ffmpeg.StreamTypeHLS, "HLS")
}
func (rs sceneRoutes) StreamDASH(w http.ResponseWriter, r *http.Request) {
rs.streamManifest(w, r, ffmpeg.StreamTypeDASHVideo, "DASH")
}
func (rs sceneRoutes) streamManifest(w http.ResponseWriter, r *http.Request, streamType *ffmpeg.StreamType, logName string) {
scene := r.Context().Value(sceneKey).(*models.Scene)
@ -196,6 +203,14 @@ func (rs sceneRoutes) StreamHLSSegment(w http.ResponseWriter, r *http.Request) {
rs.streamSegment(w, r, ffmpeg.StreamTypeHLS)
}
func (rs sceneRoutes) StreamDASHVideoSegment(w http.ResponseWriter, r *http.Request) {
rs.streamSegment(w, r, ffmpeg.StreamTypeDASHVideo)
}
func (rs sceneRoutes) StreamDASHAudioSegment(w http.ResponseWriter, r *http.Request) {
rs.streamSegment(w, r, ffmpeg.StreamTypeDASHAudio)
}
func (rs sceneRoutes) streamSegment(w http.ResponseWriter, r *http.Request, streamType *ffmpeg.StreamType) {
scene := r.Context().Value(sceneKey).(*models.Scene)

View File

@ -50,6 +50,11 @@ var (
mimeType: ffmpeg.MimeHLS,
extension: ".m3u8",
}
dashEndpointType = endpointType{
label: "DASH",
mimeType: ffmpeg.MimeDASH,
extension: ".mpd",
}
)
func GetVideoFileContainer(file *file.VideoFile) (ffmpeg.Container, error) {
@ -163,46 +168,54 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL *url.URL, maxStrea
mp4Streams := []*SceneStreamEndpoint{}
webmStreams := []*SceneStreamEndpoint{}
hlsStreams := []*SceneStreamEndpoint{}
dashStreams := []*SceneStreamEndpoint{}
if includeSceneStreamPath(models.StreamingResolutionEnumOriginal) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumOriginal))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumOriginal))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumOriginal))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumOriginal))
}
if includeSceneStreamPath(models.StreamingResolutionEnumFourK) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumFourK))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumFourK))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumFourK))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumFourK))
}
if includeSceneStreamPath(models.StreamingResolutionEnumFullHd) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumFullHd))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumFullHd))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumFullHd))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumFullHd))
}
if includeSceneStreamPath(models.StreamingResolutionEnumStandardHd) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumStandardHd))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumStandardHd))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumStandardHd))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumStandardHd))
}
if includeSceneStreamPath(models.StreamingResolutionEnumStandard) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumStandard))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumStandard))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumStandard))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumStandard))
}
if includeSceneStreamPath(models.StreamingResolutionEnumLow) {
mp4Streams = append(mp4Streams, makeStreamEndpoint(mp4EndpointType, models.StreamingResolutionEnumLow))
webmStreams = append(webmStreams, makeStreamEndpoint(webmEndpointType, models.StreamingResolutionEnumLow))
hlsStreams = append(hlsStreams, makeStreamEndpoint(hlsEndpointType, models.StreamingResolutionEnumLow))
dashStreams = append(dashStreams, makeStreamEndpoint(dashEndpointType, models.StreamingResolutionEnumLow))
}
endpoints = append(endpoints, mp4Streams...)
endpoints = append(endpoints, webmStreams...)
endpoints = append(endpoints, hlsStreams...)
endpoints = append(endpoints, dashStreams...)
return endpoints, nil
}

View File

@ -20,11 +20,14 @@ import (
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/zencoder/go-dash/v3/mpd"
)
const (
MimeHLS string = "application/vnd.apple.mpegurl"
MimeMpegTS string = "video/MP2T"
MimeDASH string = "application/dash+xml"
segmentLength = 2
@ -81,7 +84,7 @@ var (
"-avoid_negative_ts", "disabled",
"-f", "hls",
"-start_number", fmt.Sprint(segment),
"-hls_time", "2",
"-hls_time", fmt.Sprint(segmentLength),
"-hls_segment_type", "mpegts",
"-hls_playlist_type", "vod",
"-hls_segment_filename", filepath.Join(outputDir, ".%d.ts"),
@ -112,7 +115,7 @@ var (
"-avoid_negative_ts", "disabled",
"-f", "hls",
"-start_number", fmt.Sprint(segment),
"-hls_time", "2",
"-hls_time", fmt.Sprint(segmentLength),
"-hls_segment_type", "mpegts",
"-hls_playlist_type", "vod",
"-hls_segment_filename", filepath.Join(outputDir, ".%d.ts"),
@ -121,6 +124,67 @@ var (
return
},
}
StreamTypeDASHVideo = &StreamType{
Name: "dash-v",
SegmentType: SegmentTypeWEBMVideo,
ServeManifest: serveDASHManifest,
Args: func(segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) (args Args) {
// only generate the actual init segment (init_v.webm)
// when generating the first segment
init := ".init"
if segment == 0 {
init = "init"
}
args = append(args,
"-c:v", "libvpx-vp9",
"-pix_fmt", "yuv420p",
"-deadline", "realtime",
"-cpu-used", "5",
"-row-mt", "1",
"-crf", "30",
"-b:v", "0",
"-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", segmentLength),
)
args = args.VideoFilter(videoFilter)
args = append(args,
"-copyts",
"-avoid_negative_ts", "disabled",
"-map", "0:v:0",
"-f", "webm_chunk",
"-chunk_start_index", fmt.Sprint(segment),
"-header", filepath.Join(outputDir, init+"_v.webm"),
filepath.Join(outputDir, ".%d_v.webm"),
)
return
},
}
StreamTypeDASHAudio = &StreamType{
Name: "dash-a",
SegmentType: SegmentTypeWEBMAudio,
ServeManifest: serveDASHManifest,
Args: func(segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) (args Args) {
// only generate the actual init segment (init_a.webm)
// when generating the first segment
init := ".init"
if segment == 0 {
init = "init"
}
args = append(args,
"-c:a", "libopus",
"-b:a", "96000",
"-ar", "48000",
"-copyts",
"-avoid_negative_ts", "disabled",
"-map", "0:a:0",
"-f", "webm_chunk",
"-chunk_start_index", fmt.Sprint(segment),
"-audio_chunk_duration", fmt.Sprint(segmentLength*1000),
"-header", filepath.Join(outputDir, init+"_a.webm"),
filepath.Join(outputDir, ".%d_a.webm"),
)
return
},
}
)
type SegmentType struct {
@ -145,6 +209,50 @@ var (
return segment, err
},
}
SegmentTypeWEBMVideo = &SegmentType{
Format: "%d_v.webm",
MimeType: MimeWebmVideo,
MakeFilename: func(segment int) string {
if segment == -1 {
return "init_v.webm"
} else {
return fmt.Sprintf("%d_v.webm", segment)
}
},
ParseSegment: func(str string) (int, error) {
if str == "init" {
return -1, nil
} else {
segment, err := strconv.Atoi(str)
if err != nil || segment < 0 {
err = ErrInvalidSegment
}
return segment, err
}
},
}
SegmentTypeWEBMAudio = &SegmentType{
Format: "%d_a.webm",
MimeType: MimeWebmAudio,
MakeFilename: func(segment int) string {
if segment == -1 {
return "init_a.webm"
} else {
return fmt.Sprintf("%d_a.webm", segment)
}
},
ParseSegment: func(str string) (int, error) {
if str == "init" {
return -1, nil
} else {
segment, err := strconv.Atoi(str)
if err != nil || segment < 0 {
err = ErrInvalidSegment
}
return segment, err
}
},
}
)
var ErrInvalidSegment = errors.New("invalid segment")
@ -339,6 +447,97 @@ func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request,
http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(buf.Bytes()))
}
// serveDASHManifest serves a generated DASH manifest.
func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string) {
if sm.cacheDir == "" {
logger.Error("[transcode] cannot live transcode with DASH because cache dir is unset")
http.Error(w, "cannot live transcode files with DASH because cache dir is unset", http.StatusServiceUnavailable)
return
}
probeResult, err := sm.ffprobe.NewVideoFile(vf.Path)
if err != nil {
logger.Warnf("[transcode] error generating DASH manifest: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var framerate string
var videoWidth int
var videoHeight int
videoStream := probeResult.VideoStream
if videoStream != nil {
framerate = videoStream.AvgFrameRate
videoWidth = videoStream.Width
videoHeight = videoStream.Height
} else {
// extract the framerate fraction from the file framerate
// framerates 0.1% below round numbers are common,
// attempt to infer when this is the case
fileFramerate := vf.FrameRate
rate1001, off1001 := math.Modf(fileFramerate * 1.001)
var numerator int
var denominator int
switch {
case off1001 < 0.005:
numerator = int(rate1001) * 1000
denominator = 1001
case off1001 > 0.995:
numerator = (int(rate1001) + 1) * 1000
denominator = 1001
default:
numerator = int(fileFramerate * 1000)
denominator = 1000
}
framerate = fmt.Sprintf("%d/%d", numerator, denominator)
videoHeight = vf.Height
videoWidth = vf.Width
}
var urlQuery string
maxTranscodeSize := sm.config.GetMaxStreamingTranscodeSize().GetMaxResolution()
if resolution != "" {
maxTranscodeSize = models.StreamingResolutionEnum(resolution).GetMaxResolution()
urlQuery = fmt.Sprintf("?resolution=%s", resolution)
}
if maxTranscodeSize != 0 {
videoSize := videoHeight
if videoWidth < videoSize {
videoSize = videoWidth
}
if maxTranscodeSize < videoSize {
scaleFactor := float64(maxTranscodeSize) / float64(videoSize)
videoWidth = int(float64(videoWidth) * scaleFactor)
videoHeight = int(float64(videoHeight) * scaleFactor)
}
}
mediaDuration := mpd.Duration(time.Duration(probeResult.FileDuration * float64(time.Second)))
m := mpd.NewMPD(mpd.DASH_PROFILE_LIVE, mediaDuration.String(), "PT4.0S")
baseUrl := r.URL.JoinPath("/")
baseUrl.RawQuery = ""
m.BaseURL = baseUrl.String()
video, _ := m.AddNewAdaptationSetVideo(MimeWebmVideo, "progressive", true, 1)
_, _ = video.SetNewSegmentTemplate(2, "init_v.webm"+urlQuery, "$Number$_v.webm"+urlQuery, 0, 1)
_, _ = video.AddNewRepresentationVideo(200000, "vp09.00.40.08", "0", framerate, int64(videoWidth), int64(videoHeight))
if ProbeAudioCodec(vf.AudioCodec) != MissingUnsupported {
audio, _ := m.AddNewAdaptationSetAudio(MimeWebmAudio, true, 1, "und")
_, _ = audio.SetNewSegmentTemplate(2, "init_a.webm"+urlQuery, "$Number$_a.webm"+urlQuery, 0, 1)
_, _ = audio.AddNewRepresentationAudio(48000, 96000, "opus", "1")
}
var buf bytes.Buffer
_ = m.Write(&buf)
w.Header().Set("Content-Type", MimeDASH)
http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(buf.Bytes()))
}
func (sm *StreamManager) ServeManifest(w http.ResponseWriter, r *http.Request, streamType *StreamType, vf *file.VideoFile, resolution string) {
streamType.ServeManifest(sm, w, r, vf, resolution)
}
@ -355,7 +554,6 @@ func (sm *StreamManager) serveWaitingSegment(w http.ResponseWriter, r *http.Requ
w.Header().Add("Cache-Control", "no-cache")
http.ServeFile(w, r, segment.path)
} else if !errors.Is(err, context.Canceled) {
logger.Errorf("[transcode] %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
@ -442,6 +640,7 @@ func (sm *StreamManager) startTranscode(stream *runningStream, segment int, done
logger.Debugf("[transcode] starting transcode for %s at segment #%d", stream.dir, segment)
if err := os.MkdirAll(stream.outputDir, os.ModePerm); err != nil {
logger.Errorf("[transcode] %v", err)
done <- err
return
}
@ -464,7 +663,9 @@ func (sm *StreamManager) startTranscode(stream *runningStream, segment int, done
logger.Tracef("[transcode] running %s", cmd)
if err := cmd.Start(); err != nil {
lockCtx.Cancel()
done <- fmt.Errorf("error starting transcode process: %w", err)
err = fmt.Errorf("error starting transcode process: %w", err)
logger.Errorf("[transcode] %v", err)
done <- err
return
}
@ -499,7 +700,12 @@ func (sm *StreamManager) startTranscode(stream *runningStream, segment int, done
}
if err != nil {
err = fmt.Errorf("[transcode] ffmpeg error when running command <%s>: %w", strings.Join(cmd.Args, " "), err)
err = fmt.Errorf("ffmpeg error when running command <%s>: %w", strings.Join(cmd.Args, " "), err)
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
logger.Errorf("[transcode] %v", err)
}
}
}
@ -517,7 +723,9 @@ func (sm *StreamManager) startTranscode(stream *runningStream, segment int, done
sm.streamsMutex.Unlock()
done <- err
if err != nil {
done <- err
}
}()
}
@ -562,7 +770,9 @@ func (s *waitingSegment) checkAvailable(now time.Time) bool {
s.available <- nil
return true
} else if s.accessed.Add(maxSegmentWait).Before(now) {
s.available <- fmt.Errorf("timed out waiting for segment file %s to be generated", s.file)
err := fmt.Errorf("timed out waiting for segment file %s to be generated", s.file)
logger.Errorf("[transcode] %v", err)
s.available <- err
return true
}
return false

View File

@ -63,7 +63,8 @@
"string.prototype.replaceall": "^1.0.7",
"thehandy": "^1.0.3",
"universal-cookie": "^4.0.4",
"video.js": "^7.21.2",
"video.js": "^7.21.3",
"videojs-contrib-dash": "^5.1.1",
"videojs-mobile-ui": "^0.8.0",
"videojs-seek-buttons": "^3.0.1",
"videojs-vtt.js": "^0.15.4",

View File

@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/naming-convention */
declare module "videojs-contrib-dash" {
class Html5DashJS {
/**
* Get a list of hooks for a specific lifecycle.
*
* @param type the lifecycle to get hooks from
* @param hook optionally add a hook to the lifecycle
* @return an array of hooks or empty if none
*/
static hooks(type: string, hook: Function | Function[]): Function[];
/**
* Add a function hook to a specific dash lifecycle.
*
* @param type the lifecycle to hook the function to
* @param hook the function or array of functions to attach
*/
static hook(type: string, hook: Function | Function[]): void;
/**
* Remove a hook from a specific dash lifecycle.
*
* @param type the lifecycle that the function hooked to
* @param hook the hooked function to remove
* @return true if the function was removed, false if not found
*/
static removeHook(type: string, hook: Function): boolean;
}
}

View File

@ -7,6 +7,7 @@ import React, {
useState,
} from "react";
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
import "videojs-contrib-dash";
import "videojs-mobile-ui";
import "videojs-seek-buttons";
import "./live";
@ -267,6 +268,25 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
},
chaptersButton: false,
},
html5: {
nativeTextTracks: false,
dash: {
updateSettings: [
{
streaming: {
buffer: {
bufferTimeAtTopQuality: 30,
bufferTimeAtTopQualityLongForm: 30,
},
gaps: {
jumpGaps: false,
jumpLargeGaps: false,
},
},
},
],
},
},
nativeControlsForTouch: false,
playbackRates: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
inactivityTimeout: 2000,
@ -463,6 +483,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
const src = new URL(stream.url);
const isDirect =
src.pathname.endsWith("/stream") ||
src.pathname.endsWith("/stream.mpd") ||
src.pathname.endsWith("/stream.m3u8");
return {

View File

@ -1,6 +1,7 @@
##### 💥 Note: The cache directory is now required if using HLS streaming. Please set the cache directory in the System Settings page.
### ✨ New Features
* Added support for DASH streaming. ([#3275](https://github.com/stashapp/stash/pull/3275))
* Added configuration option for the maximum number of items in selector drop-downs. ([#3277](https://github.com/stashapp/stash/pull/3277))
* Added configuration option to perform generation operations sequentially after scanning a new video file. ([#3378](https://github.com/stashapp/stash/pull/3378))
* Optionally show range in generated funscript heatmaps. ([#3373](https://github.com/stashapp/stash/pull/3373))

View File

@ -2540,10 +2540,10 @@
"@typescript-eslint/types" "5.52.0"
eslint-visitor-keys "^3.3.0"
"@videojs/http-streaming@2.16.0":
version "2.16.0"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.16.0.tgz#47f86e793fc773db26dbd9aeb3e5130b767c78fa"
integrity sha512-mGNTqjENzP86XGM6HSWdWVO/KAsDlf5+idW2W7dL1+NkzWpwZlSEYhrdEVVnhoOb0A6E7JW6LM611/JA7Jn/3A==
"@videojs/http-streaming@2.16.2":
version "2.16.2"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.16.2.tgz#a9be925b4e368a41dbd67d49c4f566715169b84b"
integrity sha512-etPTUdCFu7gUWc+1XcbiPr+lrhOcBu3rV5OL1M+3PDW89zskScAkkcdqYzP4pFodBPye/ydamQoTDScOnElw5A==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "3.0.5"
@ -3049,6 +3049,28 @@ base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bcp-47-match@^1.0.0, bcp-47-match@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/bcp-47-match/-/bcp-47-match-1.0.3.tgz#cb8d03071389a10aff2062b862d6575ffd7cd7ef"
integrity sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==
bcp-47-normalize@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz#d2c76218d132f223c44e4a06a7224be3030f8ec3"
integrity sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==
dependencies:
bcp-47 "^1.0.0"
bcp-47-match "^1.0.0"
bcp-47@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/bcp-47/-/bcp-47-1.0.8.tgz#bf63ae4269faabe7c100deac0811121a48b6a561"
integrity sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==
dependencies:
is-alphabetical "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@ -3339,6 +3361,11 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
codem-isoboxer@0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz#867f670459b881d44f39168d5ff2a8f14c16151d"
integrity sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw==
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -3511,6 +3538,22 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
dashjs@^4.2.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/dashjs/-/dashjs-4.6.0.tgz#124c8371e192f1218746ce60b6aa0f175d4dcda4"
integrity sha512-0PDoSBM9PXb+Io0pRnw2CmO7aV9W8FC/BqBRNhLxzM3/e5Kfj7BLy0OWkkSB58ULg6Md6r+6jkGOTUhut/35rg==
dependencies:
bcp-47-match "^1.0.3"
bcp-47-normalize "^1.1.1"
codem-isoboxer "0.3.6"
es6-promise "^4.2.8"
fast-deep-equal "2.0.1"
html-entities "^1.2.1"
imsc "^1.0.2"
localforage "^1.7.1"
path-browserify "^1.0.1"
ua-parser-js "^1.0.2"
dataloader@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
@ -3786,6 +3829,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
esbuild@^0.16.14:
version "0.16.17"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
@ -4116,6 +4164,11 @@ fast-decode-uri-component@^1.0.1:
resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543"
integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==
fast-deep-equal@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -4424,7 +4477,7 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@^4.3.1, global@^4.4.0, global@~4.4.0:
global@^4.3.1, global@^4.3.2, global@^4.4.0, global@~4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
@ -4636,6 +4689,11 @@ hosted-git-info@^4.0.1:
dependencies:
lru-cache "^6.0.0"
html-entities@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
html-tags@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
@ -4715,6 +4773,13 @@ import-lazy@^4.0.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
imsc@^1.0.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/imsc/-/imsc-1.1.3.tgz#e96a60a50d4000dd7b44097272768b9fd6a4891d"
integrity sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA==
dependencies:
sax "1.2.1"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -5333,7 +5398,7 @@ load-json-file@^6.2.0:
strip-bom "^4.0.0"
type-fest "^0.6.0"
localforage@^1.10.0:
localforage@^1.10.0, localforage@^1.7.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
@ -6138,6 +6203,11 @@ pascal-case@^3.1.2:
no-case "^3.0.4"
tslib "^2.0.3"
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"
@ -6623,13 +6693,6 @@ regexpp@^3.2.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
rehype-react@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.2.1.tgz#9b9bf188451ad6f63796b784fe1f51165c67b73a"
integrity sha512-f9KIrjktvLvmbGc7si25HepocOg4z0MuNOtweigKzBcDjiGSTGhyz6VSgaV5K421Cq1O+z4/oxRJ5G9owo0KVg==
dependencies:
"@mapbox/hast-util-table-cell-style" "^0.2.0"
hast-to-hyperscript "^9.0.0"
regexpu-core@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb"
@ -6649,6 +6712,14 @@ regjsparser@^0.9.1:
dependencies:
jsesc "~0.5.0"
rehype-react@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.2.1.tgz#9b9bf188451ad6f63796b784fe1f51165c67b73a"
integrity sha512-f9KIrjktvLvmbGc7si25HepocOg4z0MuNOtweigKzBcDjiGSTGhyz6VSgaV5K421Cq1O+z4/oxRJ5G9owo0KVg==
dependencies:
"@mapbox/hast-util-table-cell-style" "^0.2.0"
hast-to-hyperscript "^9.0.0"
relay-runtime@12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-12.0.0.tgz#1e039282bdb5e0c1b9a7dc7f6b9a09d4f4ff8237"
@ -6851,6 +6922,11 @@ sass@^1.58.1:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
@ -7021,10 +7097,10 @@ source-map@^0.6.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
space-separated-tokens@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
space-separated-tokens@^1.0.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
spdx-correct@^3.0.0:
version "3.1.1"
@ -7535,6 +7611,11 @@ ua-parser-js@^0.7.30:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
ua-parser-js@^1.0.2:
version "1.0.33"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.33.tgz#f21f01233e90e7ed0f059ceab46eb190ff17f8f4"
integrity sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@ -7583,10 +7664,10 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
unified@^10.0.0:
version "10.1.2"
resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df"
integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==
unified@^9.0.0:
version "9.2.2"
resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975"
integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==
dependencies:
bail "^1.0.0"
extend "^3.0.0"
@ -7775,13 +7856,13 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
"video.js@^6 || ^7", video.js@^7.21.2:
version "7.21.2"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.21.2.tgz#2dbf17b435690be739b15748bd53d3002f548e3a"
integrity sha512-Zbo23oT4CbtIxeAtfTvzdl7OlN/P34ir7hDzXFtLZB+BtJsaLy0Rgh/06dBMJSGEjQCDo4MUS6uPonuX0Nl3Kg==
"video.js@^5.18.0 || ^6 || ^7", "video.js@^6 || ^7", video.js@^7.21.3:
version "7.21.3"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.21.3.tgz#1a5f6379e713de3f5dc036ecdef02efb80765bdd"
integrity sha512-fIboXbSDCT3P8eVzIEC3hnLDKC/y+6QftcHdFGUVGn5a7qmH62Mh0Bt/SrBAgdmKDQM1qdZXfXAxPg5+IaiIXQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/http-streaming" "2.16.0"
"@videojs/http-streaming" "2.16.2"
"@videojs/vhs-utils" "^3.0.4"
"@videojs/xhr" "2.6.0"
aes-decrypter "3.1.3"
@ -7794,6 +7875,15 @@ vfile@^4.0.0:
videojs-font "3.2.0"
videojs-vtt.js "^0.15.4"
videojs-contrib-dash@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/videojs-contrib-dash/-/videojs-contrib-dash-5.1.1.tgz#9f50191677815a7d816c500977811a926aee0643"
integrity sha512-MI0kPHuQ3KH9Mc2mLVLqvFKCoEyTfXzHc02fm8pqMk8v7LXrJKnIv9xfugBccRF7vZHDZISftedD/CmEJfvvrA==
dependencies:
dashjs "^4.2.0"
global "^4.3.2"
video.js "^5.18.0 || ^6 || ^7"
videojs-font@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"

13
vendor/github.com/zencoder/go-dash/v3/LICENSE generated vendored Normal file
View File

@ -0,0 +1,13 @@
Copyright Brightcove, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,49 @@
package ptrs
func Strptr(v string) *string {
p := new(string)
*p = v
return p
}
func Intptr(v int) *int {
p := new(int)
*p = v
return p
}
func Int64ptr(v int64) *int64 {
p := new(int64)
*p = v
return p
}
func Uintptr(v uint) *uint {
p := new(uint)
*p = v
return p
}
func Uint32ptr(v uint32) *uint32 {
p := new(uint32)
*p = v
return p
}
func Uint64ptr(v uint64) *uint64 {
p := new(uint64)
*p = v
return p
}
func Boolptr(v bool) *bool {
p := new(bool)
*p = v
return p
}
func Float64ptr(v float64) *float64 {
p := new(float64)
*p = v
return p
}

206
vendor/github.com/zencoder/go-dash/v3/mpd/duration.go generated vendored Normal file
View File

@ -0,0 +1,206 @@
// based on code from golang src/time/time.go
package mpd
import (
"encoding/xml"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
type Duration time.Duration
var (
rStart = "^P" // Must start with a 'P'
rDays = "(\\d+D)?" // We only allow Days for durations, not Months or Years
rTime = "(?:T" // If there's any 'time' units then they must be preceded by a 'T'
rHours = "(\\d+H)?" // Hours
rMinutes = "(\\d+M)?" // Minutes
rSeconds = "([\\d.]+S)?" // Seconds (Potentially decimal)
rEnd = ")?$" // end of regex must close "T" capture group
)
var xmlDurationRegex = regexp.MustCompile(rStart + rDays + rTime + rHours + rMinutes + rSeconds + rEnd)
func (d Duration) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
return xml.Attr{Name: name, Value: d.String()}, nil
}
func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error {
dur, err := ParseDuration(attr.Value)
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
// String renders a Duration in XML Duration Data Type format
func (d *Duration) String() string {
// Largest time is 2540400h10m10.000000000s
var buf [32]byte
w := len(buf)
u := uint64(*d)
neg := *d < 0
if neg {
u = -u
}
if u < uint64(time.Second) {
// Special case: if duration is smaller than a second,
// use smaller units, like 1.2ms
var prec int
w--
buf[w] = 'S'
w--
if u == 0 {
return "PT0S"
}
/*
switch {
case u < uint64(Millisecond):
// print microseconds
prec = 3
// U+00B5 'µ' micro sign == 0xC2 0xB5
w-- // Need room for two bytes.
copy(buf[w:], "µ")
default:
// print milliseconds
prec = 6
buf[w] = 'm'
}
*/
w, u = fmtFrac(buf[:w], u, prec)
w = fmtInt(buf[:w], u)
} else {
w--
buf[w] = 'S'
w, u = fmtFrac(buf[:w], u, 9)
// u is now integer seconds
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer minutes
if u > 0 {
w--
buf[w] = 'M'
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer hours
// Stop at hours because days can be different lengths.
if u > 0 {
w--
buf[w] = 'H'
w = fmtInt(buf[:w], u)
}
}
}
if neg {
w--
buf[w] = '-'
}
return "PT" + string(buf[w:])
}
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
// tail of buf, omitting trailing zeros. it omits the decimal
// point too when the fraction is 0. It returns the index where the
// output bytes begin and the value v/10**prec.
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
// Omit trailing zeros up to and including decimal point.
w := len(buf)
print := false
for i := 0; i < prec; i++ {
digit := v % 10
print = print || digit != 0
if print {
w--
buf[w] = byte(digit) + '0'
}
v /= 10
}
if print {
w--
buf[w] = '.'
}
return w, v
}
// fmtInt formats v into the tail of buf.
// It returns the index where the output begins.
func fmtInt(buf []byte, v uint64) int {
w := len(buf)
if v == 0 {
w--
buf[w] = '0'
} else {
for v > 0 {
w--
buf[w] = byte(v%10) + '0'
v /= 10
}
}
return w
}
func ParseDuration(str string) (time.Duration, error) {
if len(str) < 3 {
return 0, errors.New("At least one number and designator are required")
}
if strings.Contains(str, "-") {
return 0, errors.New("Duration cannot be negative")
}
// Check that only the parts we expect exist and that everything's in the correct order
if !xmlDurationRegex.Match([]byte(str)) {
return 0, errors.New("Duration must be in the format: P[nD][T[nH][nM][nS]]")
}
var parts = xmlDurationRegex.FindStringSubmatch(str)
var total time.Duration
if parts[1] != "" {
days, err := strconv.Atoi(strings.TrimRight(parts[1], "D"))
if err != nil {
return 0, fmt.Errorf("Error parsing Days: %s", err)
}
total += time.Duration(days) * time.Hour * 24
}
if parts[2] != "" {
hours, err := strconv.Atoi(strings.TrimRight(parts[2], "H"))
if err != nil {
return 0, fmt.Errorf("Error parsing Hours: %s", err)
}
total += time.Duration(hours) * time.Hour
}
if parts[3] != "" {
mins, err := strconv.Atoi(strings.TrimRight(parts[3], "M"))
if err != nil {
return 0, fmt.Errorf("Error parsing Minutes: %s", err)
}
total += time.Duration(mins) * time.Minute
}
if parts[4] != "" {
secs, err := strconv.ParseFloat(strings.TrimRight(parts[4], "S"), 64)
if err != nil {
return 0, fmt.Errorf("Error parsing Seconds: %s", err)
}
total += time.Duration(secs * float64(time.Second))
}
return total, nil
}

18
vendor/github.com/zencoder/go-dash/v3/mpd/events.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package mpd
import "encoding/xml"
type EventStream struct {
XMLName xml.Name `xml:"EventStream"`
SchemeIDURI *string `xml:"schemeIdUri,attr"`
Value *string `xml:"value,attr,omitempty"`
Timescale *uint `xml:"timescale,attr"`
Events []Event `xml:"Event,omitempty"`
}
type Event struct {
XMLName xml.Name `xml:"Event"`
ID *string `xml:"id,attr,omitempty"`
PresentationTime *uint64 `xml:"presentationTime,attr,omitempty"`
Duration *uint64 `xml:"duration,attr,omitempty"`
}

1168
vendor/github.com/zencoder/go-dash/v3/mpd/mpd.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

57
vendor/github.com/zencoder/go-dash/v3/mpd/mpd_attr.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package mpd
type AttrMPD interface {
GetStrptr() *string
}
type attrAvailabilityStartTime struct {
strptr *string
}
func (attr *attrAvailabilityStartTime) GetStrptr() *string {
return attr.strptr
}
// AttrAvailabilityStartTime returns AttrMPD object for NewMPD
func AttrAvailabilityStartTime(value string) AttrMPD {
return &attrAvailabilityStartTime{strptr: &value}
}
type attrMinimumUpdatePeriod struct {
strptr *string
}
func (attr *attrMinimumUpdatePeriod) GetStrptr() *string {
return attr.strptr
}
// AttrMinimumUpdatePeriod returns AttrMPD object for NewMPD
func AttrMinimumUpdatePeriod(value string) AttrMPD {
return &attrMinimumUpdatePeriod{strptr: &value}
}
type attrMediaPresentationDuration struct {
strptr *string
}
func (attr *attrMediaPresentationDuration) GetStrptr() *string {
return attr.strptr
}
// AttrMediaPresentationDuration returns AttrMPD object for NewMPD
func AttrMediaPresentationDuration(value string) AttrMPD {
return &attrMediaPresentationDuration{strptr: &value}
}
type attrPublishTime struct {
strptr *string
}
func (attr *attrPublishTime) GetStrptr() *string {
return attr.strptr
}
// AttrPublishTime returns AttrMPD object for NewMPD
func AttrPublishTime(value string) AttrMPD {
return &attrPublishTime{strptr: &value}
}

View File

@ -0,0 +1,87 @@
package mpd
import (
"bufio"
"bytes"
"encoding/xml"
"io"
"os"
)
// Reads an MPD XML file from disk into a MPD object.
// path - File path to an MPD on disk
func ReadFromFile(path string) (*MPD, error) {
f, err := os.OpenFile(path, os.O_RDONLY, 0666)
if err != nil {
return nil, err
}
defer f.Close()
return Read(f)
}
// Reads a string into a MPD object.
// xmlStr - MPD manifest data as a string.
func ReadFromString(xmlStr string) (*MPD, error) {
b := bytes.NewBufferString(xmlStr)
return Read(b)
}
// Reads from an io.Reader interface into an MPD object.
// r - Must implement the io.Reader interface.
func Read(r io.Reader) (*MPD, error) {
var mpd MPD
d := xml.NewDecoder(r)
err := d.Decode(&mpd)
if err != nil {
return nil, err
}
return &mpd, nil
}
// Writes an MPD object to a file on disk.
// path - Output path to write the manifest to.
func (m *MPD) WriteToFile(path string) error {
// Open the file to write the XML to
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer f.Close()
if err = m.Write(f); err != nil {
return err
}
if err = f.Sync(); err != nil {
return err
}
return err
}
// Writes an MPD object to a string.
func (m *MPD) WriteToString() (string, error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
err := m.Write(w)
if err != nil {
return "", err
}
err = w.Flush()
if err != nil {
return "", err
}
return b.String(), err
}
// Writes an MPD object to an io.Writer interface
// w - Must implement the io.Writer interface.
func (m *MPD) Write(w io.Writer) error {
b, err := xml.MarshalIndent(m, "", " ")
if err != nil {
return err
}
_, _ = w.Write([]byte(xml.Header))
_, _ = w.Write(b)
_, _ = w.Write([]byte("\n"))
return nil
}

41
vendor/github.com/zencoder/go-dash/v3/mpd/pssh.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
package mpd
import (
"bytes"
"encoding/binary"
"fmt"
)
func MakePSSHBox(systemID, payload []byte) ([]byte, error) {
if len(systemID) != 16 {
return nil, fmt.Errorf("SystemID must be 16 bytes, was: %d", len(systemID))
}
psshBuf := &bytes.Buffer{}
size := uint32(12 + 16 + 4 + len(payload)) // 3 uint32s, systemID, "pssh" string and payload
if err := binary.Write(psshBuf, binary.BigEndian, size); err != nil {
return nil, err
}
if err := binary.Write(psshBuf, binary.BigEndian, []byte("pssh")); err != nil {
return nil, err
}
if err := binary.Write(psshBuf, binary.BigEndian, uint32(0)); err != nil {
return nil, err
}
if _, err := psshBuf.Write(systemID); err != nil {
return nil, err
}
if err := binary.Write(psshBuf, binary.BigEndian, uint32(len(payload))); err != nil {
return nil, err
}
if _, err := psshBuf.Write(payload); err != nil {
return nil, err
}
return psshBuf.Bytes(), nil
}

47
vendor/github.com/zencoder/go-dash/v3/mpd/segment.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package mpd
type SegmentBase struct {
Initialization *URL `xml:"Initialization,omitempty"`
RepresentationIndex *URL `xml:"RepresentationIndex,omitempty"`
Timescale *uint32 `xml:"timescale,attr,omitempty"`
PresentationTimeOffset *uint64 `xml:"presentationTimeOffset,attr,omitempty"`
IndexRange *string `xml:"indexRange,attr,omitempty"`
IndexRangeExact *bool `xml:"indexRangeExact,attr,omitempty"`
AvailabilityTimeOffset *float32 `xml:"availabilityTimeOffset,attr,omitempty"`
AvailabilityTimeComplete *bool `xml:"availabilityTimeComplete,attr,omitempty"`
}
type MultipleSegmentBase struct {
SegmentBase
SegmentTimeline *SegmentTimeline `xml:"SegmentTimeline,omitempty"`
BitstreamSwitching *URL `xml:"BitstreamSwitching,omitempty"`
Duration *uint32 `xml:"duration,attr,omitempty"`
StartNumber *uint32 `xml:"startNumber,attr,omitempty"`
}
type SegmentList struct {
MultipleSegmentBase
SegmentURLs []*SegmentURL `xml:"SegmentURL,omitempty"`
}
type SegmentURL struct {
Media *string `xml:"media,attr,omitempty"`
MediaRange *string `xml:"mediaRange,attr,omitempty"`
Index *string `xml:"index,attr,omitempty"`
IndexRange *string `xml:"indexRange,attr,omitempty"`
}
type SegmentTimeline struct {
Segments []*SegmentTimelineSegment `xml:"S,omitempty"`
}
type SegmentTimelineSegment struct {
StartTime *uint64 `xml:"t,attr,omitempty"`
Duration uint64 `xml:"d,attr"`
RepeatCount *int `xml:"r,attr,omitempty"`
}
type URL struct {
SourceURL *string `xml:"sourceURL,attr,omitempty"`
Range *string `xml:"range,attr,omitempty"`
}

View File

@ -0,0 +1,9 @@
package mpd
// Validate checks for incomplete MPD object
func (m *MPD) Validate() error {
if m.Profiles == nil {
return ErrNoDASHProfileSet
}
return nil
}

4
vendor/modules.txt vendored
View File

@ -404,6 +404,10 @@ github.com/xWTF/chardet
# github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
## explicit
github.com/xrash/smetrics
# github.com/zencoder/go-dash/v3 v3.0.2
## explicit; go 1.13
github.com/zencoder/go-dash/v3/helpers/ptrs
github.com/zencoder/go-dash/v3/mpd
# go.uber.org/atomic v1.7.0
## explicit; go 1.13
go.uber.org/atomic