Movie media sources in progress
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
public interface IStreamCinemaInnerMediaSourceManager {
|
||||
Type InnerType { get; }
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
{
|
||||
private const string PreferredCulture = "cs";
|
||||
private const string FallbackCulture = "en";
|
||||
private const char VersionSeparator = '-';
|
||||
|
||||
/// <summary>
|
||||
/// Filtering folders can never be deleted.
|
||||
@@ -86,15 +87,18 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
// Filtered content items
|
||||
FilterSortBy sortBy;
|
||||
ItemOrder sortDir;
|
||||
if (query.OrderBy.Count == 0) {
|
||||
if (query.OrderBy.Count == 0)
|
||||
{
|
||||
sortBy = FilterSortBy.Title;
|
||||
sortDir = ItemOrder.Ascending;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
(ItemSortBy sortByJ, SortOrder sortDirJ) = query.OrderBy.First();
|
||||
sortBy = ConvertSort(sortByJ);
|
||||
sortDir = sortDirJ == SortOrder.Ascending ? ItemOrder.Ascending : ItemOrder.Descending;
|
||||
}
|
||||
|
||||
|
||||
FilterResponse? filterRes = Metadata.SearchAsync(query.SearchTerm ?? "", order: sortDir, sort: sortBy, type: ItemType, offset: offset, limit: limit).GetAwaiter().GetResult();
|
||||
if (filterRes != null && filterRes.hits != null && filterRes.hits.hits != null)
|
||||
{
|
||||
@@ -112,7 +116,8 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
|
||||
private static FilterSortBy ConvertSort(ItemSortBy sortByJ)
|
||||
{
|
||||
switch (sortByJ) {
|
||||
switch (sortByJ)
|
||||
{
|
||||
default:
|
||||
case ItemSortBy.Default: return FilterSortBy.Title;
|
||||
case ItemSortBy.AiredEpisodeOrder: return FilterSortBy.Premiered;
|
||||
@@ -250,27 +255,27 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
|
||||
bool singleLevel = res.hits.hits[0]._source?.children_count == 0;
|
||||
if (isAudio)
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.Audio.MusicAlbum>(csId, out isNew);
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.Audio.MusicAlbum>(csId, null, out isNew);
|
||||
else if (singleLevel)
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Season>(csId, out isNew);
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Season>(csId, null, out isNew);
|
||||
else
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Series>(csId, out isNew);
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Series>(csId, null, out isNew);
|
||||
|
||||
}
|
||||
else if (isAudio)
|
||||
{
|
||||
// TODO determine if this is an AudioBook od Audio
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.Audio.Audio>(csId, out isNew);
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.Audio.Audio>(csId, null, out isNew);
|
||||
|
||||
}
|
||||
else if (media.info_labels?.mediatype == "tvshow")
|
||||
{
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Episode>(csId, out isNew);
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.TV.Episode>(csId, null, out isNew);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
item = GetMediaItemById<MediaBrowser.Controller.Entities.Movies.Movie>(csId, out isNew);
|
||||
item = GetMediaItemById<StreamCinemaMovie>(csId, null, out isNew);
|
||||
}
|
||||
|
||||
if (isNew && media.info_labels != null)
|
||||
@@ -291,7 +296,9 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
//item.ParentIndexNumber = info.ParentIndexNumber;
|
||||
item.PremiereDate = media.info_labels?.premiered;
|
||||
item.ProductionYear = media.info_labels?.year;
|
||||
item.ProviderIds = media.services != null ? ConvertProviderIds(media.services) : null;
|
||||
item.ProviderIds = new Dictionary<string, string>();
|
||||
if (media.services != null)
|
||||
ConvertProviderIds(media.services, item.ProviderIds);
|
||||
//item.OfficialRating = info.OfficialRating;
|
||||
item.DateCreated = media.info_labels?.dateadded ?? DateTime.UtcNow;
|
||||
//item.Tags = info.Tags.ToArray();
|
||||
@@ -308,6 +315,9 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
}
|
||||
item.SetImagePath(ImageType.Primary, artUri.ToString());
|
||||
}
|
||||
|
||||
// Identify the item as it originates from StreamCinema
|
||||
item.ProviderIds.Add(StreamCinemaPlugin.StreamCinemaProviderName, "");
|
||||
}
|
||||
|
||||
if (item is IHasArtist hasArtists)
|
||||
@@ -412,16 +422,14 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
return preferred ?? fallback ?? first;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ConvertProviderIds(ServicesIds services)
|
||||
private static void ConvertProviderIds(ServicesIds services, Dictionary<string, string> dest)
|
||||
{
|
||||
Dictionary<string, string> result = new Dictionary<string, string>();
|
||||
if (services.imdb != null)
|
||||
result.Add("Imdb", services.imdb);
|
||||
dest.Add("Imdb", services.imdb);
|
||||
if (services.tvdb != null)
|
||||
result.Add("Tvdb", services.tvdb);
|
||||
dest.Add("Tvdb", services.tvdb);
|
||||
if (services.tmdb != null)
|
||||
result.Add("Tmdb", services.tmdb);
|
||||
return result;
|
||||
dest.Add("Tmdb", services.tmdb);
|
||||
|
||||
//public string? csfd { get; set; }
|
||||
//public string? trakt { get; set; }
|
||||
@@ -434,10 +442,21 @@ public abstract class StreamCinemaFilterFolder : Folder
|
||||
return System.IO.Path.Combine(basePath, "streamcinema", id.ToString("N", CultureInfo.InvariantCulture), "metadata");
|
||||
}
|
||||
|
||||
private T GetMediaItemById<T>(string idString, out bool isNew)
|
||||
where T : BaseItem, new()
|
||||
/// <summary>
|
||||
/// Calculates Jellyfin item identifier from CinemaStream identifier, optionally also with version identifier.
|
||||
/// </summary>
|
||||
internal static Guid GetMediaItemId(string csPrimaryId, string? csVersionId) {
|
||||
string idS = csVersionId == null ? csPrimaryId : (csPrimaryId + VersionSeparator + csVersionId);
|
||||
return StreamCinemaHost.LibraryManager.GetNewItemId(idS, typeof(StreamCinemaPlugin));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new media item (non-folder) with a proper unique identifier.
|
||||
/// </summary>
|
||||
internal static T GetMediaItemById<T>(string csPrimaryId, string? csVersionId, out bool isNew)
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
Guid id = StreamCinemaHost.LibraryManager.GetNewItemId(idString, typeof(StreamCinemaPlugin));
|
||||
Guid id = GetMediaItemId(csPrimaryId, csVersionId);
|
||||
T? item = StreamCinemaHost.LibraryManager.GetItemById(id) as T;
|
||||
|
||||
if (item == null)
|
||||
|
||||
12
StreamCinemaJellyfin/StreamCinemaInnerMediaSourceManager.cs
Normal file
12
StreamCinemaJellyfin/StreamCinemaInnerMediaSourceManager.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
class StreamCinemaInnerMediaSourceManager : IStreamCinemaInnerMediaSourceManager
|
||||
{
|
||||
private readonly Type _innerType;
|
||||
|
||||
public StreamCinemaInnerMediaSourceManager(Type innerType) {
|
||||
this._innerType = innerType;
|
||||
}
|
||||
|
||||
public Type InnerType => _innerType;
|
||||
}
|
||||
417
StreamCinemaJellyfin/StreamCinemaMediaSourceManager.cs
Normal file
417
StreamCinemaJellyfin/StreamCinemaMediaSourceManager.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
#pragma warning disable CA1002
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using StreamCinemaLib.API;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
public class StreamCinemaMediaSourceManager : IMediaSourceManager
|
||||
{
|
||||
private static readonly TimeSpan VersionValidityTimeout = TimeSpan.FromMinutes(60);
|
||||
|
||||
private static StreamCinemaMediaSourceManager? _instance;
|
||||
|
||||
private readonly IMediaSourceManager _inner;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ConcurrentDictionary<Guid, VersionSetEntry> _videoVersions;
|
||||
|
||||
public StreamCinemaMediaSourceManager(IStreamCinemaInnerMediaSourceManager innerMediaSourceManager, ILibraryManager libraryManager, IServiceProvider svc)
|
||||
{
|
||||
if (innerMediaSourceManager == null || svc == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
IMediaSourceManager? inner = svc.GetService(innerMediaSourceManager.InnerType) as IMediaSourceManager;
|
||||
if (inner == null)
|
||||
throw new InvalidOperationException("Original MediaSourceManager service not found.");
|
||||
this._inner = inner;
|
||||
|
||||
this._libraryManager = libraryManager;
|
||||
this._videoVersions = new ConcurrentDictionary<Guid, VersionSetEntry>();
|
||||
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
internal static bool TryGetInstance([NotNullWhen(true)] out StreamCinemaMediaSourceManager? instance)
|
||||
{
|
||||
instance = _instance;
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
#region IMediaSourceManager Members
|
||||
|
||||
public Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, addProbeDelay, isLiveStream, cancellationToken);
|
||||
}
|
||||
|
||||
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
|
||||
{
|
||||
_inner.AddParts(providers);
|
||||
}
|
||||
|
||||
public Task CloseLiveStream(string id)
|
||||
{
|
||||
return _inner.CloseLiveStream(id);
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetLiveStream(id, cancellationToken);
|
||||
}
|
||||
|
||||
public ILiveStream GetLiveStreamInfo(string id)
|
||||
{
|
||||
return _inner.GetLiveStreamInfo(id);
|
||||
}
|
||||
|
||||
public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
|
||||
{
|
||||
return _inner.GetLiveStreamInfoByUniqueId(uniqueId);
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetLiveStreamMediaInfo(id, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetLiveStreamWithDirectStreamProvider(id, cancellationToken);
|
||||
}
|
||||
|
||||
public List<MediaAttachment> GetMediaAttachments(Guid itemId)
|
||||
{
|
||||
return _inner.GetMediaAttachments(itemId);
|
||||
}
|
||||
|
||||
public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
|
||||
{
|
||||
return _inner.GetMediaAttachments(query);
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetMediaSource(item, mediaSourceId, liveStreamId, enablePathSubstitution, cancellationToken);
|
||||
}
|
||||
|
||||
public List<MediaStream> GetMediaStreams(Guid itemId)
|
||||
{
|
||||
// Intercept for StreamCinemaItems
|
||||
Video? item = _libraryManager.GetItemById<Video>(itemId);
|
||||
if (item == null
|
||||
|| item.ProviderIds == null
|
||||
|| !item.ProviderIds.ContainsKey(StreamCinemaPlugin.StreamCinemaProviderName)
|
||||
|| string.IsNullOrEmpty(item.ExternalId))
|
||||
return _inner.GetMediaStreams(itemId);
|
||||
|
||||
StreamCinemaLib.API.Stream? stream = GetVersion(item, default).GetAwaiter().GetResult();
|
||||
|
||||
List<MediaStream> result = new List<MediaStream>();
|
||||
if (stream != null)
|
||||
{
|
||||
// HACK: Those values are read after we return (not before)
|
||||
item.Size = stream.size;
|
||||
//stream_id;
|
||||
//stream.name;
|
||||
//stream.provider
|
||||
//stream.ident;
|
||||
//stream.media
|
||||
//stream.date_added
|
||||
|
||||
if (stream.video != null)
|
||||
{
|
||||
foreach (StreamVideo j in stream.video)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Type = MediaStreamType.Video;
|
||||
a.Width = j.width;
|
||||
a.Height = j.height;
|
||||
a.Codec = j.codec;
|
||||
a.AspectRatio = j.aspect.ToString();
|
||||
ConvertHdr(j.hasHdr, j.hdrQuality, a);
|
||||
|
||||
// HACK: Those values are read after we return (not before)
|
||||
item.RunTimeTicks = (long?)(j.duration * TimeSpan.TicksPerSecond);
|
||||
if (j.is3d)
|
||||
// Just a probability guess
|
||||
item.Video3DFormat = Video3DFormat.HalfSideBySide;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.audio != null)
|
||||
{
|
||||
foreach (StreamAudio j in stream.audio)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Type = MediaStreamType.Audio;
|
||||
a.Language = j.language;
|
||||
a.Codec = j.codec;
|
||||
a.Channels = j.channels;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.subtitles != null)
|
||||
{
|
||||
foreach (StreamSubtitle j in stream.subtitles)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Type = MediaStreamType.Subtitle;
|
||||
a.Language = j.language;
|
||||
a.IsForced = j.forced;
|
||||
a.Path = j.src;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<MediaStream> GetMediaStreams(MediaStreamQuery query)
|
||||
{
|
||||
return _inner.GetMediaStreams(query);
|
||||
}
|
||||
|
||||
public MediaProtocol GetPathProtocol(string path)
|
||||
{
|
||||
return _inner.GetPathProtocol(path);
|
||||
}
|
||||
|
||||
public Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetPlaybackMediaSources(item, user, allowMediaProbe, enablePathSubstitution, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.GetRecordingStreamMediaSources(info, cancellationToken);
|
||||
}
|
||||
|
||||
public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User? user = null)
|
||||
{
|
||||
return _inner.GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||
}
|
||||
|
||||
public Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.OpenLiveStream(request, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return _inner.OpenLiveStreamInternal(request, cancellationToken);
|
||||
}
|
||||
|
||||
public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user)
|
||||
{
|
||||
_inner.SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
|
||||
}
|
||||
|
||||
public bool SupportsDirectStream(string path, MediaProtocol protocol)
|
||||
{
|
||||
return _inner.SupportsDirectStream(path, protocol);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream metadata for a video version (ie. for Movie or Episode).
|
||||
/// </summary>
|
||||
internal async Task<IEnumerable<T>?> GetVideoVersions<T>(T item, CancellationToken cancel)
|
||||
where T : Video, new()
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
VersionSetEntry ver = await GetVersionSet(item, out BaseItem? primary, cancel);
|
||||
|
||||
return ver.Streams == null ? null : GetVideoVersionsEnumerate(item, primary!, ver.Streams);
|
||||
}
|
||||
|
||||
private IEnumerable<T> GetVideoVersionsEnumerate<T>(T item, BaseItem primary, StreamCinemaLib.API.Stream[] versions)
|
||||
where T : Video, new()
|
||||
{
|
||||
bool isFirst = true;
|
||||
foreach (var i in versions)
|
||||
{
|
||||
T a;
|
||||
if (isFirst)
|
||||
{
|
||||
// The primary item represents the first version
|
||||
a = (T)primary;
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
a = StreamCinemaFilterFolder.GetMediaItemById<T>(item.ExternalId, i._id, out bool isNew);
|
||||
if (isNew)
|
||||
{
|
||||
// Copy properties from the parent version
|
||||
// Note: non-primary versions are never persisted so a new volatile instance got created
|
||||
Guid aId = a.Id;
|
||||
item.DeepCopy(a);
|
||||
a.Id = aId;
|
||||
a.SetPrimaryVersionId(item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
// Just a volatile item
|
||||
_libraryManager.RegisterItem(a);
|
||||
}
|
||||
}
|
||||
yield return a;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a version set for a video.
|
||||
/// </summary>
|
||||
private Task<VersionSetEntry> GetVersionSet(Video item, out BaseItem? primary, CancellationToken cancel)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (item.ProviderIds == null
|
||||
|| !item.ProviderIds.ContainsKey(StreamCinemaPlugin.StreamCinemaProviderName)
|
||||
|| string.IsNullOrEmpty(item.ExternalId))
|
||||
{
|
||||
// Not a Stream Cinema video
|
||||
primary = null;
|
||||
return Task.FromResult<VersionSetEntry>(default);
|
||||
}
|
||||
|
||||
Guid primaryId;
|
||||
string csId;
|
||||
if (!string.IsNullOrEmpty(item.PrimaryVersionId))
|
||||
{
|
||||
// Move to the primary item
|
||||
primaryId = Guid.Parse(item.PrimaryVersionId, CultureInfo.InvariantCulture);
|
||||
primary = _libraryManager.GetItemById(primaryId);
|
||||
if (primary == null
|
||||
|| primary.ProviderIds == null
|
||||
|| !primary.ProviderIds.ContainsKey(StreamCinemaPlugin.StreamCinemaProviderName)
|
||||
|| string.IsNullOrEmpty(primary.ExternalId))
|
||||
// Not a Stream Cinema video
|
||||
return Task.FromResult<VersionSetEntry>(default);
|
||||
|
||||
csId = primary.ExternalId;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
primary = item;
|
||||
primaryId = item.Id;
|
||||
csId = item.ExternalId;
|
||||
}
|
||||
|
||||
return GetVersionSet(primaryId, csId, cancel);
|
||||
}
|
||||
|
||||
private async Task<VersionSetEntry> GetVersionSet(Guid primaryId, string csId, CancellationToken cancel)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
VersionSetEntry result;
|
||||
if (!_videoVersions.TryGetValue(primaryId, out result) || result.ValidUntil < now)
|
||||
{
|
||||
result.Streams = await Metadata.StreamsAsync(csId, cancel);
|
||||
result.ValidUntil = now + VersionValidityTimeout;
|
||||
_videoVersions[primaryId] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private async Task<StreamCinemaLib.API.Stream?> GetVersion(Video item, CancellationToken cancel)
|
||||
{
|
||||
VersionSetEntry vers = await GetVersionSet(item, out BaseItem? primary, cancel);
|
||||
if (vers.Streams == null)
|
||||
return null;
|
||||
|
||||
bool isFirst = true;
|
||||
foreach (var i in vers.Streams)
|
||||
{
|
||||
Guid id;
|
||||
if (isFirst)
|
||||
{
|
||||
// The primary item represents the first version
|
||||
isFirst = false;
|
||||
id = primary!.Id;
|
||||
}
|
||||
else
|
||||
id = StreamCinemaFilterFolder.GetMediaItemId(primary!.ExternalId, i._id);
|
||||
|
||||
if (id == item.Id)
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ConvertHdr(bool isHdr, string? hdr, MediaStream dst)
|
||||
{
|
||||
if (!isHdr)
|
||||
return;
|
||||
|
||||
if (hdr != null)
|
||||
{
|
||||
// Examples:
|
||||
// Dolby Vision
|
||||
// Dolby Vision / SMPTE ST 2094 App 4
|
||||
// Dolby Vision / SMPTE ST 2086
|
||||
// SMPTE ST 2086
|
||||
string[] sections = hdr.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (sections.Length != 0)
|
||||
{
|
||||
string lastValue = sections[sections.Length - 1];
|
||||
if (lastValue != "Dolby Vision")
|
||||
{
|
||||
dst.ColorTransfer = lastValue;
|
||||
switch (lastValue)
|
||||
{
|
||||
case "SMPTE ST 2094 App 4":
|
||||
// DoVi Profile 5
|
||||
dst.DvProfile = 5;
|
||||
dst.RpuPresentFlag = 1;
|
||||
dst.BlPresentFlag = 1;
|
||||
dst.DvBlSignalCompatibilityId = 0;
|
||||
dst.CodecTag = "dovi";
|
||||
break;
|
||||
|
||||
case "SMPTE ST 2084":
|
||||
// HDR10
|
||||
dst.ColorTransfer = "smpte2084";
|
||||
break;
|
||||
|
||||
case "SMPTE ST 2086":
|
||||
// fall through for basic HDR level
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate the "lowest" HDR level
|
||||
// See function GetVideoColorRange in MediaBrowser.Model/Entities/MediaStream.cs in Jellyfin for reasons
|
||||
dst.DvProfile = 10;
|
||||
dst.RpuPresentFlag = 1;
|
||||
dst.BlPresentFlag = 1;
|
||||
dst.DvBlSignalCompatibilityId = 0;
|
||||
dst.CodecTag = "dovi";
|
||||
return;
|
||||
}
|
||||
|
||||
struct VersionSetEntry
|
||||
{
|
||||
internal DateTime ValidUntil;
|
||||
internal StreamCinemaLib.API.Stream[]? Streams;
|
||||
}
|
||||
}
|
||||
31
StreamCinemaJellyfin/StreamCinemaMediaSourceProvider.cs
Normal file
31
StreamCinemaJellyfin/StreamCinemaMediaSourceProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
using System;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using StreamCinemaLib.API;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
/// <summary>
|
||||
/// Media source provider for for Stream Cinema media items.
|
||||
/// </summary>
|
||||
public class StreamCinemaMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetMediaSourcesInternal(item, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
21
StreamCinemaJellyfin/StreamCinemaMovie.cs
Normal file
21
StreamCinemaJellyfin/StreamCinemaMovie.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
/// <summary>
|
||||
/// Movie media item from Stream Cinema.
|
||||
/// </summary>
|
||||
public class StreamCinemaMovie : Movie {
|
||||
public sealed override string GetClientTypeName() => BaseItemKind.Movie.ToString();
|
||||
|
||||
protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() {
|
||||
var result = this.VideoGetAllItemsForMediaSources();
|
||||
if (result == null)
|
||||
return base.GetAllItemsForMediaSources();
|
||||
else
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ namespace Jellyfin.Plugin.StreamCinema;
|
||||
/// </summary>
|
||||
public class StreamCinemaPlugin : BasePlugin<StreamCinemaPluginConfiguration>, IHasWebPages
|
||||
{
|
||||
internal const string StreamCinemaProviderName = "StreamCinema";
|
||||
|
||||
public StreamCinemaPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -12,10 +13,31 @@ namespace Jellyfin.Plugin.StreamCinema;
|
||||
///
|
||||
public class StreamCinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
|
||||
{
|
||||
serviceCollection.AddSingleton<IImageProvider, StreamCinemaImageProvider>();
|
||||
serviceCollection.AddHostedService<StreamCinemaHost>();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
|
||||
{
|
||||
serviceCollection.AddSingleton<IImageProvider, StreamCinemaImageProvider>();
|
||||
serviceCollection.AddHostedService<StreamCinemaHost>();
|
||||
|
||||
// HACK: Replace IMediaSourceManager as StreamCinemaMediaSourceProvider is not called soon enough while
|
||||
// entering a media item page.
|
||||
Type tIf = typeof(IMediaSourceManager);
|
||||
int count = serviceCollection.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ServiceDescriptor a = serviceCollection[i];
|
||||
if (a.ServiceType == tIf && !a.IsKeyedService)
|
||||
{
|
||||
Type oldImplType = a.ImplementationType!;
|
||||
serviceCollection.RemoveAt(i);
|
||||
serviceCollection.AddSingleton<IStreamCinemaInnerMediaSourceManager>(new StreamCinemaInnerMediaSourceManager(oldImplType));
|
||||
serviceCollection.AddSingleton(oldImplType, oldImplType);
|
||||
// Replace IMediaSourceManager but also need direct access to the StreamCinemaMediaSourceManager if it itself
|
||||
// also gets replaced in a longer chain
|
||||
serviceCollection.AddSingleton<StreamCinemaMediaSourceManager>();
|
||||
serviceCollection.AddSingleton<IMediaSourceManager>(x => x.GetRequiredService<StreamCinemaMediaSourceManager>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
StreamCinemaJellyfin/StreamCinemaVideoExtensions.cs
Normal file
36
StreamCinemaJellyfin/StreamCinemaVideoExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
static class StreamCinemaVideoExtensions
|
||||
{
|
||||
public static IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)>? VideoGetAllItemsForMediaSources<T>(this T @this)
|
||||
where T : Video, new()
|
||||
{
|
||||
// Non-primary version items use default implementation
|
||||
if (!string.IsNullOrEmpty(@this.PrimaryVersionId))
|
||||
{
|
||||
return null; // means call base.GetAllItemsForMediaSources
|
||||
}
|
||||
|
||||
// Our media source manager must be present
|
||||
StreamCinemaMediaSourceManager? msm;
|
||||
if (!StreamCinemaMediaSourceManager.TryGetInstance(out msm))
|
||||
return Enumerable.Empty<(BaseItem Item, MediaSourceType MediaSourceType)>();
|
||||
|
||||
// Generate item for each StreamCinemaLib.API.Stream. Obtaining the file sub-streams themselves
|
||||
// is intercepted in StreamCinemaMediaSourceManager.GetMediaStreams
|
||||
return GetAllItemsForMediaSourcesEnumerate(@this, msm);
|
||||
}
|
||||
|
||||
private static IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSourcesEnumerate<T>(T @this, StreamCinemaMediaSourceManager msm)
|
||||
where T : Video, new()
|
||||
{
|
||||
IEnumerable<T>? versions = msm.GetVideoVersions(@this, default).GetAwaiter().GetResult();
|
||||
if (versions != null)
|
||||
foreach (T i in versions)
|
||||
yield return (i, MediaSourceType.Grouping);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user