Compare commits

..
31 Commits
Author SHA1 Message Date
Jason Dove bb5b9f9be4 update changelog for release v0.3.8-alpha [no ci] 2022-01-23 21:46:00 -06:00
Jason Dove 135628441a re-add mac launcher script 2022-01-23 21:09:04 -06:00
Jason DoveandGitHub 4aa7204984 fix ts mode with hdhr clients (#588) 2022-01-23 18:58:15 -06:00
Jason Dove 1af59a0337 don't use macos launcher script 2022-01-23 18:47:00 -06:00
Jason Dove c4c97fcc8c customize mac dmg 2022-01-23 18:36:31 -06:00
Jason Dove 9c46e42792 fix gon variables 2022-01-23 14:21:13 -06:00
Jason Dove efa803aab6 split mac artifacts job 2022-01-23 14:10:57 -06:00
Jason Dove 6ea02a2d77 use proper version number in ci artifacts [no docs] [no build] 2022-01-23 14:04:13 -06:00
Jason Dove 631f7d2d5e don't use reserved secret name 2022-01-23 13:54:37 -06:00
Jason Dove e44a4cb2e1 properly pass secrets between workflows 2022-01-23 13:53:31 -06:00
Jason Dove f4b95419a6 properly pass data between jobs 2022-01-23 13:46:47 -06:00
Jason DoveandGitHub 1a5cf49563 refactor reusable docker workflow (#587)
* refactor reusable docker workflow

* refactor reusable artifacts workflow

* fix name

* try to fix

* fix
2022-01-23 13:42:01 -06:00
Jason Dove efef0b0fee don't use single file for mac bundles 2022-01-22 17:29:35 -06:00
Jason Dove ee7b8a71ab fix docker builds [no build] 2022-01-22 14:19:49 -06:00
Jason DoveandGitHub e7c9a51e96 macos app bundle (#585)
* test signed app bundle

* fix vars

* fix condition

* typo

* fix quoting

* use recursive signing script

* fix release cleanup

* restore proper ci action
2022-01-22 14:03:42 -06:00
Jason Dove 78a954f365 link to development builds in install docs 2022-01-21 20:55:38 -06:00
Jason Dove 355c0b7be9 try to fix deleting old assets 2022-01-21 20:29:50 -06:00
Jason Dove 3bcb2d36f9 another attempt at publishing artifacts 2022-01-21 20:21:26 -06:00
Jason Dove b240de9d4a publish develop artifacts to stable release url 2022-01-21 18:44:25 -06:00
Jason Dove f5001837cb properly separate build artifacts 2022-01-21 18:20:51 -06:00
Jason Dove 6ea916b1f0 fix fetch depth 2022-01-21 15:30:40 -06:00
Jason Dove db6fd22215 try to fix build 2022-01-21 15:25:03 -06:00
Jason DoveandGitHub 691842008d upload develop binaries for every merge to main (#584)
* upload develop binaries for every merge to main

* rename step
2022-01-21 15:18:08 -06:00
Jason DoveandGitHub 685f78bef8 fix search results bug (#583) 2022-01-21 14:05:09 -06:00
Jason DoveandGitHub 3ce267863b fix hls segmenter in some cultures (#582) 2022-01-21 10:42:33 -06:00
Jason DoveandGitHub e4231cb57d upgrade from ffmpeg 4.4 to 5.0 (#581) 2022-01-20 20:57:38 -06:00
Jason DoveandGitHub 03946b13ca always use a single ffmpeg thread with realtime (#580) 2022-01-20 14:53:13 -06:00
Jason DoveandGitHub f1a81bf086 clarify library kind/media kind support (#579) [no docker] 2022-01-19 09:11:23 -06:00
Jason Dove 7a88374362 clarify flood scheduling calc [no ci] 2022-01-18 18:22:46 -06:00
Jason DoveandGitHub 663a62431b properly fix startup paths (#576) 2022-01-17 16:31:22 -06:00
Jason Dove 1d4acc284d Update changelog for release v0.3.7-alpha [no ci] 2022-01-17 15:23:39 -06:00
42 changed files with 753 additions and 310 deletions
+211
View File
@@ -0,0 +1,211 @@
name: Build Artifacts
on:
workflow_call:
inputs:
release_tag:
description: 'Release tag'
required: true
type: string
release_version:
description: 'Release version number (e.g. v0.3.7-alpha)'
required: true
type: string
info_version:
description: 'Informational version number (e.g. 0.3.7-alpha)'
required: true
type: string
secrets:
apple_developer_certificate_p12_base64:
required: true
apple_developer_certificate_password:
required: true
ac_username:
required: true
ac_password:
required: true
gh_token:
required: true
jobs:
build_and_upload_mac:
name: Mac Build & Upload
runs-on: ${{ matrix.os }}
if: contains(github.event.head_commit.message, '[no build]') == false
strategy:
matrix:
include:
- os: macos-latest
kind: macOS
target: osx-x64
- os: macos-latest
kind: macOS
target: osx-arm64
steps:
- name: Get the sources
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore -r "${{ matrix.target}}"
- name: Import Code-Signing Certificates
uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.apple_developer_certificate_p12_base64 }}
p12-password: ${{ secrets.apple_developer_certificate_password }}
- name: Calculate Release Name
shell: bash
run: |
release_name="ErsatzTV-${{ inputs.release_version }}-${{ matrix.target }}"
echo "RELEASE_NAME=${release_name}" >> $GITHUB_ENV
- name: Build
shell: bash
run: dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o publish -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true
- name: Bundle
shell: bash
run: |
plutil -replace CFBundleShortVersionString -string "${{ inputs.info_version }}" scripts/macOS/Info.plist
plutil -replace CFBundleVersion -string "${{ inputs.info_version }}" scripts/macOS/Info.plist
scripts/macOS/bundle.sh
- name: Sign
shell: bash
run: scripts/macOS/sign.sh
- name: Create DMG
shell: bash
run: |
brew install create-dmg
create-dmg \
--volname "ErsatzTV" \
--volicon "artwork/ErsatzTV.icns" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "ErsatzTV.app" 200 190 \
--hide-extension "ErsatzTV.app" \
--app-drop-link 600 185 \
"ErsatzTV.dmg" \
"ErsatzTV.app/"
- name: Notarize
shell: bash
run: |
brew tap mitchellh/gon
brew install mitchellh/gon/gon
gon -log-level=debug -log-json ./gon.json
env:
AC_USERNAME: ${{ secrets.ac_username }}
AC_PASSWORD: ${{ secrets.ac_password }}
- name: Cleanup
shell: bash
run: |
mv ErsatzTV.dmg "${{ env.RELEASE_NAME }}.dmg"
rm -r publish
rm -r ErsatzTV.app
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}
fail-if-no-assets: false
assets: |
*${{ matrix.target }}.dmg
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ inputs.release_tag }}
files: |
${{ env.RELEASE_NAME }}.dmg
env:
GITHUB_TOKEN: ${{ secrets.gh_token }}
build_and_upload:
name: Build & Upload
runs-on: ${{ matrix.os }}
if: contains(github.event.head_commit.message, '[no build]') == false
strategy:
matrix:
include:
- os: ubuntu-latest
kind: linux
target: linux-x64
- os: ubuntu-latest
kind: linux
target: linux-arm
- os: windows-latest
kind: windows
target: win-x64
steps:
- name: Get the sources
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore -r "${{ matrix.target}}"
- name: Build
shell: bash
run: |
# Define some variables for things we need
release_name="ErsatzTV-${{ inputs.release_version }}-${{ matrix.target }}"
echo "RELEASE_NAME=${release_name}" >> $GITHUB_ENV
# Build everything
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true
# Pack files
if [ "${{ matrix.kind }}" == "windows" ]; then
7z a -tzip "${release_name}.zip" "./${release_name}/*"
else
tar czvf "${release_name}.tar.gz" "$release_name"
fi
# Delete output directory
rm -r "$release_name"
env:
AC_USERNAME: ${{ secrets.ac_username }}
AC_PASSWORD: ${{ secrets.ac_password }}
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}
fail-if-no-assets: false
assets: |
*${{ matrix.target }}.zip
*${{ matrix.target }}.tar.gz
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ inputs.release_tag }}
files: |
${{ env.RELEASE_NAME }}.zip
${{ env.RELEASE_NAME }}.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.gh_token }}
+39 -91
View File
@@ -1,49 +1,19 @@
name: Build name: Build
on: on:
workflow_dispatch: workflow_dispatch:
pull_request:
push: push:
branches: branches:
- main - main
jobs: jobs:
build_and_test: calculate_version:
runs-on: ${{ matrix.os }} name: Calculate version information
strategy: runs-on: ubuntu-latest
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
steps: steps:
- name: Get the sources - name: Get the sources
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
build_and_push:
name: Build & Publish to Docker Hub
needs: build_and_test
runs-on: ubuntu-latest
if: github.event_name == 'push' && !contains(github.event.head_commit.message, '[no docker]')
steps:
- name: Checkout
uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Extract Docker Tag
- name: Extract Git Tag
shell: bash shell: bash
run: | run: |
tag=$(git describe --tags --abbrev=0) tag=$(git describe --tags --abbrev=0)
@@ -51,60 +21,38 @@ jobs:
short=$(git rev-parse --short HEAD) short=$(git rev-parse --short HEAD)
final="${tag2/alpha/$short}" final="${tag2/alpha/$short}"
echo "GIT_TAG=${final}" >> $GITHUB_ENV echo "GIT_TAG=${final}" >> $GITHUB_ENV
- name: Extract Artifacts Version
- name: Set up Docker Buildx Base shell: bash
uses: docker/setup-buildx-action@v1 run: |
id: builder-base tag=$(git describe --tags --abbrev=0)
short=$(git rev-parse --short HEAD)
- name: Set up Docker Buildx NVIDIA final="${tag/alpha/$short}"
uses: docker/setup-buildx-action@v1 echo "ARTIFACTS_VERSION=${final}" >> $GITHUB_ENV
id: builder-nvidia echo "INFO_VERSION=${tag:1}" >> $GITHUB_ENV
outputs:
- name: Set up Docker Buildx VAAPI git_tag: ${{ env.GIT_TAG }}
uses: docker/setup-buildx-action@v1 artifacts_version: ${{ env.ARTIFACTS_VERSION }}
id: builder-vaapi info_version: ${{ env.INFO_VERSION }}
build_and_upload:
- name: Login to DockerHub uses: jasongdove/ersatztv/.github/workflows/artifacts.yml@main
uses: docker/login-action@v1 needs: calculate_version
with: with:
username: ${{ secrets.DOCKER_HUB_USERNAME }} release_tag: develop
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} release_version: ${{ needs.calculate_version.outputs.artifacts_version }}
info_version: ${{ needs.calculate_version.outputs.info_version }}
- name: Build and push base secrets:
uses: docker/build-push-action@v2 apple_developer_certificate_p12_base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
with: apple_developer_certificate_password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
builder: ${{ steps.builder-base.outputs.name }} ac_username: ${{ secrets.AC_USERNAME }}
context: . ac_password: ${{ secrets.AC_PASSWORD }}
file: ./docker/Dockerfile gh_token: ${{ secrets.GITHUB_TOKEN }}
push: true build_and_push:
build-args: | uses: jasongdove/ersatztv/.github/workflows/docker.yml@main
INFO_VERSION=${{ env.GIT_TAG }}-docker needs: calculate_version
tags: | with:
jasongdove/ersatztv:develop base_version: develop
jasongdove/ersatztv:${{ github.sha }} info_version: ${{ needs.calculate_version.outputs.git_tag }}
tag_version: ${{ github.sha }}
- name: Build and push nvidia secrets:
uses: docker/build-push-action@v2 docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
with: docker_hub_access_token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
builder: ${{ steps.builder-nvidia.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-nvidia
tags: |
jasongdove/ersatztv:develop-nvidia
jasongdove/ersatztv:${{ github.sha }}-nvidia
- name: Build and push vaapi
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-vaapi
tags: |
jasongdove/ersatztv:develop-vaapi
jasongdove/ersatztv:${{ github.sha }}-vaapi
+88
View File
@@ -0,0 +1,88 @@
name: Build & Publish to Docker Hub
on:
workflow_call:
inputs:
base_version:
description: 'Base version (latest or develop)'
required: true
type: string
info_version:
description: 'Informational version number (e.g. 0.3.7-alpha)'
required: true
type: string
tag_version:
description: 'Docker tag version (e.g. v0.3.7)'
required: true
type: string
secrets:
docker_hub_username:
required: true
docker_hub_access_token:
required: true
jobs:
build_and_push:
name: Build & Publish
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[no build]') == false
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Docker Buildx Base
uses: docker/setup-buildx-action@v1
id: builder-base
- name: Set up Docker Buildx NVIDIA
uses: docker/setup-buildx-action@v1
id: builder-nvidia
- name: Set up Docker Buildx VAAPI
uses: docker/setup-buildx-action@v1
id: builder-vaapi
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.docker_hub_username }}
password: ${{ secrets.docker_hub_access_token }}
- name: Build and push base
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-base.outputs.name }}
context: .
file: ./docker/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}
jasongdove/ersatztv:${{ inputs.tag_version }}
- name: Build and push nvidia
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-nvidia.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker-nvidia
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}-nvidia
jasongdove/ersatztv:${{ inputs.tag_version }}-nvidia
- name: Build and push vaapi
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker-vaapi
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}-vaapi
jasongdove/ersatztv:${{ inputs.tag_version }}-vaapi
-1
View File
@@ -3,7 +3,6 @@ on:
push: push:
branches: branches:
- main - main
jobs: jobs:
build: build:
name: Deploy docs name: Deploy docs
+30
View File
@@ -0,0 +1,30 @@
name: Pull Request
on:
pull_request:
jobs:
build_and_test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
steps:
- name: Get the sources
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
+39 -128
View File
@@ -1,142 +1,53 @@
name: Publish name: Release
on: on:
release: release:
types: [ published ] types: [ published ]
jobs: jobs:
release: calculate_version:
name: Release name: Calculate version information
strategy: runs-on: ubuntu-latest
matrix:
include:
- os: ubuntu-latest
kind: linux
target: linux-x64
- os: ubuntu-latest
kind: linux
target: linux-arm
- os: windows-latest
kind: windows
target: win-x64
- os: macos-latest
kind: macOS
target: osx-x64
- os: macos-latest
kind: macOS
target: osx-arm64
runs-on: ${{ matrix.os }}
steps: steps:
- name: Get the sources - name: Get the sources
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
shell: bash
run: |
# Define some variables for things we need
tag=$(git describe --tags --abbrev=0)
release_name="ErsatzTV-$tag-${{ matrix.target }}"
# Build everything
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" /property:InformationalVersion="${tag:1}-${{ matrix.target }}" /property:EnableCompressionInSingleFile=true /property:DebugType=Embedded /property:PublishSingleFile=true --self-contained true
# Pack files
if [ "${{ matrix.target }}" == "win-x64" ]; then
7z a -tzip "${release_name}.zip" "./${release_name}/*"
else
tar czvf "${release_name}.tar.gz" "$release_name"
fi
# Delete output directory
rm -r "$release_name"
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
files: |
ErsatzTV*.zip
ErsatzTV*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_and_push:
name: Build & Publish to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Extract Docker Tag
- name: Extract Git Tag
shell: bash shell: bash
run: | run: |
tag=$(git describe --tags --abbrev=0) tag=$(git describe --tags --abbrev=0)
echo "GIT_TAG=${tag:1}" >> $GITHUB_ENV echo "GIT_TAG=${tag:1}" >> $GITHUB_ENV
echo "DOCKER_TAG=${tag/-alpha/}" >> $GITHUB_ENV echo "DOCKER_TAG=${tag/-alpha/}" >> $GITHUB_ENV
- name: Extract Artifacts Version
- name: Set up Docker Buildx Base shell: bash
uses: docker/setup-buildx-action@v1 run: |
id: builder-base tag=$(git describe --tags --abbrev=0)
echo "ARTIFACTS_VERSION=${tag}" >> $GITHUB_ENV
- name: Set up Docker Buildx NVIDIA echo "INFO_VERSION=${tag:1}" >> $GITHUB_ENV
uses: docker/setup-buildx-action@v1 outputs:
id: builder-nvidia git_tag: ${{ env.GIT_TAG }}
docker_tag: ${{ env.DOCKER_TAG }}
- name: Set up Docker Buildx VAAPI artifacts_version: ${{ env.ARTIFACTS_VERSION }}
uses: docker/setup-buildx-action@v1 info_version: ${{ env.INFO_VERSION }}
id: builder-vaapi build_and_upload:
uses: jasongdove/ersatztv/.github/workflows/artifacts.yml@main
- name: Login to DockerHub needs: calculate_version
uses: docker/login-action@v1 with:
with: release_tag: ${{ needs.calculate_version.outputs.artifacts_version }}
username: ${{ secrets.DOCKER_HUB_USERNAME }} release_version: ${{ needs.calculate_version.outputs.artifacts_version }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} info_version: ${{ needs.calculate_version.outputs.info_version }}
secrets:
- name: Build and push base apple_developer_certificate_p12_base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
uses: docker/build-push-action@v2 apple_developer_certificate_password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
with: ac_username: ${{ secrets.AC_USERNAME }}
builder: ${{ steps.builder-base.outputs.name }} ac_password: ${{ secrets.AC_PASSWORD }}
context: . gh_token: ${{ secrets.GITHUB_TOKEN }}
file: ./docker/Dockerfile build_and_push:
push: true uses: jasongdove/ersatztv/.github/workflows/docker.yml@main
build-args: | needs: calculate_version
INFO_VERSION=${{ env.GIT_TAG }}-docker with:
tags: | base_version: latest
jasongdove/ersatztv:latest info_version: ${{ needs.calculate_version.outputs.git_tag }}
jasongdove/ersatztv:${{ env.DOCKER_TAG }} tag_version: ${{ needs.calculate_version.outputs.docker_tag }}
secrets:
- name: Build and push nvidia docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
uses: docker/build-push-action@v2 docker_hub_access_token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
with:
builder: ${{ steps.builder-nvidia.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-nvidia
tags: |
jasongdove/ersatztv:latest-nvidia
jasongdove/ersatztv:${{ env.DOCKER_TAG }}-nvidia
- name: Build and push vaapi
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-vaapi
tags: |
jasongdove/ersatztv:latest-vaapi
jasongdove/ersatztv:${{ env.DOCKER_TAG }}-vaapi
+19 -2
View File
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
## [0.3.8-alpha] - 2022-01-23
### Fixed
- Fix issue preventing some versions of ffmpeg (usually 4.4.x) from streaming MPEG-TS (Legacy) channels at all
- The issue appears to be caused by using a thread count other than `1`
- Thread count is now forced to `1` for all streaming modes other than HLS Segmenter
- Fix bug with HLS Segmenter in cultures where `.` is a group/thousands separator
- Fix search results page crashing with some media kinds
- Always use MPEG-TS or MPEG-TS (Legacy) streaming mode with HDHR (Plex)
- Other configured modes will fall back to MPEG-TS when accessed by Plex
### Changed
- Upgrade ffmpeg from 4.4 to 5.0 in all docker images
- Upgrading from 4.4 to 5.0 is recommended for all installations
## [0.3.7-alpha] - 2022-01-17
### Fixed ### Fixed
- Fix local folder scanners to properly detect removed/re-added folders with unchanged contents - Fix local folder scanners to properly detect removed/re-added folders with unchanged contents
- Fix double-click startup on mac - Fix double-click startup on mac
@@ -909,8 +924,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Initial release to facilitate testing outside of Docker. - Initial release to facilitate testing outside of Docker.
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.6-alpha...HEAD [Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.8-alpha...HEAD
[0.3.5-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.5-alpha...v0.3.6-alpha [0.3.8-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.7-alpha...v0.3.8-alpha
[0.3.7-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.6-alpha...v0.3.7-alpha
[0.3.6-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.5-alpha...v0.3.6-alpha
[0.3.5-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.4-alpha...v0.3.5-alpha [0.3.5-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.4-alpha...v0.3.5-alpha
[0.3.4-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.3-alpha...v0.3.4-alpha [0.3.4-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.3-alpha...v0.3.4-alpha
[0.3.3-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.2-alpha...v0.3.3-alpha [0.3.3-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.2-alpha...v0.3.3-alpha
@@ -18,8 +18,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
public CalculateSettings() => _calculator = new FFmpegPlaybackSettingsCalculator(); public CalculateSettings() => _calculator = new FFmpegPlaybackSettingsCalculator();
[Test] [Test]
public void Should_UseSpecifiedThreadCount_ForTransportStream() public void Should_Not_UseSpecifiedThreadCount_ForTransportStream()
{ {
// MPEG-TS requires realtime output which is hardcoded to a single thread
FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 }; FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
@@ -31,18 +33,19 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ThreadCount.Should().Be(7); actual.ThreadCount.Should().Be(1);
} }
[Test] [Test]
public void Should_UseSpecifiedThreadCount_ForHttpLiveStreaming() public void Should_UseSpecifiedThreadCount_ForHttpLiveStreamingSegmenter()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 }; FFmpegProfile ffmpegProfile = TestProfile() with { ThreadCount = 7 };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect, StreamingMode.HttpLiveStreamingSegmenter,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), new MediaVersion(),
new MediaStream(), new MediaStream(),
@@ -50,7 +53,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ThreadCount.Should().Be(7); actual.ThreadCount.Should().Be(7);
} }
@@ -69,7 +73,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
string[] expected = { "+genpts", "+discardcorrupt", "+igndts" }; string[] expected = { "+genpts", "+discardcorrupt", "+igndts" };
actual.FormatFlags.Count.Should().Be(expected.Length); actual.FormatFlags.Count.Should().Be(expected.Length);
@@ -90,7 +95,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
string[] expected = { "+genpts", "+discardcorrupt", "+igndts" }; string[] expected = { "+genpts", "+discardcorrupt", "+igndts" };
actual.FormatFlags.Count.Should().Be(expected.Length); actual.FormatFlags.Count.Should().Be(expected.Length);
@@ -111,7 +117,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.RealtimeOutput.Should().BeTrue(); actual.RealtimeOutput.Should().BeTrue();
} }
@@ -130,7 +137,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.RealtimeOutput.Should().BeTrue(); actual.RealtimeOutput.Should().BeTrue();
} }
@@ -151,7 +159,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
now, now,
now.AddMinutes(5), now.AddMinutes(5),
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.StreamSeek.IsSome.Should().BeTrue(); actual.StreamSeek.IsSome.Should().BeTrue();
actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5)); actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5));
@@ -173,7 +182,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
now, now,
now.AddMinutes(5), now.AddMinutes(5),
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.StreamSeek.IsSome.Should().BeTrue(); actual.StreamSeek.IsSome.Should().BeTrue();
actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5)); actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5));
@@ -193,7 +203,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
} }
@@ -219,7 +230,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
} }
@@ -245,7 +257,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
} }
@@ -271,7 +284,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -299,7 +313,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue();
@@ -326,7 +341,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
IDisplaySize scaledSize = actual.ScaledSize.IfNone(new MediaVersion { Width = 0, Height = 0 }); IDisplaySize scaledSize = actual.ScaledSize.IfNone(new MediaVersion { Width = 0, Height = 0 });
scaledSize.Width.Should().Be(1280); scaledSize.Width.Should().Be(1280);
@@ -356,7 +372,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -384,7 +401,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -413,7 +431,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue();
@@ -445,7 +464,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -477,7 +497,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -508,7 +529,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -540,7 +562,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -574,7 +597,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -606,7 +630,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue();
@@ -637,7 +662,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -667,7 +693,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue();
@@ -699,7 +726,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.ScaledSize.IsNone.Should().BeTrue(); actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
@@ -727,7 +755,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioCodec.Should().Be("aac"); actual.AudioCodec.Should().Be("aac");
} }
@@ -752,7 +781,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioCodec.Should().Be("copy"); actual.AudioCodec.Should().Be("copy");
} }
@@ -778,7 +808,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioCodec.Should().Be("aac"); actual.AudioCodec.Should().Be("aac");
} }
@@ -804,7 +835,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioCodec.Should().Be("copy"); actual.AudioCodec.Should().Be("copy");
} }
@@ -831,7 +863,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioBitrate.IfNone(0).Should().Be(2424); actual.AudioBitrate.IfNone(0).Should().Be(2424);
} }
@@ -858,7 +891,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioBufferSize.IfNone(0).Should().Be(2424); actual.AudioBufferSize.IfNone(0).Should().Be(2424);
} }
@@ -885,7 +919,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioChannels.IfNone(0).Should().Be(6); actual.AudioChannels.IfNone(0).Should().Be(6);
} }
@@ -912,7 +947,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioSampleRate.IfNone(0).Should().Be(48); actual.AudioSampleRate.IfNone(0).Should().Be(48);
} }
@@ -938,7 +974,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioChannels.IfNone(0).Should().Be(6); actual.AudioChannels.IfNone(0).Should().Be(6);
} }
@@ -964,7 +1001,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.AudioSampleRate.IfNone(0).Should().Be(48); actual.AudioSampleRate.IfNone(0).Should().Be(48);
} }
@@ -991,7 +1029,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.FromMinutes(2)); TimeSpan.FromMinutes(2),
false);
actual.AudioDuration.IfNone(TimeSpan.MinValue).Should().Be(TimeSpan.FromMinutes(2)); actual.AudioDuration.IfNone(TimeSpan.MinValue).Should().Be(TimeSpan.FromMinutes(2));
} }
@@ -1017,7 +1056,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.NormalizeLoudness.Should().BeTrue(); actual.NormalizeLoudness.Should().BeTrue();
} }
@@ -1043,7 +1083,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.NormalizeLoudness.Should().BeFalse(); actual.NormalizeLoudness.Should().BeFalse();
} }
@@ -1071,7 +1112,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
DateTimeOffset.Now, DateTimeOffset.Now,
DateTimeOffset.Now, DateTimeOffset.Now,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
actual.HardwareAcceleration.Should().Be(HardwareAccelerationKind.Qsv); actual.HardwareAcceleration.Should().Be(HardwareAccelerationKind.Qsv);
} }
@@ -79,7 +79,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
playoutItems[2].FillerKind.Should().Be(FillerKind.None); playoutItems[2].FillerKind.Should().Be(FillerKind.None);
} }
[Test] [Test]
public void Should_Fill_Exactly_To_Next_Schedule_Item_Flood() public void Should_Fill_Exactly_To_Next_Schedule_Item_Flood()
{ {
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
@@ -12,7 +12,7 @@ namespace ErsatzTV.Core.FFmpeg
public List<string> FormatFlags { get; set; } public List<string> FormatFlags { get; set; }
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public string VideoDecoder { get; set; } public string VideoDecoder { get; set; }
public bool RealtimeOutput => true; public bool RealtimeOutput { get; set; }
public Option<TimeSpan> StreamSeek { get; set; } public Option<TimeSpan> StreamSeek { get; set; }
public Option<IDisplaySize> ScaledSize { get; set; } public Option<IDisplaySize> ScaledSize { get; set; }
public bool PadToDesiredResolution { get; set; } public bool PadToDesiredResolution { get; set; }
@@ -51,14 +51,22 @@ namespace ErsatzTV.Core.FFmpeg
DateTimeOffset start, DateTimeOffset start,
DateTimeOffset now, DateTimeOffset now,
TimeSpan inPoint, TimeSpan inPoint,
TimeSpan outPoint) TimeSpan outPoint,
bool hlsRealtime)
{ {
var result = new FFmpegPlaybackSettings var result = new FFmpegPlaybackSettings
{ {
ThreadCount = ffmpegProfile.ThreadCount, FormatFlags = CommonFormatFlags,
FormatFlags = CommonFormatFlags RealtimeOutput = streamingMode switch
{
StreamingMode.HttpLiveStreamingSegmenter => hlsRealtime,
_ => true
}
}; };
// always use one thread with realtime output
result.ThreadCount = result.RealtimeOutput ? 1 : ffmpegProfile.ThreadCount;
if (now != start || inPoint != TimeSpan.Zero) if (now != start || inPoint != TimeSpan.Zero)
{ {
result.StreamSeek = now - start + inPoint; result.StreamSeek = now - start + inPoint;
+8 -1
View File
@@ -379,7 +379,14 @@ namespace ErsatzTV.Core.FFmpeg
_arguments.Add($"{format}"); _arguments.Add($"{format}");
return this; return this;
} }
public FFmpegProcessBuilder WithInitialDiscontinuity()
{
_arguments.Add("-mpegts_flags");
_arguments.Add("+initial_discontinuity");
return this;
}
public FFmpegProcessBuilder WithHls(string channelNumber, Option<MediaVersion> mediaVersion) public FFmpegProcessBuilder WithHls(string channelNumber, Option<MediaVersion> mediaVersion)
{ {
const int SEGMENT_SECONDS = 4; const int SEGMENT_SECONDS = 4;
+6 -5
View File
@@ -65,7 +65,8 @@ namespace ErsatzTV.Core.FFmpeg
start, start,
now, now,
inPoint, inPoint,
outPoint); outPoint,
hlsRealtime);
Option<WatermarkOptions> watermarkOptions = Option<WatermarkOptions> watermarkOptions =
await GetWatermarkOptions(channel, globalWatermark, videoVersion, None, None); await GetWatermarkOptions(channel, globalWatermark, videoVersion, None, None);
@@ -158,10 +159,10 @@ namespace ErsatzTV.Core.FFmpeg
// HLS needs to segment and generate playlist // HLS needs to segment and generate playlist
case StreamingMode.HttpLiveStreamingSegmenter: case StreamingMode.HttpLiveStreamingSegmenter:
return builder.WithHls(channel.Number, videoVersion) return builder.WithHls(channel.Number, videoVersion)
.WithRealtimeOutput(hlsRealtime)
.Build(); .Build();
default: default:
return builder.WithFormat("mpegts") return builder.WithFormat("mpegts")
.WithInitialDiscontinuity()
.WithPipe() .WithPipe()
.Build(); .Build();
} }
@@ -221,7 +222,6 @@ namespace ErsatzTV.Core.FFmpeg
// HLS needs to segment and generate playlist // HLS needs to segment and generate playlist
case StreamingMode.HttpLiveStreamingSegmenter: case StreamingMode.HttpLiveStreamingSegmenter:
return builder.WithHls(channel.Number, None) return builder.WithHls(channel.Number, None)
.WithRealtimeOutput(hlsRealtime)
.Build(); .Build();
default: default:
return builder.WithFormat("mpegts") return builder.WithFormat("mpegts")
@@ -255,7 +255,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithThreads(1) .WithThreads(1)
.WithQuiet() .WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags) .WithFormatFlags(playbackSettings.FormatFlags)
.WithRealtimeOutput(playbackSettings.RealtimeOutput) .WithRealtimeOutput(true)
.WithInput($"http://localhost:{Settings.ListenPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter") .WithInput($"http://localhost:{Settings.ListenPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter")
.WithMap("0") .WithMap("0")
.WithCopyCodec() .WithCopyCodec()
@@ -335,7 +335,8 @@ namespace ErsatzTV.Core.FFmpeg
DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch,
DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.Zero); TimeSpan.Zero,
false);
FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, false, _logger) FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
.WithThreads(1) .WithThreads(1)
+6 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
using System.Text; using System.Text;
namespace ErsatzTV.Core.FFmpeg namespace ErsatzTV.Core.FFmpeg
@@ -55,7 +56,11 @@ namespace ErsatzTV.Core.FFmpeg
continue; continue;
} }
var duration = TimeSpan.FromSeconds(double.Parse(lines[i].TrimEnd(',').Split(':')[1])); var duration = TimeSpan.FromSeconds(
double.Parse(
lines[i].TrimEnd(',').Split(':')[1],
NumberStyles.Number,
CultureInfo.InvariantCulture));
if (currentTime < filterBefore) if (currentTime < filterBefore)
{ {
currentTime += duration; currentTime += duration;
+1 -1
View File
@@ -22,7 +22,7 @@ namespace ErsatzTV.Core.Hdhr
public string URL => _channel.StreamingMode switch public string URL => _channel.StreamingMode switch
{ {
StreamingMode.HttpLiveStreamingDirect => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.m3u8", StreamingMode.TransportStream => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.ts?mode=ts-legacy",
_ => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.ts" _ => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.ts"
}; };
} }
+1 -1
View File
@@ -45,7 +45,7 @@ namespace ErsatzTV.Core.Iptv
StreamingMode.HttpLiveStreamingDirect => "m3u8?mode=hls-direct", StreamingMode.HttpLiveStreamingDirect => "m3u8?mode=hls-direct",
StreamingMode.HttpLiveStreamingSegmenter => "m3u8?mode=segmenter", StreamingMode.HttpLiveStreamingSegmenter => "m3u8?mode=segmenter",
StreamingMode.TransportStreamHybrid => "ts", StreamingMode.TransportStreamHybrid => "ts",
_ => "ts?mode=legacy" _ => "ts?mode=ts-legacy"
}; };
string vcodec = channel.FFmpegProfile.VideoCodec.Split("_").Head(); string vcodec = channel.FFmpegProfile.VideoCodec.Split("_").Head();
@@ -72,10 +72,10 @@ namespace ErsatzTV.Core.Scheduling
itemDuration, itemDuration,
itemChapters); itemChapters);
// if the current time is before the next schedule item, but the current finish // if the next schedule item is supposed to start during this item,
// is after, we need to move on to the next schedule item // don't schedule this item and just move on
willFinishInTime = itemStartTime > peekScheduleItemStart || willFinishInTime = peekScheduleItemStart < itemStartTime ||
itemEndTimeWithFiller <= peekScheduleItemStart; peekScheduleItemStart >= itemEndTimeWithFiller;
if (willFinishInTime) if (willFinishInTime)
{ {
@@ -153,8 +153,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.AsNoTracking() .AsNoTracking()
.Filter(mm => ids.Contains(mm.MovieId)) .Filter(mm => ids.Contains(mm.MovieId))
.Include(mm => mm.Artwork) .Include(mm => mm.Artwork)
.OrderBy(mm => mm.SortTitle)
.Include(mm => mm.Movie) .Include(mm => mm.Movie)
.ThenInclude(m => m.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.OrderBy(mm => mm.SortTitle)
.ToListAsync(); .ToListAsync();
} }
@@ -133,6 +133,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Include(mvm => mvm.MusicVideo) .Include(mvm => mvm.MusicVideo)
.ThenInclude(mv => mv.Artist) .ThenInclude(mv => mv.Artist)
.ThenInclude(a => a.ArtistMetadata) .ThenInclude(a => a.ArtistMetadata)
.Include(mvm => mvm.MusicVideo)
.ThenInclude(e => e.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mvm => mvm.Artwork) .Include(mvm => mvm.Artwork)
.OrderBy(mvm => mvm.SortTitle) .OrderBy(mvm => mvm.SortTitle)
.ToListAsync(); .ToListAsync();
@@ -100,6 +100,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Filter(ovm => ids.Contains(ovm.OtherVideoId)) .Filter(ovm => ids.Contains(ovm.OtherVideoId))
.Include(ovm => ovm.OtherVideo) .Include(ovm => ovm.OtherVideo)
.Include(ovm => ovm.Artwork) .Include(ovm => ovm.Artwork)
.Include(ovm => ovm.OtherVideo)
.ThenInclude(ov => ov.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.OrderBy(ovm => ovm.SortTitle) .OrderBy(ovm => ovm.SortTitle)
.ToListAsync(); .ToListAsync();
} }
@@ -113,6 +113,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Filter(ovm => ids.Contains(ovm.SongId)) .Filter(ovm => ids.Contains(ovm.SongId))
.Include(ovm => ovm.Song) .Include(ovm => ovm.Song)
.Include(ovm => ovm.Artwork) .Include(ovm => ovm.Artwork)
.Include(sm => sm.Song)
.ThenInclude(s => s.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.OrderBy(ovm => ovm.SortTitle) .OrderBy(ovm => ovm.SortTitle)
.ToListAsync(); .ToListAsync();
} }
@@ -11,7 +11,7 @@ namespace ErsatzTV.Infrastructure.Health.Checks
{ {
public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthCheck public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthCheck
{ {
private const string BundledVersion = "N-105153-g8abc192236"; private const string BundledVersion = "N-105324-g0f5fd44dc9";
private readonly IConfigElementRepository _configElementRepository; private readonly IConfigElementRepository _configElementRepository;
public FFmpegVersionHealthCheck(IConfigElementRepository configElementRepository) public FFmpegVersionHealthCheck(IConfigElementRepository configElementRepository)
@@ -71,19 +71,15 @@ namespace ErsatzTV.Infrastructure.Health.Checks
private Option<HealthCheckResult> ValidateVersion(string version, string app) private Option<HealthCheckResult> ValidateVersion(string version, string app)
{ {
if (version.StartsWith("3.")) if (version.StartsWith("3.") || version.StartsWith("4."))
{ {
return FailResult($"{app} version {version} is too old; please install 4.4!"); return FailResult($"{app} version {version} is too old; please install 5.0!");
} }
if (version.StartsWith("4.3")) if (!version.StartsWith("5.0") && version != BundledVersion)
{ {
return WarningResult($"{app} version 4.4 is now supported, please upgrade from 4.3!"); return WarningResult(
} $"{app} version {version} is unexpected and may have problems; please install 5.0!");
if (!version.StartsWith("4.4") && version != BundledVersion)
{
return WarningResult($"{app} version {version} is unexpected and may have problems; please install 4.4!");
} }
return None; return None;
+1 -1
View File
@@ -59,7 +59,7 @@ namespace ErsatzTV.Controllers
{ {
FFmpegProcessRequest request = mode switch FFmpegProcessRequest request = mode switch
{ {
"legacy" => new GetConcatProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber), "ts-legacy" => new GetConcatProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber),
_ => new GetWrappedProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber) _ => new GetWrappedProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber)
}; };
+7
View File
@@ -59,4 +59,11 @@
<EmbeddedResource Include="Resources\ISO-639-2_utf-8.txt" /> <EmbeddedResource Include="Resources\ISO-639-2_utf-8.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="..\artwork\Ersatztv.icns">
<Link>Ersatztv.icns</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>
+26 -9
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core; using ErsatzTV.Core;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@@ -12,14 +11,32 @@ namespace ErsatzTV
{ {
public class Program public class Program
{ {
private static IConfiguration Configuration { get; } = new ConfigurationBuilder() static Program()
.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)) {
.AddJsonFile("appsettings.json", false, true) var executablePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
.AddJsonFile( var executable = Path.GetFileNameWithoutExtension(executablePath);
$"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json",
true) IConfigurationBuilder builder = new ConfigurationBuilder();
.AddEnvironmentVariables()
.Build(); if ("dotnet".Equals(executable, StringComparison.InvariantCultureIgnoreCase))
{
builder = builder.SetBasePath(Path.GetDirectoryName(typeof(Program).Assembly.Location));
}
else
{
builder = builder.SetBasePath(Path.GetDirectoryName(executablePath));
}
Configuration = builder
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile(
$"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json",
true)
.AddEnvironmentVariables()
.Build();
}
private static IConfiguration Configuration { get; }
public static async Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
IFS='.' read -ra ADDR <<< "$1"
ICONSET=${ADDR[0]}.iconset
mkdir $ICONSET
sips -z 16 16 $1 --out $ICONSET/icon_16x16.png
sips -z 32 32 $1 --out $ICONSET/icon_16x16@2x.png
sips -z 32 32 $1 --out $ICONSET/icon_32x32.png
sips -z 64 64 $1 --out $ICONSET/icon_32x32@2x.png
sips -z 128 128 $1 --out $ICONSET/icon_128x128.png
sips -z 256 256 $1 --out $ICONSET/icon_128x128@2x.png
sips -z 256 256 $1 --out $ICONSET/icon_256x256.png
sips -z 512 512 $1 --out $ICONSET/icon_256x256@2x.png
sips -z 512 512 $1 --out $ICONSET/icon_512x512.png
cp $1 $ICONSET/icon_512x512@2x.png
iconutil -c icns $ICONSET
rm -R $ICONSET
+2 -1
View File
@@ -1,6 +1,6 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime
FROM jasongdove/ffmpeg:4.4-ubuntu2004 AS runtime-base FROM jasongdove/ffmpeg:5.0-ubuntu2004 AS runtime-base
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata
@@ -13,6 +13,7 @@ WORKDIR /source
COPY *.sln . COPY *.sln .
COPY nuget.config . COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/ COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/ COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/ COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
+2 -1
View File
@@ -7,6 +7,7 @@ WORKDIR /source
COPY *.sln . COPY *.sln .
COPY nuget.config . COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/ COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/ COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/ COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
@@ -25,7 +26,7 @@ ARG INFO_VERSION="unknown"
RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION} RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION}
# final stage/image # final stage/image
FROM jasongdove/ffmpeg:4.4-nvidia2004 AS runtime-base FROM jasongdove/ffmpeg:5.0-nvidia2004 AS runtime-base
WORKDIR /app WORKDIR /app
EXPOSE 8409 EXPOSE 8409
COPY --from=build /app ./ COPY --from=build /app ./
+1 -1
View File
@@ -1,6 +1,6 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime
FROM jasongdove/ffmpeg-base:4.4-nvidia2004 AS runtime-base FROM jasongdove/ffmpeg-base:5.0-nvidia2004 AS runtime-base
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
RUN apt-get update \ RUN apt-get update \
&& DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata \ && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata \
+2 -1
View File
@@ -7,6 +7,7 @@ WORKDIR /source
COPY *.sln . COPY *.sln .
COPY nuget.config . COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/ COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/ COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/ COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
@@ -25,7 +26,7 @@ ARG INFO_VERSION="unknown"
RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION} RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION}
# final stage/image # final stage/image
FROM jasongdove/ffmpeg:4.4-vaapi2004 AS runtime-base FROM jasongdove/ffmpeg:5.0-vaapi2004 AS runtime-base
WORKDIR /app WORKDIR /app
EXPOSE 8409 EXPOSE 8409
COPY --from=build /app ./ COPY --from=build /app ./
+1 -1
View File
@@ -1,6 +1,6 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS dotnet-runtime
FROM jasongdove/ffmpeg-base:4.4-vaapi2004 AS runtime-base FROM jasongdove/ffmpeg-base:5.0-vaapi2004 AS runtime-base
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev \ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev \
tzdata \ tzdata \
+3
View File
@@ -1,6 +1,9 @@
ErsatzTV needs to know about your media items in order to create channels. ErsatzTV needs to know about your media items in order to create channels.
Four library kinds are currently supported: [Local](#local-libraries), [Plex](#plex-libraries), [Jellyfin](#jellyfin-libraries) and [Emby](#emby-libraries). Four library kinds are currently supported: [Local](#local-libraries), [Plex](#plex-libraries), [Jellyfin](#jellyfin-libraries) and [Emby](#emby-libraries).
ErsatzTV supports a subset of media kinds when synchronizing from media servers, i.e. [Plex](#plex-libraries), [Jellyfin](#jellyfin-libraries) and [Emby](#emby-libraries) only support `Movies` and `Shows`.
[Local Libraries](#local-libraries) support all media kinds: `Movies`, `Shows`, `Music Videos`, `Other Videos` and `Songs`.
## Local Libraries ## Local Libraries
Local libraries provide a way to schedule media directly from folders; no media server is necessary. Local libraries provide a way to schedule media directly from folders; no media server is necessary.
+5
View File
@@ -82,6 +82,11 @@ docker run -d \
7. Open your browser to `http://[docker-ip]:8409` (First run may take a few minutes to be available.) 7. Open your browser to `http://[docker-ip]:8409` (First run may take a few minutes to be available.)
## Development Builds
Development builds for all supported architectures can be found on the [develop release](https://github.com/jasongdove/ErsatzTV/releases/tag/develop).
As with the docker development tags, the development builds are updated with every push to the main branch and have the potential to be less stable than releases.
## Windows ## Windows
### Manual Installation ### Manual Installation
+11
View File
@@ -0,0 +1,11 @@
{
"notarize": [{
"path": "./ErsatzTV.dmg",
"bundle_id": "com.jasongdove.ersatztv",
"staple": true
}],
"apple_id": {
"password": "@env:AC_PASSWORD"
}
}
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>ErsatzTV</string>
<key>CFBundleExecutable</key>
<string>launcher.sh</string>
<key>CFBundleIconFile</key>
<string>ErsatzTV.icns</string>
<key>CFBundleIdentifier</key>
<string>com.jasongdove.ersatztv</string>
<key>CFBundleName</key>
<string>ErsatzTV</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.3.7-alpha</string>
<key>CFBundleVersion</key>
<string>0.3.7-alpha</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
#! /bin/bash
SCRIPT_FOLDER=$(dirname ${BASH_SOURCE[0]})
REPO_ROOT="$SCRIPT_FOLDER/../.."
APP_NAME="$REPO_ROOT/ErsatzTV.app"
PUBLISH_OUTPUT_DIRECTORY="$REPO_ROOT/publish/."
INFO_PLIST="$SCRIPT_FOLDER/Info.plist"
ICON_SOURCE="$REPO_ROOT/artwork/ErsatzTV.icns"
ICON_FILE="ErsatzTV.icns"
if [ -d "$APP_NAME" ]
then
rm -rf "$APP_NAME"
fi
mkdir "$APP_NAME"
mkdir "$APP_NAME/Contents"
mkdir "$APP_NAME/Contents/MacOS"
mkdir "$APP_NAME/Contents/Resources"
cp "$INFO_PLIST" "$APP_NAME/Contents/Info.plist"
cp "$ICON_SOURCE" "$APP_NAME/Contents/Resources/$ICON_FILE"
cp -a "$PUBLISH_OUTPUT_DIRECTORY" "$APP_NAME/Contents/MacOS"
cp "$SCRIPT_FOLDER/launcher.sh" "$APP_NAME/Contents/MacOS/"
+9
View File
@@ -0,0 +1,9 @@
#! /bin/sh
# Set the working directory
DIR=$(cd "$(dirname "$0")"; pwd)
# Run the application
echo "running from $DIR"
open -a Terminal $DIR/ErsatzTV
+10
View File
@@ -0,0 +1,10 @@
#! /bin/bash
SCRIPT_FOLDER=$(dirname ${BASH_SOURCE[0]})
REPO_ROOT="$SCRIPT_FOLDER/../.."
DMG_NAME="$REPO_ROOT/ErsatzTV.dmg"
ENTITLEMENTS="$SCRIPT_FOLDER/ErsatzTV.entitlements"
SIGNING_IDENTITY="C3BBCFB2D6851FF0DCA6CAC06A3EF1ECE71F9FFF"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$DMG_NAME"
+19
View File
@@ -0,0 +1,19 @@
#! /bin/bash
SCRIPT_FOLDER=$(dirname ${BASH_SOURCE[0]})
REPO_ROOT="$SCRIPT_FOLDER/../.."
APP_NAME="$REPO_ROOT/ErsatzTV.app"
ENTITLEMENTS="$SCRIPT_FOLDER/ErsatzTV.entitlements"
SIGNING_IDENTITY="C3BBCFB2D6851FF0DCA6CAC06A3EF1ECE71F9FFF"
find "$APP_NAME/Contents/MacOS/"|while read fname; do
if [[ -f $fname ]]; then
echo "[INFO] Signing $fname"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$fname"
fi
done
echo "[INFO] Signing app file"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$APP_NAME"