Files
ersatztv/ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs
T
Jason DoveandGitHub f392bab118 Database redesign (#31)
* starting database redesign

* set season and episode numbers

* use datetimes in db (utc); update movie metadata

* get movie cards from new table

* copy show/episode metadata

* remove old movie metadata type

* rename new movie metadata type

* code cleanup

* start to remove old television classes

* remove old television tables from database

* fix playout building

* fix collection views

* fix show/season views

* clean up movie metadata table

* fix scanner tests

* add libraries ui

* code cleanup

* fix movie scanning/metadata

* add library scan button to ui

* delete library path from ui

* temp disable movie scanning

* remove orphan media items and prevent duplicate paths

* attach artwork to metadata

* fix split show/season display

* fix television artwork

* store year distinct from release date

* fix collections ui

* code cleanup

* add library paths from ui

* fix adding to collections from ui

* fix schedule items loading

* schedule editing works again

* remove some todos

* more cleanup

* fix unit tests

* fix episode sorting

* fix deleting show library paths

* remove unused class

* fix playout list in ui

* fix log viewer

* start to use version/file instead of statistics

* clean up old columns

* fix playout display (time zone)

* fix playback

* fix channel guide time zone

* cascade more deletes

* fix compiler warnings

* fix adding new seasons

* use artwork for channel logo

* clean cache folder on startup (move channel logos, delete everything else)

* log database migration

* update homepage docs for libraries

* fix adding new channel with logo

* fix episode numbers in epg
2021-02-28 17:48:01 +00:00

170 lines
6.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Metadata
{
public class LocalStatisticsProvider : ILocalStatisticsProvider
{
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<LocalStatisticsProvider> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
public LocalStatisticsProvider(
IMediaItemRepository mediaItemRepository,
ILocalFileSystem localFileSystem,
ILogger<LocalStatisticsProvider> logger)
{
_mediaItemRepository = mediaItemRepository;
_localFileSystem = localFileSystem;
_logger = logger;
}
public async Task<bool> RefreshStatistics(string ffprobePath, MediaItem mediaItem)
{
try
{
string filePath = mediaItem switch
{
Movie m => m.MediaVersions.Head().MediaFiles.Head().Path,
Episode e => e.MediaVersions.Head().MediaFiles.Head().Path,
_ => throw new ArgumentOutOfRangeException(nameof(mediaItem))
};
FFprobe ffprobe = await GetProbeOutput(ffprobePath, filePath);
MediaVersion version = ProjectToMediaVersion(ffprobe);
return await ApplyVersionUpdate(mediaItem, version, filePath);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to refresh statistics for media item {Id}", mediaItem.Id);
return false;
}
}
private async Task<bool> ApplyVersionUpdate(MediaItem mediaItem, MediaVersion version, string filePath)
{
MediaVersion mediaItemVersion = mediaItem switch
{
Movie m => m.MediaVersions.Head(),
Episode e => e.MediaVersions.Head(),
_ => throw new ArgumentOutOfRangeException(nameof(mediaItem))
};
bool durationChange = mediaItemVersion.Duration != version.Duration;
mediaItemVersion.DateUpdated = _localFileSystem.GetLastWriteTime(filePath);
mediaItemVersion.Duration = version.Duration;
mediaItemVersion.AudioCodec = version.AudioCodec;
mediaItemVersion.SampleAspectRatio = version.SampleAspectRatio;
mediaItemVersion.DisplayAspectRatio = version.DisplayAspectRatio;
mediaItemVersion.Width = version.Width;
mediaItemVersion.Height = version.Height;
mediaItemVersion.VideoCodec = version.VideoCodec;
mediaItemVersion.VideoScanKind = version.VideoScanKind;
return await _mediaItemRepository.Update(mediaItem) && durationChange;
}
private Task<FFprobe> GetProbeOutput(string ffprobePath, string filePath)
{
var startInfo = new ProcessStartInfo
{
FileName = ffprobePath,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
startInfo.ArgumentList.Add("-v");
startInfo.ArgumentList.Add("quiet");
startInfo.ArgumentList.Add("-print_format");
startInfo.ArgumentList.Add("json");
startInfo.ArgumentList.Add("-show_format");
startInfo.ArgumentList.Add("-show_streams");
startInfo.ArgumentList.Add("-i");
startInfo.ArgumentList.Add(filePath);
var probe = new Process
{
StartInfo = startInfo
};
probe.Start();
return probe.StandardOutput.ReadToEndAsync().MapAsync(
async output =>
{
await probe.WaitForExitAsync();
return JsonConvert.DeserializeObject<FFprobe>(output);
});
}
private MediaVersion ProjectToMediaVersion(FFprobe probeOutput) =>
Optional(probeOutput)
.Filter(json => json?.format != null && json.streams != null)
.ToValidation<BaseError>("Unable to parse ffprobe output")
.ToEither<FFprobe>()
.Match(
json =>
{
var duration = TimeSpan.FromSeconds(double.Parse(json.format.duration));
var version = new MediaVersion { Name = "Main", Duration = duration };
FFprobeStream audioStream = json.streams.FirstOrDefault(s => s.codec_type == "audio");
if (audioStream != null)
{
version.AudioCodec = audioStream.codec_name;
}
FFprobeStream videoStream = json.streams.FirstOrDefault(s => s.codec_type == "video");
if (videoStream != null)
{
version.SampleAspectRatio = videoStream.sample_aspect_ratio;
version.DisplayAspectRatio = videoStream.display_aspect_ratio;
version.Width = videoStream.width;
version.Height = videoStream.height;
version.VideoCodec = videoStream.codec_name;
version.VideoScanKind = ScanKindFromFieldOrder(videoStream.field_order);
}
return version;
},
_ => new MediaVersion { Name = "Main" });
private VideoScanKind ScanKindFromFieldOrder(string fieldOrder) =>
fieldOrder?.ToLowerInvariant() switch
{
var x when x == "tt" || x == "bb" || x == "tb" || x == "bt" => VideoScanKind.Interlaced,
"progressive" => VideoScanKind.Progressive,
_ => VideoScanKind.Unknown
};
// ReSharper disable InconsistentNaming
public record FFprobe(FFprobeFormat format, List<FFprobeStream> streams);
public record FFprobeFormat(string duration);
public record FFprobeStream(
int index,
string codec_name,
string codec_type,
int width,
int height,
string sample_aspect_ratio,
string display_aspect_ratio,
string field_order,
string r_frame_rate);
// ReSharper restore InconsistentNaming
}
}