Compare commits

...

36 Commits

Author SHA1 Message Date
Jellyfin Release Bot 730b01fb14 Bump version to 10.9.3 2024-05-26 20:00:29 -04:00
gnattu 45e8872cc0 Extract media attachment one by one if the filename appears to be a path (#11812) 2024-05-26 11:40:28 -06:00
Joshua M. Boniface bcf884ccfa Update GitHub workflows from Master
Backport fixes from:
 - #11780
 - #11779
 - #11769
 - #11733
 - #11515
2024-05-25 11:55:48 -04:00
gnattu 2eece01acc Filter invalid IPs on external interface matching (#11766) 2024-05-25 09:44:03 -06:00
gnattu ef985896e2 Use SharedStream for LiveTV more restrictively (#11805) 2024-05-25 09:43:53 -06:00
Nyanmisaka 5e7514243c Fix the IOSurf error in QSV transcoding (#11830) 2024-05-25 09:00:30 -06:00
NotSaifA 4a344bebc0 Trickplay: kill ffmpeg when task is cancelled (#11790) 2024-05-24 07:50:55 -06:00
Tim Eisele b2d54b82fa Improve reliability of HasChanged check (#11792) 2024-05-24 07:50:09 -06:00
gnattu e7b1162cb3 Force more compatible transcoding profile for LiveTV (#11801) 2024-05-24 07:36:59 -06:00
Tim Eisele d89e5a0074 Exclude virtual items from DateLastMediaAdded calculation (#11804) 2024-05-24 07:36:39 -06:00
Tim Eisele 4a54e5ddeb Add Canceled to ended state (#11808) 2024-05-24 07:36:19 -06:00
Bond-009 d9232e05f1 Merge pull request #11798 from gnattu/fix-trickplay-image-height
Recalculate trickplay image height for anamorphic videos
2024-05-24 14:19:37 +02:00
Bond-009 52be8be28f Merge pull request #11754 from Shadowghost/fix-bd-chapter-images
Fix BD/DVD folder chapter image extraction
2024-05-24 14:18:10 +02:00
Bond-009 ab6c2424db Merge pull request #11802 from crobibero/nullable-match-term
Mark SearchHint.MatchedTerm as nullable
2024-05-24 14:17:59 +02:00
Bond-009 eb437e7163 Merge pull request #11799 from nyanmisaka/fix-va-vk-interop-chk
Disable VA-VK interop on not supported kernel versions
2024-05-24 14:17:51 +02:00
Bond-009 2ddf2a7866 Merge pull request #11781 from Bond-009/orderts
Retain order blu-ray segments
2024-05-24 14:16:45 +02:00
Bond-009 60232ce9be Merge pull request #11788 from gnattu/override-too-small-trickplay-image-interval
Override too small trickplay image interval
2024-05-24 14:16:33 +02:00
gnattu 952995f796 Ignore NullOrEmpty imageStream.AspectRatio
Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-23 23:08:29 +08:00
gnattu 933a285bf5 Use single quote
Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-23 23:03:35 +08:00
gnattu f8da69f8e5 Allow decimal aspect ratio
Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-23 23:02:57 +08:00
Cody Robibero d5e29bfce3 here you go Niels 2024-05-23 07:16:56 -06:00
nyanmisaka ab36c4c011 Disable VA-VK interop on not supported kernel versions
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2024-05-23 14:53:00 +08:00
gnattu c6e29647fc Recalculate trickplay image height for anamorphic videos
Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-23 12:36:43 +08:00
gnattu fa2bff30f6 Explicitly use decimal
Co-authored-by: Cody Robibero <cody@robibe.ro>
2024-05-22 21:17:00 +08:00
gnattu 447f73caf4 Fix bitrate calculation accuracy
Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-22 13:41:54 +08:00
gnattu 86129589ef Override too small trickplay image interval
Some users may set this value too low, causing the trickplay generation to fail. Reset this interval to the minimum valid value of 1 second when the user-configured value is too small to prevent generation failures.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-22 13:35:28 +08:00
Niels van Velzen 06a5ddda5e Merge pull request #11774 from Bond-009/downmix
Apply audio boost when downmixing regardless of downmixalgo
2024-05-21 21:40:22 +02:00
Bond_009 6777f47e0e Retain order blu-ray segments 2024-05-21 20:19:15 +02:00
Bond_009 cf04e1d8e5 Apply audio boost when downmixing regardless of downmixalgo
This is what I'd expect from the naming
Should fix #11771
2024-05-21 16:24:43 +02:00
Bond-009 d608f1e3cc Merge pull request #11713 from gnattu/fix-vt-h264-profile
Fix VideoToolbox H264 constrained profile option
2024-05-21 13:58:01 +02:00
Bond-009 86f5c93434 Merge pull request #11739 from Shadowghost/fix-trickplay
Do not run trickplay on scan if disabled
2024-05-21 13:57:41 +02:00
Bond-009 4fcbeef5e6 Merge pull request #11738 from crobibero/non-user-session
Don't require user when getting current session
2024-05-21 13:57:31 +02:00
Shadowghost 77abafca8e Fix BD/DVD folder chapter image extraction 2024-05-20 13:19:05 +02:00
Shadowghost 2cebd5e05f Do not run trickplay on scan if disabled 2024-05-19 16:52:59 +02:00
Cody Robibero b19b346670 Don't require user when getting current session 2024-05-19 08:25:38 -06:00
gnattu 53de8c0805 Fix VideoToolbox H264 constrained profile option
Like a lot of other encoders they need an underscore between two words.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-05-18 19:53:23 +08:00
30 changed files with 316 additions and 145 deletions
+4 -4
View File
@@ -20,18 +20,18 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/autobuild@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
+88 -20
View File
@@ -3,6 +3,8 @@ on:
push: push:
branches: branches:
- master - master
tags:
- 'v*'
pull_request_target: pull_request_target:
permissions: {} permissions: {}
@@ -14,7 +16,7 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -39,7 +41,7 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -99,11 +101,24 @@ jobs:
- id: read-diff - id: read-diff
name: Read openapi-diff output name: Read openapi-diff output
run: | run: |
# Read and fix markdown
body=$(cat openapi-changes.md) body=$(cat openapi-changes.md)
body="${body//'%'/'%25'}" # Write to workflow summary
body="${body//$'\n'/'%0A'}" echo "$body" >> $GITHUB_STEP_SUMMARY
body="${body//$'\r'/'%0D'}" # Set ApiChanged var
echo ::set-output name=body::$body if [ "$body" != '' ]; then
echo "ApiChanged=1" >> "$GITHUB_OUTPUT"
else
echo "ApiChanged=0" >> "$GITHUB_OUTPUT"
fi
# Add header/footer for diff comment
echo '<!--openapi-diff-workflow-comment-->' > openapi-changes-reply.md
echo "<details>" >> openapi-changes-reply.md
echo "<summary>Changes in OpenAPI specification found. Expand to see details.</summary>" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "$body" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "</details>" >> openapi-changes-reply.md
- name: Find difference comment - name: Find difference comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
id: find-comment id: find-comment
@@ -113,22 +128,15 @@ jobs:
body-includes: openapi-diff-workflow-comment body-includes: openapi-diff-workflow-comment
- name: Reply or edit difference comment (changed) - name: Reply or edit difference comment (changed)
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.body != '' }} if: ${{ steps.read-diff.outputs.ApiChanged == '1' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace edit-mode: replace
body: | body-path: openapi-changes-reply.md
<!--openapi-diff-workflow-comment-->
<details>
<summary>Changes in OpenAPI specification found. Expand to see details.</summary>
${{ steps.read-diff.outputs.body }}
</details>
- name: Edit difference comment (unchanged) - name: Edit difference comment (unchanged)
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
@@ -138,11 +146,9 @@ jobs:
No changes to OpenAPI specification found. See history of this comment for previous changes. No changes to OpenAPI specification found. See history of this comment for previous changes.
publish: publish-unstable:
name: OpenAPI - Publish Unstable Spec name: OpenAPI - Publish Unstable Spec
if: | if: ${{ github.event_name != 'pull_request_target' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
github.event_name != 'pull_request_target' &&
contains(github.repository_owner, 'jellyfin')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- openapi-head - openapi-head
@@ -201,3 +207,65 @@ jobs:
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
fi fi
) 200>/run/workflows/openapi-unstable.lock ) 200>/run/workflows/openapi-unstable.lock
publish-stable:
name: OpenAPI - Publish Stable Spec
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest
needs:
- openapi-head
steps:
- name: Set version number
id: version
run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-head
path: openapi-head
- name: Upload openapi.json (stable) to repository server
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
source: openapi-head/openapi.json
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
debug: false
script_stop: false
script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
exit 0
fi
# Move new spec into place
sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
# Delete previous jellyfin-openapi-stable_previous.json
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Create new jellyfin-openapi-stable.json symlink
sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
# Check that the previous openapi stable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
fi
) 200>/run/workflows/openapi-stable.lock
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: "${{ matrix.os }}" runs-on: "${{ matrix.os }}"
steps: steps:
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with: with:
@@ -34,7 +34,7 @@ jobs:
--verbosity minimal --verbosity minimal
- name: Merge code coverage results - name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@2a2d60ea1c7e811f54684179af6ac1ae8c1ce69a # 5.2.5 uses: danielpalme/ReportGenerator-GitHub-Action@6b06171d1a131e7fd85121120a1c00c1ed03e033 # 5.3.0
with: with:
reports: "**/coverage.cobertura.xml" reports: "**/coverage.cobertura.xml"
targetdir: "merged/" targetdir: "merged/"
+3 -3
View File
@@ -24,7 +24,7 @@ jobs:
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@@ -51,7 +51,7 @@ jobs:
reactions: eyes reactions: eyes
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@@ -128,7 +128,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull in script - name: pull in script
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
repository: jellyfin/jellyfin-triage-script repository: jellyfin/jellyfin-triage-script
- name: install python - name: install python
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
issues: write issues: write
steps: steps:
- name: pull in script - name: pull in script
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
repository: jellyfin/jellyfin-triage-script repository: jellyfin/jellyfin-triage-script
- name: install python - name: install python
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }} if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps: steps:
- name: Apply label - name: Apply label
uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}} if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with: with:
dirtyLabel: 'merge conflict' dirtyLabel: 'merge conflict'
+2 -2
View File
@@ -33,7 +33,7 @@ jobs:
yq-version: v4.9.8 yq-version: v4.9.8
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
ref: ${{ env.TAG_BRANCH }} ref: ${{ env.TAG_BRANCH }}
@@ -66,7 +66,7 @@ jobs:
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }} NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
ref: ${{ env.TAG_BRANCH }} ref: ${{ env.TAG_BRANCH }}
+1
View File
@@ -183,6 +183,7 @@
- [btopherjohnson](https://github.com/btopherjohnson) - [btopherjohnson](https://github.com/btopherjohnson)
- [GeorgeH005](https://github.com/GeorgeH005) - [GeorgeH005](https://github.com/GeorgeH005)
- [Vedant](https://github.com/viktory36/) - [Vedant](https://github.com/viktory36/)
- [NotSaifA](https://github.com/NotSaifA)
# Emby Contributors # Emby Contributors
+1 -1
View File
@@ -36,7 +36,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId> <PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+1 -1
View File
@@ -10,7 +10,7 @@ namespace Emby.Naming.TV;
public static class TvParserHelpers public static class TvParserHelpers
{ {
private static readonly string[] _continuingState = ["Pilot", "Returning Series", "Returning"]; private static readonly string[] _continuingState = ["Pilot", "Returning Series", "Returning"];
private static readonly string[] _endedState = ["Cancelled"]; private static readonly string[] _endedState = ["Cancelled", "Canceled"];
/// <summary> /// <summary>
/// Tries to parse a string into <see cref="SeriesStatus"/>. /// Tries to parse a string into <see cref="SeriesStatus"/>.
+13
View File
@@ -385,6 +385,19 @@ public class MediaInfoHelper
/// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns> /// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns>
public async Task<LiveStreamResponse> OpenMediaSource(HttpContext httpContext, LiveStreamRequest request) public async Task<LiveStreamResponse> OpenMediaSource(HttpContext httpContext, LiveStreamRequest request)
{ {
// Enforce more restrictive transcoding profile for LiveTV due to compatability reasons
// Cap the MaxStreamingBitrate to 20Mbps, because we are unable to reliably probe source bitrate,
// which will cause the client to request extremely high bitrate that may fail the player/encoder
request.MaxStreamingBitrate = request.MaxStreamingBitrate > 20000000 ? 20000000 : request.MaxStreamingBitrate;
if (request.DeviceProfile is not null)
{
// Remove all fmp4 transcoding profiles, because it causes playback error and/or A/V sync issues
// Notably: Some channels won't play on FireFox and LG webOs
// Some channels from HDHomerun will experience A/V sync issues
request.DeviceProfile.TranscodingProfiles = request.DeviceProfile.TranscodingProfiles.Where(p => !string.Equals(p.Container, "mp4", StringComparison.OrdinalIgnoreCase)).ToArray();
}
var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
var profile = request.DeviceProfile; var profile = request.DeviceProfile;
+6 -1
View File
@@ -120,7 +120,12 @@ public static class RequestHelpers
internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null) internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null)
{ {
userId ??= httpContext.User.GetUserId(); userId ??= httpContext.User.GetUserId();
var user = userManager.GetUserById(userId.Value); User? user = null;
if (!userId.IsNullOrEmpty())
{
user = userManager.GetUserById(userId.Value);
}
var session = await sessionManager.LogSessionActivity( var session = await sessionManager.LogSessionActivity(
httpContext.User.GetClient(), httpContext.User.GetClient(),
httpContext.User.GetVersion(), httpContext.User.GetVersion(),
+1 -1
View File
@@ -18,7 +18,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId> <PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -81,6 +81,12 @@ public class TrickplayManager : ITrickplayManager
_logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace); _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace);
var options = _config.Configuration.TrickplayOptions; var options = _config.Configuration.TrickplayOptions;
if (options.Interval < 1000)
{
_logger.LogWarning("Trickplay image interval {Interval} is too small, reset to the minimum valid value of 1000", options.Interval);
options.Interval = 1000;
}
foreach (var width in options.WidthResolutions) foreach (var width in options.WidthResolutions)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -267,7 +273,7 @@ public class TrickplayManager : ITrickplayManager
} }
// Update bitrate // Update bitrate
var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tilePath).Length * 8 / trickplayInfo.TileWidth / trickplayInfo.TileHeight / (trickplayInfo.Interval / 1000)); var bitrate = (int)Math.Ceiling(new FileInfo(tilePath).Length * 8m / trickplayInfo.TileWidth / trickplayInfo.TileHeight / (trickplayInfo.Interval / 1000m));
trickplayInfo.Bandwidth = Math.Max(trickplayInfo.Bandwidth, bitrate); trickplayInfo.Bandwidth = Math.Max(trickplayInfo.Bandwidth, bitrate);
} }
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId> <PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -58,6 +58,11 @@ public static class NetworkConstants
/// </summary> /// </summary>
public static readonly IPNetwork IPv4RFC1918PrivateClassC = new IPNetwork(IPAddress.Parse("192.168.0.0"), 16); public static readonly IPNetwork IPv4RFC1918PrivateClassC = new IPNetwork(IPAddress.Parse("192.168.0.0"), 16);
/// <summary>
/// IPv4 Link-Local as defined in RFC 3927.
/// </summary>
public static readonly IPNetwork IPv4RFC3927LinkLocal = new IPNetwork(IPAddress.Parse("169.254.0.0"), 16);
/// <summary> /// <summary>
/// IPv6 loopback as defined in RFC 4291. /// IPv6 loopback as defined in RFC 4291.
/// </summary> /// </summary>
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId> <PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -55,6 +55,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minKerneli915Hang = new Version(5, 18); private readonly Version _minKerneli915Hang = new Version(5, 18);
private readonly Version _maxKerneli915Hang = new Version(6, 1, 3); private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18); private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0); private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0); private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
@@ -680,16 +681,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return -1; return -1;
} }
public string GetInputPathArgument(EncodingJobInfo state)
{
return state.MediaSource.VideoType switch
{
VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource),
VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource),
_ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource)
};
}
/// <summary> /// <summary>
/// Gets the audio encoder. /// Gets the audio encoder.
/// </summary> /// </summary>
@@ -1005,7 +996,8 @@ namespace MediaBrowser.Controller.MediaEncoding
Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc"); Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
if (IsVulkanFullSupported() if (IsVulkanFullSupported()
&& _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
&& Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
{ {
args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias)); args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias)); args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
@@ -1203,7 +1195,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else else
{ {
arg.Append(" -i ") arg.Append(" -i ")
.Append(GetInputPathArgument(state)); .Append(_mediaEncoder.GetInputPathArgument(state));
} }
// sub2video for external graphical subtitles // sub2video for external graphical subtitles
@@ -2083,6 +2075,18 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "constrained_high"; profile = "constrained_high";
} }
if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
&& profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
{
profile = "constrained_baseline";
}
if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
&& profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
{
profile = "constrained_high";
}
if (!string.IsNullOrEmpty(profile)) if (!string.IsNullOrEmpty(profile))
{ {
// Currently there's no profile option in av1_nvenc encoder // Currently there's no profile option in av1_nvenc encoder
@@ -2629,10 +2633,14 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.AudioStream.Channels.HasValue && state.AudioStream.Channels.HasValue
&& state.AudioStream.Channels.Value == 6) && state.AudioStream.Channels.Value == 6)
{ {
if (!encodingOptions.DownMixAudioBoost.Equals(1))
{
filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
}
switch (encodingOptions.DownMixStereoAlgorithm) switch (encodingOptions.DownMixStereoAlgorithm)
{ {
case DownMixStereoAlgorithms.Dave750: case DownMixStereoAlgorithms.Dave750:
filters.Add("volume=4.25");
filters.Add("pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3"); filters.Add("pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3");
break; break;
case DownMixStereoAlgorithms.NightmodeDialogue: case DownMixStereoAlgorithms.NightmodeDialogue:
@@ -2640,11 +2648,6 @@ namespace MediaBrowser.Controller.MediaEncoding
break; break;
case DownMixStereoAlgorithms.None: case DownMixStereoAlgorithms.None:
default: default:
if (!encodingOptions.DownMixAudioBoost.Equals(1))
{
filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
}
break; break;
} }
} }
@@ -3161,7 +3164,9 @@ namespace MediaBrowser.Controller.MediaEncoding
int? requestedMaxHeight) int? requestedMaxHeight)
{ {
var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var scaleVal = isV4l2 ? 64 : 2; var scaleVal = isV4l2 ? 64 : 2;
var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
// If fixed dimensions were supplied // If fixed dimensions were supplied
if (requestedWidth.HasValue && requestedHeight.HasValue) if (requestedWidth.HasValue && requestedHeight.HasValue)
@@ -3190,10 +3195,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@"scale=trunc(min(max(iw\,ih*a)\,min({0}\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\,ih)\,min({0}/a\,{1}))/2)*2", @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3}\,{1}))/2)*2",
maxWidthParam, maxWidthParam,
maxHeightParam, maxHeightParam,
scaleVal); scaleVal,
targetAr);
} }
// If a fixed width was requested // If a fixed width was requested
@@ -3209,8 +3215,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale={0}:trunc(ow/a/2)*2", "scale={0}:trunc(ow/{1}/2)*2",
widthParam); widthParam,
targetAr);
} }
// If a fixed height was requested // If a fixed height was requested
@@ -3220,9 +3227,10 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:{0}", "scale=trunc(oh*{2}/{1})*{1}:{0}",
heightParam, heightParam,
scaleVal); scaleVal,
targetAr);
} }
// If a max width was requested // If a max width was requested
@@ -3232,9 +3240,10 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@"scale=trunc(min(max(iw\,ih*a)\,{0})/{1})*{1}:trunc(ow/a/2)*2", @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
maxWidthParam, maxWidthParam,
scaleVal); scaleVal,
targetAr);
} }
// If a max height was requested // If a max height was requested
@@ -3244,9 +3253,10 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\,ih)\,{0})", @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
maxHeightParam, maxHeightParam,
scaleVal); scaleVal,
targetAr);
} }
return string.Empty; return string.Empty;
@@ -4291,6 +4301,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
// map from qsv to vaapi. // map from qsv to vaapi.
mainFilters.Add("hwmap=derive_device=vaapi"); mainFilters.Add("hwmap=derive_device=vaapi");
mainFilters.Add("format=vaapi");
} }
var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12"); var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
@@ -4300,6 +4311,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
// map from vaapi to qsv. // map from vaapi to qsv.
mainFilters.Add("hwmap=derive_device=qsv"); mainFilters.Add("hwmap=derive_device=qsv");
mainFilters.Add("format=qsv");
} }
} }
@@ -4474,7 +4486,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// prefered vaapi + vulkan filters pipeline // prefered vaapi + vulkan filters pipeline
if (_mediaEncoder.IsVaapiDeviceAmd if (_mediaEncoder.IsVaapiDeviceAmd
&& isVaapiVkSupported && isVaapiVkSupported
&& _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
&& Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
{ {
// AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support. // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder); return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
@@ -245,6 +245,21 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>A playlist.</returns> /// <returns>A playlist.</returns>
IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path); IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path);
/// <summary>
/// Gets the input path argument from <see cref="EncodingJobInfo"/>.
/// </summary>
/// <param name="state">The <see cref="EncodingJobInfo"/>.</param>
/// <returns>The input path argument.</returns>
string GetInputPathArgument(EncodingJobInfo state);
/// <summary>
/// Gets the input path argument.
/// </summary>
/// <param name="path">The item path.</param>
/// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
/// <returns>The input path argument.</returns>
string GetInputPathArgument(string path, MediaSourceInfo mediaSource);
/// <summary> /// <summary>
/// Generates a FFmpeg concat config for the source. /// Generates a FFmpeg concat config for the source.
/// </summary> /// </summary>
@@ -89,15 +89,28 @@ namespace MediaBrowser.MediaEncoding.Attachments
string outputPath, string outputPath,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) var shouldExtractOneByOne = mediaSource.MediaAttachments.Any(a => a.FileName.Contains('/', StringComparison.OrdinalIgnoreCase) || a.FileName.Contains('\\', StringComparison.OrdinalIgnoreCase));
if (shouldExtractOneByOne)
{ {
if (!Directory.Exists(outputPath)) var attachmentIndexes = mediaSource.MediaAttachments.Select(a => a.Index);
foreach (var i in attachmentIndexes)
{ {
await ExtractAllAttachmentsInternal( var newName = Path.Join(outputPath, i.ToString(CultureInfo.InvariantCulture));
_mediaEncoder.GetInputArgument(inputFile, mediaSource), await ExtractAttachment(inputFile, mediaSource, i, newName, cancellationToken).ConfigureAwait(false);
outputPath, }
false, }
cancellationToken).ConfigureAwait(false); else
{
using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false))
{
if (!Directory.Exists(outputPath))
{
await ExtractAllAttachmentsInternal(
_mediaEncoder.GetInputArgument(inputFile, mediaSource),
outputPath,
false,
cancellationToken).ConfigureAwait(false);
}
} }
} }
} }
@@ -30,10 +30,8 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Nikse.SubtitleEdit.Core.Common.IfoParser;
namespace MediaBrowser.MediaEncoding.Encoder namespace MediaBrowser.MediaEncoding.Encoder
{ {
@@ -621,7 +619,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
ImageFormat? targetFormat, ImageFormat? targetFormat,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var inputArgument = GetInputArgument(inputFile, mediaSource); var inputArgument = GetInputPathArgument(inputFile, mediaSource);
if (!isAudio) if (!isAudio)
{ {
@@ -824,6 +822,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
options.EnableTonemapping = false; options.EnableTonemapping = false;
} }
if (imageStream.Width is not null && imageStream.Height is not null && !string.IsNullOrEmpty(imageStream.AspectRatio))
{
// For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions
var darParts = imageStream.AspectRatio.Split(':');
var (wa, ha) = (double.Parse(darParts[0], CultureInfo.InvariantCulture), double.Parse(darParts[1], CultureInfo.InvariantCulture));
// When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched.
// Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it.
var shouldResetHeight = Math.Abs((imageStream.Width.Value * ha) - (imageStream.Height.Value * wa)) > .05;
if (shouldResetHeight)
{
// SAR = DAR * Height / Width
// RealHeight = Height / SAR = Height / (DAR * Height / Width) = Width / DAR
imageStream.Height = Convert.ToInt32(imageStream.Width.Value * ha / wa);
}
}
var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) }; var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) };
var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) var jobState = new EncodingJobInfo(TranscodingJobType.Progressive)
{ {
@@ -945,7 +959,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs; var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
timeoutMs = timeoutMs <= 0 ? DefaultHdrImageExtractionTimeout : timeoutMs; timeoutMs = timeoutMs <= 0 ? DefaultHdrImageExtractionTimeout : timeoutMs;
while (isResponsive) while (isResponsive && !cancellationToken.IsCancellationRequested)
{ {
try try
{ {
@@ -959,8 +973,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
// We don't actually expect the process to be finished in one timeout span, just that one image has been generated. // We don't actually expect the process to be finished in one timeout span, just that one image has been generated.
} }
cancellationToken.ThrowIfCancellationRequested();
var jpegCount = _fileSystem.GetFilePaths(targetDirectory).Count(); var jpegCount = _fileSystem.GetFilePaths(targetDirectory).Count();
isResponsive = jpegCount > lastCount; isResponsive = jpegCount > lastCount;
@@ -969,7 +981,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!ranToCompletion) if (!ranToCompletion)
{ {
_logger.LogInformation("Stopping trickplay extraction due to process inactivity."); if (!isResponsive)
{
_logger.LogInformation("Trickplay process unresponsive.");
}
_logger.LogInformation("Stopping trickplay extraction.");
StopProcess(processWrapper, 1000); StopProcess(processWrapper, 1000);
} }
} }
@@ -1137,16 +1154,29 @@ namespace MediaBrowser.MediaEncoding.Encoder
var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files;
// Get all files from the BDMV/STREAMING directory // Get all files from the BDMV/STREAMING directory
var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM"));
// Only return playable local .m2ts files // Only return playable local .m2ts files
return directoryFiles return validPlaybackFiles
.Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) .Select(f => _fileSystem.GetFileInfo(Path.Join(path, "BDMV", "STREAM", f)))
.Where(f => f.Exists)
.Select(f => f.FullName) .Select(f => f.FullName)
.Order()
.ToList(); .ToList();
} }
/// <inheritdoc />
public string GetInputPathArgument(EncodingJobInfo state)
=> GetInputPathArgument(state.MediaPath, state.MediaSource);
/// <inheritdoc />
public string GetInputPathArgument(string path, MediaSourceInfo mediaSource)
{
return mediaSource.VideoType switch
{
VideoType.Dvd => GetInputArgument(GetPrimaryPlaylistVobFiles(path, null).ToList(), mediaSource),
VideoType.BluRay => GetInputArgument(GetPrimaryPlaylistM2tsFiles(path).ToList(), mediaSource),
_ => GetInputArgument(path, mediaSource)
};
}
/// <inheritdoc /> /// <inheritdoc />
public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath)
{ {
+1 -1
View File
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId> <PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+1 -1
View File
@@ -43,7 +43,7 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the matched term. /// Gets or sets the matched term.
/// </summary> /// </summary>
/// <value>The matched term.</value> /// <value>The matched term.</value>
public string MatchedTerm { get; set; } public string? MatchedTerm { get; set; }
/// <summary> /// <summary>
/// Gets or sets the index number. /// Gets or sets the index number.
@@ -398,7 +398,8 @@ namespace MediaBrowser.Providers.Manager
foreach (var child in children) foreach (var child in children)
{ {
if (!child.IsFolder) // Exclude any folders and virtual items since they are only placeholders
if (!child.IsFolder && !child.IsVirtualItem)
{ {
var childDateCreated = child.DateCreated; var childDateCreated = child.DateCreated;
if (childDateCreated > dateLastMediaAdded) if (childDateCreated > dateLastMediaAdded)
@@ -1,6 +1,7 @@
#nullable disable #nullable disable
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -141,19 +142,15 @@ namespace MediaBrowser.Providers.MediaInfo
&& item.SupportsLocalMetadata && item.SupportsLocalMetadata
&& !video.IsPlaceHolder) && !video.IsPlaceHolder)
{ {
if (!video.SubtitleFiles.SequenceEqual( var externalFiles = new HashSet<string>(_subtitleResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
_subtitleResolver.GetExternalFiles(video, directoryService, false) if (!new HashSet<string>(video.SubtitleFiles, StringComparer.Ordinal).SetEquals(externalFiles))
.Select(info => info.Path).ToList(),
StringComparer.Ordinal))
{ {
_logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
return true; return true;
} }
if (!video.AudioFiles.SequenceEqual( externalFiles = new HashSet<string>(_audioResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
_audioResolver.GetExternalFiles(video, directoryService, false) if (!new HashSet<string>(video.AudioFiles, StringComparer.Ordinal).SetEquals(externalFiles))
.Select(info => info.Path).ToList(),
StringComparer.Ordinal))
{ {
_logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path);
return true; return true;
@@ -161,14 +158,14 @@ namespace MediaBrowser.Providers.MediaInfo
} }
if (item is Audio audio if (item is Audio audio
&& item.SupportsLocalMetadata && item.SupportsLocalMetadata)
&& !audio.LyricFiles.SequenceEqual(
_lyricResolver.GetExternalFiles(audio, directoryService, false)
.Select(info => info.Path).ToList(),
StringComparer.Ordinal))
{ {
_logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path); var externalFiles = new HashSet<string>(_lyricResolver.GetExternalFiles(audio, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
return true; if (!new HashSet<string>(audio.LyricFiles, StringComparer.Ordinal).SetEquals(externalFiles))
{
_logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
return true;
}
} }
return false; return false;
@@ -101,7 +101,7 @@ public class TrickplayProvider : ICustomMetadataProvider<Episode>,
bool? enableDuringScan = libraryOptions?.ExtractTrickplayImagesDuringLibraryScan; bool? enableDuringScan = libraryOptions?.ExtractTrickplayImagesDuringLibraryScan;
bool replace = options.ReplaceAllImages; bool replace = options.ReplaceAllImages;
if (options.IsAutomated && !enableDuringScan.GetValueOrDefault(false)) if (!enableDuringScan.GetValueOrDefault(false))
{ {
return ItemUpdateType.None; return ItemUpdateType.None;
} }
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.9.2")] [assembly: AssemblyVersion("10.9.3")]
[assembly: AssemblyFileVersion("10.9.2")] [assembly: AssemblyFileVersion("10.9.3")]
@@ -15,7 +15,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId> <PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.9.2</VersionPrefix> <VersionPrefix>10.9.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+23 -34
View File
@@ -30,25 +30,8 @@ namespace Jellyfin.LiveTv.TunerHosts
{ {
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{ {
private static readonly string[] _disallowedMimeTypes = private static readonly string[] _mimeTypesCanShareHttpStream = ["video/MP2T"];
{ private static readonly string[] _extensionsCanShareHttpStream = [".ts", ".tsv", ".m2t"];
"text/plain",
"text/html",
"video/x-matroska",
"video/mp4",
"application/vnd.apple.mpegurl",
"application/mpegurl",
"application/x-mpegurl",
"video/vnd.mpeg.dash.mpd"
};
private static readonly string[] _disallowedSharedStreamExtensions =
{
".mkv",
".mp4",
".m3u8",
".mpd"
};
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
@@ -113,27 +96,33 @@ namespace Jellyfin.LiveTv.TunerHosts
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
{ {
using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path); var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(message, cancellationToken)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode) if (string.IsNullOrEmpty(extension))
{ {
if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase)) try
{ {
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(message, cancellationToken)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
if (_mimeTypesCanShareHttpStream.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
}
catch (Exception)
{
Logger.LogWarning("HEAD request to check MIME type failed, shared stream disabled");
} }
} }
else else if (_extensionsCanShareHttpStream.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
// Fallback to check path extension when the server does not support HEAD method return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
// Use UriBuilder to remove all query string as GetExtension will include them when used directly
var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path);
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
} }
} }
@@ -903,6 +903,17 @@ public class NetworkManager : INetworkManager, IDisposable
return false; return false;
} }
/// <summary>
/// Get if the IPAddress is Link-local.
/// </summary>
/// <param name="address">The IP Address.</param>
/// <returns>Bool indicates if the address is link-local.</returns>
public bool IsLinkLocalAddress(IPAddress address)
{
ArgumentNullException.ThrowIfNull(address);
return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address) public bool IsInLocalNetwork(IPAddress address)
{ {
@@ -1084,7 +1095,11 @@ public class NetworkManager : INetworkManager, IDisposable
private bool MatchesExternalInterface(IPAddress source, out string result) private bool MatchesExternalInterface(IPAddress source, out string result)
{ {
// Get the first external interface address that isn't a loopback. // Get the first external interface address that isn't a loopback.
var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray(); var extResult = _interfaces
.Where(p => !IsInLocalNetwork(p.Address))
.Where(p => p.Address.AddressFamily.Equals(source.AddressFamily))
.Where(p => !IsLinkLocalAddress(p.Address))
.OrderBy(x => x.Index).ToArray();
// No external interface found // No external interface found
if (extResult.Length == 0) if (extResult.Length == 0)