Compare commits

..
Author SHA1 Message Date
LogalDeveloper 764e41f2b7 Fixed thumbnail generator using wrong init segment filename for multi-variant streams.
Automated API tests / test (push) Failing after 6s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 15:49:45 -04:00
LogalDeveloper 84cd880753 Fixed offline master playlist not being processed by the storage provider.
Automated API tests / test (push) Failing after 6s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 14:22:25 -04:00
LogalDeveloper 20c52a51ae Replaced per-request map allocations with a switch statement in cache duration function.
Automated API tests / test (push) Failing after 5s
Browser Tests / cypress-run (push) Failing after 4s
Lint / Go linter (push) Failing after 3s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 13:39:52 -04:00
LogalDeveloper 6b93ac0d78 Fixed segment cleanup deleting init.mp4 and reduced its cache duration to 20 seconds.
Automated API tests / test (push) Failing after 5s
Browser Tests / cypress-run (push) Failing after 5s
Lint / Go linter (push) Failing after 4s
HLS tests / tests (push) Failing after 5s
Go Tests / test (push) Has been cancelled
Go Tests / test-bsds (map[name:freebsd version:12.2]) (push) Has been cancelled
Go Tests / test-bsds (map[name:openbsd version:6.8]) (push) Has been cancelled
2026-04-03 09:52:15 -04:00
LogalDeveloper e0673b084b Fixed linter errors in offline state and cache duration functions. 2026-04-02 09:21:55 -04:00
LogalDeveloper f0aa545d32 Fixed offline video state to use fMP4 segments instead of launching ffmpeg with the old TS file. 2026-04-02 09:11:00 -04:00
Gabe Kangas 9c290fa0d7 feat(video): move from ts to fMP4 segments 2026-03-24 11:14:36 -07:00
156 changed files with 1950 additions and 2662 deletions
@@ -0,0 +1,57 @@
name: ActivityPub Federation Tests
on:
push:
paths:
- "activitypub/**"
- "webserver/**"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-federation-tests.yaml"
pull_request:
paths:
- "activitypub/**"
- "webserver/**"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-federation-tests.yaml"
jobs:
federation-test:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: "same_content_newer"
- name: Check out repository code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "^1.23"
cache: true
- name: Install Caddy
run: |
sudo apt-get update
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt-get update
sudo apt-get install -y caddy
- name: Install dependencies
run: |
sudo apt-get install -y snac2 ffmpeg
- name: Run setup
run: |
cd test/automated/activitypub
sudo ./setup.sh
- name: Run federation test
run: |
cd test/automated/activitypub
CI=true USER_COUNT=20 ./run.sh
@@ -0,0 +1,47 @@
name: ActivityPub Follower Validation Tests
on:
push:
paths:
- "activitypub/**"
- "config/**"
- "main.go"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-follower-validation-tests.yaml"
pull_request:
paths:
- "activitypub/**"
- "config/**"
- "main.go"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-follower-validation-tests.yaml"
jobs:
follower-validation-test:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: "same_content_newer"
- name: Check out repository code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "^1.23"
cache: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y sqlite3 ffmpeg jq
- name: Run follower validation test
timeout-minutes: 15
run: |
cd test/automated/activitypub
./test-follower-validation.sh
-130
View File
@@ -1,130 +0,0 @@
name: ActivityPub Tests
on:
push:
paths:
- "activitypub/**"
- "core/chat/**"
- "config/**"
- "main.go"
- "webserver/**"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-tests.yaml"
pull_request:
paths:
- "activitypub/**"
- "core/chat/**"
- "config/**"
- "main.go"
- "webserver/**"
- "test/automated/activitypub/**"
- ".github/workflows/activitypub-tests.yaml"
jobs:
federation-test:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: "same_content_newer"
- name: Check out repository code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "^1.23"
cache: true
- name: Install Caddy
run: |
sudo apt-get update
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt-get update
sudo apt-get install -y caddy
- name: Install dependencies
run: |
sudo apt-get install -y snac2 ffmpeg
- name: Run setup
run: |
cd test/automated/activitypub
sudo ./setup.sh
- name: Run federation test
run: |
cd test/automated/activitypub
CI=true USER_COUNT=20 ./test-federation.sh
chat-sanitization-test:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: "same_content_newer"
- name: Check out repository code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "^1.23"
cache: true
- name: Install Caddy
run: |
sudo apt-get update
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt-get update
sudo apt-get install -y caddy
- name: Install dependencies
run: |
sudo apt-get install -y snac2 ffmpeg
- name: Run setup
run: |
cd test/automated/activitypub
sudo ./setup.sh
- name: Run chat sanitization test
run: |
cd test/automated/activitypub
CI=true ./test-chat-sanitization.sh
follower-validation-test:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: "same_content_newer"
- name: Check out repository code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "^1.23"
cache: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y sqlite3 ffmpeg jq
- name: Run follower validation test
timeout-minutes: 15
run: |
cd test/automated/activitypub
./test-follower-validation.sh
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
issues: write issues: write
steps: steps:
- name: Add comment - name: Add comment
uses: peter-evans/create-or-update-comment@57232238742e38b2ccc27136ce596ccae7ca28b4 uses: peter-evans/create-or-update-comment@9143c495e69aa6ad64fa02b7515ccab6bd35aad1
with: with:
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
body: | body: |
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
- name: Publish to Chromatic - name: Publish to Chromatic
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' && steps.changed-files-yaml.outputs.src_any_changed == 'true' }} if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' && steps.changed-files-yaml.outputs.src_any_changed == 'true' }}
uses: chromaui/action@v16 uses: chromaui/action@v15
with: with:
workingDir: web workingDir: web
projectToken: f47410569b62 projectToken: f47410569b62
@@ -38,7 +38,6 @@ on:
jobs: jobs:
formatting: formatting:
name: Code formatting name: Code formatting
if: github.actor != 'renovate[bot]' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -134,7 +133,6 @@ jobs:
unused-code: unused-code:
name: Test for unused code name: Test for unused code
if: github.actor != 'renovate[bot]' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -199,7 +197,7 @@ jobs:
web-bundle: web-bundle:
name: Build and bundle web project name: Build and bundle web project
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'owncast/owncast' && !cancelled() if: github.repository == 'owncast/owncast'
needs: [formatting, unused-code] needs: [formatting, unused-code]
steps: steps:
- id: skip_check - id: skip_check
-24
View File
@@ -15,7 +15,6 @@ on:
permissions: permissions:
contents: read contents: read
pull-requests: write
jobs: jobs:
validate-checklist: validate-checklist:
@@ -45,7 +44,6 @@ jobs:
} }
- name: Validate required checkboxes - name: Validate required checkboxes
id: validate
if: ${{ steps.membership.outputs.result == 'false' }} if: ${{ steps.membership.outputs.result == 'false' }}
uses: actions/github-script@v8 uses: actions/github-script@v8
with: with:
@@ -57,7 +55,6 @@ jobs:
const match = prBody.match(markerPattern); const match = prBody.match(markerPattern);
if (!match) { if (!match) {
core.setOutput('missing_checklist', 'true');
core.setFailed( core.setFailed(
'## PR Checklist Validation Failed\n\n' + '## PR Checklist Validation Failed\n\n' +
'The required checklist section is missing from your PR description.\n\n' + 'The required checklist section is missing from your PR description.\n\n' +
@@ -89,24 +86,3 @@ jobs:
} else { } else {
console.log('All required checklist items are checked'); console.log('All required checklist items are checked');
} }
- name: Comment on PR about missing checklist
if: ${{ steps.validate.outputs.missing_checklist == 'true' }}
uses: actions/github-script@v8
with:
script: |
const body =
'## PR Checklist Missing\n\n' +
'It looks like the **required checklist section** was removed from your PR description. ' +
'This section is needed for the PR checks to pass.\n\n' +
'Please edit your PR description and restore the checklist from ' +
'[the PR template](https://raw.githubusercontent.com/owncast/owncast/refs/heads/develop/.github/PULL_REQUEST_TEMPLATE.MD). ' +
'Once restored, check off each item to confirm you have completed them.\n\n' +
'The checklist section begins with `<!-- REQUIRED-CHECKLIST:START -->` and ends with `<!-- REQUIRED-CHECKLIST:END -->`.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: body
});
-2
View File
@@ -16,8 +16,6 @@ on:
# Run the workflow every hour # Run the workflow every hour
- cron: '0 * * * *' - cron: '0 * * * *'
push: push:
branches:
- develop
paths: paths:
- 'web/i18n/en/translation.json' - 'web/i18n/en/translation.json'
- 'web/**/*.tsx' - 'web/**/*.tsx'
+4 -13
View File
@@ -4,24 +4,12 @@ import (
"fmt" "fmt"
"github.com/go-fed/activity/streams/vocab" "github.com/go-fed/activity/streams/vocab"
"github.com/microcosm-cc/bluemonday"
"github.com/owncast/owncast/activitypub/resolvers" "github.com/owncast/owncast/activitypub/resolvers"
"github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/persistence/configrepository" "github.com/owncast/owncast/persistence/configrepository"
) )
// sanitizeActorName strips HTML tags from the ActivityPub actor display name.
// Falls back to the username if the display name is empty or entirely HTML.
func sanitizeActorName(displayName, username string) string {
strict := bluemonday.StrictPolicy()
name := strict.Sanitize(displayName)
if name == "" {
name = strict.Sanitize(username)
}
return name
}
func handleEngagementActivity(eventType events.EventType, isLiveNotification bool, actorReference vocab.ActivityStreamsActorProperty, action string) error { func handleEngagementActivity(eventType events.EventType, isLiveNotification bool, actorReference vocab.ActivityStreamsActorProperty, action string) error {
configRepository := configrepository.Get() configRepository := configrepository.Get()
@@ -42,7 +30,10 @@ func handleEngagementActivity(eventType events.EventType, isLiveNotification boo
} }
// Send chat message // Send chat message
actorName := sanitizeActorName(actor.Name, actor.Username) actorName := actor.Name
if actorName == "" {
actorName = actor.Username
}
actorIRI := actor.ActorIriString() actorIRI := actor.ActorIriString()
userPrefix := fmt.Sprintf("%s ", actorName) userPrefix := fmt.Sprintf("%s ", actorName)
-120
View File
@@ -1,120 +0,0 @@
package inbox
import (
"testing"
)
func TestSanitizeActorName(t *testing.T) {
tests := []struct {
name string
displayName string
username string
expected string
}{
{
name: "plain display name",
displayName: "Alice",
username: "alice",
expected: "Alice",
},
{
name: "display name with emoji",
displayName: "Alice 🦊",
username: "alice",
expected: "Alice 🦊",
},
{
name: "display name with unicode",
displayName: "Ålice Böb",
username: "alice",
expected: "Ålice Böb",
},
{
name: "empty display name falls back to username",
displayName: "",
username: "alice",
expected: "alice",
},
{
name: "script tag in display name",
displayName: `<script>alert("xss")</script>`,
username: "alice",
expected: "alice",
},
{
name: "iframe injection in display name",
displayName: `<iframe src="https://evil.com" style="position:fixed;top:0;left:0;width:100%;height:100%"></iframe>`,
username: "alice",
expected: "alice",
},
{
name: "img tag in display name",
displayName: `<img src="https://evil.com/track.png">`,
username: "alice",
expected: "alice",
},
{
name: "form injection in display name",
displayName: `<form action="https://evil.com/steal"><input name="pw" type="password"></form>`,
username: "alice",
expected: "alice",
},
{
name: "meta refresh in display name",
displayName: `<meta http-equiv="refresh" content="0;url=https://evil.com">`,
username: "alice",
expected: "alice",
},
{
name: "mixed text and HTML in display name",
displayName: `Alice <script>alert(1)</script> Bob`,
username: "alice",
expected: "Alice Bob",
},
{
name: "custom emoji HTML in display name",
displayName: `Alice :blobcat: <img src="https://instance.com/emoji/blobcat.png" class="custom-emoji">`,
username: "alice",
expected: "Alice :blobcat: ",
},
{
name: "HTML in both display name and username",
displayName: `<script>alert(1)</script>`,
username: `<b>alice</b>`,
expected: "alice",
},
{
name: "entirely HTML display name falls back to username",
displayName: `<div></div>`,
username: "alice",
expected: "alice",
},
{
name: "style tag in display name",
displayName: `<style>body{display:none}</style>Alice`,
username: "alice",
expected: "Alice",
},
{
name: "nested HTML tags",
displayName: `<div><span><a href="https://evil.com">Click me</a></span></div>`,
username: "alice",
expected: "Click me",
},
{
name: "event handler attributes",
displayName: `<img src=x onerror="alert(1)">Alice`,
username: "alice",
expected: "Alice",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := sanitizeActorName(tt.displayName, tt.username)
if result != tt.expected {
t.Errorf("sanitizeActorName(%q, %q) = %q, want %q", tt.displayName, tt.username, result, tt.expected)
}
})
}
}
+1
View File
@@ -126,6 +126,7 @@ func SendFediverseAction(eventType string, userAccountName string, image *string
} }
message.SetDefaults() message.SetDefaults()
message.RenderBody()
if err := Broadcast(&message); err != nil { if err := Broadcast(&message); err != nil {
log.Errorln("error sending system message", err) log.Errorln("error sending system message", err)
+41 -10
View File
@@ -1,6 +1,7 @@
package core package core
import ( import (
"fmt"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -98,23 +99,53 @@ func createInitialOfflineState() error {
// offline video stream state only. No live stream HLS segments will continue to be // offline video stream state only. No live stream HLS segments will continue to be
// referenced. // referenced.
func transitionToOfflineVideoStreamContent() { func transitionToOfflineVideoStreamContent() {
log.Traceln("Firing transcoder with offline stream state") log.Traceln("Placing offline fMP4 content into HLS directories")
_transcoder := transcoder.NewTranscoder() configRepository := configrepository.Get()
_transcoder.SetIdentifier("offline")
_transcoder.SetLatencyLevel(models.GetLatencyLevel(4))
_transcoder.SetIsEvent(true)
offlineFilePath, err := saveOfflineClipToDisk("offline-v2.ts") offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk()
if err != nil { if err != nil {
log.Fatalln("unable to save offline clip:", err) log.Fatalln("unable to save offline fMP4 files:", err)
} }
_transcoder.SetInput(offlineFilePath) variants := configRepository.GetStreamOutputVariants()
go _transcoder.Start(false) if len(variants) == 0 {
// Ensure at least one variant directory exists.
variants = make([]models.StreamOutputVariant, 1)
}
for index := range variants {
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
if err := os.MkdirAll(variantDir, 0o750); err != nil {
log.Errorln("unable to create variant directory:", err)
continue
}
makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath)
}
// Write the master playlist that points to each variant's playlist.
masterPlaylistPath := filepath.Join(config.HLSStoragePath, "stream.m3u8")
masterFile, err := os.Create(masterPlaylistPath) //nolint:gosec
if err != nil {
log.Errorln("unable to create master playlist:", err)
} else {
_, _ = masterFile.WriteString("#EXTM3U\n")
_, _ = masterFile.WriteString("#EXT-X-VERSION:7\n")
_, _ = masterFile.WriteString("#EXT-X-INDEPENDENT-SEGMENTS\n")
for index := range variants {
_, _ = fmt.Fprintf(masterFile, "#EXT-X-STREAM-INF:BANDWIDTH=0\n")
_, _ = fmt.Fprintf(masterFile, "%d/stream.m3u8\n", index)
}
if err := masterFile.Close(); err != nil {
log.Errorln("unable to close master playlist:", err)
}
// Notify the storage provider so it can rewrite variant URLs
// to absolute paths for S3 or remote serving endpoints.
_storage.MasterPlaylistWritten(masterPlaylistPath)
}
// Copy the logo to be the thumbnail // Copy the logo to be the thumbnail
configRepository := configrepository.Get()
logo := configRepository.GetLogoPath() logo := configRepository.GetLogoPath()
dst := filepath.Join(config.TempDir, "thumbnail.jpg") dst := filepath.Join(config.TempDir, "thumbnail.jpg")
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil { if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
+61 -33
View File
@@ -5,14 +5,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/grafov/m3u8"
"github.com/owncast/owncast/config" "github.com/owncast/owncast/config"
"github.com/owncast/owncast/static" "github.com/owncast/owncast/static"
"github.com/owncast/owncast/utils" "github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func appendOfflineToVariantPlaylist(index int, playlistFilePath string) { func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFilename string, segmentFilename string) {
existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec
if err != nil { if err != nil {
log.Debugln("unable to read existing playlist file", err) log.Debugln("unable to read existing playlist file", err)
@@ -34,9 +33,10 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
// Manually append the offline clip to the end of the media playlist. // Manually append the offline clip to the end of the media playlist.
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-DISCONTINUITY\n") _, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-DISCONTINUITY\n")
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
// If "offline" content gets changed then change the duration below // If "offline" content gets changed then change the duration below
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n") _, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n")
_, _ = atomicWriteTmpPlaylistFile.WriteString("offline-v2.ts\n") _, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "%s\n", segmentFilename)
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n") _, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n")
if err := atomicWriteTmpPlaylistFile.Close(); err != nil { if err := atomicWriteTmpPlaylistFile.Close(); err != nil {
@@ -48,61 +48,89 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
} }
} }
func makeVariantIndexOffline(index int, offlineFilePath string, offlineFilename string) { func makeVariantIndexOffline(index int, offlineInitPath string, offlineSegmentPath string) {
playlistFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/stream.m3u8"), index) variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
segmentFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/%s"), index, offlineFilename) playlistFilePath := filepath.Join(variantDir, "stream.m3u8")
if err := utils.Copy(offlineFilePath, segmentFilePath); err != nil { initDest := filepath.Join(variantDir, "offline-init.mp4")
segmentDest := filepath.Join(variantDir, "offline-v2.m4s")
if err := utils.Copy(offlineInitPath, initDest); err != nil {
log.Warnln(err)
return
}
if _, err := _storage.Save(initDest, 0); err != nil {
log.Warnln(err) log.Warnln(err)
} }
if _, err := _storage.Save(segmentFilePath, 0); err != nil { if err := utils.Copy(offlineSegmentPath, segmentDest); err != nil {
log.Warnln(err)
return
}
if _, err := _storage.Save(segmentDest, 0); err != nil {
log.Warnln(err) log.Warnln(err)
} }
if utils.DoesFileExists(playlistFilePath) { if utils.DoesFileExists(playlistFilePath) {
appendOfflineToVariantPlaylist(index, playlistFilePath) appendOfflineToVariantPlaylist(index, playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
} else { } else {
createEmptyOfflinePlaylist(playlistFilePath, offlineFilename) createEmptyOfflinePlaylist(playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
} }
if _, err := _storage.Save(playlistFilePath, 0); err != nil { if _, err := _storage.Save(playlistFilePath, 0); err != nil {
log.Warnln(err) log.Warnln(err)
} }
} }
func createEmptyOfflinePlaylist(playlistFilePath string, offlineFilename string) { func createEmptyOfflinePlaylist(playlistFilePath string, initFilename string, segmentFilename string) {
p, err := m3u8.NewMediaPlaylist(1, 1)
if err != nil {
log.Errorln(err)
}
// If "offline" content gets changed then change the duration below
if err := p.Append(offlineFilename, 8.0, ""); err != nil {
log.Errorln(err)
}
p.Close()
f, err := os.Create(playlistFilePath) //nolint:gosec f, err := os.Create(playlistFilePath) //nolint:gosec
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return
} }
defer f.Close() defer f.Close()
if _, err := f.Write(p.Encode().Bytes()); err != nil {
log.Errorln(err) _, _ = f.WriteString("#EXTM3U\n")
} _, _ = f.WriteString("#EXT-X-VERSION:7\n")
_, _ = f.WriteString("#EXT-X-TARGETDURATION:8\n")
_, _ = fmt.Fprintf(f, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
// If "offline" content gets changed then change the duration below
_, _ = f.WriteString("#EXTINF:8.000000,\n")
_, _ = fmt.Fprintf(f, "%s\n", segmentFilename)
_, _ = f.WriteString("#EXT-X-ENDLIST\n")
} }
func saveOfflineClipToDisk(offlineFilename string) (string, error) { func saveOfflineFMP4ToDisk() (initPath string, segmentPath string, err error) {
offlineFileData := static.GetOfflineSegment() initData := static.GetOfflineInitSegment()
offlineTmpFile, err := os.CreateTemp(config.TempDir, offlineFilename) initTmp, err := os.CreateTemp(config.TempDir, "offline-init-*.mp4")
if err != nil { if err != nil {
log.Errorln("unable to create temp file for offline video segment", err) return "", "", fmt.Errorf("unable to create temp file for offline init segment: %s", err)
} }
if _, err = offlineTmpFile.Write(offlineFileData); err != nil { if _, err = initTmp.Write(initData); err != nil {
return "", fmt.Errorf("unable to write offline segment to disk: %s", err) return "", "", fmt.Errorf("unable to write offline init segment to disk: %s", err)
} }
offlineFilePath := offlineTmpFile.Name() if err := initTmp.Close(); err != nil {
return filepath.Abs(offlineFilePath) return "", "", fmt.Errorf("unable to close offline init segment: %s", err)
}
initPath, _ = filepath.Abs(initTmp.Name())
segData := static.GetOfflineMediaSegment()
segTmp, err := os.CreateTemp(config.TempDir, "offline-v2-*.m4s")
if err != nil {
return "", "", fmt.Errorf("unable to create temp file for offline media segment: %s", err)
}
if _, err = segTmp.Write(segData); err != nil {
return "", "", fmt.Errorf("unable to write offline media segment to disk: %s", err)
}
if err := segTmp.Close(); err != nil {
return "", "", fmt.Errorf("unable to close offline media segment: %s", err)
}
segmentPath, _ = filepath.Abs(segTmp.Name())
return initPath, segmentPath, nil
} }
+1 -1
View File
@@ -82,7 +82,7 @@ func getAllFilesRecursive(baseDirectory string) (map[string][]os.FileInfo, error
directory = info.Name() directory = info.Name()
} }
if filepath.Ext(info.Name()) == ".ts" { if filepath.Ext(info.Name()) == ".m4s" {
files[directory] = append(files[directory], info) files[directory] = append(files[directory], info)
} }
+1 -1
View File
@@ -317,7 +317,7 @@ func (s *S3Storage) retrieveAllVideoSegments() ([]s3object, error) {
err := s.s3Client.ListObjectsPages(allObjectsListRequest, err := s.s3Client.ListObjectsPages(allObjectsListRequest,
func(page *s3.ListObjectsOutput, lastPage bool) bool { func(page *s3.ListObjectsOutput, lastPage bool) bool {
for _, item := range page.Contents { for _, item := range page.Contents {
if strings.HasSuffix(*item.Key, ".ts") { if strings.HasSuffix(*item.Key, ".m4s") {
allObjects = append(allObjects, s3object{ allObjects = append(allObjects, s3object{
key: *item.Key, key: *item.Key,
lastModified: *item.LastModified, lastModified: *item.LastModified,
+2 -4
View File
@@ -96,9 +96,7 @@ func SetStreamAsDisconnected() {
_stats.LastConnectTime = nil _stats.LastConnectTime = nil
_broadcaster = nil _broadcaster = nil
offlineFilename := "offline-v2.ts" offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk()
offlineFilePath, err := saveOfflineClipToDisk(offlineFilename)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return return
@@ -122,7 +120,7 @@ func SetStreamAsDisconnected() {
} }
for index := range _currentBroadcast.OutputSettings { for index := range _currentBroadcast.OutputSettings {
makeVariantIndexOffline(index, offlineFilePath, offlineFilename) makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath)
} }
StartOfflineCleanupTimer() StartOfflineCleanupTimer()
+4 -1
View File
@@ -18,6 +18,7 @@ type FileWriterReceiverServiceCallback interface {
SegmentWritten(localFilePath string) SegmentWritten(localFilePath string)
VariantPlaylistWritten(localFilePath string) VariantPlaylistWritten(localFilePath string)
MasterPlaylistWritten(localFilePath string) MasterPlaylistWritten(localFilePath string)
InitSegmentWritten(localFilePath string)
} }
// FileWriterReceiverService accepts transcoder responses via HTTP and fires the callbacks. // FileWriterReceiverService accepts transcoder responses via HTTP and fires the callbacks.
@@ -81,8 +82,10 @@ func (s *FileWriterReceiverService) uploadHandler(w http.ResponseWriter, r *http
func (s *FileWriterReceiverService) fileWritten(path string) { func (s *FileWriterReceiverService) fileWritten(path string) {
if utils.GetRelativePathFromAbsolutePath(path) == "hls/stream.m3u8" { if utils.GetRelativePathFromAbsolutePath(path) == "hls/stream.m3u8" {
s.callbacks.MasterPlaylistWritten(path) s.callbacks.MasterPlaylistWritten(path)
} else if strings.HasSuffix(path, ".ts") { } else if strings.HasSuffix(path, ".m4s") {
s.callbacks.SegmentWritten(path) s.callbacks.SegmentWritten(path)
} else if strings.HasSuffix(path, "init.mp4") {
s.callbacks.InitSegmentWritten(path)
} else if strings.HasSuffix(path, ".m3u8") { } else if strings.HasSuffix(path, ".m3u8") {
s.callbacks.VariantPlaylistWritten(path) s.callbacks.VariantPlaylistWritten(path)
} }
+5
View File
@@ -23,3 +23,8 @@ func (h *HLSHandler) VariantPlaylistWritten(localFilePath string) {
func (h *HLSHandler) MasterPlaylistWritten(localFilePath string) { func (h *HLSHandler) MasterPlaylistWritten(localFilePath string) {
h.Storage.MasterPlaylistWritten(localFilePath) h.Storage.MasterPlaylistWritten(localFilePath)
} }
// InitSegmentWritten is fired when an fMP4 init segment is written to disk.
func (h *HLSHandler) InitSegmentWritten(localFilePath string) {
h.Storage.SegmentWritten(localFilePath)
}
+18 -3
View File
@@ -1,6 +1,7 @@
package transcoder package transcoder
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -64,7 +65,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
var modTime time.Time var modTime time.Time
var names []string var names []string
for _, f := range files { for _, f := range files {
if path.Ext(f.Name()) != ".ts" { if path.Ext(f.Name()) != ".m4s" {
continue continue
} }
@@ -88,7 +89,21 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
return nil return nil
} }
configRepository := configrepository.Get() configRepository := configrepository.Get()
// With multiple variants ffmpeg names init segments init_0.mp4, init_1.mp4, etc.
// With a single variant it uses init.mp4.
initSegment := path.Join(framePath, fmt.Sprintf("init_%d.mp4", variantIndex))
if !utils.DoesFileExists(initSegment) {
initSegment = path.Join(framePath, "init.mp4")
}
mostRecentFile := path.Join(framePath, names[0]) mostRecentFile := path.Join(framePath, names[0])
// The segment may have been cleaned up between ReadDir and now.
if !utils.DoesFileExists(mostRecentFile) {
return nil
}
// fMP4 segments require the init segment to be readable by ffmpeg
inputArg := fmt.Sprintf("concat:%s|%s", initSegment, mostRecentFile)
ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath()) ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg") outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg")
@@ -96,7 +111,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
"-y", // Overwrite file "-y", // Overwrite file
"-threads", "1", // Low priority processing "-threads", "1", // Low priority processing
"-t", "1", // Pull from frame 1 "-t", "1", // Pull from frame 1
"-i", mostRecentFile, // Input "-i", inputArg, // Input (init segment + media segment)
"-f", "image2", // format "-f", "image2", // format
"-vframes", "1", // Single frame "-vframes", "1", // Single frame
outputFileTemp, outputFileTemp,
@@ -111,7 +126,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
log.Errorln(err) log.Errorln(err)
} }
makeAnimatedGifPreview(mostRecentFile, previewGifFile) makeAnimatedGifPreview(inputArg, previewGifFile)
return nil return nil
} }
+4 -2
View File
@@ -245,7 +245,9 @@ func (t *Transcoder) getFlags() *execInfo {
ffmpegFlags = append(ffmpegFlags, hlsOptionsString...) ffmpegFlags = append(ffmpegFlags, hlsOptionsString...)
ffmpegFlags = append(ffmpegFlags, hlsEventString...) ffmpegFlags = append(ffmpegFlags, hlsEventString...)
ffmpegFlags = append(ffmpegFlags, []string{ ffmpegFlags = append(ffmpegFlags, []string{
"-segment_format_options", "mpegts_flags=mpegts_copyts=1", "-hls_segment_type", "fmp4",
"-hls_fmp4_init_filename", "init.mp4",
"-bsf:a", "aac_adtstoasc", // Convert AAC ADTS headers to ASC for fMP4 container compatibility
}...) }...)
ffmpegFlags = append(ffmpegFlags, t.codec.ExtraArguments()...) ffmpegFlags = append(ffmpegFlags, t.codec.ExtraArguments()...)
@@ -257,7 +259,7 @@ func (t *Transcoder) getFlags() *execInfo {
// Filenames // Filenames
"-master_pl_name", "stream.m3u8", "-master_pl_name", "stream.m3u8",
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.ts", // Send HLS segments back to us over HTTP "-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.m4s", // Send HLS segments back to us over HTTP
"-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0 "-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0
"-method", "PUT", // HLS results sent back to us will be over PUTs "-method", "PUT", // HLS results sent back to us will be over PUTs
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegNvencCommand(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected { 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)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegOmxCommand(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -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.m4s -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)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegQuicksyncCommand(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -pix_fmt qsv -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected { 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)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegVaapiCommand(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -pix_fmt vaapi -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected { 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)
@@ -42,7 +42,7 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -pix_fmt nv12 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.m4s -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected { 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)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegx264Command(t *testing.T) {
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 -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 -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -bsf:a aac_adtstoasc -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.m4s -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)
+11 -7
View File
@@ -16,25 +16,25 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-retryablehttp v0.7.8
github.com/jellydator/ttlcache/v3 v3.4.0 github.com/jellydator/ttlcache/v3 v3.4.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/mattn/go-sqlite3 v1.14.40 github.com/mattn/go-sqlite3 v1.14.37
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/mssola/user_agent v0.6.0 github.com/mssola/user_agent v0.6.0
github.com/nakabonne/tstorage v0.3.6 github.com/nakabonne/tstorage v0.3.6
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590 github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590
github.com/oapi-codegen/runtime v1.3.1 github.com/oapi-codegen/runtime v1.3.0
github.com/oschwald/geoip2-golang v1.13.0 github.com/oschwald/geoip2-golang v1.13.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/schollz/sqlite3dump v1.3.1 github.com/schollz/sqlite3dump v1.3.1
github.com/shirou/gopsutil/v4 v4.25.8 github.com/shirou/gopsutil/v4 v4.26.2
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-emoji v1.0.6 github.com/yuin/goldmark-emoji v1.0.6
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.49.0
golang.org/x/mod v0.33.0 golang.org/x/mod v0.34.0
golang.org/x/net v0.51.0 golang.org/x/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
@@ -48,7 +48,8 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect github.com/ebitengine/purego v0.10.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@@ -59,6 +60,7 @@ require (
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lestrrat-go/strftime v1.1.1 // indirect github.com/lestrrat-go/strftime v1.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -66,14 +68,16 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.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.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+18 -25
View File
@@ -24,10 +24,10 @@ github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
@@ -87,19 +87,13 @@ github.com/lestrrat-go/strftime v1.1.1 h1:zgf8QCsgj27GlKBy3SU9/8MMgegZ8UCzlCyHYr
github.com/lestrrat-go/strftime v1.1.1/go.mod h1:YDrzHJAODYQ+xxvrn5SG01uFIQAeDTzpxNVppCz7Nmw= github.com/lestrrat-go/strftime v1.1.1/go.mod h1:YDrzHJAODYQ+xxvrn5SG01uFIQAeDTzpxNVppCz7Nmw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg= github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4=
github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.39 h1:sIwSjlJGOaRJjw44/HXaeTblZMjseqr6OOio1tz/+JI=
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/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=
@@ -112,8 +106,6 @@ github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590 h1:PnxRU8L8Y2q82vFC2Qd
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko= github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko=
github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/4vMYA= github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/4vMYA=
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/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY=
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI= github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
@@ -138,12 +130,12 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA= github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI= github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -161,10 +153,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI= github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI= github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g= github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
@@ -194,8 +186,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= 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=
@@ -274,8 +266,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v5 v5.9.11 h1:OMPeiLomOQwe8+Ku4nwXsdOmrRw2vGUpP3XgLj3ojNw= gopkg.in/evanphx/json-patch.v5 v5.9.11 h1:OMPeiLomOQwe8+Ku4nwXsdOmrRw2vGUpP3XgLj3ojNw=
gopkg.in/evanphx/json-patch.v5 v5.9.11/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= gopkg.in/evanphx/json-patch.v5 v5.9.11/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+13 -5
View File
@@ -51,12 +51,20 @@ func GetWebIndexTemplate() (*template.Template, error) {
return tmpl, err return tmpl, err
} }
//go:embed offline-v2.ts //go:embed offline-init.mp4
var offlineVideoSegment []byte var offlineInitSegment []byte
// GetOfflineSegment will return the offline video segment data. // GetOfflineInitSegment will return the fMP4 init segment for the offline clip.
func GetOfflineSegment() []byte { func GetOfflineInitSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-v2.ts", offlineVideoSegment) return getFileSystemStaticFileOrDefault("offline-init.mp4", offlineInitSegment)
}
//go:embed offline-v2.m4s
var offlineMediaSegment []byte
// GetOfflineMediaSegment will return the fMP4 media segment for the offline clip.
func GetOfflineMediaSegment() []byte {
return getFileSystemStaticFileOrDefault("offline-v2.m4s", offlineMediaSegment)
} }
//go:embed img/logo.png //go:embed img/logo.png
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.VideoJS_player__GD36e{width:100%;position:relative;overflow:hidden}@media(hover:hover)and (pointer:fine){.VideoJS_player__GD36e .vjs-control-bar .vjs-control:hover .vjs-control-text{display:block!important;visibility:visible!important;width:auto!important;height:auto!important;clip:auto!important;margin:0!important;position:absolute;bottom:45px;left:50%;transform:translateX(-50%);white-space:nowrap;z-index:1000;padding:5px 10px;background:hsla(0,0%,8%,.9);color:#fff;border-radius:4px;font-size:12px;line-height:1.2;pointer-events:none}.VideoJS_player__GD36e .vjs-control-bar .vjs-control:hover .vjs-control-text:empty,.VideoJS_player__GD36e .vjs-volume-panel:hover .vjs-mute-control:not(:hover) .vjs-control-text{display:none!important}.VideoJS_player__GD36e .vjs-control:first-child:hover .vjs-control-text{left:0;transform:translateX(6px)}.VideoJS_player__GD36e .vjs-control:first-child:hover .vjs-control-text:after{left:15%}.VideoJS_player__GD36e .vjs-control:last-child:hover .vjs-control-text{left:auto;right:0;transform:translateX(-6px)}.VideoJS_player__GD36e .vjs-control:last-child:hover .vjs-control-text:after{left:auto;right:15%;transform:translateX(50%)}}.VideoPoster_poster__6rnLj{display:flex;justify-content:center;width:100%;height:100%}.VideoPoster_image__8kRcw{background-color:#000}.OwncastPlayer_container__CR5Ry{display:grid;width:100%;justify-items:center;height:var(--player-container-height);aspect-ratio:16/9}@media(width <= 1200px){.OwncastPlayer_container__CR5Ry{height:100%;max-height:var(--player-container-height)}}@media only screen and (width <= 768px){.OwncastPlayer_container__CR5Ry{height:var(--player-container-height);max-height:var(--player-container-height)}}.OwncastPlayer_container__CR5Ry .OwncastPlayer_player__dCDjy,.OwncastPlayer_container__CR5Ry .OwncastPlayer_poster__tbpwE{width:100%;grid-column:1;grid-row:1}
+1
View File
@@ -0,0 +1 @@
.VideoJS_player__GD36e{width:100%}.VideoPoster_poster__6rnLj{display:flex;justify-content:center;width:100%;height:100%}.VideoPoster_image__8kRcw{background-color:#000}.OwncastPlayer_container__CR5Ry{display:grid;width:100%;justify-items:center;height:var(--player-container-height);aspect-ratio:16/9}@media(width <= 1200px){.OwncastPlayer_container__CR5Ry{height:100%;max-height:var(--player-container-height)}}@media only screen and (width <= 768px){.OwncastPlayer_container__CR5Ry{height:var(--player-container-height);max-height:var(--player-container-height)}}.OwncastPlayer_container__CR5Ry .OwncastPlayer_player__dCDjy,.OwncastPlayer_container__CR5Ry .OwncastPlayer_poster__tbpwE{width:100%;grid-column:1;grid-row:1}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More