525 lines
18 KiB
C#
525 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Controller.Channels;
|
|
using MediaBrowser.Controller.Entities;
|
|
using MediaBrowser.Controller.Providers;
|
|
using MediaBrowser.Model.Channels;
|
|
using MediaBrowser.Model.Dto;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.LiveTv;
|
|
using MediaBrowser.Model.MediaInfo;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.Enigma2
|
|
{
|
|
public class RecordingsChannel : IChannel, IHasCacheKey, ISupportsDelete, ISupportsLatestMedia, ISupportsMediaProbe, IHasFolderAttributes
|
|
{
|
|
private readonly ILogger<LiveTvService> _logger;
|
|
|
|
public RecordingsChannel(ILoggerFactory loggerFactory)
|
|
{
|
|
_logger = loggerFactory.CreateLogger<LiveTvService>();
|
|
_logger.LogDebug("[Enigma2] RecordingsChannel()");
|
|
}
|
|
|
|
public string Name => "Enigma2 Recordings";
|
|
|
|
public string[] Attributes => new[] { "Recordings" };
|
|
|
|
public string Description => "Enigma2 Recordings";
|
|
|
|
public string DataVersion => "1";
|
|
|
|
public string HomePageUrl => null;
|
|
|
|
public ChannelParentalRating ParentalRating => ChannelParentalRating.GeneralAudience;
|
|
|
|
public string GetCacheKey(string userId)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
|
|
var values = new List<string>
|
|
{
|
|
now.DayOfYear.ToString(CultureInfo.InvariantCulture),
|
|
now.Hour.ToString(CultureInfo.InvariantCulture)
|
|
};
|
|
|
|
double minute = now.Minute;
|
|
minute /= 5;
|
|
|
|
values.Add(Math.Floor(minute).ToString(CultureInfo.InvariantCulture));
|
|
|
|
values.Add(GetService().LastRecordingChange.Ticks.ToString(CultureInfo.InvariantCulture));
|
|
|
|
return string.Join("-", values.ToArray());
|
|
}
|
|
|
|
public InternalChannelFeatures GetChannelFeatures()
|
|
{
|
|
return new InternalChannelFeatures
|
|
{
|
|
ContentTypes = new List<ChannelMediaContentType>
|
|
{
|
|
ChannelMediaContentType.Movie,
|
|
ChannelMediaContentType.Episode,
|
|
ChannelMediaContentType.Clip
|
|
},
|
|
MediaTypes = new List<ChannelMediaType>
|
|
{
|
|
ChannelMediaType.Audio,
|
|
ChannelMediaType.Video
|
|
},
|
|
SupportsContentDownloading = true
|
|
};
|
|
}
|
|
|
|
public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
|
|
{
|
|
if (type == ImageType.Primary)
|
|
{
|
|
return Task.FromResult(new DynamicImageResponse
|
|
{
|
|
Path = "https://github.com/CompoUK/MediaBrowser.Plugins.VuPlus/blob/master/src/Images/Vu+.png?raw=true",
|
|
Protocol = MediaProtocol.Http,
|
|
HasImage = true
|
|
});
|
|
}
|
|
|
|
return Task.FromResult(new DynamicImageResponse
|
|
{
|
|
HasImage = false
|
|
});
|
|
}
|
|
|
|
public IEnumerable<ImageType> GetSupportedChannelImages()
|
|
{
|
|
return new List<ImageType>
|
|
{
|
|
ImageType.Primary
|
|
};
|
|
}
|
|
|
|
public bool IsEnabledFor(string userId)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
private LiveTvService GetService()
|
|
{
|
|
return LiveTvService.Instance;
|
|
}
|
|
|
|
public bool CanDelete(BaseItem item)
|
|
{
|
|
return !item.IsFolder;
|
|
}
|
|
|
|
public Task DeleteItem(string id, CancellationToken cancellationToken)
|
|
{
|
|
return GetService().DeleteRecordingAsync(id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken)
|
|
{
|
|
var result = await GetChannelItems(new InternalChannelItemQuery(), i => true, cancellationToken).ConfigureAwait(false);
|
|
|
|
return result.Items.OrderByDescending(i => i.DateCreated ?? DateTime.MinValue);
|
|
}
|
|
|
|
public Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(query.FolderId))
|
|
{
|
|
return GetRecordingGroups(query, cancellationToken);
|
|
}
|
|
|
|
if (query.FolderId.StartsWith("series_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var hash = query.FolderId.Split('_')[1];
|
|
return GetChannelItems(query, i => i.IsSeries && string.Equals(i.Name.GetMD5().ToString("N"), hash, StringComparison.Ordinal), cancellationToken);
|
|
}
|
|
|
|
if (string.Equals(query.FolderId, "kids", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return GetChannelItems(query, i => i.IsKids, cancellationToken);
|
|
}
|
|
|
|
if (string.Equals(query.FolderId, "movies", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return GetChannelItems(query, i => i.IsMovie, cancellationToken);
|
|
}
|
|
|
|
if (string.Equals(query.FolderId, "news", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return GetChannelItems(query, i => i.IsNews, cancellationToken);
|
|
}
|
|
|
|
if (string.Equals(query.FolderId, "sports", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return GetChannelItems(query, i => i.IsSports, cancellationToken);
|
|
}
|
|
|
|
if (string.Equals(query.FolderId, "others", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return GetChannelItems(query, i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries, cancellationToken);
|
|
}
|
|
|
|
var result = new ChannelItemResult()
|
|
{
|
|
Items = new List<ChannelItemInfo>()
|
|
};
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, Func<MyRecordingInfo, bool> filter, CancellationToken cancellationToken)
|
|
{
|
|
var service = GetService();
|
|
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
return new ChannelItemResult()
|
|
{
|
|
Items = allRecordings.Where(filter).Select(ConvertToChannelItem).ToList()
|
|
};
|
|
}
|
|
|
|
private ChannelItemInfo ConvertToChannelItem(MyRecordingInfo item)
|
|
{
|
|
var path = string.IsNullOrEmpty(item.Path) ? item.Url : item.Path;
|
|
|
|
var channelItem = new ChannelItemInfo
|
|
{
|
|
Name = string.IsNullOrEmpty(item.EpisodeTitle) ? item.Name : item.EpisodeTitle,
|
|
SeriesName = !string.IsNullOrEmpty(item.EpisodeTitle) || item.IsSeries ? item.Name : null,
|
|
OfficialRating = item.OfficialRating,
|
|
CommunityRating = item.CommunityRating,
|
|
ContentType = item.IsMovie ? ChannelMediaContentType.Movie : (item.IsSeries ? ChannelMediaContentType.Episode : ChannelMediaContentType.Clip),
|
|
Genres = item.Genres,
|
|
ImageUrl = item.ImageUrl,
|
|
//HomePageUrl = item.HomePageUrl
|
|
Id = item.Id,
|
|
//IndexNumber = item.IndexNumber,
|
|
MediaType = item.ChannelType == ChannelType.TV ? ChannelMediaType.Video : ChannelMediaType.Audio,
|
|
MediaSources = new List<MediaSourceInfo>
|
|
{
|
|
new MediaSourceInfo
|
|
{
|
|
Path = path,
|
|
Protocol = path.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? MediaProtocol.Http : MediaProtocol.File,
|
|
Id = item.Id
|
|
}
|
|
},
|
|
//ParentIndexNumber = item.ParentIndexNumber,
|
|
PremiereDate = item.OriginalAirDate,
|
|
//ProductionYear = item.ProductionYear,
|
|
//Studios = item.Studios,
|
|
Type = ChannelItemType.Media,
|
|
DateModified = item.DateLastUpdated,
|
|
Overview = item.Overview,
|
|
//People = item.People
|
|
IsLiveStream = item.Status == RecordingStatus.InProgress,
|
|
Etag = item.Status.ToString()
|
|
};
|
|
|
|
return channelItem;
|
|
}
|
|
|
|
private async Task<ChannelItemResult> GetRecordingGroups(InternalChannelItemQuery query, CancellationToken cancellationToken)
|
|
{
|
|
var service = GetService();
|
|
|
|
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
var series = allRecordings
|
|
.Where(i => i.IsSeries)
|
|
.ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
|
|
|
var items = new List<ChannelItemInfo>();
|
|
|
|
items.AddRange(series.OrderBy(i => i.Key).Select(i => new ChannelItemInfo
|
|
{
|
|
Name = i.Key,
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "series_" + i.Key.GetMD5().ToString("N"),
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = i.First().ImageUrl
|
|
}));
|
|
|
|
var kids = allRecordings.FirstOrDefault(i => i.IsKids);
|
|
|
|
if (kids != null)
|
|
{
|
|
items.Add(new ChannelItemInfo
|
|
{
|
|
Name = "Kids",
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "kids",
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = kids.ImageUrl
|
|
});
|
|
}
|
|
|
|
var movies = allRecordings.FirstOrDefault(i => i.IsMovie);
|
|
if (movies != null)
|
|
{
|
|
items.Add(new ChannelItemInfo
|
|
{
|
|
Name = "Movies",
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "movies",
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = movies.ImageUrl
|
|
});
|
|
}
|
|
|
|
var news = allRecordings.FirstOrDefault(i => i.IsNews);
|
|
if (news != null)
|
|
{
|
|
items.Add(new ChannelItemInfo
|
|
{
|
|
Name = "News",
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "news",
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = news.ImageUrl
|
|
});
|
|
}
|
|
|
|
var sports = allRecordings.FirstOrDefault(i => i.IsSports);
|
|
if (sports != null)
|
|
{
|
|
items.Add(new ChannelItemInfo
|
|
{
|
|
Name = "Sports",
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "sports",
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = sports.ImageUrl
|
|
});
|
|
}
|
|
|
|
var other = allRecordings.FirstOrDefault(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries);
|
|
if (other != null)
|
|
{
|
|
items.Add(new ChannelItemInfo
|
|
{
|
|
Name = "Others",
|
|
FolderType = ChannelFolderType.Container,
|
|
Id = "others",
|
|
Type = ChannelItemType.Folder,
|
|
ImageUrl = other.ImageUrl
|
|
});
|
|
}
|
|
|
|
return new ChannelItemResult()
|
|
{
|
|
Items = items
|
|
};
|
|
}
|
|
}
|
|
public class MyRecordingInfo
|
|
{
|
|
/// <summary>
|
|
/// Id of the recording.
|
|
/// </summary>
|
|
public string Id { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the series timer identifier.
|
|
/// </summary>
|
|
/// <value>The series timer identifier.</value>
|
|
public string SeriesTimerId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the timer identifier.
|
|
/// </summary>
|
|
/// <value>The timer identifier.</value>
|
|
public string TimerId { get; set; }
|
|
|
|
/// <summary>
|
|
/// ChannelId of the recording.
|
|
/// </summary>
|
|
public string ChannelId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the type of the channel.
|
|
/// </summary>
|
|
/// <value>The type of the channel.</value>
|
|
public ChannelType ChannelType { get; set; }
|
|
|
|
/// <summary>
|
|
/// Name of the recording.
|
|
/// </summary>
|
|
public string Name { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the path.
|
|
/// </summary>
|
|
/// <value>The path.</value>
|
|
public string Path { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the URL.
|
|
/// </summary>
|
|
/// <value>The URL.</value>
|
|
public string Url { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the overview.
|
|
/// </summary>
|
|
/// <value>The overview.</value>
|
|
public string Overview { get; set; }
|
|
|
|
/// <summary>
|
|
/// The start date of the recording, in UTC.
|
|
/// </summary>
|
|
public DateTime StartDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// The end date of the recording, in UTC.
|
|
/// </summary>
|
|
public DateTime EndDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the program identifier.
|
|
/// </summary>
|
|
/// <value>The program identifier.</value>
|
|
public string ProgramId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the status.
|
|
/// </summary>
|
|
/// <value>The status.</value>
|
|
public RecordingStatus Status { get; set; }
|
|
|
|
/// <summary>
|
|
/// Genre of the program.
|
|
/// </summary>
|
|
public List<string> Genres { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is repeat.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value>
|
|
public bool IsRepeat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the episode title.
|
|
/// </summary>
|
|
/// <value>The episode title.</value>
|
|
public string EpisodeTitle { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is hd.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
|
|
public bool? IsHD { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the audio.
|
|
/// </summary>
|
|
/// <value>The audio.</value>
|
|
public ProgramAudio? Audio { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the original air date.
|
|
/// </summary>
|
|
/// <value>The original air date.</value>
|
|
public DateTime? OriginalAirDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is movie.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
|
|
public bool IsMovie { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is sports.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
|
|
public bool IsSports { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is series.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
|
|
public bool IsSeries { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is live.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
|
|
public bool IsLive { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is news.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
|
|
public bool IsNews { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is kids.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
|
|
public bool IsKids { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance is premiere.
|
|
/// </summary>
|
|
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
|
|
public bool IsPremiere { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the official rating.
|
|
/// </summary>
|
|
/// <value>The official rating.</value>
|
|
public string OfficialRating { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the community rating.
|
|
/// </summary>
|
|
/// <value>The community rating.</value>
|
|
public float? CommunityRating { get; set; }
|
|
|
|
/// <summary>
|
|
/// Supply the image path if it can be accessed directly from the file system
|
|
/// </summary>
|
|
/// <value>The image path.</value>
|
|
public string ImagePath { get; set; }
|
|
|
|
/// <summary>
|
|
/// Supply the image url if it can be downloaded
|
|
/// </summary>
|
|
/// <value>The image URL.</value>
|
|
public string ImageUrl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this instance has image.
|
|
/// </summary>
|
|
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
|
public bool? HasImage { get; set; }
|
|
/// <summary>
|
|
/// Gets or sets the show identifier.
|
|
/// </summary>
|
|
/// <value>The show identifier.</value>
|
|
public string ShowId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the date last updated.
|
|
/// </summary>
|
|
/// <value>The date last updated.</value>
|
|
public DateTime DateLastUpdated { get; set; }
|
|
|
|
public MyRecordingInfo()
|
|
{
|
|
Genres = new List<string>();
|
|
}
|
|
}
|
|
|
|
}
|