Merge branch 'master' into AsyncKeyedLock-migration

pull/10801/head
Mark Cilia Vincenti 4 months ago
commit 6a257e1b40

@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"version": "8.0.1",
"commands": [
"dotnet-ef"
]

@ -0,0 +1,28 @@
{
"name": "Development Jellyfin Server",
"image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
"postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
// reads the extensions list and installs them
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "none",
"dotnetRuntimeVersions": "8.0",
"aspNetCoreRuntimeVersions": "8.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"preserve_apt_list": false,
"packages": ["libfontconfig1"]
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
},
"hostRequirements": {
"memory": "8gb",
"cpus": 4
}
}

@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12
uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12
uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12
uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0

@ -25,7 +25,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0
with:
name: openapi-head
retention-days: 14
@ -59,7 +59,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0
with:
name: openapi-base
retention-days: 14
@ -78,12 +78,12 @@ jobs:
- openapi-base
steps:
- name: Download openapi-head
uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with:
name: openapi-base
path: openapi-base

@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@ -24,7 +24,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true
with:
@ -33,7 +33,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@ -47,7 +47,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true
with:
@ -56,7 +56,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true
with:

@ -1,13 +1,11 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"GitHub.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csdevkit"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]

@ -81,6 +81,7 @@
- [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai)

@ -18,46 +18,46 @@
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.1.1" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.1" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.13" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="0.11.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="PlaylistsNET" Version="1.4.0" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.0" />
<PackageVersion Include="PlaylistsNET" Version="1.4.1" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.2.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
@ -67,9 +67,9 @@
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.5" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" />
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="1.0.0.2" />
@ -78,7 +78,7 @@
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.1" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.1.0" />
@ -86,6 +86,6 @@
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.6.4" />
<PackageVersion Include="xunit" Version="2.6.5" />
</ItemGroup>
</Project>

@ -15,7 +15,6 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Emby.Naming.Common;
using Emby.Photos;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography;
@ -25,7 +24,6 @@ using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
@ -504,8 +502,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(_xmlSerializer);
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
@ -557,8 +553,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
serviceCollection.AddSingleton<IDtoService, DtoService>();
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
@ -567,9 +561,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
serviceCollection.AddSingleton<LiveTvDtoService>();
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();

@ -104,6 +104,13 @@ namespace Emby.Server.Implementations.Data
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult))
{
// If the resulting DateTimeKind is Unspecified it is actually Utc.
// This is required downstream for the Json serializer.
if (dateTimeResult.Kind == DateTimeKind.Unspecified)
{
dateTimeResult = DateTime.SpecifyKind(dateTimeResult, DateTimeKind.Utc);
}
result = dateTimeResult;
return true;
}

@ -418,15 +418,6 @@ namespace Emby.Server.Implementations.Dto
{
dto.PlayAccess = item.GetPlayAccess(user);
}
if (options.ContainsField(ItemFields.BasicSyncInfo))
{
var userCanSync = user is not null && user.HasPermission(PermissionKind.EnableContentDownloading);
if (userCanSync && item.SupportsExternalTransfer)
{
dto.SupportsSync = true;
}
}
}
private static int GetChildCount(Folder folder, User user)

@ -22,7 +22,6 @@
<ItemGroup>
<PackageReference Include="DiscUtils.Udf" />
<PackageReference Include="Jellyfin.XmlTv" />
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />

@ -11,14 +11,15 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using EasyCaching.Core.Configurations;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
@ -37,6 +38,7 @@ namespace Emby.Server.Implementations.Library
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_';
private readonly IServerApplicationHost _appHost;
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
@ -55,6 +57,7 @@ namespace Emby.Server.Implementations.Library
private IMediaSourceProvider[] _providers;
public MediaSourceManager(
IServerApplicationHost appHost,
IItemRepository itemRepo,
IApplicationPaths applicationPaths,
ILocalizationManager localizationManager,
@ -66,6 +69,7 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder,
IDirectoryService directoryService)
{
_appHost = appHost;
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
@ -799,6 +803,35 @@ namespace Emby.Server.Implementations.Library
return result.Item1;
}
public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
{
var stream = new MediaSourceInfo
{
EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
Id = info.Id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true,
RequiresOpening = false,
RequiresClosing = false,
BufferMs = 0,
IgnoreDts = true,
IgnoreIndex = true
};
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
stream
};
}
public async Task CloseLiveStream(string id)
{
ArgumentException.ThrowIfNullOrEmpty(id);

@ -83,8 +83,8 @@
"UserDeletedWithName": "Uživatel {0} byl smazán",
"UserDownloadingItemWithValues": "{0} stahuje {1}",
"UserLockedOutWithName": "Uživatel {0} byl odemčen",
"UserOfflineFromDevice": "{0} se odpojil od {1}",
"UserOnlineFromDevice": "{0} se připojil z {1}",
"UserOfflineFromDevice": "{0} se odpojil ze zařízení {1}",
"UserOnlineFromDevice": "{0} se připojil ze zařízení {1}",
"UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}",
"UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány",
"UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}",

@ -121,8 +121,8 @@
"Default": "Standard",
"TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.",
"TaskOptimizeDatabase": "Datenbank optimieren",
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
"TaskKeyframeExtractor": "Keyframe Extraktor",
"TaskKeyframeExtractorDescription": "Extrahiert Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
"TaskKeyframeExtractor": "Keyframe-Extraktor",
"External": "Extern",
"HearingImpaired": "Hörgeschädigt",
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",

@ -123,5 +123,7 @@
"External": "Externo",
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"HearingImpaired": "Discapacidad auditiva"
"HearingImpaired": "Discapacidad auditiva",
"TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.",
"TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción"
}

@ -123,5 +123,7 @@
"TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.",
"TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.",
"TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.",
"TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს."
"TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს.",
"TaskRefreshTrickplayImagesDescription": "ქმნის trickplay წინასწარ ხედებს ვიდეოებისთვის ჩართულ ბიბლიოთეკებში.",
"TaskRefreshTrickplayImages": "Trickplay სურათების გენერირება"
}

@ -123,5 +123,7 @@
"External": "Ārējais",
"HearingImpaired": "Ar dzirdes traucējumiem",
"TaskKeyframeExtractor": "Atslēgkadru ekstraktors",
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs."
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs.",
"TaskRefreshTrickplayImages": "Ģenerēt partīšanas attēlus",
"TaskRefreshTrickplayImagesDescription": "Izveido priekšskatījumus videoklipu pārtīšanai iespējotajās bibliotēkās."
}

@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Slett aktivitetslogg",
"Undefined": "Udefinert",
"Forced": "Tvungen",
"Default": "Standard"
"Default": "Standard",
"External": "Ekstern"
}

@ -18,16 +18,16 @@
"HeaderContinueWatching": "Продовжити перегляд",
"HeaderAlbumArtists": "Виконавці альбому",
"Genres": "Жанри",
"Folders": "Каталоги",
"Folders": "Теки",
"Favorites": "Обрані",
"DeviceOnlineWithName": "Пристрій {0} підключився",
"DeviceOfflineWithName": "Пристрій {0} відключився",
"Collections": "Добірки",
"ChapterNameValue": "Розділ {0}",
"Collections": "Колекції",
"ChapterNameValue": "Сцена {0}",
"Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографія завантажена з {0}",
"CameraImageUploadedFrom": "Нову фотографію завантажено з {0}",
"Books": "Книги",
"AuthenticationSucceededWithUserName": "{0} успішно автентифіковано",
"AuthenticationSucceededWithUserName": "{0} успішно авторизовано",
"Artists": "Виконавці",
"Application": "Додаток",
"AppDeviceValues": "Додаток: {0}, Пристрій: {1}",
@ -83,7 +83,7 @@
"SubtitleDownloadFailureFromForItem": "Не вдалося завантажити субтитри з {0} для {1}",
"StartupEmbyServerIsLoading": "Jellyfin Server завантажується. Будь ласка, спробуйте трішки пізніше.",
"Songs": "Пісні",
"Shows": "Шоу",
"Shows": "Телепередачі",
"ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити",
"ScheduledTaskStartedWithName": "{0} розпочато",
"ScheduledTaskFailedWithName": "{0} незавершено, збій",

@ -1,15 +1,15 @@
{
"Albums": "專輯",
"AppDeviceValues": "程式: {0}, 設備: {1}",
"AppDeviceValues": "程式{0},設備:{1}",
"Application": "應用程式",
"Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} 授權成功",
"AuthenticationSucceededWithUserName": "成功授權 {0}",
"Books": "書籍",
"CameraImageUploadedFrom": "{0} 成功上傳一張新照片",
"Channels": "頻道",
"ChapterNameValue": "第 {0} 章",
"Collections": "系列",
"DeviceOfflineWithName": "{0} 已連接",
"DeviceOfflineWithName": "{0} 已斷連接",
"DeviceOnlineWithName": "{0} 已連接",
"FailedLoginAttemptWithUserName": "{0} 登入失敗",
"Favorites": "我的最愛",
@ -27,15 +27,15 @@
"HeaderRecordingGroups": "錄製組",
"HomeVideos": "家庭影片",
"Inherit": "繼承",
"ItemAddedWithName": "{0} 已被加至媒體庫",
"ItemAddedWithName": "{0} 已被至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 地址: {0}",
"LabelRunningTimeValue": "運行時間: {0}",
"LabelIpAddressValue": "IP 地址{0}",
"LabelRunningTimeValue": "運作時間:{0}",
"Latest": "最新",
"MessageApplicationUpdated": "Jellyfin 已被更新",
"MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新",
"MessageServerConfigurationUpdated": "伺服器設定已經被更新",
"MessageServerConfigurationUpdated": "已更新伺服器設定",
"MixedContent": "混合內容",
"Movies": "電影",
"Music": "音樂",
@ -43,23 +43,23 @@
"NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知的季度",
"NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。",
"NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的更新",
"NotificationOptionApplicationUpdateInstalled": "應用程式已被更新",
"NotificationOptionAudioPlayback": "開始播放音訊",
"NotificationOptionApplicationUpdateInstalled": "完成更新應用程式",
"NotificationOptionAudioPlayback": "播放音訊",
"NotificationOptionAudioPlaybackStopped": "停止播放音訊",
"NotificationOptionCameraImageUploaded": "相片已被上傳",
"NotificationOptionCameraImageUploaded": "相片上傳",
"NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已添加新内容",
"NotificationOptionPluginError": "插件出現錯誤",
"NotificationOptionPluginInstalled": "插件已被安裝",
"NotificationOptionPluginUninstalled": "插件已被移除",
"NotificationOptionPluginUpdateInstalled": "插件已被更新",
"NotificationOptionNewLibraryContent": "新增媒體",
"NotificationOptionPluginError": "插件錯誤",
"NotificationOptionPluginInstalled": "安裝插件",
"NotificationOptionPluginUninstalled": "解除安裝插件",
"NotificationOptionPluginUpdateInstalled": "完成更新插件",
"NotificationOptionServerRestartRequired": "伺服器需要重啟",
"NotificationOptionTaskFailed": "排程任務執行失敗",
"NotificationOptionUserLockedOut": "用戶已被鎖定",
"NotificationOptionVideoPlayback": "開始播放影片",
"NotificationOptionVideoPlaybackStopped": "停止播放影片",
"NotificationOptionTaskFailed": "排程工作執行失敗",
"NotificationOptionUserLockedOut": "封鎖用戶",
"NotificationOptionVideoPlayback": "播放影片",
"NotificationOptionVideoPlaybackStopped": "停止播放影片",
"Photos": "相片",
"Playlists": "播放清單",
"Plugin": "插件",
@ -68,7 +68,7 @@
"PluginUpdatedWithName": "已更新 {0}",
"ProviderValue": "提供者:{0}",
"ScheduledTaskFailedWithName": "{0} 執行失敗",
"ScheduledTaskStartedWithName": "{0} 開始執行",
"ScheduledTaskStartedWithName": "開始執行 {0}",
"ServerNameNeedsToBeRestarted": "{0} 需要重啟",
"Shows": "節目",
"Songs": "歌曲",
@ -79,50 +79,52 @@
"System": "系統",
"TvShows": "電視節目",
"User": "用戶",
"UserCreatedWithName": "用戶 {0} 已被建立",
"UserCreatedWithName": "建立新用戶 {0}",
"UserDeletedWithName": "用戶 {0} 已被移除",
"UserDownloadingItemWithValues": "{0} 正在下載 {1}",
"UserLockedOutWithName": "使用者 {0} 已被鎖定",
"UserLockedOutWithName": "用戶 {0} 已被封鎖",
"UserOfflineFromDevice": "{0} 從 {1} 斷開連接",
"UserOnlineFromDevice": "{0} 從 {1} 連線",
"UserPasswordChangedWithName": "{0} 的密碼已被變改",
"UserPolicyUpdatedWithName": "使用者協議已更新為 {0}",
"UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}",
"UserPolicyUpdatedWithName": "使用條款已更新為 {0}",
"UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}",
"UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 上播放 {1}",
"ValueHasBeenAddedToLibrary": "已添加 {0} 到你的媒體庫",
"ValueSpecialEpisodeName": "特典 - {0}",
"VersionNumber": "版本 {0}",
"TaskDownloadMissingSubtitles": "下載少的字幕",
"TaskDownloadMissingSubtitles": "下載缺字幕",
"TaskUpdatePlugins": "更新插件",
"TasksApplicationCategory": "應用程式",
"TaskRefreshLibraryDescription": "掃描媒體庫以加入新增檔案及重新載入 metadata。",
"TaskRefreshLibraryDescription": "掃描媒體庫以加入新增的檔案及重新載入元數據。",
"TasksMaintenanceCategory": "維護",
"TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在互聯網上搜索缺少的字幕。",
"TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在網上搜尋欠缺的字幕。",
"TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。",
"TaskRefreshChannels": "重新載入頻道",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。",
"TaskCleanTranscode": "清理轉碼目錄",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
"TaskCleanTranscode": "清理轉碼檔資料夾",
"TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。",
"TaskRefreshPeopleDescription": "更新媒體中演員和導演的元數據。",
"TaskRefreshPeopleDescription": "更新你的媒體中有關的演員和導演的元數據。",
"TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。",
"TaskCleanLogs": "清理紀錄檔目錄",
"TaskCleanLogs": "清理紀錄檔資料夾",
"TaskRefreshLibrary": "掃描媒體庫",
"TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。",
"TaskRefreshChapterImages": "提取章節圖像",
"TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。",
"TaskCleanCache": "清理緩存目錄",
"TaskCleanCache": "清理緩存資料夾",
"TasksChannelsCategory": "網絡頻道",
"TasksLibraryCategory": "庫",
"TasksLibraryCategory": "媒體庫",
"TaskRefreshPeople": "重新載入人物",
"TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義",
"Forced": "強制",
"Default": "預設",
"TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
"TaskOptimizeDatabaseDescription": "壓縮數據庫及釋放可用空間。完成任何會修改數據庫的工作(例如掃描媒體庫)後,執行此工作或可提升伺服器速度。",
"TaskOptimizeDatabase": "最佳化數據庫",
"TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。",
"TaskKeyframeExtractorDescription": "提取關鍵幀以建立更準確的 HLS 播放列表。此工作或需要使用較長時間來完成。",
"TaskKeyframeExtractor": "關鍵提取器",
"TaskKeyframeExtractorDescription": "提取關鍵影格Keyframe以建立更準確的 HLS playlist。此工作可能需要使用較長時間來完成。",
"TaskKeyframeExtractor": "關鍵影格提取器",
"External": "外部",
"HearingImpaired": "聽力障礙"
"HearingImpaired": "聽力障礙",
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
"TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。"
}

@ -1670,7 +1670,6 @@ namespace Emby.Server.Implementations.Session
var fields = dtoOptions.Fields.ToList();
fields.Remove(ItemFields.BasicSyncInfo);
fields.Remove(ItemFields.CanDelete);
fields.Remove(ItemFields.CanDownload);
fields.Remove(ItemFields.ChildCount);

@ -42,16 +42,15 @@ public class DevicesController : BaseJellyfinApiController
/// <summary>
/// Get Devices.
/// </summary>
/// <param name="supportsSync">Gets or sets a value indicating whether [supports synchronize].</param>
/// <param name="userId">Gets or sets the user identifier.</param>
/// <response code="200">Devices retrieved.</response>
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false);
}
/// <summary>

@ -385,7 +385,6 @@ public class SessionController : BaseJellyfinApiController
/// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param>
/// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param>
/// <param name="supportsMediaControl">Determines whether media can be played remotely..</param>
/// <param name="supportsSync">Determines whether sync is supported.</param>
/// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
/// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
@ -397,7 +396,6 @@ public class SessionController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
[FromQuery] bool supportsMediaControl = false,
[FromQuery] bool supportsSync = false,
[FromQuery] bool supportsPersistentIdentifier = true)
{
if (string.IsNullOrWhiteSpace(id))
@ -410,7 +408,6 @@ public class SessionController : BaseJellyfinApiController
PlayableMediaTypes = playableMediaTypes,
SupportedCommands = supportedCommands,
SupportsMediaControl = supportsMediaControl,
SupportsSync = supportsSync,
SupportsPersistentIdentifier = supportsPersistentIdentifier
});
return NoContent();

@ -90,7 +90,6 @@ public class UserViewsController : BaseJellyfinApiController
fields.Add(ItemFields.PrimaryImageAspectRatio);
fields.Add(ItemFields.DisplayPreferencesId);
fields.Remove(ItemFields.BasicSyncInfo);
dtoOptions.Fields = fields.ToArray();
var user = _userManager.GetUserById(userId);

@ -41,6 +41,8 @@ public class IPBasedAccessValidationMiddleware
if (!networkManager.HasRemoteAccess(remoteIP))
{
// No access from network, respond with 503 instead of 200.
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
return;
}

@ -1,3 +1,4 @@
using System.Net;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@ -40,6 +41,8 @@ public class LanFilteringMiddleware
var host = httpContext.GetNormalizedRemoteIP();
if (!networkManager.IsInLocalNetwork(host))
{
// No access from network, respond with 503 instead of 200.
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
return;
}

@ -30,26 +30,11 @@ public class ClientCapabilitiesDto
/// </summary>
public bool SupportsMediaControl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether session supports content uploading.
/// </summary>
public bool SupportsContentUploading { get; set; }
/// <summary>
/// Gets or sets the message callback url.
/// </summary>
public string? MessageCallbackUrl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether session supports a persistent identifier.
/// </summary>
public bool SupportsPersistentIdentifier { get; set; }
/// <summary>
/// Gets or sets a value indicating whether session supports sync.
/// </summary>
public bool SupportsSync { get; set; }
/// <summary>
/// Gets or sets the device profile.
/// </summary>
@ -76,10 +61,7 @@ public class ClientCapabilitiesDto
PlayableMediaTypes = PlayableMediaTypes,
SupportedCommands = SupportedCommands,
SupportsMediaControl = SupportsMediaControl,
SupportsContentUploading = SupportsContentUploading,
MessageCallbackUrl = MessageCallbackUrl,
SupportsPersistentIdentifier = SupportsPersistentIdentifier,
SupportsSync = SupportsSync,
DeviceProfile = DeviceProfile,
AppStoreUrl = AppStoreUrl,
IconUrl = IconUrl

@ -0,0 +1,22 @@
namespace Jellyfin.Data.Enums;
/// <summary>
/// An enum representing formats of spatial audio.
/// </summary>
public enum AudioSpatialFormat
{
/// <summary>
/// None audio spatial format.
/// </summary>
None,
/// <summary>
/// Dolby Atmos audio spatial format.
/// </summary>
DolbyAtmos,
/// <summary>
/// DTS:X audio spatial format.
/// </summary>
DTSX,
}

@ -167,7 +167,7 @@ namespace Jellyfin.Server.Implementations.Devices
}
/// <inheritdoc />
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync)
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
@ -178,10 +178,6 @@ namespace Jellyfin.Server.Implementations.Devices
.ThenBy(d => d.DeviceId)
.SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o })
.AsAsyncEnumerable();
if (supportsSync.HasValue)
{
sessions = sessions.Where(i => GetCapabilities(i.Device.DeviceId).SupportsSync == supportsSync.Value);
}
if (userId.HasValue)
{

@ -1,7 +1,7 @@
#pragma warning disable CA1307
#pragma warning disable CA1309 // Use ordinal string comparison - EF can't translate this
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@ -46,8 +46,6 @@ namespace Jellyfin.Server.Implementations.Users
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IDictionary<Guid, User> _users;
/// <summary>
/// Initializes a new instance of the <see cref="UserManager"/> class.
/// </summary>
@ -58,6 +56,8 @@ namespace Jellyfin.Server.Implementations.Users
/// <param name="imageProcessor">The image processor.</param>
/// <param name="logger">The logger.</param>
/// <param name="serverConfigurationManager">The system config manager.</param>
/// <param name="passwordResetProviders">The password reset providers.</param>
/// <param name="authenticationProviders">The authentication providers.</param>
public UserManager(
IDbContextFactory<JellyfinDbContext> dbProvider,
IEventManager eventManager,
@ -65,7 +65,9 @@ namespace Jellyfin.Server.Implementations.Users
IApplicationHost appHost,
IImageProcessor imageProcessor,
ILogger<UserManager> logger,
IServerConfigurationManager serverConfigurationManager)
IServerConfigurationManager serverConfigurationManager,
IEnumerable<IPasswordResetProvider> passwordResetProviders,
IEnumerable<IAuthenticationProvider> authenticationProviders)
{
_dbProvider = dbProvider;
_eventManager = eventManager;
@ -75,35 +77,36 @@ namespace Jellyfin.Server.Implementations.Users
_logger = logger;
_serverConfigurationManager = serverConfigurationManager;
_passwordResetProviders = appHost.GetExports<IPasswordResetProvider>();
_authenticationProviders = appHost.GetExports<IAuthenticationProvider>();
_passwordResetProviders = passwordResetProviders.ToList();
_authenticationProviders = authenticationProviders.ToList();
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
_users = new ConcurrentDictionary<Guid, User>();
using var dbContext = _dbProvider.CreateDbContext();
foreach (var user in dbContext.Users
.AsSplitQuery()
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage)
.AsEnumerable())
{
_users.Add(user.Id, user);
}
}
/// <inheritdoc/>
public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
/// <inheritdoc/>
public IEnumerable<User> Users => _users.Values;
public IEnumerable<User> Users
{
get
{
using var dbContext = _dbProvider.CreateDbContext();
return GetUsersInternal(dbContext).ToList();
}
}
/// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _users.Keys;
public IEnumerable<Guid> UsersIds
{
get
{
using var dbContext = _dbProvider.CreateDbContext();
return dbContext.Users.Select(u => u.Id).ToList();
}
}
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
@ -119,8 +122,8 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Guid can't be empty", nameof(id));
}
_users.TryGetValue(id, out var user);
return user;
using var dbContext = _dbProvider.CreateDbContext();
return GetUsersInternal(dbContext).FirstOrDefault(u => u.Id.Equals(id));
}
/// <inheritdoc/>
@ -131,7 +134,9 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(name));
}
return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
using var dbContext = _dbProvider.CreateDbContext();
return GetUsersInternal(dbContext)
.FirstOrDefault(u => string.Equals(u.Username, name));
}
/// <inheritdoc/>
@ -196,8 +201,6 @@ namespace Jellyfin.Server.Implementations.Users
user.AddDefaultPermissions();
user.AddDefaultPreferences();
_users.Add(user.Id, user);
return user;
}
@ -232,40 +235,46 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task DeleteUserAsync(Guid userId)
{
if (!_users.TryGetValue(userId, out var user))
{
throw new ResourceNotFoundException(nameof(userId));
}
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
if (_users.Count == 1)
await using (dbContext.ConfigureAwait(false))
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"The user '{0}' cannot be deleted because there must be at least one user in the system.",
user.Username));
}
var user = await dbContext.Users
.AsSingleQuery()
.Include(u => u.Permissions)
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
.ConfigureAwait(false);
if (user is null)
{
throw new ResourceNotFoundException(nameof(userId));
}
if (user.HasPermission(PermissionKind.IsAdministrator)
&& Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
{
throw new ArgumentException(
string.Format(
if (await dbContext.Users.CountAsync().ConfigureAwait(false) == 1)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
user.Username),
nameof(userId));
}
"The user '{0}' cannot be deleted because there must be at least one user in the system.",
user.Username));
}
if (user.HasPermission(PermissionKind.IsAdministrator)
&& await dbContext.Users
.CountAsync(u => u.Permissions.Any(p => p.Kind == PermissionKind.IsAdministrator && p.Value))
.ConfigureAwait(false) == 1)
{
throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
user.Username),
nameof(userId));
}
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
dbContext.Users.Remove(user);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
_users.Remove(userId);
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
}
}
/// <inheritdoc/>
@ -532,23 +541,23 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc />
public async Task InitializeAsync()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
if (_users.Any())
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
return;
}
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
{
return;
}
var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{
defaultName = "MyJellyfinUser";
}
var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{
defaultName = "MyJellyfinUser";
}
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
newUser.SetPermission(PermissionKind.IsAdministrator, true);
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
@ -595,12 +604,9 @@ namespace Jellyfin.Server.Implementations.Users
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var user = dbContext.Users
.Include(u => u.Permissions)
.Include(u => u.Preferences)
.Include(u => u.AccessSchedules)
.Include(u => u.ProfileImage)
.FirstOrDefault(u => u.Id.Equals(userId))
var user = await GetUsersInternal(dbContext)
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
.ConfigureAwait(false)
?? throw new ArgumentException("No user exists with given Id!");
user.SubtitleMode = config.SubtitleMode;
@ -628,7 +634,6 @@ namespace Jellyfin.Server.Implementations.Users
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
dbContext.Update(user);
_users[user.Id] = user;
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
@ -639,12 +644,9 @@ namespace Jellyfin.Server.Implementations.Users
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var user = dbContext.Users
.Include(u => u.Permissions)
.Include(u => u.Preferences)
.Include(u => u.AccessSchedules)
.Include(u => u.ProfileImage)
.FirstOrDefault(u => u.Id.Equals(userId))
var user = await GetUsersInternal(dbContext)
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
.ConfigureAwait(false)
?? throw new ArgumentException("No user exists with given Id!");
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
@ -704,7 +706,6 @@ namespace Jellyfin.Server.Implementations.Users
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
dbContext.Update(user);
_users[user.Id] = user;
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
@ -725,7 +726,6 @@ namespace Jellyfin.Server.Implementations.Users
}
user.ProfileImage = null;
_users[user.Id] = user;
}
internal static void ThrowIfInvalidUsername(string name)
@ -872,8 +872,15 @@ namespace Jellyfin.Server.Implementations.Users
private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
{
dbContext.Users.Update(user);
_users[user.Id] = user;
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
private IQueryable<User> GetUsersInternal(JellyfinDbContext dbContext)
=> dbContext.Users
.AsSplitQuery()
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage);
}
}

@ -6,6 +6,8 @@ using Emby.Server.Implementations.Session;
using Jellyfin.Api.WebSocketListeners;
using Jellyfin.Drawing;
using Jellyfin.Drawing.Skia;
using Jellyfin.LiveTv;
using Jellyfin.LiveTv.Channels;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Devices;
@ -14,16 +16,20 @@ using Jellyfin.Server.Implementations.Security;
using Jellyfin.Server.Implementations.Trickplay;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.BaseItemManager;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.Lyric;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -78,6 +84,9 @@ namespace Jellyfin.Server
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
serviceCollection.AddSingleton<IUserManager, UserManager>();
serviceCollection.AddSingleton<IAuthenticationProvider, DefaultAuthenticationProvider>();
serviceCollection.AddSingleton<IAuthenticationProvider, InvalidAuthProvider>();
serviceCollection.AddSingleton<IPasswordResetProvider, DefaultPasswordResetProvider>();
serviceCollection.AddScoped<IDisplayPreferencesManager, DisplayPreferencesManager>();
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
serviceCollection.AddSingleton<ITrickplayManager, TrickplayManager>();
@ -92,6 +101,11 @@ namespace Jellyfin.Server
serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
serviceCollection.AddSingleton<LiveTvDtoService>();
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
foreach (var type in GetExportTypes<ILyricProvider>())
{
serviceCollection.AddSingleton(typeof(ILyricProvider), type);
@ -113,6 +127,9 @@ namespace Jellyfin.Server
// Jellyfin.Server.Implementations
yield return typeof(JellyfinDbContext).Assembly;
// Jellyfin.LiveTv
yield return typeof(LiveTvManager).Assembly;
}
}
}

@ -58,6 +58,7 @@
<ProjectReference Include="..\src\Jellyfin.Drawing\Jellyfin.Drawing.csproj" />
<ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
<ProjectReference Include="..\src\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" />
<ProjectReference Include="..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" />
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
<ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" />
</ItemGroup>

@ -6,6 +6,7 @@ using System.Net.Mime;
using System.Text;
using Jellyfin.Api.Middleware;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking;
using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks;
@ -13,7 +14,6 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Extensions;
using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder;
@ -121,6 +121,8 @@ namespace Jellyfin.Server
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
services.AddHlsPlaylistGenerator();
services.AddHostedService<AutoDiscoveryHost>();
}
/// <summary>

@ -87,6 +87,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes.Tests", "tests\Jellyfin.MediaEncoding.Keyframes.Tests\Jellyfin.MediaEncoding.Keyframes.Tests.csproj", "{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv.Tests", "tests\Jellyfin.LiveTv.Tests\Jellyfin.LiveTv.Tests.csproj", "{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv", "src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj", "{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -233,6 +237,14 @@ Global
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.Build.0 = Release|Any CPU
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.Build.0 = Release|Any CPU
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -259,6 +271,8 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{154872D9-6C12-4007-96E3-8F70A58386CE} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{0A3FCC4D-C714-4072-B90F-E374A15F9FF9} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

@ -59,9 +59,8 @@ namespace MediaBrowser.Controller.Devices
/// Gets the devices.
/// </summary>
/// <param name="userId">The user's id, or <c>null</c>.</param>
/// <param name="supportsSync">A value indicating whether the device supports sync, or <c>null</c>.</param>
/// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns>
Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync);
Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId);
Task DeleteDevice(Device device);

@ -773,8 +773,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The remote trailers.</value>
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
public virtual bool SupportsExternalTransfer => false;
public virtual double GetDefaultPrimaryImageAspectRatio()
{
return 0;

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@ -116,6 +117,14 @@ namespace MediaBrowser.Controller.Library
/// <returns>An instance of <see cref="ILiveStream"/>.</returns>
public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId);
/// <summary>
/// Gets the media sources for an active recording.
/// </summary>
/// <param name="info">The <see cref="ActiveRecordingInfo"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A task containing the <see cref="MediaSourceInfo"/>'s for the recording.</returns>
Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken);
/// <summary>
/// Closes the media source.
/// </summary>

@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The services.</value>
IReadOnlyList<ILiveTvService> Services { get; }
IListingsProvider[] ListingProviders { get; }
IReadOnlyList<IListingsProvider> ListingProviders { get; }
/// <summary>
/// Gets the new timer defaults asynchronous.

@ -1068,7 +1068,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// hw transpose filters should be added manually.
args.Append(" -autorotate 0");
args.Append(" -noautorotate");
return args.ToString().Trim();
}
@ -1159,7 +1159,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4))
{
arg.Append(" -autoscale 0");
arg.Append(" -noautoscale");
}
return arg.ToString();

@ -31,6 +31,8 @@ namespace MediaBrowser.Model.Configuration
public bool EnableLUFSScan { get; set; }
public bool UseReplayGainTags { get; set; }
public bool EnableChapterImageExtraction { get; set; }
public bool ExtractChapterImagesDuringLibraryScan { get; set; }

@ -1,63 +0,0 @@
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Model.Dlna
{
public class DeviceIdentification
{
/// <summary>
/// Gets or sets the name of the friendly.
/// </summary>
/// <value>The name of the friendly.</value>
public string FriendlyName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the model number.
/// </summary>
/// <value>The model number.</value>
public string ModelNumber { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the serial number.
/// </summary>
/// <value>The serial number.</value>
public string SerialNumber { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
/// <value>The name of the model.</value>
public string ModelName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the model description.
/// </summary>
/// <value>The model description.</value>
public string ModelDescription { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the model URL.
/// </summary>
/// <value>The model URL.</value>
public string ModelUrl { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the manufacturer.
/// </summary>
/// <value>The manufacturer.</value>
public string Manufacturer { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the manufacturer URL.
/// </summary>
/// <value>The manufacturer URL.</value>
public string ManufacturerUrl { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the headers.
/// </summary>
/// <value>The headers.</value>
public HttpHeaderInfo[] Headers { get; set; } = Array.Empty<HttpHeaderInfo>();
}
}

@ -1,11 +1,7 @@
#pragma warning disable CA1819 // Properties should not return arrays
using System;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{
@ -17,7 +13,6 @@ namespace MediaBrowser.Model.Dlna
/// the device is able to direct play (without transcoding or remuxing),
/// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't.
/// </summary>
[XmlRoot("Profile")]
public class DeviceProfile
{
/// <summary>
@ -31,104 +26,6 @@ namespace MediaBrowser.Model.Dlna
[XmlIgnore]
public string? Id { get; set; }
/// <summary>
/// Gets or sets the Identification.
/// </summary>
public DeviceIdentification? Identification { get; set; }
/// <summary>
/// Gets or sets the friendly name of the device profile, which can be shown to users.
/// </summary>
public string? FriendlyName { get; set; }
/// <summary>
/// Gets or sets the manufacturer of the device which this profile represents.
/// </summary>
public string? Manufacturer { get; set; }
/// <summary>
/// Gets or sets an url for the manufacturer of the device which this profile represents.
/// </summary>
public string? ManufacturerUrl { get; set; }
/// <summary>
/// Gets or sets the model name of the device which this profile represents.
/// </summary>
public string? ModelName { get; set; }
/// <summary>
/// Gets or sets the model description of the device which this profile represents.
/// </summary>
public string? ModelDescription { get; set; }
/// <summary>
/// Gets or sets the model number of the device which this profile represents.
/// </summary>
public string? ModelNumber { get; set; }
/// <summary>
/// Gets or sets the ModelUrl.
/// </summary>
public string? ModelUrl { get; set; }
/// <summary>
/// Gets or sets the serial number of the device which this profile represents.
/// </summary>
public string? SerialNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableAlbumArtInDidl.
/// </summary>
[DefaultValue(false)]
public bool EnableAlbumArtInDidl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableSingleAlbumArtLimit.
/// </summary>
[DefaultValue(false)]
public bool EnableSingleAlbumArtLimit { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableSingleSubtitleLimit.
/// </summary>
[DefaultValue(false)]
public bool EnableSingleSubtitleLimit { get; set; }
/// <summary>
/// Gets or sets the SupportedMediaTypes.
/// </summary>
public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video";
/// <summary>
/// Gets or sets the UserId.
/// </summary>
public string? UserId { get; set; }
/// <summary>
/// Gets or sets the AlbumArtPn.
/// </summary>
public string? AlbumArtPn { get; set; }
/// <summary>
/// Gets or sets the MaxAlbumArtWidth.
/// </summary>
public int? MaxAlbumArtWidth { get; set; }
/// <summary>
/// Gets or sets the MaxAlbumArtHeight.
/// </summary>
public int? MaxAlbumArtHeight { get; set; }
/// <summary>
/// Gets or sets the maximum allowed width of embedded icons.
/// </summary>
public int? MaxIconWidth { get; set; }
/// <summary>
/// Gets or sets the maximum allowed height of embedded icons.
/// </summary>
public int? MaxIconHeight { get; set; }
/// <summary>
/// Gets or sets the maximum allowed bitrate for all streamed content.
/// </summary>
@ -149,51 +46,6 @@ namespace MediaBrowser.Model.Dlna
/// </summary>
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
/// <summary>
/// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.
/// </summary>
public string? SonyAggregationFlags { get; set; }
/// <summary>
/// Gets or sets the ProtocolInfo.
/// </summary>
public string? ProtocolInfo { get; set; }
/// <summary>
/// Gets or sets the TimelineOffsetSeconds.
/// </summary>
[DefaultValue(0)]
public int TimelineOffsetSeconds { get; set; }
/// <summary>
/// Gets or sets a value indicating whether RequiresPlainVideoItems.
/// </summary>
[DefaultValue(false)]
public bool RequiresPlainVideoItems { get; set; }
/// <summary>
/// Gets or sets a value indicating whether RequiresPlainFolders.
/// </summary>
[DefaultValue(false)]
public bool RequiresPlainFolders { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar.
/// </summary>
[DefaultValue(false)]
public bool EnableMSMediaReceiverRegistrar { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests.
/// </summary>
[DefaultValue(false)]
public bool IgnoreTranscodeByteRangeRequests { get; set; }
/// <summary>
/// Gets or sets the XmlRootAttributes.
/// </summary>
public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty<XmlAttribute>();
/// <summary>
/// Gets or sets the direct play profiles.
/// </summary>
@ -214,298 +66,9 @@ namespace MediaBrowser.Model.Dlna
/// </summary>
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>();
/// <summary>
/// Gets or sets the ResponseProfiles.
/// </summary>
public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty<ResponseProfile>();
/// <summary>
/// Gets or sets the subtitle profiles.
/// </summary>
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>();
/// <summary>
/// The GetSupportedMediaTypes.
/// </summary>
/// <returns>The .</returns>
public MediaType[] GetSupportedMediaTypes()
{
return ContainerProfile.SplitValue(SupportedMediaTypes)
.Select(m => Enum.TryParse<MediaType>(m, out var parsed) ? parsed : MediaType.Unknown)
.Where(m => m != MediaType.Unknown)
.ToArray();
}
/// <summary>
/// Gets the audio transcoding profile.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="audioCodec">The audio Codec.</param>
/// <returns>A <see cref="TranscodingProfile"/>.</returns>
public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec)
{
container = (container ?? string.Empty).TrimStart('.');
foreach (var i in TranscodingProfiles)
{
if (i.Type != DlnaProfileType.Audio)
{
continue;
}
if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
return i;
}
return null;
}
/// <summary>
/// Gets the video transcoding profile.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="audioCodec">The audio Codec.</param>
/// <param name="videoCodec">The video Codec.</param>
/// <returns>The <see cref="TranscodingProfile"/>.</returns>
public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec)
{
container = (container ?? string.Empty).TrimStart('.');
foreach (var i in TranscodingProfiles)
{
if (i.Type != DlnaProfileType.Video)
{
continue;
}
if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase))
{
continue;
}
return i;
}
return null;
}
/// <summary>
/// Gets the audio media profile.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="audioCodec">The audio codec.</param>
/// <param name="audioChannels">The audio channels.</param>
/// <param name="audioBitrate">The audio bitrate.</param>
/// <param name="audioSampleRate">The audio sample rate.</param>
/// <param name="audioBitDepth">The audio bit depth.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile? GetAudioMediaProfile(string? container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
{
foreach (var i in ResponseProfiles)
{
if (i.Type != DlnaProfileType.Audio)
{
continue;
}
if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
{
continue;
}
var audioCodecs = i.GetAudioCodecs();
if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
if (!ConditionProcessor.IsAudioConditionSatisfied(GetModelProfileCondition(c), audioChannels, audioBitrate, audioSampleRate, audioBitDepth))
{
anyOff = true;
break;
}
}
if (anyOff)
{
continue;
}
return i;
}
return null;
}
/// <summary>
/// Gets the model profile condition.
/// </summary>
/// <param name="c">The c<see cref="ProfileCondition"/>.</param>
/// <returns>The <see cref="ProfileCondition"/>.</returns>
private ProfileCondition GetModelProfileCondition(ProfileCondition c)
{
return new ProfileCondition
{
Condition = c.Condition,
IsRequired = c.IsRequired,
Property = c.Property,
Value = c.Value
};
}
/// <summary>
/// Gets the image media profile.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height)
{
foreach (var i in ResponseProfiles)
{
if (i.Type != DlnaProfileType.Photo)
{
continue;
}
if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
{
continue;
}
var anyOff = false;
foreach (var c in i.Conditions)
{
if (!ConditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
{
anyOff = true;
break;
}
}
if (anyOff)
{
continue;
}
return i;
}
return null;
}
/// <summary>
/// Gets the video media profile.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="audioCodec">The audio codec.</param>
/// <param name="videoCodec">The video codec.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="bitDepth">The bit depth.</param>
/// <param name="videoBitrate">The video bitrate.</param>
/// <param name="videoProfile">The video profile.</param>
/// <param name="videoRangeType">The video range type.</param>
/// <param name="videoLevel">The video level.</param>
/// <param name="videoFramerate">The video framerate.</param>
/// <param name="packetLength">The packet length.</param>
/// <param name="timestamp">The timestamp<see cref="TransportStreamTimestamp"/>.</param>
/// <param name="isAnamorphic">True if anamorphic.</param>
/// <param name="isInterlaced">True if interlaced.</param>
/// <param name="refFrames">The ref frames.</param>
/// <param name="numVideoStreams">The number of video streams.</param>
/// <param name="numAudioStreams">The number of audio streams.</param>
/// <param name="videoCodecTag">The video Codec tag.</param>
/// <param name="isAvc">True if Avc.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile? GetVideoMediaProfile(
string? container,
string? audioCodec,
string? videoCodec,
int? width,
int? height,
int? bitDepth,
int? videoBitrate,
string? videoProfile,
VideoRangeType videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
TransportStreamTimestamp timestamp,
bool? isAnamorphic,
bool? isInterlaced,
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
string? videoCodecTag,
bool? isAvc)
{
foreach (var i in ResponseProfiles)
{
if (i.Type != DlnaProfileType.Video)
{
continue;
}
if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
{
continue;
}
var audioCodecs = i.GetAudioCodecs();
if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var videoCodecs = i.GetVideoCodecs();
if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
anyOff = true;
break;
}
}
if (anyOff)
{
continue;
}
return i;
}
return null;
}
}
}

@ -1,26 +0,0 @@
#nullable disable
#pragma warning disable CS1591
namespace MediaBrowser.Model.Dlna
{
public class DeviceProfileInfo
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public DeviceProfileType Type { get; set; }
}
}

@ -1,10 +0,0 @@
#pragma warning disable CS1591
namespace MediaBrowser.Model.Dlna
{
public enum DeviceProfileType
{
System = 0,
User = 1
}
}

@ -1,11 +0,0 @@
#pragma warning disable CS1591
namespace MediaBrowser.Model.Dlna
{
public enum HeaderMatchType
{
Equals = 0,
Regex = 1,
Substring = 2
}
}

@ -1,19 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
{
public class HttpHeaderInfo
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
[XmlAttribute("match")]
public HeaderMatchType Match { get; set; }
}
}

@ -1,51 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
{
public class ResponseProfile
{
public ResponseProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
[XmlAttribute("container")]
public string Container { get; set; }
[XmlAttribute("audioCodec")]
public string AudioCodec { get; set; }
[XmlAttribute("videoCodec")]
public string VideoCodec { get; set; }
[XmlAttribute("type")]
public DlnaProfileType Type { get; set; }
[XmlAttribute("orgPn")]
public string OrgPn { get; set; }
[XmlAttribute("mimeType")]
public string MimeType { get; set; }
public ProfileCondition[] Conditions { get; set; }
public string[] GetContainers()
{
return ContainerProfile.SplitValue(Container);
}
public string[] GetAudioCodecs()
{
return ContainerProfile.SplitValue(AudioCodec);
}
public string[] GetVideoCodecs()
{
return ContainerProfile.SplitValue(VideoCodec);
}
}
}

@ -1,25 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
{
/// <summary>
/// Defines the <see cref="XmlAttribute" />.
/// </summary>
public class XmlAttribute
{
/// <summary>
/// Gets or sets the name of the attribute.
/// </summary>
[XmlAttribute("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the value of the attribute.
/// </summary>
[XmlAttribute("value")]
public string Value { get; set; }
}
}

@ -85,11 +85,6 @@ namespace MediaBrowser.Model.Dto
public string PreferredMetadataCountryCode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [supports synchronize].
/// </summary>
public bool? SupportsSync { get; set; }
public string Container { get; set; }
/// <summary>

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
@ -214,6 +215,27 @@ namespace MediaBrowser.Model.Entities
}
}
/// <summary>
/// Gets the audio spatial format.
/// </summary>
/// <value>The audio spatial format.</value>
[DefaultValue(AudioSpatialFormat.None)]
public AudioSpatialFormat AudioSpatialFormat
{
get
{
if (Type != MediaStreamType.Audio || string.IsNullOrEmpty(Profile))
{
return AudioSpatialFormat.None;
}
return
Profile.Contains("Dolby Atmos", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DolbyAtmos :
Profile.Contains("DTS:X", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DTSX :
AudioSpatialFormat.None;
}
}
public string LocalizedUndefined { get; set; }
public string LocalizedDefault { get; set; }

@ -175,13 +175,6 @@ namespace MediaBrowser.Model.Querying
/// </summary>
Studios,
BasicSyncInfo,
/// <summary>
/// The synchronize information.
/// </summary>
SyncInfo,
/// <summary>
/// The taglines of the item.
/// </summary>

@ -23,14 +23,8 @@ namespace MediaBrowser.Model.Session
public bool SupportsMediaControl { get; set; }
public bool SupportsContentUploading { get; set; }
public string MessageCallbackUrl { get; set; }
public bool SupportsPersistentIdentifier { get; set; }
public bool SupportsSync { get; set; }
public DeviceProfile DeviceProfile { get; set; }
public string AppStoreUrl { get; set; }

@ -61,6 +61,9 @@ namespace MediaBrowser.Providers.MediaInfo
[GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
private static partial Regex LUFSRegex();
[GeneratedRegex(@"REPLAYGAIN_TRACK_GAIN:\s+-?([0-9.]+)\s+dB")]
private static partial Regex ReplayGainTagRegex();
/// <summary>
/// Probes the specified item for metadata.
/// </summary>
@ -104,8 +107,50 @@ namespace MediaBrowser.Providers.MediaInfo
}
var libraryOptions = _libraryManager.GetLibraryOptions(item);
bool foundLUFSValue = false;
if (libraryOptions.UseReplayGainTags)
{
using (var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = _mediaEncoder.ProbePath,
Arguments = $"-hide_banner -i \"{path}\"",
RedirectStandardOutput = false,
RedirectStandardError = true
},
})
{
try
{
process.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ffmpeg");
throw;
}
using var reader = process.StandardError;
var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
Match split = ReplayGainTagRegex().Match(output);
if (split.Success)
{
item.LUFS = DefaultLUFSValue - float.Parse(split.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
foundLUFSValue = true;
}
else
{
item.LUFS = DefaultLUFSValue;
}
}
}
if (libraryOptions.EnableLUFSScan)
if (libraryOptions.EnableLUFSScan && !foundLUFSValue)
{
using (var process = new Process()
{
@ -144,7 +189,8 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
}
else
if (!libraryOptions.EnableLUFSScan && !libraryOptions.UseReplayGainTags)
{
item.LUFS = DefaultLUFSValue;
}

@ -142,6 +142,17 @@ cd Jellyfin.Server/bin/Debug/net8.0 # Change into the build output directory
2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
### Running from GH-Codespaces
As Jellyfin will run on a container on a github hosted server, JF needs to handle some things differently.
**NOTE:** If you want to access the JF instance from outside, like with a WebClient on another PC, remember to set the "ports" in the lower VsCode window to public.
#### FFmpeg installation.
Because sometimes you need FFMPEG to test certain cases, follow the instructions from the wiki on the dev enviorment:
https://jellyfin.org/docs/general/installation/linux/#ffmpeg-installation
**NOTE:** When first opening the server instance with any WebUI, you will be send to the login instead of the setup page. Refresh the login page once and you should be redirected to the Setup.
### Running The Tests
This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests.

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Channels
namespace Jellyfin.LiveTv.Channels
{
/// <summary>
/// A media source provider for channels.

@ -7,7 +7,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Channels
namespace Jellyfin.LiveTv.Channels
{
/// <summary>
/// An image provider for channels.

@ -34,7 +34,7 @@ using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Channels
namespace Jellyfin.LiveTv.Channels
{
/// <summary>
/// The LiveTV channel manager.
@ -812,11 +812,16 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult is not null)
var jsonStream = AsyncFile.OpenRead(cachePath);
await using (jsonStream.ConfigureAwait(false))
{
return null;
var cachedResult = await JsonSerializer
.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken)
.ConfigureAwait(false);
if (cachedResult is not null)
{
return null;
}
}
}
}
@ -835,11 +840,16 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult is not null)
var jsonStream = AsyncFile.OpenRead(cachePath);
await using (jsonStream.ConfigureAwait(false))
{
return null;
var cachedResult = await JsonSerializer
.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken)
.ConfigureAwait(false);
if (cachedResult is not null)
{
return null;
}
}
}
}
@ -867,7 +877,7 @@ namespace Emby.Server.Implementations.Channels
throw new InvalidOperationException("Channel returned a null result from GetChannelItems");
}
await CacheResponse(result, cachePath);
await CacheResponse(result, cachePath).ConfigureAwait(false);
return result;
}
@ -883,8 +893,11 @@ namespace Emby.Server.Implementations.Channels
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
await using FileStream createStream = File.Create(path);
await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);
var createStream = File.Create(path);
await using (createStream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);
}
}
catch (Exception ex)
{

@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
namespace Jellyfin.LiveTv.Channels
{
/// <summary>
/// A task to remove all non-installed channels from the database.

@ -9,7 +9,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
namespace Jellyfin.LiveTv.Channels
{
/// <summary>
/// The "Refresh Channels" scheduled task.

@ -5,7 +5,6 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Streaming;
@ -13,7 +12,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public sealed class DirectRecorder : IRecorder
{

@ -14,14 +14,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@ -39,7 +37,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
@ -47,7 +45,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private const int TunerDiscoveryDurationMs = 3000;
private readonly IServerApplicationHost _appHost;
private readonly ILogger<EmbyTV> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
@ -76,7 +73,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private bool _disposed;
public EmbyTV(
IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger<EmbyTV> logger,
@ -91,7 +87,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Current = this;
_appHost = appHost;
_logger = logger;
_httpClientFactory = httpClientFactory;
_config = config;
@ -1021,35 +1016,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
{
var stream = new MediaSourceInfo
{
EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
Id = info.Id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true,
RequiresOpening = false,
RequiresClosing = false,
BufferMs = 0,
IgnoreDts = true,
IgnoreIndex = true
};
await new LiveStreamHelper(_mediaEncoder, _logger, _config.CommonApplicationPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
stream
};
}
public Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
return Task.CompletedTask;

@ -23,7 +23,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public class EncodedRecorder : IRecorder
{

@ -3,7 +3,7 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public sealed class EntryPoint : IServerEntryPoint
{

@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
internal class EpgChannelData
{

@ -6,7 +6,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public interface IRecorder : IDisposable
{

@ -9,7 +9,7 @@ using System.Text.Json;
using Jellyfin.Extensions.Json;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public class ItemDataProvider<T>
where T : class

@ -1,7 +1,7 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
/// <summary>
/// Class containing extension methods for working with the nfo configuration.

@ -5,7 +5,7 @@ using System.Globalization;
using System.Text;
using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
internal static class RecordingHelper
{

@ -4,7 +4,7 @@ using System;
using MediaBrowser.Controller.LiveTv;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{

@ -10,7 +10,7 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
namespace Jellyfin.LiveTv.EmbyTV
{
public class TimerManager : ItemDataProvider<TimerInfo>
{

@ -1,5 +1,6 @@
#nullable disable
#pragma warning disable CA1711
#pragma warning disable CS1591
using System;
@ -10,7 +11,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Library
namespace Jellyfin.LiveTv
{
public sealed class ExclusiveLiveStream : ILiveStream
{

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Jellyfin.LiveTv.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.XmlTv" />
<PackageReference Include="System.Linq.Async" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup>
</Project>

@ -16,9 +16,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using Jellyfin.LiveTv.Listings.SchedulesDirectDtos;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.LiveTv;
@ -27,7 +27,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.Listings
namespace Jellyfin.LiveTv.Listings
{
public class SchedulesDirect : IListingsProvider, IDisposable
{
@ -613,6 +613,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
// Response is automatically disposed in the calling function,
// so dispose manually if not returning.
#pragma warning disable IDISP016, IDISP017
response.Dispose();
if (!enableRetry || (int)response.StatusCode >= 500)
{
@ -621,6 +622,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
null,
response.StatusCode);
}
#pragma warning restore IDISP016, IDISP017
_tokens.Clear();
options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false));

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Broadcaster dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Caption dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Cast dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Channel dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Content rating dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Crew dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Day dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Description 1_000 dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Description 100 dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Descriptions program dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Event details dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Gracenote dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Headends dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Image data dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// The lineup dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Lineups dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Logo dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Map dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata programs dto.

@ -1,7 +1,7 @@
using System;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Metadata schedule dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Movie dto.

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Multipart dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Program details dto.

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
/// <summary>
/// Program dto.

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

Loading…
Cancel
Save