mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Add RecordingsMetadataManager service
This commit is contained in:
parent
ca1a8ced48
commit
7baf2d6c6b
@ -10,16 +10,15 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
|
||||||
using AsyncKeyedLock;
|
using AsyncKeyedLock;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using Jellyfin.LiveTv.Configuration;
|
using Jellyfin.LiveTv.Configuration;
|
||||||
using Jellyfin.LiveTv.IO;
|
using Jellyfin.LiveTv.IO;
|
||||||
|
using Jellyfin.LiveTv.Recordings;
|
||||||
using Jellyfin.LiveTv.Timers;
|
using Jellyfin.LiveTv.Timers;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@ -44,8 +43,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
|
public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
|
||||||
{
|
{
|
||||||
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
|
|
||||||
|
|
||||||
private readonly ILogger<EmbyTV> _logger;
|
private readonly ILogger<EmbyTV> _logger;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
@ -61,6 +58,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
private readonly LiveTvDtoService _tvDtoService;
|
private readonly LiveTvDtoService _tvDtoService;
|
||||||
private readonly TimerManager _timerManager;
|
private readonly TimerManager _timerManager;
|
||||||
private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerManager;
|
private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerManager;
|
||||||
|
private readonly RecordingsMetadataManager _recordingsMetadataManager;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
|
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
|
||||||
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
|
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@ -84,7 +82,8 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
IListingsManager listingsManager,
|
IListingsManager listingsManager,
|
||||||
LiveTvDtoService tvDtoService,
|
LiveTvDtoService tvDtoService,
|
||||||
TimerManager timerManager,
|
TimerManager timerManager,
|
||||||
SeriesTimerManager seriesTimerManager)
|
SeriesTimerManager seriesTimerManager,
|
||||||
|
RecordingsMetadataManager recordingsMetadataManager)
|
||||||
{
|
{
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
@ -103,6 +102,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
_tvDtoService = tvDtoService;
|
_tvDtoService = tvDtoService;
|
||||||
_timerManager = timerManager;
|
_timerManager = timerManager;
|
||||||
_seriesTimerManager = seriesTimerManager;
|
_seriesTimerManager = seriesTimerManager;
|
||||||
|
_recordingsMetadataManager = recordingsMetadataManager;
|
||||||
|
|
||||||
_timerManager.TimerFired += OnTimerManagerTimerFired;
|
_timerManager.TimerFired += OnTimerManagerTimerFired;
|
||||||
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
|
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
|
||||||
@ -998,7 +998,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
timer.Status = RecordingStatus.InProgress;
|
timer.Status = RecordingStatus.InProgress;
|
||||||
_timerManager.AddOrUpdate(timer, false);
|
_timerManager.AddOrUpdate(timer, false);
|
||||||
|
|
||||||
await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false);
|
await _recordingsMetadataManager.SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false);
|
||||||
|
|
||||||
await CreateRecordingFolders().ConfigureAwait(false);
|
await CreateRecordingFolders().ConfigureAwait(false);
|
||||||
|
|
||||||
@ -1377,452 +1377,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveRecordingImage(string recordingPath, LiveTvProgram program, ItemImageInfo image)
|
|
||||||
{
|
|
||||||
if (!image.IsLocalFile)
|
|
||||||
{
|
|
||||||
image = await _libraryManager.ConvertImageToLocal(program, image, 0).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
string imageSaveFilenameWithoutExtension = image.Type switch
|
|
||||||
{
|
|
||||||
ImageType.Primary => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "poster",
|
|
||||||
ImageType.Logo => "logo",
|
|
||||||
ImageType.Thumb => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "landscape",
|
|
||||||
ImageType.Backdrop => "fanart",
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (imageSaveFilenameWithoutExtension is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageSavePath = Path.Combine(Path.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
|
|
||||||
|
|
||||||
// preserve original image extension
|
|
||||||
imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path));
|
|
||||||
|
|
||||||
File.Copy(image.Path, imageSavePath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program)
|
|
||||||
{
|
|
||||||
var image = program.IsSeries ?
|
|
||||||
(program.GetImageInfo(ImageType.Thumb, 0) ?? program.GetImageInfo(ImageType.Primary, 0)) :
|
|
||||||
(program.GetImageInfo(ImageType.Primary, 0) ?? program.GetImageInfo(ImageType.Thumb, 0));
|
|
||||||
|
|
||||||
if (image is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error saving recording image");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!program.IsSeries)
|
|
||||||
{
|
|
||||||
image = program.GetImageInfo(ImageType.Backdrop, 0);
|
|
||||||
if (image is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error saving recording image");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image = program.GetImageInfo(ImageType.Thumb, 0);
|
|
||||||
if (image is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error saving recording image");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image = program.GetImageInfo(ImageType.Logo, 0);
|
|
||||||
if (image is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error saving recording image");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
|
|
||||||
{
|
|
||||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
|
||||||
Limit = 1,
|
|
||||||
ExternalId = timer.ProgramId,
|
|
||||||
DtoOptions = new DtoOptions(true)
|
|
||||||
}).FirstOrDefault() as LiveTvProgram;
|
|
||||||
|
|
||||||
// dummy this up
|
|
||||||
if (program is null)
|
|
||||||
{
|
|
||||||
program = new LiveTvProgram
|
|
||||||
{
|
|
||||||
Name = timer.Name,
|
|
||||||
Overview = timer.Overview,
|
|
||||||
Genres = timer.Genres,
|
|
||||||
CommunityRating = timer.CommunityRating,
|
|
||||||
OfficialRating = timer.OfficialRating,
|
|
||||||
ProductionYear = timer.ProductionYear,
|
|
||||||
PremiereDate = timer.OriginalAirDate,
|
|
||||||
IndexNumber = timer.EpisodeNumber,
|
|
||||||
ParentIndexNumber = timer.SeasonNumber
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.IsSports)
|
|
||||||
{
|
|
||||||
program.AddGenre("Sports");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.IsKids)
|
|
||||||
{
|
|
||||||
program.AddGenre("Kids");
|
|
||||||
program.AddGenre("Children");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.IsNews)
|
|
||||||
{
|
|
||||||
program.AddGenre("News");
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = _config.GetLiveTvConfiguration();
|
|
||||||
|
|
||||||
if (config.SaveRecordingNFO)
|
|
||||||
{
|
|
||||||
if (timer.IsProgramSeries)
|
|
||||||
{
|
|
||||||
await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false);
|
|
||||||
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else if (!timer.IsMovie || timer.IsSports || timer.IsNews)
|
|
||||||
{
|
|
||||||
await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.SaveRecordingImages)
|
|
||||||
{
|
|
||||||
await SaveRecordingImages(recordingPath, program).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error saving nfo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveSeriesNfoAsync(TimerInfo timer, string seriesPath)
|
|
||||||
{
|
|
||||||
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
|
|
||||||
|
|
||||||
if (File.Exists(nfoPath))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
|
||||||
await using (stream.ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var settings = new XmlWriterSettings
|
|
||||||
{
|
|
||||||
Indent = true,
|
|
||||||
Encoding = Encoding.UTF8,
|
|
||||||
Async = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var writer = XmlWriter.Create(stream, settings);
|
|
||||||
await using (writer.ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
|
||||||
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
|
|
||||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var id))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "imdb_id", null, id).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "tmdbid", null, id).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "zap2itid", null, id).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(timer.Name))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "title", null, timer.Name).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(timer.OfficialRating))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "mpaa", null, timer.OfficialRating).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var genre in timer.Genres)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
|
||||||
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveVideoNfoAsync(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
|
|
||||||
{
|
|
||||||
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
|
|
||||||
|
|
||||||
if (File.Exists(nfoPath))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
|
||||||
await using (stream.ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var settings = new XmlWriterSettings
|
|
||||||
{
|
|
||||||
Indent = true,
|
|
||||||
Encoding = Encoding.UTF8,
|
|
||||||
Async = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var options = _config.GetNfoConfiguration();
|
|
||||||
|
|
||||||
var isSeriesEpisode = timer.IsProgramSeries;
|
|
||||||
|
|
||||||
var writer = XmlWriter.Create(stream, settings);
|
|
||||||
await using (writer.ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (isSeriesEpisode)
|
|
||||||
{
|
|
||||||
await writer.WriteStartElementAsync(null, "episodedetails", null).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "title", null, timer.EpisodeTitle).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
|
|
||||||
|
|
||||||
if (premiereDate.HasValue)
|
|
||||||
{
|
|
||||||
var formatString = options.ReleaseDateFormat;
|
|
||||||
|
|
||||||
await writer.WriteElementStringAsync(
|
|
||||||
null,
|
|
||||||
"aired",
|
|
||||||
null,
|
|
||||||
premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "episode", null, item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.ParentIndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "season", null, item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await writer.WriteStartElementAsync(null, "movie", null).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Name))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "title", null, item.Name).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.OriginalTitle))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "originaltitle", null, item.OriginalTitle).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.PremiereDate.HasValue)
|
|
||||||
{
|
|
||||||
var formatString = options.ReleaseDateFormat;
|
|
||||||
|
|
||||||
await writer.WriteElementStringAsync(
|
|
||||||
null,
|
|
||||||
"premiered",
|
|
||||||
null,
|
|
||||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
await writer.WriteElementStringAsync(
|
|
||||||
null,
|
|
||||||
"releasedate",
|
|
||||||
null,
|
|
||||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.WriteElementStringAsync(
|
|
||||||
null,
|
|
||||||
"dateadded",
|
|
||||||
null,
|
|
||||||
DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (item.ProductionYear.HasValue)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.OfficialRating))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "mpaa", null, item.OfficialRating).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var overview = (item.Overview ?? string.Empty)
|
|
||||||
.StripHtml()
|
|
||||||
.Replace(""", "'", StringComparison.Ordinal);
|
|
||||||
|
|
||||||
await writer.WriteElementStringAsync(null, "plot", null, overview).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (item.CommunityRating.HasValue)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var genre in item.Genres)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var people = item.Id.IsEmpty() ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
|
|
||||||
|
|
||||||
var directors = people
|
|
||||||
.Where(i => i.IsType(PersonKind.Director))
|
|
||||||
.Select(i => i.Name)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var person in directors)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "director", null, person).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var writers = people
|
|
||||||
.Where(i => i.IsType(PersonKind.Writer))
|
|
||||||
.Select(i => i.Name)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var person in writers)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "writer", null, person).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var person in writers)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "credits", null, person).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tmdbCollection))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "collectionnumber", null, tmdbCollection).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
|
||||||
if (!string.IsNullOrEmpty(imdb))
|
|
||||||
{
|
|
||||||
if (!isSeriesEpisode)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "id", null, imdb).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.WriteElementStringAsync(null, "imdbid", null, imdb).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// No need to lock if we have identified the content already
|
|
||||||
lockData = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
|
||||||
if (!string.IsNullOrEmpty(tvdb))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "tvdbid", null, tvdb).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// No need to lock if we have identified the content already
|
|
||||||
lockData = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
|
||||||
if (!string.IsNullOrEmpty(tmdb))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "tmdbid", null, tmdb).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// No need to lock if we have identified the content already
|
|
||||||
lockData = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lockData)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "lockdata", null, "true").ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.CriticRating.HasValue)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "criticrating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Tagline))
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "tagline", null, item.Tagline).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var studio in item.Studios)
|
|
||||||
{
|
|
||||||
await writer.WriteElementStringAsync(null, "studio", null, studio).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
|
||||||
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LiveTvProgram GetProgramInfoFromCache(string programId)
|
private LiveTvProgram GetProgramInfoFromCache(string programId)
|
||||||
{
|
{
|
||||||
var query = new InternalItemsQuery
|
var query = new InternalItemsQuery
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Jellyfin.LiveTv.Guide;
|
using Jellyfin.LiveTv.Guide;
|
||||||
using Jellyfin.LiveTv.IO;
|
using Jellyfin.LiveTv.IO;
|
||||||
using Jellyfin.LiveTv.Listings;
|
using Jellyfin.LiveTv.Listings;
|
||||||
|
using Jellyfin.LiveTv.Recordings;
|
||||||
using Jellyfin.LiveTv.Timers;
|
using Jellyfin.LiveTv.Timers;
|
||||||
using Jellyfin.LiveTv.TunerHosts;
|
using Jellyfin.LiveTv.TunerHosts;
|
||||||
using Jellyfin.LiveTv.TunerHosts.HdHomerun;
|
using Jellyfin.LiveTv.TunerHosts.HdHomerun;
|
||||||
@ -26,6 +27,7 @@ public static class LiveTvServiceCollectionExtensions
|
|||||||
services.AddSingleton<LiveTvDtoService>();
|
services.AddSingleton<LiveTvDtoService>();
|
||||||
services.AddSingleton<TimerManager>();
|
services.AddSingleton<TimerManager>();
|
||||||
services.AddSingleton<SeriesTimerManager>();
|
services.AddSingleton<SeriesTimerManager>();
|
||||||
|
services.AddSingleton<RecordingsMetadataManager>();
|
||||||
services.AddSingleton<ILiveTvManager, LiveTvManager>();
|
services.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
services.AddSingleton<IChannelManager, ChannelManager>();
|
services.AddSingleton<IChannelManager, ChannelManager>();
|
||||||
services.AddSingleton<IStreamHelper, StreamHelper>();
|
services.AddSingleton<IStreamHelper, StreamHelper>();
|
||||||
|
502
src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
Normal file
502
src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
using Jellyfin.LiveTv.Configuration;
|
||||||
|
using Jellyfin.LiveTv.EmbyTV;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller.Dto;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.LiveTv.Recordings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A service responsible for saving recording metadata.
|
||||||
|
/// </summary>
|
||||||
|
public class RecordingsMetadataManager
|
||||||
|
{
|
||||||
|
private const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
private readonly ILogger<RecordingsMetadataManager> _logger;
|
||||||
|
private readonly IConfigurationManager _config;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RecordingsMetadataManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||||
|
/// <param name="config">The <see cref="IConfigurationManager"/>.</param>
|
||||||
|
/// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
|
||||||
|
public RecordingsMetadataManager(
|
||||||
|
ILogger<RecordingsMetadataManager> logger,
|
||||||
|
IConfigurationManager config,
|
||||||
|
ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the metadata for a provided recording.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timer">The recording timer.</param>
|
||||||
|
/// <param name="recordingPath">The recording path.</param>
|
||||||
|
/// <param name="seriesPath">The series path.</param>
|
||||||
|
/// <returns>A task representing the metadata saving.</returns>
|
||||||
|
public async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string? seriesPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
{
|
||||||
|
IncludeItemTypes = [BaseItemKind.LiveTvProgram],
|
||||||
|
Limit = 1,
|
||||||
|
ExternalId = timer.ProgramId,
|
||||||
|
DtoOptions = new DtoOptions(true)
|
||||||
|
}).FirstOrDefault() as LiveTvProgram;
|
||||||
|
|
||||||
|
// dummy this up
|
||||||
|
program ??= new LiveTvProgram
|
||||||
|
{
|
||||||
|
Name = timer.Name,
|
||||||
|
Overview = timer.Overview,
|
||||||
|
Genres = timer.Genres,
|
||||||
|
CommunityRating = timer.CommunityRating,
|
||||||
|
OfficialRating = timer.OfficialRating,
|
||||||
|
ProductionYear = timer.ProductionYear,
|
||||||
|
PremiereDate = timer.OriginalAirDate,
|
||||||
|
IndexNumber = timer.EpisodeNumber,
|
||||||
|
ParentIndexNumber = timer.SeasonNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
if (timer.IsSports)
|
||||||
|
{
|
||||||
|
program.AddGenre("Sports");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.IsKids)
|
||||||
|
{
|
||||||
|
program.AddGenre("Kids");
|
||||||
|
program.AddGenre("Children");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.IsNews)
|
||||||
|
{
|
||||||
|
program.AddGenre("News");
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = _config.GetLiveTvConfiguration();
|
||||||
|
|
||||||
|
if (config.SaveRecordingNFO)
|
||||||
|
{
|
||||||
|
if (timer.IsProgramSeries)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(seriesPath);
|
||||||
|
|
||||||
|
await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false);
|
||||||
|
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (!timer.IsMovie || timer.IsSports || timer.IsNews)
|
||||||
|
{
|
||||||
|
await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.SaveRecordingImages)
|
||||||
|
{
|
||||||
|
await SaveRecordingImages(recordingPath, program).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving nfo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SaveSeriesNfoAsync(TimerInfo timer, string seriesPath)
|
||||||
|
{
|
||||||
|
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
|
||||||
|
|
||||||
|
if (File.Exists(nfoPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var settings = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
Indent = true,
|
||||||
|
Encoding = Encoding.UTF8,
|
||||||
|
Async = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var writer = XmlWriter.Create(stream, settings);
|
||||||
|
await using (writer.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||||
|
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
|
||||||
|
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var id))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "imdb_id", null, id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "tmdbid", null, id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "zap2itid", null, id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(timer.Name))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "title", null, timer.Name).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(timer.OfficialRating))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "mpaa", null, timer.OfficialRating).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var genre in timer.Genres)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
||||||
|
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveVideoNfoAsync(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
|
||||||
|
{
|
||||||
|
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
|
||||||
|
|
||||||
|
if (File.Exists(nfoPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var settings = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
Indent = true,
|
||||||
|
Encoding = Encoding.UTF8,
|
||||||
|
Async = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = _config.GetNfoConfiguration();
|
||||||
|
|
||||||
|
var isSeriesEpisode = timer.IsProgramSeries;
|
||||||
|
|
||||||
|
var writer = XmlWriter.Create(stream, settings);
|
||||||
|
await using (writer.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isSeriesEpisode)
|
||||||
|
{
|
||||||
|
await writer.WriteStartElementAsync(null, "episodedetails", null).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "title", null, timer.EpisodeTitle).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
|
||||||
|
|
||||||
|
if (premiereDate.HasValue)
|
||||||
|
{
|
||||||
|
var formatString = options.ReleaseDateFormat;
|
||||||
|
|
||||||
|
await writer.WriteElementStringAsync(
|
||||||
|
null,
|
||||||
|
"aired",
|
||||||
|
null,
|
||||||
|
premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "episode", null, item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.ParentIndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "season", null, item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await writer.WriteStartElementAsync(null, "movie", null).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Name))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "title", null, item.Name).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.OriginalTitle))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "originaltitle", null, item.OriginalTitle).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.PremiereDate.HasValue)
|
||||||
|
{
|
||||||
|
var formatString = options.ReleaseDateFormat;
|
||||||
|
|
||||||
|
await writer.WriteElementStringAsync(
|
||||||
|
null,
|
||||||
|
"premiered",
|
||||||
|
null,
|
||||||
|
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
await writer.WriteElementStringAsync(
|
||||||
|
null,
|
||||||
|
"releasedate",
|
||||||
|
null,
|
||||||
|
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteElementStringAsync(
|
||||||
|
null,
|
||||||
|
"dateadded",
|
||||||
|
null,
|
||||||
|
DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (item.ProductionYear.HasValue)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(item.OfficialRating))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "mpaa", null, item.OfficialRating).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var overview = (item.Overview ?? string.Empty)
|
||||||
|
.StripHtml()
|
||||||
|
.Replace(""", "'", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
await writer.WriteElementStringAsync(null, "plot", null, overview).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (item.CommunityRating.HasValue)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var genre in item.Genres)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var people = item.Id.IsEmpty() ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
|
||||||
|
|
||||||
|
var directors = people
|
||||||
|
.Where(i => i.IsType(PersonKind.Director))
|
||||||
|
.Select(i => i.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var person in directors)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "director", null, person).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var writers = people
|
||||||
|
.Where(i => i.IsType(PersonKind.Writer))
|
||||||
|
.Select(i => i.Name)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var person in writers)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "writer", null, person).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var person in writers)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "credits", null, person).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(tmdbCollection))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "collectionnumber", null, tmdbCollection).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||||
|
if (!string.IsNullOrEmpty(imdb))
|
||||||
|
{
|
||||||
|
if (!isSeriesEpisode)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "id", null, imdb).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteElementStringAsync(null, "imdbid", null, imdb).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// No need to lock if we have identified the content already
|
||||||
|
lockData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
||||||
|
if (!string.IsNullOrEmpty(tvdb))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "tvdbid", null, tvdb).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// No need to lock if we have identified the content already
|
||||||
|
lockData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
||||||
|
if (!string.IsNullOrEmpty(tmdb))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "tmdbid", null, tmdb).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// No need to lock if we have identified the content already
|
||||||
|
lockData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockData)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "lockdata", null, "true").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.CriticRating.HasValue)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "criticrating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Tagline))
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "tagline", null, item.Tagline).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var studio in item.Studios)
|
||||||
|
{
|
||||||
|
await writer.WriteElementStringAsync(null, "studio", null, studio).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
||||||
|
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program)
|
||||||
|
{
|
||||||
|
var image = program.IsSeries ?
|
||||||
|
(program.GetImageInfo(ImageType.Thumb, 0) ?? program.GetImageInfo(ImageType.Primary, 0)) :
|
||||||
|
(program.GetImageInfo(ImageType.Primary, 0) ?? program.GetImageInfo(ImageType.Thumb, 0));
|
||||||
|
|
||||||
|
if (image is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving recording image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!program.IsSeries)
|
||||||
|
{
|
||||||
|
image = program.GetImageInfo(ImageType.Backdrop, 0);
|
||||||
|
if (image is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving recording image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image = program.GetImageInfo(ImageType.Thumb, 0);
|
||||||
|
if (image is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving recording image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image = program.GetImageInfo(ImageType.Logo, 0);
|
||||||
|
if (image is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveRecordingImage(recordingPath, program, image).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving recording image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveRecordingImage(string recordingPath, LiveTvProgram program, ItemImageInfo image)
|
||||||
|
{
|
||||||
|
if (!image.IsLocalFile)
|
||||||
|
{
|
||||||
|
image = await _libraryManager.ConvertImageToLocal(program, image, 0).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageSaveFilenameWithoutExtension = image.Type switch
|
||||||
|
{
|
||||||
|
ImageType.Primary => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "poster",
|
||||||
|
ImageType.Logo => "logo",
|
||||||
|
ImageType.Thumb => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "landscape",
|
||||||
|
ImageType.Backdrop => "fanart",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (imageSaveFilenameWithoutExtension is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageSavePath = Path.Combine(Path.GetDirectoryName(recordingPath)!, imageSaveFilenameWithoutExtension);
|
||||||
|
|
||||||
|
// preserve original image extension
|
||||||
|
imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path));
|
||||||
|
|
||||||
|
File.Copy(image.Path, imageSavePath, true);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user