diff --git a/.circleci/Dockerfile b/.circleci/Dockerfile
index d8ccf48c12..185dd45135 100644
--- a/.circleci/Dockerfile
+++ b/.circleci/Dockerfile
@@ -11,3 +11,4 @@ RUN curl -O https://dl.google.com/go/go1.10.2.linux-amd64.tar.gz && tar xvf go*.
ENV GOPATH=$HOME/work
ENV PATH="${PATH}:/usr/local/go/bin:$GOPATH/bin"
RUN go get github.com/aktau/github-release
+RUN npm install -g yarn
\ No newline at end of file
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2adedc5c44..cf59704235 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,7 +2,7 @@ version: 2
defaults: &defaults
docker:
- - image: gallileo/radarr-cci-primary:5.8.7
+ - image: gallileo/radarr-cci-primary:5.8.8
environment:
BUILD_VERSION: 0.2.0
@@ -62,6 +62,7 @@ jobs:
- _tests
- setup
- .circleci
+ - deploy.sh
unit_tests:
<<: *defaults
steps:
@@ -105,10 +106,10 @@ jobs:
zip -r _packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.windows.zip _packages/Radarr
rm -rf _packages/Radarr
cp -r _output_mono/ _packages/Radarr
- tar -zcvf _packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.linux.tar.gz _packages/Radarr
+ tar -zcvf _packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.linux.tar.gz -C _packages Radarr
rm -rf _packages/Radarr
cp -r _output_osx/ _packages/Radarr
- tar -zcvf _packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.osx.tar.gz _packages/Radarr
+ tar -zcvf _packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.osx.tar.gz -C _packages Radarr
rm -rf _packages/Radarr
cd _output_osx_app/
zip -r ../_packages/Radarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.osx-app.zip *
@@ -118,6 +119,9 @@ jobs:
- store_artifacts:
path: _packages
destination: artifacts
+ - run:
+ name: "Deploying"
+ command: chmod +x deploy.sh && ./deploy.sh
- persist_to_workspace:
root: .
# Must be relative path from root
@@ -150,16 +154,16 @@ workflows:
- unit_tests:
requires:
- build
- - integration_tests:
- requires:
- - build
+ #- integration_tests:
+ # requires:
+ # - build
- publish_artifacts:
requires:
- build
- - request_deploy:
- type: approval
- requires:
- - publish_artifacts
- - deploy:
- requires:
- - request_deploy
+ #- request_deploy:
+ # type: approval
+ # requires:
+ # - publish_artifacts
+ #- deploy:
+ # requires:
+ # - request_deploy
diff --git a/.gitchangelog.rc.release b/.gitchangelog.rc.release
index b1ef30292d..56c23fc620 100644
--- a/.gitchangelog.rc.release
+++ b/.gitchangelog.rc.release
@@ -82,13 +82,13 @@ ignore_regexps = [
## whenever you are tweaking this variable.
##
section_regexps = [
- ('**New features**', [
+ ('**New features:**', [
r'^[aA]dded?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^[uU]pdated?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^[cC]hanged?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^[nN]ew?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
- ('**Fixes**', [
+ ('**Fixes:**', [
r'^(?![mM]erge\s*)'
]
),
@@ -151,7 +151,7 @@ subject_process = (strip |
ReSub(r'^([cC]hang(ed?)?)(\s?:?\s)(.*)$', r'\4') |
ReSub(r'^([fF]ix(ed?)?)(\s?:?\s)(.*)$', r'\4') |
ReSub(r'^([uU]pdat(ed?)?)(\s?:?\s)(.*)$', r'\4') |
- ReSub(r'#(\d{3,4})', r'\1') |
+ ReSub(r'#(\d{3,4})', r'Issue #\1') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11e612f3de..0f8bfa4116 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,16 @@
## (unreleased)
### **New features**
+-  64bit mediainfo.dll to 32bit to resolve issue: https://github.com/Radarr/Radarr/issues/3138. [geogolem]
+-  Refactor MediaInfo tokens (fixes old tokens adds new stuff) ([#3058](https://github.com/Radarr/Radarr/issues/3058)) [Ricardo Amaral]
+-  Don't hide custom formats behind advanced settings when editing quality. [Leonardo Galli]
+-  Upped rate at which we scan the download client. Should reduce cpu and ram usage as well as decrease pressure on download clients. [Leonardo Galli]
+-  Improve model and UI handling for lists. Should finally fix root folder errors. ([#3133](https://github.com/Radarr/Radarr/issues/3133)) [Ricardo Amaral]
+-  Don't return unmapped folders on rootfolder API call. Massively improves loading time. ([#3116](https://github.com/Radarr/Radarr/issues/3116)) [Justin Kromlinger]
+-  Support for Homebrew-installed mono ([#3090](https://github.com/Radarr/Radarr/issues/3090)) [Jeff Byrnes]
+-  mk3d file format ([#2795](https://github.com/Radarr/Radarr/issues/2795)) [Qstick]
+-  "Add Paused" option to Deluge and Transmission ([#3038](https://github.com/Radarr/Radarr/issues/3038)) [cookandy]
+-  All-around small improvements ([#3032](https://github.com/Radarr/Radarr/issues/3032)) [Ricardo Amaral]
-  Czech Language ([#2948](https://github.com/Radarr/Radarr/issues/2948)) [halali]
-  Fallback to Bitrate_Nominal for MediaInfo ([#2886](https://github.com/Radarr/Radarr/issues/2886)) [Qstick]
-  All new custom formats 9000! (Rescan old files, delete formats, polish UI, etc. See discord for full changes): [Leonardo Galli]
@@ -13,6 +23,21 @@
-  "importing an episode" to "importing a movie file" ([#2829](https://github.com/Radarr/Radarr/issues/2829)) [Travis Boss]
### **Fixes**
+-  Fallback to 'VideoCodec' if 'VideoFormat' is unavailable ([#3142](https://github.com/Radarr/Radarr/issues/3142)) [Ricardo Amaral]
+-  Read video 'BitRate_Nominal' if 'BitRate' is empty ([#3144](https://github.com/Radarr/Radarr/issues/3144)) [Ricardo Amaral]
+-  UpdateMovieQualityService Tests. [Leonardo Galli]
+-  Ignore "special drives" from System » Disk Space ([#3050](https://github.com/Radarr/Radarr/issues/3050)) [Ricardo Amaral]
+-  Tweak style of movie path template on "add movies" screen ([#3108](https://github.com/Radarr/Radarr/issues/3108)) [Ricardo Amaral]
+-  Unable to update custom formats for releases with bad Source Titles. [Leonardo Galli]
+-  Do not search movie if unmonitored ([#3131](https://github.com/Radarr/Radarr/issues/3131)) [Ricardo Amaral]
+-  Quality badges not being shown on bulk import. ([#3121](https://github.com/Radarr/Radarr/issues/3121)) [Ricardo Amaral]
+-  Trim filename from Kodi movie path before sending library scan request. ([#3097](https://github.com/Radarr/Radarr/issues/3097)) [Lawrence]
+-  Hopefully fixed bulk import not showing files. [Leonardo Galli]
+-  MPEG-2 remuxes being detected as "Raw-HD" quality. [Leonardo Galli]
+-  Allow directory to be parsed similar to past implementation ([#3057](https://github.com/Radarr/Radarr/issues/3057)) [Ricardo Amaral]
+-  Class names on the 'add movies screen' ([#3047](https://github.com/Radarr/Radarr/issues/3047)) [Ricardo Amaral]
+-  Use proper cursor for text and linked labels ([#3041](https://github.com/Radarr/Radarr/issues/3041)) [Ricardo Amaral]
+-  Donate button requiring two clicks to actually work. [Leonardo Galli]
-  Templates for custom format using wrong modifiers. [Leonardo Galli]
-  Profiles always failing validation. [Leonardo Galli]
-  ImdbIds not being padded with zeroes, which messes up matching. [Leonardo Galli]
diff --git a/changelog_release.tpl b/changelog_release.tpl
index 6f1e0bc4a1..f4eb2c1ba7 100644
--- a/changelog_release.tpl
+++ b/changelog_release.tpl
@@ -5,7 +5,7 @@
{{#sections}}
{{{label}}}
{{#commits}}
-- {{{subject}}} [{{{author}}}]
+- {{{subject}}} [{{{author}}}]
{{/commits}}
{{/sections}}
diff --git a/deploy.sh b/deploy.sh
new file mode 100644
index 0000000000..7ad83a091a
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,7 @@
+if [ -z "$CIRCLE_PULL_REQUEST" ]; then
+ echo "We are building a normal branch, deploying as such..."
+ curl "http://pr.radarr.video:4466/deploy?url=https%3A%2F%2F${CIRCLE_BUILD_NUM}-77323220-gh.circle-artifacts.com%2F0%2Fartifacts%2FRadarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.linux.tar.gz&b=branch&name=${CIRCLE_BRANCH}"
+else
+ echo "We are building a pr, deploying as such..."
+ curl "http://pr.radarr.video:4466/deploy?url=https%3A%2F%2F${CIRCLE_BUILD_NUM}-77323220-gh.circle-artifacts.com%2F0%2Fartifacts%2FRadarr.${CIRCLE_BRANCH//\//-}.$BUILD_VERSION.$CIRCLE_BUILD_NUM.linux.tar.gz&b=pr&name=${CIRCLE_PR_NUMBER}"
+fi
\ No newline at end of file
diff --git a/osx/Radarr b/osx/Radarr
index 7933f38934..d9c88956a5 100644
--- a/osx/Radarr
+++ b/osx/Radarr
@@ -9,7 +9,11 @@ APPNAME="Radarr"
#set up environment
if [[ -x '/opt/local/bin/mono' ]]; then
+ # Macports and mono-supplied installer path
export PATH="/opt/local/bin:$PATH"
+elif [[ -x '/usr/local/bin/mono' ]]; then
+ # Homebrew-supplied path to mono
+ export PATH="/usr/local/bin:$PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DIR"
diff --git a/src/Libraries/MediaInfo/MediaInfo.dll b/src/Libraries/MediaInfo/MediaInfo.dll
index 24e6cb986a..ca4ce4fb69 100644
Binary files a/src/Libraries/MediaInfo/MediaInfo.dll and b/src/Libraries/MediaInfo/MediaInfo.dll differ
diff --git a/src/Libraries/MediaInfo/libmediainfo.0.dylib b/src/Libraries/MediaInfo/libmediainfo.0.dylib
index 5e5383ded2..73ff0ba4fb 100644
Binary files a/src/Libraries/MediaInfo/libmediainfo.0.dylib and b/src/Libraries/MediaInfo/libmediainfo.0.dylib differ
diff --git a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs
index dee23aa3f0..6e958f1f69 100644
--- a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs
+++ b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs
@@ -14,6 +14,7 @@
using NzbDrone.Core.RootFolders;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Movies;
+using NzbDrone.Core.Profiles;
namespace NzbDrone.Api.Movies
{
@@ -34,12 +35,13 @@ public class MovieBulkImportModule : NzbDroneRestModule
private readonly IDiskScanService _diskScanService;
private readonly ICached _mappedMovies;
private readonly IParsingService _parsingService;
+ private readonly IProfileService _profileService;
private readonly IMovieService _movieService;
public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService,
IMakeImportDecision importDecisionMaker,
IDiskScanService diskScanService, ICacheManager cacheManager,
- IParsingService parsingService, IMovieService movieService)
+ IParsingService parsingService, IProfileService profileService, IMovieService movieService)
: base("/movies/bulkimport")
{
_searchProxy = searchProxy;
@@ -48,6 +50,7 @@ public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService
_diskScanService = diskScanService;
_mappedMovies = cacheManager.GetCache(GetType(), "mappedMoviesCache");
_movieService = movieService;
+ _profileService = profileService;
_parsingService = parsingService;
Get["/"] = x => Search();
}
@@ -60,6 +63,8 @@ private Response Search()
//Todo error handling
}
+ Profile tempProfile = _profileService.All().First();
+
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
int page = Request.Query.page;
@@ -100,6 +105,7 @@ private Response Search()
{
Title = f.Name.Replace(".", " ").Replace("-", " "),
Path = f.Path,
+ Profile = tempProfile
};
}
else
@@ -111,7 +117,8 @@ private Response Search()
Title = parsedTitle.MovieTitle,
Year = parsedTitle.Year,
ImdbId = parsedTitle.ImdbId,
- Path = f.Path
+ Path = f.Path,
+ Profile = tempProfile
};
}
diff --git a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
index e87e581de4..30bfcc6356 100644
--- a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
+++ b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
@@ -52,7 +52,7 @@ private int CreateRootFolder(RootFolderResource rootFolderResource)
private List GetRootFolders()
{
- return _rootFolderService.AllWithUnmappedFolders().ToResource();
+ return _rootFolderService.AllWithSpace().ToResource();
}
private void DeleteFolder(int id)
diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs
index 247274e291..687a7e6de5 100644
--- a/src/NzbDrone.Common/Extensions/StringExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs
@@ -78,6 +78,21 @@ public static bool IsNotNullOrWhiteSpace(this string text)
return !string.IsNullOrWhiteSpace(text);
}
+ public static bool StartsWithIgnoreCase(this string text, string startsWith)
+ {
+ return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ public static bool EndsWithIgnoreCase(this string text, string startsWith)
+ {
+ return text.EndsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ public static bool EqualsIgnoreCase(this string text, string equals)
+ {
+ return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase);
+ }
+
public static bool ContainsIgnoreCase(this string text, string contains)
{
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;
@@ -118,4 +133,4 @@ public static string FromOctalString(this string octalValue)
return Encoding.ASCII.GetString(new [] { byteResult });
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs
new file mode 100644
index 0000000000..3e43fbe0c6
--- /dev/null
+++ b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.DiskSpace;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Movies;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.DiskSpace
+{
+ [TestFixture]
+ public class DiskSpaceServiceFixture : CoreTest
+ {
+ private string _moviesFolder;
+ private string _moviesFolder2;
+ private string _droneFactoryFolder;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _moviesFolder = @"G:\fasdlfsdf\movies".AsOsAgnostic();
+ _moviesFolder2 = @"G:\fasdlfsdf\movies2".AsOsAgnostic();
+ _droneFactoryFolder = @"G:\dronefactory".AsOsAgnostic();
+
+ Mocker.GetMock()
+ .Setup(v => v.GetMounts())
+ .Returns(new List());
+
+ Mocker.GetMock()
+ .Setup(v => v.GetPathRoot(It.IsAny()))
+ .Returns(@"G:\".AsOsAgnostic());
+
+ Mocker.GetMock()
+ .Setup(v => v.GetAvailableSpace(It.IsAny()))
+ .Returns(0);
+
+ Mocker.GetMock()
+ .Setup(v => v.GetTotalSize(It.IsAny()))
+ .Returns(0);
+
+ GivenMovies();
+ }
+
+ private void GivenMovies(params Movie[] movies)
+ {
+ Mocker.GetMock()
+ .Setup(v => v.GetAllMovies())
+ .Returns(movies.ToList());
+ }
+
+ private void GivenExistingFolder(string folder)
+ {
+ Mocker.GetMock()
+ .Setup(v => v.FolderExists(folder))
+ .Returns(true);
+ }
+
+ [Test]
+ public void should_check_diskspace_for_movies_folders()
+ {
+ GivenMovies(new Movie { Path = _moviesFolder });
+
+ GivenExistingFolder(_moviesFolder);
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().NotBeEmpty();
+ }
+
+ [Test]
+ public void should_check_diskspace_for_same_root_folder_only_once()
+ {
+ GivenMovies(new Movie { Path = _moviesFolder }, new Movie { Path = _moviesFolder2 });
+
+ GivenExistingFolder(_moviesFolder);
+ GivenExistingFolder(_moviesFolder2);
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().HaveCount(1);
+
+ Mocker.GetMock()
+ .Verify(v => v.GetAvailableSpace(It.IsAny()), Times.Once());
+ }
+
+ [Test]
+ [Ignore("Unknown failure")]
+ public void should_not_check_diskspace_for_missing_movies_folders()
+ {
+ GivenMovies(new Movie { Path = _moviesFolder });
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().BeEmpty();
+
+ Mocker.GetMock()
+ .Verify(v => v.GetAvailableSpace(It.IsAny()), Times.Never());
+ }
+
+ [Test]
+ public void should_check_diskspace_for_dronefactory_folder()
+ {
+ Mocker.GetMock()
+ .SetupGet(v => v.DownloadedMoviesFolder)
+ .Returns(_droneFactoryFolder);
+
+ GivenExistingFolder(_droneFactoryFolder);
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().NotBeEmpty();
+ }
+
+ [Test]
+ [Ignore("Unknown failure")]
+ public void should_not_check_diskspace_for_missing_dronefactory_folder()
+ {
+ Mocker.GetMock()
+ .SetupGet(v => v.DownloadedMoviesFolder)
+ .Returns(_droneFactoryFolder);
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().BeEmpty();
+
+ Mocker.GetMock()
+ .Verify(v => v.GetAvailableSpace(It.IsAny()), Times.Never());
+ }
+
+ [TestCase("/boot")]
+ [TestCase("/var/lib/rancher")]
+ [TestCase("/var/lib/rancher/volumes")]
+ [TestCase("/var/lib/kubelet")]
+ [TestCase("/var/lib/docker")]
+ [TestCase("/some/place/docker/aufs")]
+ [TestCase("/etc/network")]
+ [TestCase("/snap/filebot/9")]
+ [TestCase("/snap/core/5145")]
+ public void should_not_check_diskspace_for_irrelevant_mounts(string path)
+ {
+ var mount = new Mock();
+ mount.SetupGet(v => v.RootDirectory).Returns(path);
+ mount.SetupGet(v => v.DriveType).Returns(System.IO.DriveType.Fixed);
+
+ Mocker.GetMock()
+ .Setup(v => v.GetMounts())
+ .Returns(new List { mount.Object });
+
+ var freeSpace = Subject.GetFreeSpace();
+
+ freeSpace.Should().BeEmpty();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs
new file mode 100644
index 0000000000..7a80c9af4c
--- /dev/null
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs
@@ -0,0 +1,177 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.MediaFiles.MediaInfo;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
+{
+ [TestFixture]
+ public class FormatAudioChannelsFixture : TestBase
+ {
+ [Test]
+ public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 6,
+ AudioChannelPositions = null,
+ AudioChannelPositionsText = "Front: L C R, Side: L R, LFE"
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
+ }
+
+ [Test]
+ public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = null,
+ AudioChannelPositionsText = "Front: L R"
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
+ }
+
+ [Test]
+ public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = null,
+ AudioChannelPositionsText = null,
+ SchemaRevision = 2
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
+ }
+
+ [Test]
+ public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = null,
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
+ }
+
+ [Test]
+ public void should_sum_AudioChannelPositions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "2/0/0",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
+ }
+
+ [Test]
+ public void should_sum_AudioChannelPositions_including_decimal()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "3/2/0.1",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
+ }
+
+ [Test]
+ public void should_cleanup_extraneous_text_from_AudioChannelPositions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "Object Based / 3/2/2.1",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
+ }
+
+ [Test]
+ public void should_skip_empty_groups_in_AudioChannelPositions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = " / 2/0/0.0",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
+ }
+
+ [Test]
+ public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
+ }
+
+ [Test]
+ public void should_sum_dual_mono_representation_AudioChannelPositions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "1+1",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
+ }
+
+ [Test]
+ public void should_use_AudioChannelPositionText_when_AudioChannelChannelPosition_is_invalid()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 6,
+ AudioChannelPositions = "15 objects",
+ AudioChannelPositionsText = "15 objects / Front: L C R, Side: L R, LFE",
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
+ }
+
+ [Test]
+ public void should_remove_atmos_objects_from_AudioChannelPostions()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioChannels = 2,
+ AudioChannelPositions = "15 objects / 3/2.1",
+ AudioChannelPositionsText = null,
+ SchemaRevision = 3
+ };
+
+ MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs
new file mode 100644
index 0000000000..cfdbb02da2
--- /dev/null
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs
@@ -0,0 +1,73 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.MediaFiles.MediaInfo;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
+{
+ [TestFixture]
+ public class FormatAudioCodecFixture : TestBase
+ {
+ private static string sceneName = "My.Series.S01E01-Sonarr";
+
+ [TestCase("AC-3", "AC3")]
+ [TestCase("E-AC-3", "EAC3")]
+ [TestCase("MPEG Audio", "MPEG Audio")]
+ [TestCase("DTS", "DTS")]
+ public void should_format_audio_format_legacy(string audioFormat, string expectedFormat)
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioFormat = audioFormat
+ };
+
+ MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
+ }
+
+ [TestCase("MPEG Audio, A_MPEG/L2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
+ [TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget)", "DB Super HDTV", "Vorbis")]
+ [TestCase("PCM, 1, , ", "DW DVDRip XviD-idTV", "PCM")] // Dubbed most likely
+ [TestCase("TrueHD, A_TRUEHD, , ", "", "TrueHD")]
+ [TestCase("WMA, 161, , ", "Droned.wmv", "WMA")]
+ [TestCase("WMA, 162, Pro, ", "B.N.S04E18.720p.WEB-DL", "WMA")]
+ [TestCase("Opus, A_OPUS, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
+ [TestCase("mp3 , 0, , ", "climbing.mp4", "MP3")]
+ public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
+ {
+ var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioFormat = split[0],
+ AudioCodecID = split[1],
+ AudioProfile = split[2],
+ AudioCodecLibrary = split[3]
+ };
+
+ MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
+ }
+
+ [Test]
+ public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioFormat = "MPEG Audio",
+ AudioProfile = "Layer 3"
+ };
+
+ MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be("MP3");
+ }
+
+ [Test]
+ public void should_return_AudioFormat_by_default()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ AudioFormat = "Other Audio Format",
+ AudioCodecID = "Other Audio Codec"
+ };
+
+ MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(mediaInfoModel.AudioFormat);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs
new file mode 100644
index 0000000000..fa9d6b8a7a
--- /dev/null
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs
@@ -0,0 +1,92 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.MediaFiles.MediaInfo;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
+{
+ [TestFixture]
+ public class FormatVideoCodecFixture : TestBase
+ {
+ [TestCase("AVC", null, "x264")]
+ [TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
+ [TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
+ [TestCase("V_MPEGH/ISO/HEVC", null, "x265")]
+ [TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
+ [TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
+ [TestCase("MPEG-2 Video", null, "MPEG2")]
+ public void should_format_video_codec_with_source_title_legacy(string videoCodec, string sceneName, string expectedFormat)
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ VideoCodec = videoCodec
+ };
+
+ MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
+ }
+
+ [TestCase("MPEG Video, 2, Main@High, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
+ [TestCase("MPEG Video, V_MPEG2, Main@High, ", "", "MPEG2")]
+ [TestCase("MPEG Video, , , ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
+ [TestCase("VC-1, WVC1, Advanced@L4, ", "B.N.S04E18.720p.WEB-DL", "VC1")]
+ [TestCase("VC-1, V_MS/VFW/FOURCC / WVC1, Advanced@L3, ", "", "VC1")]
+ [TestCase("VC-1, WMV3, MP@LL, ", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
+ [TestCase("V.MPEG4/ISO/AVC, V.MPEG4/ISO/AVC, , ", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
+ [TestCase("WMV2, WMV2, , ", "Droned.wmv", "WMV")]
+ [TestCase("xvid, xvid, , ", "", "XviD")]
+ [TestCase("div3, div3, , ", "spsm.dvdrip.divx.avi'.", "DivX")]
+ [TestCase("VP6, 4, , ", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
+ [TestCase("VP7, VP70, General, ", "Sweet Seymour.avi", "VP7")]
+ [TestCase("VP8, V_VP8, , ", "Dick.mkv", "VP8")]
+ [TestCase("VP9, V_VP9, , ", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
+ [TestCase("x264, x264, , ", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
+ [TestCase("V_MPEGH/ISO/HEVC, V_MPEGH/ISO/HEVC, , ", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
+ [TestCase("MPEG-4 Visual, 20, Simple@L1, Lavc52.29.0", "Will.And.Grace.S08E14.WS.DVDrip.XviD.I.Love.L.Gay-Obfuscated", "XviD")]
+ [TestCase("MPEG-4 Visual, 20, Advanced Simple@L5, XviD0046", "", "XviD")]
+ [TestCase("mp4v, mp4v, , ", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
+ public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
+ {
+ var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
+ var mediaInfoModel = new MediaInfoModel
+ {
+ VideoFormat = split[0],
+ VideoCodecID = split[1],
+ VideoProfile = split[2],
+ VideoCodecLibrary = split[3]
+ };
+
+ MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
+ }
+
+ [TestCase("AVC, AVC, , x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
+ [TestCase("HEVC, HEVC, , x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
+ [TestCase("AVC, AVC, , ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
+ [TestCase("HEVC, HEVC, , ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
+ [TestCase("AVC, AVC, , ", "Some.Video.S01E01", "h264")] // Default value
+ [TestCase("HEVC, HEVC, , ", "Some.Video.S01E01", "h265")] // Default value
+ public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
+ {
+ var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
+ var mediaInfoModel = new MediaInfoModel
+ {
+ VideoFormat = split[0],
+ VideoCodecID = split[1],
+ VideoProfile = split[2],
+ VideoCodecLibrary = split[3]
+ };
+
+ MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
+ }
+
+ [Test]
+ public void should_return_VideoFormat_by_default()
+ {
+ var mediaInfoModel = new MediaInfoModel
+ {
+ VideoFormat = "VideoCodec"
+ };
+
+ MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, null).Should().Be(mediaInfoModel.VideoFormat);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs
index 10f78375f9..c41d09ba43 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs
@@ -16,15 +16,15 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[TestFixture]
public class UpdateMediaInfoServiceFixture : CoreTest
{
- private Movie _series;
+ private Movie _movie;
[SetUp]
public void Setup()
{
- _series = new Movie
+ _movie = new Movie
{
Id = 1,
- Path = @"C:\series".AsOsAgnostic()
+ Path = @"C:\movie".AsOsAgnostic()
};
Mocker.GetMock()
@@ -60,7 +60,7 @@ public void should_skip_up_to_date_media_info()
.All()
.With(v => v.RelativePath = "media.mkv")
.TheFirst(1)
- .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = 3 })
+ .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
.BuildList();
Mocker.GetMock()
@@ -70,10 +70,36 @@ public void should_skip_up_to_date_media_info()
GivenFileExists();
GivenSuccessfulScan();
- Subject.Handle(new MovieScannedEvent(_series));
+ Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock()
- .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2));
+ .Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
+
+ Mocker.GetMock()
+ .Verify(v => v.Update(It.IsAny()), Times.Exactly(2));
+ }
+
+ [Test]
+ public void should_skip_not_yet_date_media_info()
+ {
+ var episodeFiles = Builder.CreateListOfSize(3)
+ .All()
+ .With(v => v.RelativePath = "media.mkv")
+ .TheFirst(1)
+ .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
+ .BuildList();
+
+ Mocker.GetMock()
+ .Setup(v => v.GetFilesByMovie(1))
+ .Returns(episodeFiles);
+
+ GivenFileExists();
+ GivenSuccessfulScan();
+
+ Subject.Handle(new MovieScannedEvent(_movie));
+
+ Mocker.GetMock()
+ .Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
Mocker.GetMock()
.Verify(v => v.Update(It.IsAny()), Times.Exactly(2));
@@ -96,10 +122,10 @@ public void should_update_outdated_media_info()
GivenFileExists();
GivenSuccessfulScan();
- Subject.Handle(new MovieScannedEvent(_series));
+ Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock()
- .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(3));
+ .Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(3));
Mocker.GetMock()
.Verify(v => v.Update(It.IsAny()), Times.Exactly(3));
@@ -119,7 +145,7 @@ public void should_ignore_missing_files()
GivenSuccessfulScan();
- Subject.Handle(new MovieScannedEvent(_series));
+ Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock()
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
@@ -144,12 +170,12 @@ public void should_continue_after_failure()
GivenFileExists();
GivenSuccessfulScan();
- GivenFailedScan(Path.Combine(_series.Path, "media2.mkv"));
+ GivenFailedScan(Path.Combine(_movie.Path, "media2.mkv"));
- Subject.Handle(new MovieScannedEvent(_series));
+ Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock()
- .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(1));
+ .Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(1));
Mocker.GetMock()
.Verify(v => v.Update(It.IsAny()), Times.Exactly(1));
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
index 617a4e41cc..42ad53e72b 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
@@ -31,10 +31,8 @@ public void get_runtime()
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
Subject.GetRunTime(path).Seconds.Should().Be(10);
-
}
-
[Test]
public void get_info()
{
@@ -42,21 +40,27 @@ public void get_info()
var info = Subject.GetMediaInfo(path);
-
+ info.VideoFormat.Should().Be("AVC");
+ info.VideoCodecID.Should().Be("avc1");
+ info.VideoProfile.Should().Be("Baseline@L2.1");
+ info.VideoCodecLibrary.Should().Be("");
+ info.VideoMultiViewCount.Should().Be(0);
+ info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
+ info.VideoTransferCharacteristics.Should().Be("BT.709");
+ info.AudioFormat.Should().Be("AAC");
+ info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
+ info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannels.Should().Be(2);
- info.AudioFormat.Should().Be("AAC");
info.AudioLanguages.Should().Be("English");
- info.AudioProfile.Should().Be("LC");
+ info.AudioAdditionalFeatures.Should().Be("");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
- info.VideoCodec.Should().Be("AVC");
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
-
}
[Test]
@@ -73,20 +77,27 @@ public void get_info_unicode()
var info = Subject.GetMediaInfo(path);
+ info.VideoFormat.Should().Be("AVC");
+ info.VideoCodecID.Should().Be("avc1");
+ info.VideoProfile.Should().Be("Baseline@L2.1");
+ info.VideoCodecLibrary.Should().Be("");
+ info.VideoMultiViewCount.Should().Be(0);
+ info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
+ info.VideoTransferCharacteristics.Should().Be("BT.709");
+ info.AudioFormat.Should().Be("AAC");
+ info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
+ info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannels.Should().Be(2);
- info.AudioFormat.Should().Be("AAC");
info.AudioLanguages.Should().Be("English");
- info.AudioProfile.Should().Be("LC");
+ info.AudioAdditionalFeatures.Should().Be("");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
- info.VideoCodec.Should().Be("AVC");
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
-
}
[Test]
@@ -100,23 +111,5 @@ public void should_dispose_file_after_scanning_mediainfo()
stream.Close();
}
-
- [Test]
- [TestCase("/ Front: L R", 2.0)]
- public void should_correctly_read_audio_channels(string ChannelPositions, decimal formattedChannels)
- {
- var info = new MediaInfoModel()
- {
- VideoCodec = "AVC",
- AudioFormat = "DTS",
- AudioLanguages = "English",
- Subtitles = "English",
- AudioChannels = 2,
- AudioChannelPositions = ChannelPositions,
- SchemaRevision = 3,
- };
-
- info.FormattedAudioChannels.Should().Be(formattedChannels);
- }
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs
index 88cb9a64a9..39ab4a6bd3 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs
@@ -10,6 +10,7 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles
{
@@ -58,6 +59,8 @@ public void should_not_update_if_unable_to_parse()
{
ExecuteCommand();
+ ExceptionVerification.ExpectedWarns(1);
+
Mocker.GetMock().Verify(s => s.Update(It.IsAny()), Times.Never());
}
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index bfd83ac263..695786ddbb 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -168,6 +168,7 @@
+
@@ -314,6 +315,9 @@
+
+
+
@@ -580,4 +584,4 @@
-->
-
\ No newline at end of file
+
diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
index 38d0e5aa7b..f95e3171f0 100644
--- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
+++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
@@ -260,14 +260,14 @@ public void should_format_mediainfo_properly()
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
- VideoCodec = "AVC",
+ VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English/Spanish",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
- .Should().Be("South.Park.X264.DTS[EN+ES].[EN+ES+IT]");
+ .Should().Be("South.Park.H264.DTS[EN+ES].[EN+ES+IT]");
}
[Test]
@@ -277,14 +277,52 @@ public void should_exclude_english_in_mediainfo_audio_language()
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
- VideoCodec = "AVC",
+ VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
- .Should().Be("South.Park.X264.DTS.[EN+ES+IT]");
+ .Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
+ }
+
+ [Test]
+ public void should_format_mediainfo_3d_properly()
+ {
+ _namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.3D}.{MediaInfo.Simple}";
+
+ _movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
+ {
+ VideoFormat = "AVC",
+ VideoMultiViewCount = 2,
+ AudioFormat = "DTS",
+ AudioLanguages = "English",
+ Subtitles = "English/Spanish/Italian"
+ };
+
+ Subject.BuildFileName(_movie, _movieFile)
+ .Should().Be("South.Park.3D.h264.DTS");
+ }
+
+ [Test]
+ public void should_format_mediainfo_hdr_properly()
+ {
+ _namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.HDR}.{MediaInfo.Simple}";
+
+ _movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
+ {
+ VideoFormat = "AVC",
+ VideoBitDepth = 10,
+ VideoColourPrimaries = "BT.2020",
+ VideoTransferCharacteristics = "PQ",
+ AudioFormat = "DTS",
+ AudioLanguages = "English",
+ Subtitles = "English/Spanish/Italian"
+ };
+
+ Subject.BuildFileName(_movie, _movieFile)
+ .Should().Be("South.Park.HDR.h264.DTS");
}
[Test]
diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
index 7e50e69460..97772a131f 100644
--- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
+++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
@@ -236,6 +236,8 @@ public void should_parse_bluray576p_quality(string title)
[TestCase("Contract.to.Kill.2016.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-iFT")]
[TestCase("27.Dresses.2008.REMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
[TestCase("27.Dresses.2008.BDREMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
+ [TestCase("The.Stoning.of.Soraya.M.2008.USA.BluRay.Remux.1080p.MPEG-2.DD.5.1-TDD")]
+ [TestCase("Wildling.2018.1080p.BluRay.REMUX.MPEG-2.DTS-HD.MA.5.1-EPSiLON")]
public void should_parse_remux1080p_quality(string title)
{
ParseAndVerifyQuality(title, Source.BLURAY, false, Resolution.R1080P, Modifier.REMUX);
@@ -344,7 +346,10 @@ public void should_parse_hardcoded_subs(string postTitle, string sub)
private void ParseAndVerifyQuality(string title, Source source, bool proper, Resolution resolution, Modifier modifier = Modifier.NONE)
{
var result = QualityParser.ParseQuality(title);
- result.Resolution.Should().Be(resolution);
+ if (resolution != Resolution.Unknown)
+ {
+ result.Resolution.Should().Be(resolution);
+ }
result.Source.Should().Be(source);
if (modifier != Modifier.NONE)
{
diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs
index ee5065f813..9a603b7da2 100644
--- a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs
+++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
@@ -22,6 +23,8 @@ public class DiskSpaceService : IDiskSpaceService
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
+ private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc|snap)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
+
public DiskSpaceService(IMovieService movieService, IConfigService configService, IDiskProvider diskProvider, Logger logger)
{
_movieService = movieService;
@@ -59,7 +62,10 @@ private IEnumerable GetDroneFactoryFreeSpace()
private IEnumerable GetFixedDisksFreeSpace()
{
- return GetDiskSpace(_diskProvider.GetMounts().Where(d => d.DriveType == DriveType.Fixed).Select(d => d.RootDirectory), true);
+ return GetDiskSpace(_diskProvider.GetMounts()
+ .Where(d => d.DriveType == DriveType.Fixed)
+ .Where(d => !_regexSpecialDrive.IsMatch(d.RootDirectory))
+ .Select(d => d.RootDirectory), true);
}
private IEnumerable GetDiskSpace(IEnumerable paths, bool suppressWarnings = false)
diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
index 51f898ea32..6b3c6980c0 100644
--- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
+++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
@@ -12,6 +12,7 @@
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
@@ -32,7 +33,7 @@ public XbmcMetadata(IDetectXbmcNfo detectNfo,
_mediaCoverService = mediaCoverService;
_diskProvider = diskProvider;
_detectNfo = detectNfo;
-
+
}
private static readonly Regex MovieImagesRegex = new Regex(@"^(?poster|banner|fanart|clearart|discart|landscape|logo|backdrop|clearlogo)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -157,13 +158,15 @@ public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFil
if (movieFile.MediaInfo != null)
{
+ var sceneName = movieFile.GetSceneOrFileName();
+
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float)movieFile.MediaInfo.Width / (float)movieFile.MediaInfo.Height));
video.Add(new XElement("bitrate", movieFile.MediaInfo.VideoBitrate));
- video.Add(new XElement("codec", movieFile.MediaInfo.VideoCodec));
+ video.Add(new XElement("codec", MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName)));
video.Add(new XElement("framerate", movieFile.MediaInfo.VideoFps));
video.Add(new XElement("height", movieFile.MediaInfo.Height));
video.Add(new XElement("scantype", movieFile.MediaInfo.ScanType));
@@ -180,7 +183,7 @@ public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFil
var audio = new XElement("audio");
audio.Add(new XElement("bitrate", movieFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", movieFile.MediaInfo.AudioChannels));
- audio.Add(new XElement("codec", GetAudioCodec(movieFile.MediaInfo.AudioFormat)));
+ audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName)));
audio.Add(new XElement("language", movieFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
@@ -251,16 +254,6 @@ private string GetMovieMetadataFilename(string movieFilePath)
}
}
- private string GetAudioCodec(string audioCodec)
- {
- if (audioCodec == "AC-3")
- {
- return "AC3";
- }
-
- return audioCodec;
- }
-
private bool GetExistingWatchedStatus(Movie movie, string movieFilePath)
{
var fullPath = Path.Combine(movie.Path, GetMovieMetadataFilename(movieFilePath));
diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
index 597817ac62..1552706898 100644
--- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
+++ b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs
@@ -46,6 +46,7 @@ public void Execute(MoviesSearchCommand message)
if (!movies.Monitored)
{
_logger.Debug("Movie {0} is not monitored, skipping search", movies.Title);
+ continue;
}
var decisions = _nzbSearchService.MovieSearch(movieId, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual);
diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs
index 4ad1b65df5..7133803ca7 100644
--- a/src/NzbDrone.Core/Jobs/TaskManager.cs
+++ b/src/NzbDrone.Core/Jobs/TaskManager.cs
@@ -71,7 +71,7 @@ public void Handle(ApplicationStartedEvent message)
var defaultTasks = new[]
{
- new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
+ new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
new ScheduledTask{ Interval = 1*60, TypeName = typeof(PreDBSyncCommand).FullName},
new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName},
new ScheduledTask{ Interval = updateInterval, TypeName = typeof(ApplicationUpdateCommand).FullName},
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
new file mode 100644
index 0000000000..a2d6f9f71e
--- /dev/null
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
@@ -0,0 +1,470 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using NLog;
+using NLog.Fluent;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Instrumentation;
+using NzbDrone.Common.Instrumentation.Extensions;
+
+namespace NzbDrone.Core.MediaFiles.MediaInfo
+{
+ public static class MediaInfoFormatter
+ {
+ private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
+
+ public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
+ {
+ var audioChannels = FormatAudioChannelsFromAudioChannelPositions(mediaInfo);
+
+ if (audioChannels == null)
+ {
+ audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
+ }
+
+ if (audioChannels == null)
+ {
+ audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
+ }
+
+ return audioChannels ?? 0;
+ }
+
+ public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
+ {
+ if (mediaInfo.AudioCodecID == null)
+ {
+ return FormatAudioCodecLegacy(mediaInfo, sceneName);
+ }
+
+ var audioFormat = mediaInfo.AudioFormat;
+ var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
+ var audioProfile = mediaInfo.AudioProfile ?? string.Empty;
+ var audioAdditionalFeatures = mediaInfo.AudioAdditionalFeatures ?? string.Empty;
+ var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty;
+
+ if (audioFormat.IsNullOrWhiteSpace())
+ {
+ return string.Empty;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AC-3"))
+ {
+ return "AC3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("E-AC-3"))
+ {
+ return "EAC3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AAC"))
+ {
+ if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
+ {
+ return "HE-AAC";
+ }
+
+ return "AAC";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("DTS"))
+ {
+ if (audioAdditionalFeatures.StartsWithIgnoreCase("XLL"))
+ {
+ if (audioAdditionalFeatures.EndsWithIgnoreCase("X"))
+ {
+ return "DTS-X";
+ }
+
+ return "DTS-HD MA";
+ }
+
+ if (audioAdditionalFeatures.EqualsIgnoreCase("ES"))
+ {
+ return "DTS-ES";
+ }
+
+ if (audioAdditionalFeatures.EqualsIgnoreCase("XBR"))
+ {
+ return "DTS-HD HRA";
+ }
+
+ return "DTS";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("FLAC"))
+ {
+ return "FLAC";
+ }
+
+ if (audioFormat.Trim().EqualsIgnoreCase("mp3"))
+ {
+ return "MP3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("MPEG Audio"))
+ {
+ if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
+ {
+ return "MP3";
+ }
+
+ if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
+ {
+ return "MP2";
+ }
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Opus"))
+ {
+ return "Opus";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("PCM"))
+ {
+ return "PCM";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("MLP FBA"))
+ {
+ if (audioAdditionalFeatures == "16-ch")
+ {
+ return "TrueHD Atmos";
+ }
+
+ return "TrueHD";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Vorbis"))
+ {
+ return "Vorbis";
+ }
+
+ if (audioFormat == "WMA")
+ {
+ return "WMA";
+ }
+
+ Logger.Debug("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioAdditionalFeatures, audioCodecLibrary), sceneName);
+
+ return audioFormat;
+ }
+
+ public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
+ {
+ var audioFormat = mediaInfo.AudioFormat;
+
+ if (audioFormat.IsNullOrWhiteSpace())
+ {
+ return audioFormat;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AC-3"))
+ {
+ return "AC3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("E-AC-3"))
+ {
+ return "EAC3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AAC"))
+ {
+ return "AAC";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
+ {
+ return "MP3";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("DTS"))
+ {
+ return "DTS";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("TrueHD"))
+ {
+ return "TrueHD";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("FLAC"))
+ {
+ return "FLAC";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Vorbis"))
+ {
+ return "Vorbis";
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Opus"))
+ {
+ return "Opus";
+ }
+
+ return audioFormat;
+ }
+
+ public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
+ {
+ if (mediaInfo.VideoFormat == null)
+ {
+ return FormatVideoCodecLegacy(mediaInfo, sceneName);
+ }
+
+ var videoFormat = mediaInfo.VideoFormat;
+ var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
+ var videoProfile = mediaInfo.VideoProfile ?? string.Empty;
+ var videoCodecLibrary = mediaInfo.VideoCodecLibrary ?? string.Empty;
+
+ var result = videoFormat;
+
+ if (videoFormat.IsNullOrWhiteSpace())
+ {
+ return result;
+ }
+
+ if (videoFormat == "x264")
+ {
+ return "x264";
+ }
+
+ if (videoFormat == "AVC" || videoFormat == "V.MPEG4/ISO/AVC")
+ {
+ if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
+ {
+ return "x264";
+ }
+
+ return GetSceneNameMatch(sceneName, "AVC", "x264", "h264");
+ }
+
+ if (videoFormat == "HEVC" || videoFormat == "V_MPEGH/ISO/HEVC")
+ {
+ if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
+ {
+ return "x265";
+ }
+
+ return GetSceneNameMatch(sceneName, "HEVC", "x265", "h265");
+ }
+
+ if (videoFormat == "MPEG Video")
+ {
+ if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
+ {
+ return "MPEG2";
+ }
+
+ if (videoCodecID.IsNullOrWhiteSpace())
+ {
+ return "MPEG";
+ }
+ }
+
+ if (videoFormat == "MPEG-2 Video")
+ {
+ return "MPEG2";
+ }
+
+ if (videoFormat == "MPEG-4 Visual")
+ {
+ if (videoCodecID.ContainsIgnoreCase("XVID") ||
+ videoCodecLibrary.StartsWithIgnoreCase("XviD"))
+ {
+ return "XviD";
+ }
+
+ if (videoCodecID.ContainsIgnoreCase("DIV3") ||
+ videoCodecID.ContainsIgnoreCase("DIVX") ||
+ videoCodecID.ContainsIgnoreCase("DX50") ||
+ videoCodecLibrary.StartsWithIgnoreCase("DivX"))
+ {
+ return "DivX";
+ }
+ }
+
+ if (videoFormat == "MPEG-4 Visual" || videoFormat == "mp4v")
+ {
+ result = GetSceneNameMatch(sceneName, "XviD", "DivX", "");
+ if (result.IsNotNullOrWhiteSpace())
+ {
+ return result;
+ }
+ }
+
+ if (videoFormat == "VC-1")
+ {
+ return "VC1";
+ }
+
+ if (videoFormat.EqualsIgnoreCase("VP6") || videoFormat.EqualsIgnoreCase("VP7") ||
+ videoFormat.EqualsIgnoreCase("VP8") || videoFormat.EqualsIgnoreCase("VP9"))
+ {
+ return videoFormat.ToUpperInvariant();
+ }
+
+ if (videoFormat == "WMV2")
+ {
+ return "WMV";
+ }
+
+ if (videoFormat.EqualsIgnoreCase("DivX") || videoFormat.EqualsIgnoreCase("div3"))
+ {
+ return "DivX";
+ }
+
+ if (videoFormat.EqualsIgnoreCase("XviD"))
+ {
+ return "XviD";
+ }
+
+ Logger.Debug("Unknown video format: '{0}' in '{1}'.", string.Join(", ", videoFormat, videoCodecID, videoProfile, videoCodecLibrary), sceneName);
+
+ return result;
+ }
+
+ public static string FormatVideoCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
+ {
+ var videoCodec = mediaInfo.VideoCodec;
+
+ if (videoCodec.IsNullOrWhiteSpace())
+ {
+ return videoCodec;
+ }
+
+ if (videoCodec == "AVC")
+ {
+ return GetSceneNameMatch(sceneName, "AVC", "h264", "x264");
+ }
+
+ if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
+ {
+ return GetSceneNameMatch(sceneName, "HEVC", "h265", "x265");
+ }
+
+ if (videoCodec == "MPEG-2 Video")
+ {
+ return "MPEG2";
+ }
+
+ if (videoCodec == "MPEG-4 Visual")
+ {
+ return GetSceneNameMatch(sceneName, "DivX", "XviD");
+ }
+
+ if (videoCodec.StartsWithIgnoreCase("XviD"))
+ {
+ return "XviD";
+ }
+
+ if (videoCodec.StartsWithIgnoreCase("DivX"))
+ {
+ return "DivX";
+ }
+
+ if (videoCodec.EqualsIgnoreCase("VC-1"))
+ {
+ return "VC1";
+ }
+
+ return videoCodec;
+ }
+
+ private static decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfoModel mediaInfo)
+ {
+ var audioChannelPositions = mediaInfo.AudioChannelPositions;
+
+ if (audioChannelPositions.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ try
+ {
+ Logger.Debug("Formatting audio channels using 'AudioChannelPositions', with a value of: '{0}'", audioChannelPositions);
+
+ if (audioChannelPositions.Contains("+"))
+ {
+ return audioChannelPositions.Split('+')
+ .Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture));
+ }
+
+ if (audioChannelPositions.Contains("/"))
+ {
+ return Regex.Replace(audioChannelPositions, @"^\d+\sobjects", "", RegexOptions.Compiled | RegexOptions.IgnoreCase)
+ .Replace("Object Based / ", "")
+ .Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries)
+ .FirstOrDefault()
+ ?.Split('/')
+ .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Warn(e, "Unable to format audio channels using 'AudioChannelPositions'");
+ }
+
+ return null;
+ }
+
+ private static decimal? FormatAudioChannelsFromAudioChannelPositionsText(MediaInfoModel mediaInfo)
+ {
+ var audioChannelPositionsText = mediaInfo.AudioChannelPositionsText;
+ var audioChannels = mediaInfo.AudioChannels;
+
+ if (audioChannelPositionsText.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ try
+ {
+ Logger.Debug("Formatting audio channels using 'AudioChannelPositionsText', with a value of: '{0}'", audioChannelPositionsText);
+
+ return audioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels;
+ }
+ catch (Exception e)
+ {
+ Logger.Warn(e, "Unable to format audio channels using 'AudioChannelPositionsText'");
+ }
+
+ return null;
+ }
+
+ private static decimal? FormatAudioChannelsFromAudioChannels(MediaInfoModel mediaInfo)
+ {
+ var audioChannels = mediaInfo.AudioChannels;
+
+ if (mediaInfo.SchemaRevision >= 3)
+ {
+ Logger.Debug("Formatting audio channels using 'AudioChannels', with a value of: '{0}'", audioChannels);
+
+ return audioChannels;
+ }
+
+ return null;
+ }
+
+ private static string GetSceneNameMatch(string sceneName, params string[] tokens)
+ {
+ sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
+
+ foreach (var token in tokens)
+ {
+ if (sceneName.ContainsIgnoreCase(token))
+ {
+ return token;
+ }
+ }
+
+ // Last token is the default.
+ return tokens.Last();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
index 8d8b0342e8..fe62216cd1 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
@@ -1,7 +1,6 @@
using System;
using System.Globalization;
using System.Linq;
-using System.Linq.Expressions;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
@@ -10,12 +9,23 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class MediaInfoModel : IEmbeddedDocument
{
+ public string ContainerFormat { get; set; }
public string VideoCodec { get; set; }
+ public string VideoFormat { get; set; }
+ public string VideoCodecID { get; set; }
+ public string VideoProfile { get; set; }
+ public string VideoCodecLibrary { get; set; }
public int VideoBitrate { get; set; }
public int VideoBitDepth { get; set; }
+ public int VideoMultiViewCount { get; set; }
+ public string VideoColourPrimaries { get; set; }
+ public string VideoTransferCharacteristics { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string AudioFormat { get; set; }
+ public string AudioCodecID { get; set; }
+ public string AudioCodecLibrary { get; set; }
+ public string AudioAdditionalFeatures { get; set; }
public int AudioBitrate { get; set; }
public TimeSpan RunTime { get; set; }
public int AudioStreamCount { get; set; }
@@ -28,40 +38,5 @@ public class MediaInfoModel : IEmbeddedDocument
public string Subtitles { get; set; }
public string ScanType { get; set; }
public int SchemaRevision { get; set; }
-
- [JsonIgnore]
- public decimal FormattedAudioChannels
- {
- get
- {
- try
- {
- return
- AudioChannelPositions.Replace("Object Based /", "").Replace(" / ", "$")
- .Split('$')
- .First()
- .Split('/')
- .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
- }
- catch
- {
-
- if (AudioChannelPositionsText.IsNullOrWhiteSpace())
- {
- if (SchemaRevision >= 3)
- {
- return AudioChannels;
- }
-
- return 0;
- }
-
- return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels;
-
-
- }
-
- }
- }
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs
index e1ab325d9b..0c10a33c8a 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs
@@ -18,8 +18,6 @@ public class UpdateMediaInfoService : IHandle
private readonly IConfigService _configService;
private readonly Logger _logger;
- private const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 3;
-
public UpdateMediaInfoService(IDiskProvider diskProvider,
IMediaFileService mediaFileService,
IVideoFileInfoReader videoFileInfoReader,
@@ -49,7 +47,6 @@ private void UpdateMediaInfo(Movie movie, List mediaFiles)
if (mediaFile.MediaInfo != null)
{
- mediaFile.MediaInfo.SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION;
_mediaFileService.Update(mediaFile);
_logger.Debug("Updated MediaInfo for '{0}'", path);
}
@@ -65,7 +62,7 @@ public void Handle(MovieScannedEvent message)
}
var allMediaFiles = _mediaFileService.GetFilesByMovie(message.Movie.Id);
- var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
+ var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
UpdateMediaInfo(message.Movie, filteredMediaFiles);
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
index 5f141b1886..a3f03eed15 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
@@ -17,6 +17,8 @@ public class VideoFileInfoReader : IVideoFileInfoReader
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
+ public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 4;
+ public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 5;
public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger)
{
@@ -90,6 +92,7 @@ public MediaInfoModel GetMediaInfo(string filename)
int audioChannels;
int videoBitDepth;
decimal videoFrameRate;
+ int videoMultiViewCount;
string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
@@ -102,65 +105,60 @@ public MediaInfoModel GetMediaInfo(string filename)
}
decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth);
+ int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "MultiView_Count"), out videoMultiViewCount);
//Runtime
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);
- string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate");
- int aBindex = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
- if (aBindex > 0)
- {
- aBitRate = aBitRate.Remove(aBindex);
- }
+ string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
int.TryParse(aBitRate, out audioBitRate);
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
-
- string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
- int aCindex = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
-
- if (aCindex > 0)
- {
- audioChannelsStr = audioChannelsStr.Remove(aCindex);
- }
+ string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
- string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile");
- int aPindex = audioProfile.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
-
- if (aPindex > 0)
- {
- audioProfile = audioProfile.Remove(aPindex);
- }
+ 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();
int.TryParse(audioChannelsStr, out audioChannels);
var mediaInfoModel = new MediaInfoModel
- {
- VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
- VideoBitrate = videoBitRate,
- VideoBitDepth = videoBitDepth,
- Height = height,
- Width = width,
- AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
- AudioBitrate = audioBitRate,
- RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
- AudioStreamCount = streamCount,
- AudioChannels = audioChannels,
- AudioChannelPositions = audioChannelPositions,
- AudioChannelPositionsText = audioChannelPositionsText,
- AudioProfile = audioProfile.Trim(),
- VideoFps = videoFrameRate,
- AudioLanguages = audioLanguages,
- Subtitles = subtitles,
- ScanType = scanType
- };
+ {
+ 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"),
+ VideoBitrate = videoBitRate,
+ VideoBitDepth = videoBitDepth,
+ VideoMultiViewCount = videoMultiViewCount,
+ VideoColourPrimaries = mediaInfo.Get(StreamKind.Video, 0, "colour_primaries"),
+ VideoTransferCharacteristics = mediaInfo.Get(StreamKind.Video, 0, "transfer_characteristics"),
+ Height = height,
+ Width = width,
+ AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
+ AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
+ AudioProfile = audioProfile,
+ AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
+ AudioAdditionalFeatures = mediaInfo.Get(StreamKind.Audio, 0, "Format_AdditionalFeatures"),
+ AudioBitrate = audioBitRate,
+ RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
+ AudioStreamCount = streamCount,
+ AudioChannels = audioChannels,
+ AudioChannelPositions = audioChannelPositions,
+ AudioChannelPositionsText = audioChannelPositionsText,
+ VideoFps = videoFrameRate,
+ AudioLanguages = audioLanguages,
+ Subtitles = subtitles,
+ ScanType = scanType,
+ SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION
+ };
return mediaInfoModel;
}
@@ -175,7 +173,7 @@ public MediaInfoModel GetMediaInfo(string filename)
}
catch (Exception ex)
{
- _logger.Error(ex, "Unable to parse media info from file: " + filename);
+ _logger.Error(ex, "Unable to parse media info from file: {0}", filename);
}
finally
{
diff --git a/src/NzbDrone.Core/MediaFiles/MovieFile.cs b/src/NzbDrone.Core/MediaFiles/MovieFile.cs
index 0414611c87..da51c9f3a8 100644
--- a/src/NzbDrone.Core/MediaFiles/MovieFile.cs
+++ b/src/NzbDrone.Core/MediaFiles/MovieFile.cs
@@ -6,6 +6,7 @@
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies;
using NzbDrone.Core.MediaFiles.MediaInfo;
+using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.MediaFiles
{
@@ -27,5 +28,20 @@ public override string ToString()
{
return string.Format("[{0}] {1}", Id, RelativePath);
}
+
+ public string GetSceneOrFileName()
+ {
+ if (SceneName.IsNotNullOrWhiteSpace())
+ {
+ return SceneName;
+ }
+
+ if (RelativePath.IsNotNullOrWhiteSpace())
+ {
+ return System.IO.Path.GetFileName(RelativePath);
+ }
+
+ return string.Empty;
+}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs
index 9b6899253c..610e61f7b1 100644
--- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs
+++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using NLog;
+using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@@ -37,6 +38,7 @@ public class ImportDecisionMaker : IMakeImportDecision
private readonly IQualityDefinitionService _qualitiesService;
private readonly IConfigService _config;
private readonly IHistoryService _historyService;
+ private readonly ICached _warnedFiles;
private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable specifications,
@@ -48,6 +50,7 @@ public ImportDecisionMaker(IEnumerable speci
IQualityDefinitionService qualitiesService,
IConfigService config,
IHistoryService historyService,
+ ICacheManager cacheManager,
Logger logger)
{
_specifications = specifications;
@@ -59,6 +62,7 @@ public ImportDecisionMaker(IEnumerable speci
_qualitiesService = qualitiesService;
_config = config;
_historyService = historyService;
+ _warnedFiles = cacheManager.GetCache(this.GetType());
_logger = logger;
}
@@ -148,7 +152,16 @@ private ImportDecision GetDecision(string file, Movie movie, DownloadClientItem
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(file)))
{
- _logger.Warn("Unable to parse movie info from path {0}", file);
+ if (_warnedFiles.Find(file) == null)
+ {
+ _warnedFiles.Set(file, "warned");
+ _logger.Warn("Unable to parse movie info from path {0}", file);
+ }
+ else
+ {
+ _logger.Trace("Already warned user that we are unable to parse movie info from path: {0}", file);
+ }
+
}
decision = new ImportDecision(localMovie, new Rejection("Unable to parse file"));
diff --git a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs b/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs
index ece7770537..d77f94001e 100644
--- a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs
+++ b/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
+using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles.Commands;
@@ -9,6 +10,7 @@
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
+using RestSharp.Extensions;
namespace NzbDrone.Core.MediaFiles
{
@@ -51,6 +53,7 @@ public void Execute(UpdateMovieFileQualityCommand command)
var history = _historyService.FindByMovieId(movieFile.MovieId).OrderByDescending(h => h.Date);
var latestImported = history.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadFolderImported);
+ var latestImportedName = latestImported?.SourceTitle;
var latestGrabbed = history.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
var sizeMovie = new LocalMovie();
sizeMovie.Size = movieFile.Size;
@@ -67,7 +70,18 @@ public void Execute(UpdateMovieFileQualityCommand command)
helpers.Add(latestGrabbed);
}
- var parsedMovieInfo = _parsingService.ParseMovieInfo(latestImported?.SourceTitle ?? movieFile.RelativePath, helpers);
+ ParsedMovieInfo parsedMovieInfo = null;
+
+ if (latestImportedName?.IsNotNullOrWhiteSpace() == true)
+ {
+ parsedMovieInfo = _parsingService.ParseMovieInfo(latestImportedName, helpers);
+ }
+
+ if (parsedMovieInfo == null)
+ {
+ _logger.Debug("Could not parse movie info from history source title, using current path instead: {0}.", movieFile.RelativePath);
+ parsedMovieInfo = _parsingService.ParseMovieInfo(movieFile.RelativePath, helpers);
+ }
//Only update Custom formats for now.
if (parsedMovieInfo != null)
@@ -78,7 +92,7 @@ public void Execute(UpdateMovieFileQualityCommand command)
}
else
{
- _logger.Debug("Could not update custom formats for {0}, since it's title could not be parsed!", movieFile);
+ _logger.Warn("Could not update custom formats for {0}, since it's title could not be parsed!", movieFile);
}
count++;
diff --git a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
index b253cad5ef..59f5a23e45 100644
--- a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
+++ b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
@@ -1,9 +1,11 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Notifications.Xbmc.Model;
using NzbDrone.Core.Movies;
+using NzbDrone.Common.Disk;
+using System.IO;
namespace NzbDrone.Core.Notifications.Xbmc
{
@@ -85,6 +87,7 @@ private void UpdateMovieLibrary(XbmcSettings settings, Movie movie)
if (moviePath != null)
{
+ moviePath = new OsPath(moviePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
_logger.Debug("Updating movie {0} (Path: {1}) on XBMC host: {2}", movie, moviePath, settings.Address);
}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 866ba4b6a2..691004d60d 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -847,6 +847,7 @@
Code
+
@@ -1318,11 +1319,11 @@
-
-
\ No newline at end of file
+
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index 71b06837d6..e31c573716 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -9,6 +9,7 @@
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies;
@@ -333,12 +334,12 @@ private void AddImdbIdTokens(Dictionary> tokenH
tokenHandlers["{IMDb Id}"] = m => $"{imdbId}";
}
- private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile episodeFile)
+ private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile movieFile)
{
- tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
- tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile);
+ tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile);
+ tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile);
//tokenHandlers["{IMDb Id}"] = m =>
- tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Radarr");
+ tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr");
}
private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile)
@@ -366,98 +367,22 @@ private void AddMediaInfoTokens(Dictionary> tok
{
if (movieFile.MediaInfo == null) return;
- string videoCodec;
- switch (movieFile.MediaInfo.VideoCodec)
+ var sceneName = movieFile.GetSceneOrFileName();
+
+ var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName);
+ var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName);
+ var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
+
+ // Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release
+ if (audioCodec.EqualsIgnoreCase("DTS-X"))
{
- case "AVC":
- if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h264"))
- {
- videoCodec = "h264";
- }
- else
- {
- videoCodec = "x264";
- }
- break;
-
- case "V_MPEGH/ISO/HEVC":
- if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h265"))
- {
- videoCodec = "h265";
- }
- else
- {
- videoCodec = "x265";
- }
- break;
-
- case "MPEG-2 Video":
- videoCodec = "MPEG2";
- break;
-
- default:
- videoCodec = movieFile.MediaInfo.VideoCodec;
- break;
- }
-
- string audioCodec;
- switch (movieFile.MediaInfo.AudioFormat)
- {
- case "AC-3":
- audioCodec = "AC3";
- break;
-
- case "E-AC-3":
- audioCodec = "EAC3";
- break;
-
- case "Atmos / TrueHD":
- audioCodec = "Atmos TrueHD";
- break;
-
- case "MPEG Audio":
- if (movieFile.MediaInfo.AudioProfile == "Layer 3")
- {
- audioCodec = "MP3";
- }
- else
- {
- audioCodec = movieFile.MediaInfo.AudioFormat;
- }
- break;
-
- case "DTS":
- if (movieFile.MediaInfo.AudioProfile == "ES" || movieFile.MediaInfo.AudioProfile == "ES Discrete" || movieFile.MediaInfo.AudioProfile == "ES Matrix")
- {
- audioCodec = "DTS-ES";
- }
- else if (movieFile.MediaInfo.AudioProfile == "MA")
- {
- audioCodec = "DTS-HD MA";
- }
- else if (movieFile.MediaInfo.AudioProfile == "HRA")
- {
- audioCodec = "DTS-HD HRA";
- }
- else if (movieFile.MediaInfo.AudioProfile == "X")
- {
- audioCodec = "DTS-X";
- }
- else
- {
- audioCodec = movieFile.MediaInfo.AudioFormat;
- }
- break;
-
- default:
- audioCodec = movieFile.MediaInfo.AudioFormat;
- break;
+ audioChannels = audioChannels - 1 + 0.1m;
}
var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
{
- mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages);
+ mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
}
var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages;
if (mediaInfoAudioLanguages == "[EN]")
@@ -465,17 +390,32 @@ private void AddMediaInfoTokens(Dictionary> tok
mediaInfoAudioLanguages = string.Empty;
}
-
var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles);
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
{
- mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages);
+ mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
}
var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
- var audioChannels = movieFile.MediaInfo.FormattedAudioChannels > 0 ?
- movieFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) :
- string.Empty;
+ var audioChannelsFormatted = audioChannels > 0 ?
+ audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
+ string.Empty;
+
+ var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty;
+
+ var videoColourPrimaries = movieFile.MediaInfo.VideoColourPrimaries ?? string.Empty;
+ var videoTransferCharacteristics = movieFile.MediaInfo.VideoTransferCharacteristics ?? string.Empty;
+ var mediaInfoHDR = string.Empty;
+
+ if (movieFile.MediaInfo.VideoBitDepth >= 10 && !videoColourPrimaries.IsNullOrWhiteSpace() && !videoTransferCharacteristics.IsNullOrWhiteSpace())
+ {
+ string[] validTransferFunctions = new string[] { "PQ", "HLG" };
+
+ if (videoColourPrimaries.EqualsIgnoreCase("BT.2020") && validTransferFunctions.Any(videoTransferCharacteristics.Contains))
+ {
+ mediaInfoHDR = "HDR";
+ }
+ }
tokenHandlers["{MediaInfo Video}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec;
@@ -483,14 +423,17 @@ private void AddMediaInfoTokens(Dictionary> tok
tokenHandlers["{MediaInfo Audio}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec;
- tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels;
-
- tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec);
-
- tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
+ tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted;
tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages;
tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll;
+
tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages;
+
+ tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D;
+ tokenHandlers["{MediaInfo HDR}"] = m => mediaInfoHDR;
+
+ tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}";
+ tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
}
private string GetLanguagesToken(string mediaInfoLanguages)
@@ -610,24 +553,24 @@ private string GetQualityReal(Movie movie, QualityModel quality)
return string.Empty;
}
- private string GetOriginalTitle(MovieFile episodeFile)
+ private string GetOriginalTitle(MovieFile movieFile)
{
- if (episodeFile.SceneName.IsNullOrWhiteSpace())
+ if (movieFile.SceneName.IsNullOrWhiteSpace())
{
- return GetOriginalFileName(episodeFile);
+ return GetOriginalFileName(movieFile);
}
- return episodeFile.SceneName;
+ return movieFile.SceneName;
}
- private string GetOriginalFileName(MovieFile episodeFile)
+ private string GetOriginalFileName(MovieFile movieFile)
{
- if (episodeFile.RelativePath.IsNullOrWhiteSpace())
+ if (movieFile.RelativePath.IsNullOrWhiteSpace())
{
- return Path.GetFileNameWithoutExtension(episodeFile.Path);
+ return Path.GetFileNameWithoutExtension(movieFile.Path);
}
- return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
+ return Path.GetFileNameWithoutExtension(movieFile.RelativePath);
}
}
diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
index 58d785b98a..28cb60982a 100644
--- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
@@ -26,8 +26,11 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
var mediaInfo = new MediaInfoModel()
{
- VideoCodec = "AVC",
- VideoBitDepth = 8,
+ VideoFormat = "AVC",
+ VideoBitDepth = 10,
+ VideoMultiViewCount = 2,
+ VideoColourPrimaries = "BT.2020",
+ VideoTransferCharacteristics = "PQ",
AudioFormat = "DTS",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",
@@ -37,8 +40,11 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
var mediaInfoAnime = new MediaInfoModel()
{
- VideoCodec = "AVC",
- VideoBitDepth = 8,
+ VideoFormat = "AVC",
+ VideoBitDepth = 10,
+ VideoMultiViewCount = 2,
+ VideoColourPrimaries = "BT.2020",
+ VideoTransferCharacteristics = "PQ",
AudioFormat = "DTS",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",
diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs
index 2c32ef7e64..dcf5568ceb 100644
--- a/src/NzbDrone.Core/Parser/ParsingService.cs
+++ b/src/NzbDrone.Core/Parser/ParsingService.cs
@@ -26,7 +26,7 @@ public interface IParsingService
MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
ParsedMovieInfo ParseMovieInfo(string title, List