Compare commits
6 Commits
master
...
release-10
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e80e621c0 | |||
| c9fda519d8 | |||
| 898a541c18 | |||
| 2033a30ddb | |||
| bfe40864d8 | |||
| 5f027d0a66 |
@@ -5,10 +5,6 @@ steps:
|
||||
- name: build
|
||||
image: mcr.microsoft.com/dotnet/sdk:9.0
|
||||
commands:
|
||||
# HACK Waiting for "Assembly strong name Linux compatibility" (https://github.com/gluck/il-repack/pull/366) to propagate to the ILRepack.Lib.MSBuild.Task nuget package
|
||||
- dotnet restore CinemaJellyfin/CinemaJellyfin.csproj
|
||||
- wget https://www.ivasoft.cz/share/ILRepack.Lib.MSBuild.Task.dll -O /root/.nuget/packages/ilrepack.lib.msbuild.task/2.0.34.2/build/ILRepack.Lib.MSBuild.Task.dll
|
||||
# END HACK
|
||||
- dotnet build --configuration Release CinemaJellyfin/CinemaJellyfin.csproj
|
||||
- dotnet publish -c Release -o out
|
||||
|
||||
@@ -16,8 +12,8 @@ steps:
|
||||
image: git.ivasoft.cz/sw/docker-wine-dotnet
|
||||
commands:
|
||||
- wine Eazfuscator.NET.exe CinemaJellyfin/bin/Release/net8.0/CinemaJellyfin.dll -k key.snk -n --newline-flush
|
||||
#when:
|
||||
# event: tag
|
||||
when:
|
||||
event: tag
|
||||
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
|
||||
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using CinemaLib.API;
|
||||
using CinemaJellyfin;
|
||||
|
||||
namespace Jellyfin.Plugin.Cinema;
|
||||
|
||||
@@ -165,11 +166,11 @@ public abstract class CinemaFilterFolder : Folder
|
||||
|
||||
private static T CreateFilterFolderInternal<T>(CinemaFilterFolder? parent, string localizedName) where T : CinemaFilterFolder, new()
|
||||
{
|
||||
Guid folderId = CinemaHost.LibraryManager.GetNewItemId("folder", typeof(T));
|
||||
string folderPath = GetInternalMetadataPath(CinemaHost.InternalMetadataPath!, folderId);
|
||||
Guid folderId = CinemaGlobals.LibraryManager.GetNewItemId("folder", typeof(T));
|
||||
string folderPath = GetInternalMetadataPath(CinemaGlobals.InternalMetadataPath!, folderId);
|
||||
Directory.CreateDirectory(folderPath);
|
||||
|
||||
T? folder = CinemaHost.LibraryManager.GetItemById(folderId) as T;
|
||||
T? folder = CinemaGlobals.LibraryManager.GetItemById(folderId) as T;
|
||||
bool isNew;
|
||||
bool forceUpdate = false;
|
||||
if (isNew = folder == null)
|
||||
@@ -177,8 +178,8 @@ public abstract class CinemaFilterFolder : Folder
|
||||
folder = new T
|
||||
{
|
||||
Id = folderId,
|
||||
DateCreated = CinemaHost.FileSystem.GetCreationTimeUtc(folderPath),
|
||||
DateModified = CinemaHost.FileSystem.GetLastWriteTimeUtc(folderPath)
|
||||
DateCreated = CinemaGlobals.FileSystem.GetCreationTimeUtc(folderPath),
|
||||
DateModified = CinemaGlobals.FileSystem.GetLastWriteTimeUtc(folderPath)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,11 +201,11 @@ public abstract class CinemaFilterFolder : Folder
|
||||
if (isNew)
|
||||
{
|
||||
folder.OnMetadataChanged();
|
||||
CinemaHost.LibraryManager.CreateItem(folder, parent);
|
||||
CinemaGlobals.LibraryManager.CreateItem(folder, parent);
|
||||
}
|
||||
|
||||
folder.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(CinemaHost.FileSystem)) { ForceSave = !isNew && forceUpdate },
|
||||
new MetadataRefreshOptions(new DirectoryService(CinemaGlobals.FileSystem)) { ForceSave = !isNew && forceUpdate },
|
||||
default
|
||||
).GetAwaiter().GetResult();
|
||||
|
||||
|
||||
36
CinemaJellyfin/CinemaGlobals.cs
Normal file
36
CinemaJellyfin/CinemaGlobals.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace CinemaJellyfin;
|
||||
|
||||
/// <summary>
|
||||
/// Global services that are resolved at startup and are
|
||||
/// otherwise inaccessible to some classes (mostly folders).
|
||||
/// </summary>
|
||||
static class CinemaGlobals
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private static ILibraryManager _libraryManager;
|
||||
private static IServerConfigurationManager _config;
|
||||
private static IFileSystem _fileSystem;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
internal static void Initialize(ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public static ILibraryManager LibraryManager => _libraryManager;
|
||||
|
||||
public static IFileSystem FileSystem => _fileSystem;
|
||||
|
||||
public static string InternalMetadataPath => _config.ApplicationPaths.InternalMetadataPath;
|
||||
|
||||
public static string PreferredCulture => _config.Configuration.PreferredMetadataLanguage;
|
||||
|
||||
public static string FallbackCulture => "en";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq.Expressions;
|
||||
using CinemaJellyfin;
|
||||
using CinemaLib.Webshare;
|
||||
using Jellyfin.Plugin.Cinema.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -19,9 +20,6 @@ sealed class CinemaHost : IHostedService
|
||||
#pragma warning disable CS8618
|
||||
// This instance is specially registered and gets created before all classes
|
||||
// except CinemaServiceRegistrator and CinemaPlugin.
|
||||
private static ILibraryManager _libraryManager;
|
||||
private static IServerConfigurationManager _config;
|
||||
private static IFileSystem _fileSystem;
|
||||
private static Session? _webshare;
|
||||
#pragma warning restore CS8618
|
||||
private readonly ILogger<CinemaHost> _logger;
|
||||
@@ -33,22 +31,10 @@ sealed class CinemaHost : IHostedService
|
||||
/// </summary>
|
||||
public CinemaHost(ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger<CinemaHost> logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
CinemaGlobals.Initialize(libraryManager, config, fileSystem);
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
public static ILibraryManager LibraryManager => _libraryManager;
|
||||
|
||||
public static string InternalMetadataPath => _config.ApplicationPaths.InternalMetadataPath;
|
||||
|
||||
public static IFileSystem FileSystem => _fileSystem;
|
||||
|
||||
public static string PreferredCulture => _config.Configuration.PreferredMetadataLanguage;
|
||||
|
||||
public static string FallbackCulture => "en";
|
||||
|
||||
public static Session? Webshare => _webshare;
|
||||
|
||||
public static async Task<bool> IsWebshareFreeAccount(CancellationToken cancel)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.10.3" />
|
||||
<!--ProjectReference Include="..\..\..\jellyfin\MediaBrowser.Model\MediaBrowser.Model.csproj" /-->
|
||||
|
||||
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.34.2" PrivateAssets="All"/>
|
||||
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -15,6 +15,9 @@ using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using CinemaJellyfin;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
||||
namespace Jellyfin.Plugin.Cinema;
|
||||
|
||||
@@ -33,6 +36,9 @@ sealed class CinemaLibraryManager : ILibraryManager
|
||||
throw new InvalidOperationException("Original LibraryManager service not found.");
|
||||
this._inner = inner;
|
||||
this._userData = userData;
|
||||
|
||||
// We may run before CinemaHost if migrations are executed
|
||||
CinemaGlobals.Initialize(this, svc.GetRequiredService<IServerConfigurationManager>(), svc.GetRequiredService<IFileSystem>());
|
||||
}
|
||||
|
||||
#region ILibraryManager Members
|
||||
|
||||
@@ -136,6 +136,11 @@ sealed class CinemaMediaAnalyzer
|
||||
if (i.BitRate == null || i.BitRate < 16 * 1024)
|
||||
i.BitRate = 16 * 1024;
|
||||
break;
|
||||
|
||||
case MediaStreamType.Subtitle:
|
||||
// Required for subtitle extraction
|
||||
i.SupportsExternalStream = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Add unrecognized bogus streams so stream Index values are continous and sorted in the
|
||||
|
||||
@@ -6,7 +6,13 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
@@ -16,11 +22,9 @@ using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using CinemaLib.API;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LinkGenerator = CinemaLib.Webshare.LinkGenerator;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Jellyfin.Plugin.Cinema;
|
||||
|
||||
@@ -54,12 +58,13 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IApplicationPaths _pathManager;
|
||||
private readonly ConcurrentDictionary<Guid, VersionSetEntry> _videoVersions;
|
||||
private readonly ConcurrentDictionary<LinkKey, LinkEntry> _links;
|
||||
|
||||
public CinemaMediaSourceManager(CinemaInnerMediaSourceManager innerMediaSourceManager, ILibraryManager libraryManager, IServerApplicationHost host,
|
||||
IServiceProvider svc, IHttpContextAccessor http, IHttpClientFactory httpClientFactory, IMediaEncoder mediaEncoder,
|
||||
IServerConfigurationManager serverConfigurationManager, IUserManager userManager)
|
||||
IServerConfigurationManager serverConfigurationManager, IUserManager userManager, IApplicationPaths pathManager)
|
||||
{
|
||||
if (innerMediaSourceManager == null || svc == null)
|
||||
throw new ArgumentNullException();
|
||||
@@ -76,6 +81,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
this._mediaEncoder = mediaEncoder;
|
||||
this._serverConfigurationManager = serverConfigurationManager;
|
||||
this._userManager = userManager;
|
||||
this._pathManager = pathManager;
|
||||
this._videoVersions = new ConcurrentDictionary<Guid, VersionSetEntry>();
|
||||
this._links = new ConcurrentDictionary<LinkKey, LinkEntry>();
|
||||
|
||||
@@ -241,25 +247,28 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
// We need to directly inspect the video file and therefore need to know which
|
||||
// version to specifically return. For free accounts it is not possible to just
|
||||
// read files at random, for VIP accounts that would slow playback startup also.
|
||||
Guid mediaSourceId;
|
||||
if (ctx == null
|
||||
|| !ctx.Items.TryGetValue(ContextItemsMediaSourceIdKey, out object? mediaSourceIdO)
|
||||
|| mediaSourceIdO is not string mediaSourceIdS
|
||||
|| (!Guid.TryParse(mediaSourceIdS, out mediaSourceId)))
|
||||
throw new InvalidOperationException("For precise stream indexing knowing mediaSourceId is required.");
|
||||
|
||||
// Warning: We assume GetVideoVersionsEnumerate keeps the order between its input and output collections
|
||||
int idxVer = 0;
|
||||
Video? videoVer = null;
|
||||
foreach (Video i in items)
|
||||
if ((!sortByPrefs && i.Id == mediaSourceId)
|
||||
|| (sortByPrefs && idxVer == bestIdx))
|
||||
{
|
||||
videoVer = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
idxVer++;
|
||||
Video? videoVer;
|
||||
int idxVer;
|
||||
if (!sortByPrefs) {
|
||||
Guid mediaSourceId;
|
||||
if (ctx == null
|
||||
|| !ctx.Items.TryGetValue(ContextItemsMediaSourceIdKey, out object? mediaSourceIdO)
|
||||
|| mediaSourceIdO is not string mediaSourceIdS
|
||||
|| (!Guid.TryParse(mediaSourceIdS, out mediaSourceId)))
|
||||
throw new InvalidOperationException("For precise stream indexing knowing mediaSourceId is required.");
|
||||
|
||||
idxVer = -1;
|
||||
videoVer = items.Where((x, idx) => {
|
||||
bool isMatch = x.Id == mediaSourceId;
|
||||
if (isMatch)
|
||||
idxVer = idx;
|
||||
return isMatch;
|
||||
}).FirstOrDefault();
|
||||
} else {
|
||||
idxVer = bestIdx;
|
||||
videoVer = items.Skip(bestIdx).FirstOrDefault();
|
||||
}
|
||||
if (videoVer == null)
|
||||
// Version not found, return empty set
|
||||
break;
|
||||
@@ -270,7 +279,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
Uri link = await GenerateLink(metaVer.Meta.provider, metaVer.Meta.ident, metaVer.Meta.name, cancellationToken);
|
||||
if (metaVer.AnalyzedInfo == null)
|
||||
metaVer.AnalyzedInfo = await CinemaMediaAnalyzer.AnalyzeResourceAsync(link, metaVer.Meta.size, metaVer.Meta, _httpClientFactory, _mediaEncoder, _serverConfigurationManager, cancellationToken);
|
||||
result.Add(GetVersionInfo(videoVer, metaVer.Meta, metaVer.AnalyzedInfo.MediaStreams, isFreeAccount, link));
|
||||
result.Add(await GetVersionInfo(videoVer, metaVer.Meta, metaVer.AnalyzedInfo.MediaStreams, isFreeAccount, link, cancellationToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -278,7 +287,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
// Note: Also makes sure BaseItems exist for each version
|
||||
int idx = 0;
|
||||
foreach (Video i in items)
|
||||
result.Add(GetVersionInfo(i, ver.Versions[idx++].Meta, null, isFreeAccount, FakeVideoUri));
|
||||
result.Add(await GetVersionInfo(i, ver.Versions[idx++].Meta, null, isFreeAccount, FakeVideoUri, cancellationToken));
|
||||
|
||||
if (sortByPrefs && ver.Versions.Length != 0 && bestIdx >= 0)
|
||||
{
|
||||
@@ -356,7 +365,8 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
|
||||
if (m.subtitles != null && subtitleLang != null)
|
||||
foreach (StreamSubtitle j in m.subtitles)
|
||||
if (ISO639_1ToISO639_2(j.language) == subtitleLang)
|
||||
if (ISO639_1ToISO639_2(j.language).Primary == subtitleLang
|
||||
|| ISO639_1ToISO639_2(j.language).Synonym == subtitleLang)
|
||||
{
|
||||
score += 4; // proper subtitles are preferred but much better resulution still rules
|
||||
break;
|
||||
@@ -364,7 +374,8 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
|
||||
if (m.audio != null && audioLang != null)
|
||||
foreach (StreamAudio j in m.audio)
|
||||
if (ISO639_1ToISO639_2(j.language) == audioLang)
|
||||
if (ISO639_1ToISO639_2(j.language).Primary == audioLang
|
||||
|| ISO639_1ToISO639_2(j.language).Synonym == audioLang)
|
||||
{
|
||||
score += 25; // proper audio channel overrides resolution and other minor bonuses
|
||||
break;
|
||||
@@ -558,17 +569,17 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
|
||||
bool isFreeAccount = await CinemaHost.IsWebshareFreeAccount(cancel);
|
||||
|
||||
return GetVersionInfo(item, ver.Meta, null, isFreeAccount, FakeVideoUri);
|
||||
return await GetVersionInfo(item, ver.Meta, null, isFreeAccount, FakeVideoUri, cancel);
|
||||
}
|
||||
|
||||
private MediaSourceInfo GetVersionInfo(Video item, CinemaLib.API.Stream ver, IReadOnlyList<MediaStream>? analyzedStreams, bool isFreeAccount, Uri path)
|
||||
private async ValueTask<MediaSourceInfo> GetVersionInfo(Video item, CinemaLib.API.Stream ver, IReadOnlyList<MediaStream>? analyzedStreams, bool isFreeAccount, Uri path, CancellationToken cancel)
|
||||
{
|
||||
MediaSourceInfo result = new MediaSourceInfo();
|
||||
result.VideoType = VideoType.VideoFile;
|
||||
result.Id = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
result.Protocol = MediaProtocol.Http;
|
||||
// Warning: We set some properties on item that get used below
|
||||
result.MediaStreams = VersionToMediaStreams(item, ver, analyzedStreams);
|
||||
result.MediaStreams = await VersionToMediaStreams(result.Id, item, ver, analyzedStreams, cancel);
|
||||
|
||||
double bitRate = (ver.size ?? 0.0) * 8 / (item.RunTimeTicks ?? 1.0) * TimeSpan.TicksPerSecond;
|
||||
bool showBitrateWarning = isFreeAccount && bitRate / (1 + BitrateMargin) > WebshareFreeBitrate;
|
||||
@@ -629,8 +640,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<MediaStream> VersionToMediaStreams(Video item, CinemaLib.API.Stream ver, IReadOnlyList<MediaStream>? analyzedStreams)
|
||||
{
|
||||
private async ValueTask<List<MediaStream>> VersionToMediaStreams(string mediaSourceId, Video item, CinemaLib.API.Stream ver, IReadOnlyList<MediaStream>? analyzedStreams, CancellationToken cancel) {
|
||||
List<MediaStream> result = new List<MediaStream>();
|
||||
|
||||
if (analyzedStreams != null && analyzedStreams.Count != 0)
|
||||
@@ -641,15 +651,40 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
{
|
||||
foreach (StreamSubtitle j in ver.subtitles)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(j.src))
|
||||
if (!string.IsNullOrEmpty(j.src) && Uri.TryCreate(j.src, UriKind.Absolute, out Uri? src))
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
a.Index = -1;
|
||||
a.Index = result.Count;
|
||||
a.Type = MediaStreamType.Subtitle;
|
||||
a.Language = ISO639_1ToISO639_2(j.language);
|
||||
a.Language = ISO639_1ToISO639_2(j.language).Primary;
|
||||
a.IsForced = j.forced;
|
||||
a.DeliveryUrl = j.src;
|
||||
a.IsExternalUrl = true;
|
||||
a.IsExternal = true;
|
||||
// Note: This prevents subtitle burn-in and does not restart playback while activating
|
||||
a.SupportsExternalStream = true;
|
||||
a.Codec = "subrip";
|
||||
#if NOT_BROKEN_GetStream
|
||||
a.Path = (await GenerateLinkOrKeepUrl(src, cancel)).ToString();
|
||||
#else
|
||||
// BUG: SubtitleEncoder.GetStream is broken as it disposes the returned Stream
|
||||
// before returning. Therefore we must cache the subtitle file locally
|
||||
// HACK: We expect the ordering in ver.subtitles is stable as Jellyfin
|
||||
// uses index to determine the filename
|
||||
string subtitleCachePath = Path.Combine(_pathManager.DataPath, "subtitles");
|
||||
string mediaSourceIdS = Guid.Parse(mediaSourceId).ToString("D", CultureInfo.InvariantCulture);
|
||||
string folderPath = Path.Join(subtitleCachePath, mediaSourceIdS[..2], mediaSourceIdS);
|
||||
string path = Path.Join(folderPath, a.Index.ToString(CultureInfo.InvariantCulture) + ".srt");
|
||||
a.IsExternal = true;
|
||||
a.Path = path;
|
||||
|
||||
if (!File.Exists(path)) {
|
||||
// PERF: Subtitle files are expected not to be large (< 1 MB)
|
||||
Uri link = await GenerateLinkOrKeepUrl(src, cancel);
|
||||
using HttpResponseMessage response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(link, cancel);
|
||||
byte[] contentB = await response.Content.ReadAsByteArrayAsync(cancel);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
await File.WriteAllBytesAsync(path, contentB, cancel);
|
||||
}
|
||||
#endif
|
||||
result.Add(a);
|
||||
}
|
||||
}
|
||||
@@ -702,7 +737,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
MediaStream a = new MediaStream();
|
||||
a.Index = uniqueId--;
|
||||
a.Type = MediaStreamType.Audio;
|
||||
a.Language = ISO639_1ToISO639_2(j.language);
|
||||
a.Language = ISO639_1ToISO639_2(j.language).Primary;
|
||||
a.Codec = j.codec;
|
||||
a.Channels = j.channels;
|
||||
|
||||
@@ -718,10 +753,12 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
MediaStream a = new MediaStream();
|
||||
a.Index = uniqueId--;
|
||||
a.Type = MediaStreamType.Subtitle;
|
||||
a.Language = ISO639_1ToISO639_2(j.language);
|
||||
a.Language = ISO639_1ToISO639_2(j.language).Primary;
|
||||
a.IsForced = j.forced;
|
||||
a.DeliveryUrl = j.src;
|
||||
a.IsExternalUrl = true;
|
||||
if (a.IsExternal = j.src != null) {
|
||||
a.Path = j.src;
|
||||
a.DeliveryMethod = MediaBrowser.Model.Dlna.SubtitleDeliveryMethod.External;
|
||||
}
|
||||
|
||||
result.Add(a);
|
||||
}
|
||||
@@ -876,7 +913,7 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<Uri> GenerateLink(string provider, string ident, string? name, CancellationToken cancel)
|
||||
public async ValueTask<Uri> GenerateLink(string provider, string ident, string? name, CancellationToken cancel)
|
||||
{
|
||||
name = name ?? "";
|
||||
DateTime now = DateTime.UtcNow;
|
||||
@@ -898,198 +935,212 @@ sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return entry.Link;
|
||||
}
|
||||
|
||||
private ValueTask<Uri> GenerateLinkOrKeepUrl(Uri uri, CancellationToken cancel) {
|
||||
switch (uri.Host) {
|
||||
case "webshare.cz":
|
||||
const string WebsharePrefix = "#/file/";
|
||||
if (uri.AbsolutePath == "/" && uri.Fragment.StartsWith(WebsharePrefix)) {
|
||||
string ident = uri.Fragment.Substring(WebsharePrefix.Length);
|
||||
return GenerateLink("webshare", ident, name: null, cancel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return new ValueTask<Uri>(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts two-letter 639-1 language code to three letter 639-2.
|
||||
/// </summary>
|
||||
private static string? ISO639_1ToISO639_2(string? twoLetter639_1)
|
||||
private static (string? Primary, string? Synonym) ISO639_1ToISO639_2(string? twoLetter639_1)
|
||||
{
|
||||
// Based on https://www.loc.gov/standards/iso639-2/php/code_list-utf8.php
|
||||
switch (twoLetter639_1)
|
||||
{
|
||||
case "aa": return "aar";
|
||||
case "ab": return "abk";
|
||||
case "af": return "afr";
|
||||
case "ak": return "aka";
|
||||
case "sq": return "alb";
|
||||
case "am": return "amh";
|
||||
case "ar": return "ara";
|
||||
case "an": return "arg";
|
||||
case "hy": return "arm";
|
||||
case "as": return "asm";
|
||||
case "av": return "ava";
|
||||
case "ae": return "ave";
|
||||
case "ay": return "aym";
|
||||
case "az": return "aze";
|
||||
case "ba": return "bak";
|
||||
case "bm": return "bam";
|
||||
case "eu": return "baq";
|
||||
case "be": return "bel";
|
||||
case "bn": return "ben";
|
||||
case "bi": return "bis";
|
||||
case "bo": return "tib";
|
||||
case "bs": return "bos";
|
||||
case "br": return "bre";
|
||||
case "bg": return "bul";
|
||||
case "my": return "bur";
|
||||
case "ca": return "cat";
|
||||
case "cs": return "cze";
|
||||
case "ch": return "cha";
|
||||
case "ce": return "che";
|
||||
case "zh": return "chi";
|
||||
case "cu": return "chu";
|
||||
case "cv": return "chv";
|
||||
case "kw": return "cor";
|
||||
case "co": return "cos";
|
||||
case "cr": return "cre";
|
||||
case "cy": return "wel";
|
||||
case "da": return "dan";
|
||||
case "de": return "ger";
|
||||
case "dv": return "div";
|
||||
case "nl": return "dut";
|
||||
case "dz": return "dzo";
|
||||
case "el": return "gre";
|
||||
case "en": return "eng";
|
||||
case "eo": return "epo";
|
||||
case "et": return "est";
|
||||
case "ee": return "ewe";
|
||||
case "fo": return "fao";
|
||||
case "fa": return "per";
|
||||
case "fj": return "fij";
|
||||
case "fi": return "fin";
|
||||
case "fr": return "fre";
|
||||
case "fy": return "fry";
|
||||
case "ff": return "ful";
|
||||
case "ka": return "geo";
|
||||
case "gd": return "gla";
|
||||
case "ga": return "gle";
|
||||
case "gl": return "glg";
|
||||
case "gv": return "glv";
|
||||
case "gn": return "grn";
|
||||
case "gu": return "guj";
|
||||
case "ht": return "hat";
|
||||
case "ha": return "hau";
|
||||
case "he": return "heb";
|
||||
case "hz": return "her";
|
||||
case "hi": return "hin";
|
||||
case "ho": return "hmo";
|
||||
case "hr": return "hrv";
|
||||
case "hu": return "hun";
|
||||
case "ig": return "ibo";
|
||||
case "is": return "ice";
|
||||
case "io": return "ido";
|
||||
case "ii": return "iii";
|
||||
case "iu": return "iku";
|
||||
case "ie": return "ile";
|
||||
case "ia": return "ina";
|
||||
case "id": return "ind";
|
||||
case "ik": return "ipk";
|
||||
case "it": return "ita";
|
||||
case "jv": return "jav";
|
||||
case "ja": return "jpn";
|
||||
case "kl": return "kal";
|
||||
case "kn": return "kan";
|
||||
case "ks": return "kas";
|
||||
case "kr": return "kau";
|
||||
case "kk": return "kaz";
|
||||
case "km": return "khm";
|
||||
case "ki": return "kik";
|
||||
case "rw": return "kin";
|
||||
case "ky": return "kir";
|
||||
case "kv": return "kom";
|
||||
case "kg": return "kon";
|
||||
case "ko": return "kor";
|
||||
case "kj": return "kua";
|
||||
case "ku": return "kur";
|
||||
case "lo": return "lao";
|
||||
case "la": return "lat";
|
||||
case "lv": return "lav";
|
||||
case "li": return "lim";
|
||||
case "ln": return "lin";
|
||||
case "lt": return "lit";
|
||||
case "lb": return "ltz";
|
||||
case "lu": return "lub";
|
||||
case "lg": return "lug";
|
||||
case "mk": return "mac";
|
||||
case "mh": return "mah";
|
||||
case "ml": return "mal";
|
||||
case "mi": return "mao";
|
||||
case "mr": return "mar";
|
||||
case "ms": return "may";
|
||||
case "mg": return "mlg";
|
||||
case "mt": return "mlt";
|
||||
case "mn": return "mon";
|
||||
case "na": return "nau";
|
||||
case "nv": return "nav";
|
||||
case "nr": return "nbl";
|
||||
case "nd": return "nde";
|
||||
case "ng": return "ndo";
|
||||
case "ne": return "nep";
|
||||
case "nn": return "nno";
|
||||
case "nb": return "nob";
|
||||
case "no": return "nor";
|
||||
case "ny": return "nya";
|
||||
case "oc": return "oci";
|
||||
case "oj": return "oji";
|
||||
case "or": return "ori";
|
||||
case "om": return "orm";
|
||||
case "os": return "oss";
|
||||
case "pa": return "pan";
|
||||
case "pi": return "pli";
|
||||
case "pl": return "pol";
|
||||
case "pt": return "por";
|
||||
case "ps": return "pus";
|
||||
case "qu": return "que";
|
||||
case "rm": return "roh";
|
||||
case "ro": return "rum";
|
||||
case "rn": return "run";
|
||||
case "ru": return "rus";
|
||||
case "sg": return "sag";
|
||||
case "sa": return "san";
|
||||
case "si": return "sin";
|
||||
case "sk": return "slo";
|
||||
case "sl": return "slv";
|
||||
case "se": return "sme";
|
||||
case "sm": return "smo";
|
||||
case "sn": return "sna";
|
||||
case "sd": return "snd";
|
||||
case "so": return "som";
|
||||
case "st": return "sot";
|
||||
case "es": return "spa";
|
||||
case "sc": return "srd";
|
||||
case "sr": return "srp";
|
||||
case "ss": return "ssw";
|
||||
case "su": return "sun";
|
||||
case "sw": return "swa";
|
||||
case "sv": return "swe";
|
||||
case "ty": return "tah";
|
||||
case "ta": return "tam";
|
||||
case "tt": return "tat";
|
||||
case "te": return "tel";
|
||||
case "tg": return "tgk";
|
||||
case "tl": return "tgl";
|
||||
case "th": return "tha";
|
||||
case "ti": return "tir";
|
||||
case "to": return "ton";
|
||||
case "tn": return "tsn";
|
||||
case "ts": return "tso";
|
||||
case "tk": return "tuk";
|
||||
case "tr": return "tur";
|
||||
case "tw": return "twi";
|
||||
case "ug": return "uig";
|
||||
case "uk": return "ukr";
|
||||
case "ur": return "urd";
|
||||
case "uz": return "uzb";
|
||||
case "ve": return "ven";
|
||||
case "vi": return "vie";
|
||||
case "vo": return "vol";
|
||||
case "wa": return "wln";
|
||||
case "wo": return "wol";
|
||||
case "xh": return "xho";
|
||||
case "yi": return "yid";
|
||||
case "yo": return "yor";
|
||||
case "za": return "zha";
|
||||
case "zu": return "zul";
|
||||
default: return twoLetter639_1;
|
||||
case "aa": return ("aar", null);
|
||||
case "ab": return ("abk", null);
|
||||
case "af": return ("afr", null);
|
||||
case "ak": return ("aka", null);
|
||||
case "sq": return ("alb", "sqi");
|
||||
case "am": return ("amh", null);
|
||||
case "ar": return ("ara", null);
|
||||
case "an": return ("arg", null);
|
||||
case "hy": return ("arm", "hye");
|
||||
case "as": return ("asm", null);
|
||||
case "av": return ("ava", null);
|
||||
case "ae": return ("ave", null);
|
||||
case "ay": return ("aym", null);
|
||||
case "az": return ("aze", null);
|
||||
case "ba": return ("bak", null);
|
||||
case "bm": return ("bam", null);
|
||||
case "eu": return ("baq", "eus");
|
||||
case "be": return ("bel", null);
|
||||
case "bn": return ("ben", null);
|
||||
case "bi": return ("bis", null);
|
||||
case "bo": return ("tib", "bod");
|
||||
case "bs": return ("bos", null);
|
||||
case "br": return ("bre", null);
|
||||
case "bg": return ("bul", null);
|
||||
case "my": return ("bur", "mya");
|
||||
case "ca": return ("cat", null);
|
||||
case "cs": return ("cze", "ces");
|
||||
case "ch": return ("cha", null);
|
||||
case "ce": return ("che", null);
|
||||
case "zh": return ("chi", "zho");
|
||||
case "cu": return ("chu", null);
|
||||
case "cv": return ("chv", null);
|
||||
case "kw": return ("cor", null);
|
||||
case "co": return ("cos", null);
|
||||
case "cr": return ("cre", null);
|
||||
case "cy": return ("wel", "cym");
|
||||
case "da": return ("dan", null);
|
||||
case "de": return ("ger", "deu");
|
||||
case "dv": return ("div", null);
|
||||
case "nl": return ("dut", "nld");
|
||||
case "dz": return ("dzo", null);
|
||||
case "el": return ("gre", "ell");
|
||||
case "en": return ("eng", null);
|
||||
case "eo": return ("epo", null);
|
||||
case "et": return ("est", null);
|
||||
case "ee": return ("ewe", null);
|
||||
case "fo": return ("fao", null);
|
||||
case "fa": return ("per", "fas");
|
||||
case "fj": return ("fij", null);
|
||||
case "fi": return ("fin", null);
|
||||
case "fr": return ("fre", "fra");
|
||||
case "fy": return ("fry", null);
|
||||
case "ff": return ("ful", null);
|
||||
case "ka": return ("geo", "kat");
|
||||
case "gd": return ("gla", null);
|
||||
case "ga": return ("gle", null);
|
||||
case "gl": return ("glg", null);
|
||||
case "gv": return ("glv", null);
|
||||
case "gn": return ("grn", null);
|
||||
case "gu": return ("guj", null);
|
||||
case "ht": return ("hat", null);
|
||||
case "ha": return ("hau", null);
|
||||
case "he": return ("heb", null);
|
||||
case "hz": return ("her", null);
|
||||
case "hi": return ("hin", null);
|
||||
case "ho": return ("hmo", null);
|
||||
case "hr": return ("hrv", null);
|
||||
case "hu": return ("hun", null);
|
||||
case "ig": return ("ibo", null);
|
||||
case "is": return ("ice", "isl");
|
||||
case "io": return ("ido", null);
|
||||
case "ii": return ("iii", null);
|
||||
case "iu": return ("iku", null);
|
||||
case "ie": return ("ile", null);
|
||||
case "ia": return ("ina", null);
|
||||
case "id": return ("ind", null);
|
||||
case "ik": return ("ipk", null);
|
||||
case "it": return ("ita", null);
|
||||
case "jv": return ("jav", null);
|
||||
case "ja": return ("jpn", null);
|
||||
case "kl": return ("kal", null);
|
||||
case "kn": return ("kan", null);
|
||||
case "ks": return ("kas", null);
|
||||
case "kr": return ("kau", null);
|
||||
case "kk": return ("kaz", null);
|
||||
case "km": return ("khm", null);
|
||||
case "ki": return ("kik", null);
|
||||
case "rw": return ("kin", null);
|
||||
case "ky": return ("kir", null);
|
||||
case "kv": return ("kom", null);
|
||||
case "kg": return ("kon", null);
|
||||
case "ko": return ("kor", null);
|
||||
case "kj": return ("kua", null);
|
||||
case "ku": return ("kur", null);
|
||||
case "lo": return ("lao", null);
|
||||
case "la": return ("lat", null);
|
||||
case "lv": return ("lav", null);
|
||||
case "li": return ("lim", null);
|
||||
case "ln": return ("lin", null);
|
||||
case "lt": return ("lit", null);
|
||||
case "lb": return ("ltz", null);
|
||||
case "lu": return ("lub", null);
|
||||
case "lg": return ("lug", null);
|
||||
case "mk": return ("mac", "mkd");
|
||||
case "mh": return ("mah", null);
|
||||
case "ml": return ("mal", null);
|
||||
case "mi": return ("mao", "mri");
|
||||
case "mr": return ("mar", null);
|
||||
case "ms": return ("may", "msa");
|
||||
case "mg": return ("mlg", null);
|
||||
case "mt": return ("mlt", null);
|
||||
case "mn": return ("mon", null);
|
||||
case "na": return ("nau", null);
|
||||
case "nv": return ("nav", null);
|
||||
case "nr": return ("nbl", null);
|
||||
case "nd": return ("nde", null);
|
||||
case "ng": return ("ndo", null);
|
||||
case "ne": return ("nep", null);
|
||||
case "nn": return ("nno", null);
|
||||
case "nb": return ("nob", null);
|
||||
case "no": return ("nor", null);
|
||||
case "ny": return ("nya", null);
|
||||
case "oc": return ("oci", null);
|
||||
case "oj": return ("oji", null);
|
||||
case "or": return ("ori", null);
|
||||
case "om": return ("orm", null);
|
||||
case "os": return ("oss", null);
|
||||
case "pa": return ("pan", null);
|
||||
case "pi": return ("pli", null);
|
||||
case "pl": return ("pol", null);
|
||||
case "pt": return ("por", null);
|
||||
case "ps": return ("pus", null);
|
||||
case "qu": return ("que", null);
|
||||
case "rm": return ("roh", null);
|
||||
case "ro": return ("rum", "ron");
|
||||
case "rn": return ("run", null);
|
||||
case "ru": return ("rus", null);
|
||||
case "sg": return ("sag", null);
|
||||
case "sa": return ("san", null);
|
||||
case "si": return ("sin", null);
|
||||
case "sk": return ("slo", "slk");
|
||||
case "sl": return ("slv", null);
|
||||
case "se": return ("sme", null);
|
||||
case "sm": return ("smo", null);
|
||||
case "sn": return ("sna", null);
|
||||
case "sd": return ("snd", null);
|
||||
case "so": return ("som", null);
|
||||
case "st": return ("sot", null);
|
||||
case "es": return ("spa", null);
|
||||
case "sc": return ("srd", null);
|
||||
case "sr": return ("srp", null);
|
||||
case "ss": return ("ssw", null);
|
||||
case "su": return ("sun", null);
|
||||
case "sw": return ("swa", null);
|
||||
case "sv": return ("swe", null);
|
||||
case "ty": return ("tah", null);
|
||||
case "ta": return ("tam", null);
|
||||
case "tt": return ("tat", null);
|
||||
case "te": return ("tel", null);
|
||||
case "tg": return ("tgk", null);
|
||||
case "tl": return ("tgl", null);
|
||||
case "th": return ("tha", null);
|
||||
case "ti": return ("tir", null);
|
||||
case "to": return ("ton", null);
|
||||
case "tn": return ("tsn", null);
|
||||
case "ts": return ("tso", null);
|
||||
case "tk": return ("tuk", null);
|
||||
case "tr": return ("tur", null);
|
||||
case "tw": return ("twi", null);
|
||||
case "ug": return ("uig", null);
|
||||
case "uk": return ("ukr", null);
|
||||
case "ur": return ("urd", null);
|
||||
case "uz": return ("uzb", null);
|
||||
case "ve": return ("ven", null);
|
||||
case "vi": return ("vie", null);
|
||||
case "vo": return ("vol", null);
|
||||
case "wa": return ("wln", null);
|
||||
case "wo": return ("wol", null);
|
||||
case "xh": return ("xho", null);
|
||||
case "yi": return ("yid", null);
|
||||
case "yo": return ("yor", null);
|
||||
case "za": return ("zha", null);
|
||||
case "zu": return ("zul", null);
|
||||
default: return (twoLetter639_1, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CinemaJellyfin;
|
||||
using CinemaLib.API;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -206,7 +207,7 @@ static class CinemaQueryExtensions
|
||||
{
|
||||
// HACK: We use RegisterItem that is volatile intead of CreateItem
|
||||
//_libraryManager.CreateItem(item, parentFolder);
|
||||
CinemaHost.LibraryManager.RegisterItem(item);
|
||||
CinemaGlobals.LibraryManager.RegisterItem(item);
|
||||
|
||||
if (media.cast != null && media.cast.Count > 0)
|
||||
{
|
||||
@@ -264,9 +265,9 @@ static class CinemaQueryExtensions
|
||||
InfoLabelI18n? preferred = null;
|
||||
InfoLabelI18n? fallback = null;
|
||||
foreach (InfoLabelI18n i in media.i18n_info_labels)
|
||||
if (i.lang == CinemaHost.PreferredCulture)
|
||||
if (i.lang == CinemaGlobals.PreferredCulture)
|
||||
preferred = i;
|
||||
else if (i.lang == CinemaHost.FallbackCulture)
|
||||
else if (i.lang == CinemaGlobals.FallbackCulture)
|
||||
fallback = i;
|
||||
else if (first == null)
|
||||
first = i;
|
||||
@@ -280,7 +281,7 @@ static class CinemaQueryExtensions
|
||||
internal static Guid GetMediaItemId(string csPrimaryId, string? csVersionId)
|
||||
{
|
||||
string idS = csVersionId == null ? csPrimaryId : (csPrimaryId + VersionSeparator + csVersionId);
|
||||
return CinemaHost.LibraryManager.GetNewItemId(idS, typeof(CinemaPlugin));
|
||||
return CinemaGlobals.LibraryManager.GetNewItemId(idS, typeof(CinemaPlugin));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -290,7 +291,7 @@ static class CinemaQueryExtensions
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
Guid id = GetMediaItemId(csPrimaryId, csVersionId);
|
||||
T? item = CinemaHost.LibraryManager.GetItemById(id) as T;
|
||||
T? item = CinemaGlobals.LibraryManager.GetItemById(id) as T;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
|
||||
@@ -75,13 +75,16 @@ public class CinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
{
|
||||
ControllerModel? mediaInfo = application.Controllers.Where(x => x.ControllerName == "MediaInfo").FirstOrDefault();
|
||||
ControllerModel? dynamicHls = application.Controllers.Where(x => x.ControllerName == "DynamicHls").FirstOrDefault();
|
||||
ActionModel? getPostedPlaybackInfo, getMasterHlsVideoPlaylist, getVariantHlsVideoPlaylist, getHlsVideoSegment;
|
||||
ControllerModel? subtitle = application.Controllers.Where(x => x.ControllerName == "Subtitle").FirstOrDefault();
|
||||
ActionModel? getPostedPlaybackInfo, getMasterHlsVideoPlaylist, getVariantHlsVideoPlaylist, getHlsVideoSegment, getSubtitleWithTicks;
|
||||
if (mediaInfo == null
|
||||
|| (getPostedPlaybackInfo = mediaInfo.Actions.Where(x => x.ActionName == "GetPostedPlaybackInfo").FirstOrDefault()) == null
|
||||
|| dynamicHls == null
|
||||
|| (getMasterHlsVideoPlaylist = dynamicHls.Actions.Where(x => x.ActionName == "GetMasterHlsVideoPlaylist").FirstOrDefault()) == null
|
||||
|| (getVariantHlsVideoPlaylist = dynamicHls.Actions.Where(x => x.ActionName == "GetVariantHlsVideoPlaylist").FirstOrDefault()) == null
|
||||
|| (getHlsVideoSegment = dynamicHls.Actions.Where(x => x.ActionName == "GetHlsVideoSegment").FirstOrDefault()) == null)
|
||||
|| (getHlsVideoSegment = dynamicHls.Actions.Where(x => x.ActionName == "GetHlsVideoSegment").FirstOrDefault()) == null
|
||||
|| subtitle == null
|
||||
|| (getSubtitleWithTicks = subtitle.Actions.Where(x => x.ActionName == "GetSubtitleWithTicks").FirstOrDefault()) == null)
|
||||
throw new InvalidOperationException("Failed to register for MediaSourceId extraction from the Jellyfin's MediaInfo controller.");
|
||||
|
||||
getPostedPlaybackInfo.Filters.Add(new GetPostedPlaybackInfoMediaSourceIdFilter());
|
||||
@@ -90,6 +93,8 @@ public class CinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
getMasterHlsVideoPlaylist.Filters.Add(new DynamicHlsMediaSourceIdFilter());
|
||||
getVariantHlsVideoPlaylist.Filters.Add(new DynamicHlsMediaSourceIdFilter());
|
||||
getHlsVideoSegment.Filters.Add(new DynamicHlsMediaSourceIdFilter());
|
||||
// Slightly different parameter name
|
||||
getSubtitleWithTicks.Filters.Add(new SubtitleMediaSourceIdFilter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +109,9 @@ public class CinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
// Fallback to the obsolete query argument
|
||||
if (!context.ActionArguments.TryGetValue("mediaSourceId", out object? mediaSourceIdO)
|
||||
|| (mediaSourceId = mediaSourceIdO as string) == null)
|
||||
throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's action MediaInfo.GetPostedPlaybackInfo.");
|
||||
// It is optional in certain situations
|
||||
return;
|
||||
//throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's action MediaInfo.GetPostedPlaybackInfo.");
|
||||
}
|
||||
|
||||
context.HttpContext.Items.Add(CinemaMediaSourceManager.ContextItemsMediaSourceIdKey, mediaSourceId);
|
||||
@@ -131,4 +138,21 @@ public class CinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SubtitleMediaSourceIdFilter : IActionFilter
|
||||
{
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
string? mediaSourceId;
|
||||
if (!context.ActionArguments.TryGetValue("routeMediaSourceId", out object? mediaSourceIdO)
|
||||
|| (mediaSourceId = mediaSourceIdO as string) == null)
|
||||
throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's controller Subtitle.");
|
||||
|
||||
context.HttpContext.Items.Add(CinemaMediaSourceManager.ContextItemsMediaSourceIdKey, mediaSourceId);
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user