Merge branch 'develop' into AddMetadataURL

This commit is contained in:
FuNK3Y 2018-11-29 20:46:04 +01:00 committed by GitHub
commit abf738ffee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1427 additions and 309 deletions

View file

@ -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

View file

@ -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

View file

@ -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'<a href="https://github.com/Radarr/Radarr/issues/\1">\1</a>') |
ReSub(r'#(\d{3,4})', r'Issue #\1') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)

View file

@ -3,6 +3,16 @@
## (unreleased)
### **New features**
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) 64bit mediainfo.dll to 32bit to resolve issue: https://github.com/Radarr/Radarr/issues/3138. [<a href="https://github.com/geogolem">geogolem</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) Refactor MediaInfo tokens (fixes old tokens adds new stuff) ([#3058](https://github.com/Radarr/Radarr/issues/3058)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) Don't hide custom formats behind advanced settings when editing quality. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) Upped rate at which we scan the download client. Should reduce cpu and ram usage as well as decrease pressure on download clients. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) Improve model and UI handling for lists. Should finally fix root folder errors. ([#3133](https://github.com/Radarr/Radarr/issues/3133)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) Don't return unmapped folders on rootfolder API call. Massively improves loading time. ([#3116](https://github.com/Radarr/Radarr/issues/3116)) [<a href="https://github.com/Justin Kromlinger">Justin Kromlinger</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) Support for Homebrew-installed mono ([#3090](https://github.com/Radarr/Radarr/issues/3090)) [<a href="https://github.com/Jeff Byrnes">Jeff Byrnes</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) mk3d file format ([#2795](https://github.com/Radarr/Radarr/issues/2795)) [<a href="https://github.com/Qstick">Qstick</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) "Add Paused" option to Deluge and Transmission ([#3038](https://github.com/Radarr/Radarr/issues/3038)) [<a href="https://github.com/cookandy">cookandy</a>]
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) All-around small improvements ([#3032](https://github.com/Radarr/Radarr/issues/3032)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) Czech Language ([#2948](https://github.com/Radarr/Radarr/issues/2948)) [<a href="https://github.com/halali">halali</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) Fallback to Bitrate_Nominal for MediaInfo ([#2886](https://github.com/Radarr/Radarr/issues/2886)) [<a href="https://github.com/Qstick">Qstick</a>]
- ![New](https://img.shields.io/badge/--%20-New-brightgreen.svg?style=flat-square) All new custom formats 9000! (Rescan old files, delete formats, polish UI, etc. See discord for full changes): [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
@ -13,6 +23,21 @@
- ![Changed](https://img.shields.io/badge/--%20-Changed-orange.svg?style=flat-square) "importing an episode" to "importing a movie file" ([#2829](https://github.com/Radarr/Radarr/issues/2829)) [<a href="https://github.com/Travis Boss">Travis Boss</a>]
### **Fixes**
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Fallback to 'VideoCodec' if 'VideoFormat' is unavailable ([#3142](https://github.com/Radarr/Radarr/issues/3142)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Read video 'BitRate_Nominal' if 'BitRate' is empty ([#3144](https://github.com/Radarr/Radarr/issues/3144)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) UpdateMovieQualityService Tests. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Ignore "special drives" from System » Disk Space ([#3050](https://github.com/Radarr/Radarr/issues/3050)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Tweak style of movie path template on "add movies" screen ([#3108](https://github.com/Radarr/Radarr/issues/3108)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Unable to update custom formats for releases with bad Source Titles. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Do not search movie if unmonitored ([#3131](https://github.com/Radarr/Radarr/issues/3131)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Quality badges not being shown on bulk import. ([#3121](https://github.com/Radarr/Radarr/issues/3121)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Trim filename from Kodi movie path before sending library scan request. ([#3097](https://github.com/Radarr/Radarr/issues/3097)) [<a href="https://github.com/Lawrence">Lawrence</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Hopefully fixed bulk import not showing files. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) MPEG-2 remuxes being detected as "Raw-HD" quality. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Allow directory to be parsed similar to past implementation ([#3057](https://github.com/Radarr/Radarr/issues/3057)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Class names on the 'add movies screen' ([#3047](https://github.com/Radarr/Radarr/issues/3047)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Use proper cursor for text and linked labels ([#3041](https://github.com/Radarr/Radarr/issues/3041)) [<a href="https://github.com/Ricardo Amaral">Ricardo Amaral</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Donate button requiring two clicks to actually work. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Templates for custom format using wrong modifiers. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) Profiles always failing validation. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]
- ![Fixed](https://img.shields.io/badge/--%20-Fixed-red.svg?style=flat-square) ImdbIds not being padded with zeroes, which messes up matching. [<a href="https://github.com/Leonardo Galli">Leonardo Galli</a>]

View file

@ -5,7 +5,7 @@
{{#sections}}
{{{label}}}
{{#commits}}
- {{{subject}}} [<a href="https://github.com/{{{author}}}">{{{author}}}</a>]
- {{{subject}}} [{{{author}}}]
{{/commits}}
{{/sections}}

7
deploy.sh Normal file
View file

@ -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

View file

@ -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"

View file

@ -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<MovieResource>
private readonly IDiskScanService _diskScanService;
private readonly ICached<Core.Movies.Movie> _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<Core.Movies.Movie>(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
};
}

View file

@ -52,7 +52,7 @@ private int CreateRootFolder(RootFolderResource rootFolderResource)
private List<RootFolderResource> GetRootFolders()
{
return _rootFolderService.AllWithUnmappedFolders().ToResource();
return _rootFolderService.AllWithSpace().ToResource();
}
private void DeleteFolder(int id)

View file

@ -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 });
}
}
}
}

View file

@ -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<DiskSpaceService>
{
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<IDiskProvider>()
.Setup(v => v.GetMounts())
.Returns(new List<IMount>());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetPathRoot(It.IsAny<string>()))
.Returns(@"G:\".AsOsAgnostic());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetAvailableSpace(It.IsAny<string>()))
.Returns(0);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetTotalSize(It.IsAny<string>()))
.Returns(0);
GivenMovies();
}
private void GivenMovies(params Movie[] movies)
{
Mocker.GetMock<IMovieService>()
.Setup(v => v.GetAllMovies())
.Returns(movies.ToList());
}
private void GivenExistingFolder(string folder)
{
Mocker.GetMock<IDiskProvider>()
.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<IDiskProvider>()
.Verify(v => v.GetAvailableSpace(It.IsAny<string>()), 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<IDiskProvider>()
.Verify(v => v.GetAvailableSpace(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_check_diskspace_for_dronefactory_folder()
{
Mocker.GetMock<IConfigService>()
.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<IConfigService>()
.SetupGet(v => v.DownloadedMoviesFolder)
.Returns(_droneFactoryFolder);
var freeSpace = Subject.GetFreeSpace();
freeSpace.Should().BeEmpty();
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetAvailableSpace(It.IsAny<string>()), 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<IMount>();
mount.SetupGet(v => v.RootDirectory).Returns(path);
mount.SetupGet(v => v.DriveType).Returns(System.IO.DriveType.Fixed);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetMounts())
.Returns(new List<IMount> { mount.Object });
var freeSpace = Subject.GetFreeSpace();
freeSpace.Should().BeEmpty();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -16,15 +16,15 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[TestFixture]
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
{
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<IConfigService>()
@ -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<IMediaFileService>()
@ -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<IVideoFileInfoReader>()
.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<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(2));
}
[Test]
public void should_skip_not_yet_date_media_info()
{
var episodeFiles = Builder<MovieFile>.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<IMediaFileService>()
.Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), 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<IVideoFileInfoReader>()
.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<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), 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<IVideoFileInfoReader>()
.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<IVideoFileInfoReader>()
.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<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(1));

View file

@ -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);
}
}
}
}

View file

@ -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<IMediaFileService>().Verify(s => s.Update(It.IsAny<MovieFile>()), Times.Never());
}

View file

@ -168,6 +168,7 @@
<Compile Include="DecisionEngineTests\Search\MovieSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RawDiskSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
<Compile Include="DiskSpace\DiskSpaceServiceFixture.cs" />
<Compile Include="Download\CompletedDownloadServiceFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
<Compile Include="Download\DownloadClientTests\Blackhole\ScanWatchFolderFixture.cs" />
@ -314,6 +315,9 @@
<Compile Include="OrganizerTests\CleanFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTests\FilterFixture.cs" />
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioChannelsFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoCodecFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
<Compile Include="MediaFiles\RenameMovieFileServiceFixture.cs" />
@ -580,4 +584,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View file

@ -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]

View file

@ -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)
{

View file

@ -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<DiskSpace> GetDroneFactoryFreeSpace()
private IEnumerable<DiskSpace> 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<DiskSpace> GetDiskSpace(IEnumerable<string> paths, bool suppressWarnings = false)

View file

@ -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(@"^(?<type>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));

View file

@ -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);

View file

@ -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},

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -18,8 +18,6 @@ public class UpdateMediaInfoService : IHandle<MovieScannedEvent>
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<MovieFile> 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);
}

View file

@ -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
{

View file

@ -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;
}
}
}

View file

@ -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<string> _warnedFiles;
private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications,
@ -48,6 +50,7 @@ public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> speci
IQualityDefinitionService qualitiesService,
IConfigService config,
IHistoryService historyService,
ICacheManager cacheManager,
Logger logger)
{
_specifications = specifications;
@ -59,6 +62,7 @@ public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> speci
_qualitiesService = qualitiesService;
_config = config;
_historyService = historyService;
_warnedFiles = cacheManager.GetCache<string>(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"));

View file

@ -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++;

View file

@ -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);
}

View file

@ -847,6 +847,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatter.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoLib.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
@ -1318,11 +1319,11 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View file

@ -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<string, Func<TokenMatch, string>> tokenH
tokenHandlers["{IMDb Id}"] = m => $"{imdbId}";
}
private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile episodeFile)
private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> 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<string, Func<TokenMatch, string>> tokenHandlers, Movie movie, MovieFile movieFile)
@ -366,98 +367,22 @@ private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> 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<string, Func<TokenMatch, string>> 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<string, Func<TokenMatch, string>> 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);
}
}

View file

@ -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",

View file

@ -26,7 +26,7 @@ public interface IParsingService
MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
ParsedMovieInfo ParseMovieInfo(string title, List<object> helpers);
ParsedMovieInfo ParseMoviePathInfo(string path, List<object> helpers);
ParsedMovieInfo ParseMinimalMovieInfo(string path);
ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false);
ParsedMovieInfo ParseMinimalPathMovieInfo(string path);
List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo);
List<FormatTagMatchResult> MatchFormatTags(ParsedMovieInfo movieInfo);
@ -192,16 +192,16 @@ public LocalMovie GetLocalMovie(string filename, ParsedMovieInfo minimalInfo, Mo
};
}
public ParsedMovieInfo ParseMinimalMovieInfo(string file)
public ParsedMovieInfo ParseMinimalMovieInfo(string file, bool isDir = false)
{
return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0);
return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir);
}
public ParsedMovieInfo ParseMinimalPathMovieInfo(string path)
{
var fileInfo = new FileInfo(path);
var result = ParseMinimalMovieInfo(fileInfo.Name);
var result = ParseMinimalMovieInfo(fileInfo.Name, true);
if (result == null)
{

View file

@ -104,13 +104,6 @@ public static QualityModel ParseQuality(string name)
}
}
if (RawHDRegex.IsMatch(normalizedName))
{
result.Modifier = Modifier.RAWHD;
result.Source = Source.TV;
return result;
}
var sourceMatch = SourceRegex.Matches(normalizedName).OfType<Match>().LastOrDefault();
var resolution = ParseResolution(normalizedName);
var codecRegex = CodecRegex.Match(normalizedName);
@ -129,6 +122,13 @@ public static QualityModel ParseQuality(string name)
result.Source = Source.BLURAY;
return result; //We found remux!
}
if (RawHDRegex.IsMatch(normalizedName) && result.Modifier != Modifier.BRDISK)
{
result.Modifier = Modifier.RAWHD;
result.Source = Source.TV;
return result;
}
if (sourceMatch != null && sourceMatch.Success)
{

View file

@ -15,6 +15,7 @@ public interface IRootFolderService
{
List<RootFolder> All();
List<RootFolder> AllWithUnmappedFolders();
List<RootFolder> AllWithSpace();
RootFolder Add(RootFolder rootDir);
void Remove(int id);
RootFolder Get(int id);
@ -62,6 +63,31 @@ public List<RootFolder> All()
return rootFolders;
}
public List<RootFolder> AllWithSpace()
{
var rootFolders = _rootFolderRepository.All().ToList();
rootFolders.ForEach(folder =>
{
try
{
if (folder.Path.IsPathValid() && _diskProvider.FolderExists(folder.Path))
{
folder.FreeSpace = _diskProvider.GetAvailableSpace(folder.Path);
folder.TotalSpace = _diskProvider.GetTotalSize(folder.Path);
}
}
//We don't want an exception to prevent the root folders from loading in the UI, so they can still be deleted
catch (Exception ex)
{
folder.FreeSpace = 0;
_logger.Error(ex, "Unable to get free space for root folder {0}", folder.Path);
}
});
return rootFolders;
}
public List<RootFolder> AllWithUnmappedFolders()
{
var rootFolders = _rootFolderRepository.All().ToList();

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -189,8 +189,6 @@ private uint GetGroupId(string group)
}
return g.gr_gid;
}
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -111,6 +111,18 @@ private IMount ParseLine(string line)
return null;
}
if (mount.StartsWith("/var/lib/"))
{
// Could be /var/lib/docker when docker uses zfs. Very unlikely that a useful mount is located in /var/lib.
return null;
}
if (mount.StartsWith("/snap/"))
{
// Mount point for snap packages
return null;
}
var driveType = FindDriveType.Find(type);
if (name.StartsWith("/dev/") || GetFileSystems().GetValueOrDefault(type, false))

View file

@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}"

View file

@ -1,2 +1,4 @@
{{path}}<br>
<span title="{{#if movieFile.relativePath}}&nbsp;{{movieFile.relativePath}}{{/if}}" class="hint" style="font-size: 12px;">{{#if movieFile.relativePath}}&nbsp;{{movieFile.relativePath}}{{else}}&nbsp;Movie File Not Found{{/if}}</span>
<span title="{{#if movieFile.relativePath}}&nbsp;{{movieFile.relativePath}}{{/if}}" class="hint path">
{{#if movieFile.relativePath}}&#9493;&nbsp;{{movieFile.relativePath}}{{else}}&#9493;&nbsp;Movie File Not Found{{/if}}
</span>

View file

@ -1,5 +1,5 @@
{{#if_gt proper compare="1"}}
<span class="badge badge-info" title="PROPER">{{movieFile.quality.qualityDefinition.title}}</span>
<span class="badge badge-info" title="PROPER">{{movieFile.quality.quality.name}}</span>
{{else}}
<span class="badge" title="{{#if movieFile.quality.hardcodedSubs}}Warning: {{movieFile.quality.hardcodedSubs}}{{/if}}">{{movieFile.quality.qualityDefinition.title}}</span>
<span class="badge" title="{{#if movieFile.quality.hardcodedSubs}}Warning: {{movieFile.quality.hardcodedSubs}}{{/if}}">{{movieFile.quality.quality.name}}</span>
{{/if_gt}}

View file

@ -1,3 +1,4 @@
@import "../Content/Bootstrap/mixins";
@import "../Shared/Styles/card.less";
@import "../Shared/Styles/clickable.less";
@ -130,6 +131,13 @@
.hint {
color : #999999;
font-style : italic;
&.path {
.text-overflow();
display: block;
font-size: 12px;
}
}
.monitor-tooltip-contents {

View file

@ -12,7 +12,7 @@ module.exports = NzbDroneCell.extend({
if (runtime) {
runtime = runtime.split(".")[0];
}
var video = "{0} ({1}x{2}) ({3})".format(info.videoCodec, info.width, info.height, runtime);
var video = "{0} ({1}x{2}) ({3})".format(info.videoFormat || info.videoCodec, info.width, info.height, runtime);
var audio = "{0} ({1})".format(info.audioFormat, info.audioLanguages);
this.$el.html(video + " " + audio);
}

View file

@ -31,11 +31,15 @@
</div>
</div>
<div class="form-group advanced-setting">
<div class="form-group">
<label class="col-sm-4 control-label">Custom Formats</label>
<div class="col-sm-4">
<input type="text" class="form-control x-tags" tag-source="{{formats}}" tag-class-name="label label-success">
</div>
<span class="help-inline-checkbox">
<i class="icon-radarr-form-info" title="Edit matched custom formats for this file. ADVANCED USERS ONLY!"/>
</span>
</div>
</div>

View file

@ -44,11 +44,9 @@ var view = Marionette.ItemView.extend({
onRender : function() {
var rootFolder = this.model.get("rootFolderPath");
if (rootFolder !== "") {
//this.ui.rootFolder.val(rootFolder);
this.ui.rootFolder.children().filter(function() {
//may want to use $.trim in here
return $(this).text() === rootFolder;
}).attr('selected', true);
return $.trim($(this).text()) === rootFolder;
}).prop('selected', true);
} else {
var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId);
if (RootFolders.get(defaultRoot)) {
@ -58,7 +56,7 @@ var view = Marionette.ItemView.extend({
},
_onBeforeSave : function() {
var profile = this.ui.profile.val();
var profile = parseInt(this.ui.profile.val(), 10);
var minAvail = this.ui.minimumAvailability.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
this.model.set({