mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05: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