2018-04-14 20:01:24 +00:00
|
|
|
using System;
|
2015-04-11 11:28:37 +00:00
|
|
|
using System.Globalization;
|
2013-01-21 00:35:42 +00:00
|
|
|
using System.IO;
|
|
|
|
using NLog;
|
2014-01-06 06:20:08 +00:00
|
|
|
using NzbDrone.Common.Disk;
|
2020-03-22 11:01:20 +00:00
|
|
|
using NzbDrone.Common.Extensions;
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2013-09-13 05:55:33 +00:00
|
|
|
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
2013-01-21 00:35:42 +00:00
|
|
|
{
|
2013-04-15 01:41:39 +00:00
|
|
|
public interface IVideoFileInfoReader
|
|
|
|
{
|
|
|
|
MediaInfoModel GetMediaInfo(string filename);
|
2017-06-11 01:07:24 +00:00
|
|
|
TimeSpan? GetRunTime(string filename);
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public class VideoFileInfoReader : IVideoFileInfoReader
|
2013-01-21 00:35:42 +00:00
|
|
|
{
|
2013-05-10 23:53:50 +00:00
|
|
|
private readonly IDiskProvider _diskProvider;
|
2013-04-15 01:41:39 +00:00
|
|
|
private readonly Logger _logger;
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2017-08-14 18:02:15 +00:00
|
|
|
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3;
|
2021-02-03 01:19:22 +00:00
|
|
|
public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 7;
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2013-05-10 23:53:50 +00:00
|
|
|
public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger)
|
2013-01-21 00:35:42 +00:00
|
|
|
{
|
|
|
|
_diskProvider = diskProvider;
|
2013-04-15 01:41:39 +00:00
|
|
|
_logger = logger;
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-04-15 01:41:39 +00:00
|
|
|
public MediaInfoModel GetMediaInfo(string filename)
|
2013-01-21 00:35:42 +00:00
|
|
|
{
|
|
|
|
if (!_diskProvider.FileExists(filename))
|
2016-08-16 23:46:27 +00:00
|
|
|
{
|
2013-01-21 00:35:42 +00:00
|
|
|
throw new FileNotFoundException("Media file does not exist: " + filename);
|
2016-08-16 23:46:27 +00:00
|
|
|
}
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2015-05-12 21:58:46 +00:00
|
|
|
MediaInfo mediaInfo = null;
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2018-04-14 20:01:24 +00:00
|
|
|
// TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
|
|
|
|
|
2013-01-21 00:35:42 +00:00
|
|
|
try
|
|
|
|
{
|
2015-05-12 21:58:46 +00:00
|
|
|
mediaInfo = new MediaInfo();
|
2014-03-13 20:12:42 +00:00
|
|
|
_logger.Debug("Getting media info from {0}", filename);
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2015-08-15 10:34:54 +00:00
|
|
|
if (filename.ToLower().EndsWith(".ts"))
|
|
|
|
{
|
2020-03-22 11:01:20 +00:00
|
|
|
// For .ts files we often have to scan more of the file to get all the info we need
|
2015-08-15 10:34:54 +00:00
|
|
|
mediaInfo.Option("ParseSpeed", "0.3");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mediaInfo.Option("ParseSpeed", "0.0");
|
|
|
|
}
|
2015-04-11 11:28:37 +00:00
|
|
|
|
2015-05-17 16:24:00 +00:00
|
|
|
int open;
|
|
|
|
|
|
|
|
using (var stream = _diskProvider.OpenReadStream(filename))
|
|
|
|
{
|
|
|
|
open = mediaInfo.Open(stream);
|
|
|
|
}
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2015-06-02 19:57:47 +00:00
|
|
|
if (open != 0)
|
|
|
|
{
|
|
|
|
int audioRuntime;
|
|
|
|
int videoRuntime;
|
|
|
|
int generalRuntime;
|
|
|
|
|
2020-03-22 11:01:20 +00:00
|
|
|
// Runtime
|
2015-10-03 17:45:26 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
|
2015-06-02 19:57:47 +00:00
|
|
|
|
2020-03-22 11:01:20 +00:00
|
|
|
// Audio Channels
|
|
|
|
var audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
|
|
|
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
|
|
|
|
int.TryParse(audioChannelsStr, out var audioChannels);
|
|
|
|
|
2015-06-02 19:57:47 +00:00
|
|
|
if (audioRuntime == 0 && videoRuntime == 0 && generalRuntime == 0)
|
|
|
|
{
|
2020-03-22 11:01:20 +00:00
|
|
|
// No runtime, ask mediainfo to scan the whole file
|
2021-03-05 23:15:45 +00:00
|
|
|
_logger.Trace("No runtime value found, rescanning at 1.0 scan depth");
|
2015-06-02 19:57:47 +00:00
|
|
|
mediaInfo.Option("ParseSpeed", "1.0");
|
|
|
|
|
2020-03-22 11:01:20 +00:00
|
|
|
using (var stream = _diskProvider.OpenReadStream(filename))
|
|
|
|
{
|
|
|
|
open = mediaInfo.Open(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (audioChannels > 2 && audioChannelPositions.IsNullOrWhiteSpace())
|
|
|
|
{
|
|
|
|
// Some files with DTS don't have ChannelPositions unless more of the file is scanned
|
2021-03-05 23:15:45 +00:00
|
|
|
_logger.Trace("DTS audio without expected channel information, rescanning at 0.3 scan depth");
|
2020-03-22 11:01:20 +00:00
|
|
|
mediaInfo.Option("ParseSpeed", "0.3");
|
|
|
|
|
2015-06-02 19:57:47 +00:00
|
|
|
using (var stream = _diskProvider.OpenReadStream(filename))
|
|
|
|
{
|
|
|
|
open = mediaInfo.Open(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-21 00:35:42 +00:00
|
|
|
if (open != 0)
|
|
|
|
{
|
|
|
|
int width;
|
|
|
|
int height;
|
|
|
|
int videoBitRate;
|
|
|
|
int audioBitRate;
|
2013-12-06 05:59:47 +00:00
|
|
|
int audioRuntime;
|
|
|
|
int videoRuntime;
|
|
|
|
int generalRuntime;
|
2013-01-21 00:35:42 +00:00
|
|
|
int streamCount;
|
|
|
|
int audioChannels;
|
2020-11-15 06:07:47 +00:00
|
|
|
int audioChannelsOrig;
|
2016-08-11 22:49:20 +00:00
|
|
|
int videoBitDepth;
|
2013-12-03 07:31:31 +00:00
|
|
|
decimal videoFrameRate;
|
2019-02-03 05:52:37 +00:00
|
|
|
int videoMultiViewCount;
|
2013-01-21 00:35:42 +00:00
|
|
|
|
|
|
|
string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
|
|
|
|
string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
|
2015-10-03 17:45:26 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
|
2019-02-03 05:52:37 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
|
2018-07-08 16:36:00 +00:00
|
|
|
if (videoBitRate <= 0)
|
|
|
|
{
|
2019-02-03 05:52:37 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate);
|
2018-07-08 16:36:00 +00:00
|
|
|
}
|
2015-10-03 17:45:26 +00:00
|
|
|
decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);
|
2016-08-11 22:49:20 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth);
|
2019-02-03 05:52:37 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "MultiView_Count"), out videoMultiViewCount);
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2013-12-06 05:59:47 +00:00
|
|
|
//Runtime
|
2015-10-03 17:45:26 +00:00
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
|
2013-12-06 05:59:47 +00:00
|
|
|
|
2017-07-30 19:30:34 +00:00
|
|
|
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2015-10-03 17:45:26 +00:00
|
|
|
int.TryParse(aBitRate, out audioBitRate);
|
|
|
|
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
|
2017-01-05 23:32:17 +00:00
|
|
|
|
2013-01-21 00:35:42 +00:00
|
|
|
|
2020-11-15 06:07:47 +00:00
|
|
|
var audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
2020-03-22 11:01:20 +00:00
|
|
|
int.TryParse(audioChannelsStr, out audioChannels);
|
2016-08-11 22:49:20 +00:00
|
|
|
|
2020-11-15 06:07:47 +00:00
|
|
|
var audioChannelsStrOrig = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)_Original").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
|
|
|
int.TryParse(audioChannelsStrOrig, out audioChannelsOrig);
|
|
|
|
|
2016-08-16 23:46:27 +00:00
|
|
|
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
|
2020-11-15 06:07:47 +00:00
|
|
|
var audioChannelPositionsTextOrig = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions_Original");
|
|
|
|
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
|
2013-01-21 00:35:42 +00:00
|
|
|
|
|
|
|
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
|
2016-08-11 22:49:20 +00:00
|
|
|
|
2017-07-30 19:30:34 +00:00
|
|
|
string videoProfile = mediaInfo.Get(StreamKind.Video, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
|
|
|
string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
2013-01-21 00:35:42 +00:00
|
|
|
|
|
|
|
var mediaInfoModel = new MediaInfoModel
|
2017-01-05 23:32:17 +00:00
|
|
|
{
|
2017-07-30 19:30:34 +00:00
|
|
|
ContainerFormat = mediaInfo.Get(StreamKind.General, 0, "Format"),
|
|
|
|
VideoFormat = mediaInfo.Get(StreamKind.Video, 0, "Format"),
|
|
|
|
VideoCodecID = mediaInfo.Get(StreamKind.Video, 0, "CodecID"),
|
|
|
|
VideoProfile = videoProfile,
|
|
|
|
VideoCodecLibrary = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"),
|
2017-01-05 23:32:17 +00:00
|
|
|
VideoBitrate = videoBitRate,
|
|
|
|
VideoBitDepth = videoBitDepth,
|
2019-02-03 05:52:37 +00:00
|
|
|
VideoMultiViewCount = videoMultiViewCount,
|
|
|
|
VideoColourPrimaries = mediaInfo.Get(StreamKind.Video, 0, "colour_primaries"),
|
|
|
|
VideoTransferCharacteristics = mediaInfo.Get(StreamKind.Video, 0, "transfer_characteristics"),
|
2021-02-03 01:19:22 +00:00
|
|
|
VideoHdrFormat = mediaInfo.Get(StreamKind.Video, 0, "HDR_Format"),
|
|
|
|
VideoHdrFormatCompatibility = mediaInfo.Get(StreamKind.Video, 0, "HDR_Format_Compatibility"),
|
2017-01-05 23:32:17 +00:00
|
|
|
Height = height,
|
|
|
|
Width = width,
|
|
|
|
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
|
2017-07-30 19:30:34 +00:00
|
|
|
AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
|
|
|
|
AudioProfile = audioProfile,
|
|
|
|
AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
|
2019-02-03 05:52:37 +00:00
|
|
|
AudioAdditionalFeatures = mediaInfo.Get(StreamKind.Audio, 0, "Format_AdditionalFeatures"),
|
2017-01-05 23:32:17 +00:00
|
|
|
AudioBitrate = audioBitRate,
|
|
|
|
RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
|
|
|
|
AudioStreamCount = streamCount,
|
2020-11-15 06:07:47 +00:00
|
|
|
AudioChannelsContainer = audioChannels,
|
|
|
|
AudioChannelsStream = audioChannelsOrig,
|
2017-01-05 23:32:17 +00:00
|
|
|
AudioChannelPositions = audioChannelPositions,
|
2020-11-15 06:07:47 +00:00
|
|
|
AudioChannelPositionsTextContainer = audioChannelPositionsText,
|
|
|
|
AudioChannelPositionsTextStream = audioChannelPositionsTextOrig,
|
2017-01-05 23:32:17 +00:00
|
|
|
VideoFps = videoFrameRate,
|
|
|
|
AudioLanguages = audioLanguages,
|
|
|
|
Subtitles = subtitles,
|
2017-08-14 18:02:15 +00:00
|
|
|
ScanType = scanType,
|
|
|
|
SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION
|
2017-01-05 23:32:17 +00:00
|
|
|
};
|
2013-01-21 00:35:42 +00:00
|
|
|
|
|
|
|
return mediaInfoModel;
|
|
|
|
}
|
2015-04-11 11:28:37 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.Warn("Unable to open media info from file: " + filename);
|
|
|
|
}
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
2013-11-21 16:13:40 +00:00
|
|
|
catch (DllNotFoundException ex)
|
2013-01-21 00:35:42 +00:00
|
|
|
{
|
2016-02-11 21:13:42 +00:00
|
|
|
_logger.Error(ex, "mediainfo is required but was not found");
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
2017-01-05 23:32:17 +00:00
|
|
|
_logger.Error(ex, "Unable to parse media info from file: {0}", filename);
|
2013-09-06 18:56:48 +00:00
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
2017-01-05 23:32:17 +00:00
|
|
|
mediaInfo?.Close();
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
|
|
|
|
2013-11-21 16:13:40 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-06-11 01:07:24 +00:00
|
|
|
public TimeSpan? GetRunTime(string filename)
|
2013-11-21 16:13:40 +00:00
|
|
|
{
|
|
|
|
var info = GetMediaInfo(filename);
|
|
|
|
|
2017-06-11 01:07:24 +00:00
|
|
|
return info?.RunTime;
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
2013-12-06 05:59:47 +00:00
|
|
|
|
|
|
|
private TimeSpan GetBestRuntime(int audio, int video, int general)
|
|
|
|
{
|
|
|
|
if (video == 0)
|
|
|
|
{
|
|
|
|
if (audio == 0)
|
|
|
|
{
|
|
|
|
return TimeSpan.FromMilliseconds(general);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TimeSpan.FromMilliseconds(audio);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TimeSpan.FromMilliseconds(video);
|
|
|
|
}
|
2013-01-21 00:35:42 +00:00
|
|
|
}
|
|
|
|
}
|