1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-16 21:15:33 -04:00

Compare commits

..

21 Commits

Author SHA1 Message Date
Bogdan
afbe0ebcd4 Fixed: Validation for remote path mapping
Fixes #11092
2025-05-26 21:26:07 +03:00
Bogdan
bfbb7532a2 Bump version to 5.25.0 2025-05-26 13:25:58 +03:00
Bogdan
c92d8c08f1 Follow redirects for usenet grabs on non-prod builds 2025-05-25 20:05:18 +03:00
bakerboy448
358ce92f85 Fixed: Production builds erroneously marked as not Production
build number exceeded; bump to 20k
fixes #11089
2025-05-25 11:48:29 -05:00
Servarr
3ec5a4b78a Automated API Docs update 2025-05-25 16:43:04 +03:00
Bogdan
cb59ce891a New: Keywords custom filter and autotagging for movies 2025-05-25 14:55:45 +03:00
Weblate
4d3d46d796 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alan <1790727569@qq.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: warkurre86 <tom.novo.86@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translation: Servarr/Radarr
2025-05-25 14:12:23 +03:00
Bogdan
0941e51d27 Bump version to 5.24.1 2025-05-25 14:11:22 +03:00
Bogdan
ff393a3f65 Show movie titles when poster is missing on collections page 2025-05-24 00:26:48 +03:00
Aden Northcote
f5faf52469 Fixed: Update AutoTags on movie add (#11079)
Co-authored-by: aden <aden@hughorse.net>
2025-05-23 18:56:02 +03:00
Bogdan
b5b4d4b971 Return error with missing field for movie files endpoint
Fixes #10555
2025-05-23 18:50:36 +03:00
Bogdan
873299701b Use UTC dates for TMDB Popular lists 2025-05-23 18:12:54 +03:00
Bogdan
d14cca30d7 Use the thrown exception in http timeout handling
(cherry picked from commit 14e324ee30694ae017a39fd6f66392dc2d104617)
2025-05-23 12:25:47 +03:00
Stevie Robinson
5af61b5900 New: Ignore volumes containing .timemachine from Disk Space
(cherry picked from commit a853c537db0a6bd499a2277987dc170d2a1f5645)
2025-05-23 12:24:50 +03:00
carrossos
a10759c7e9 Treat HTTP 410 response for failed download similarly to HTTP 404
(cherry picked from commit 818ae02a7a8f0a8ea0a44e0015e2667d96453332)
2025-05-23 12:24:37 +03:00
Stevie Robinson
ac2d92007e New: Don't allow remote path to start with space
(cherry picked from commit 5ba3ff598770fdf9e5a53d490c8bcbdd6a59c4cc)
2025-05-23 12:13:27 +03:00
Mark McDowall
09cfdc3fa2 Increase maximum backup restoration size to 5GB
(cherry picked from commit e38deb34221ebf131adcce9551774898f46b1f7f)
2025-05-23 12:13:12 +03:00
Stevie Robinson
04f26dbff7 Ensure Custom Format Maximum Size won't overflow
(cherry picked from commit a50d2562649bbe77d0feb9fbfc594d56952e0a5e)
2025-05-23 12:12:37 +03:00
Bogdan
159f5df8cc Fix jump to character for Collections and Discover
Fix for a regression introduced in react-virtualized 9.21.2 when WindowScroller is used with Grids
2025-05-22 15:40:29 +03:00
Elerir
b823ad8e65 New: Add Mongolian language
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-05-21 12:08:15 +03:00
Bogdan
cc8bffc272 Bump version to 5.24.0 2025-05-17 15:56:03 +03:00
54 changed files with 453 additions and 91 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.23.3'
majorVersion: '5.25.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -9,12 +9,13 @@ $hoverScale: 1.05;
box-shadow: 0 0 10px var(--black);
transition: all 200ms ease-in;
.poster {
.poster,
.overlayTitle {
opacity: 0.5;
transition: opacity 100ms linear 100ms;
}
.overlayTitle {
.overlayHoverTitle {
opacity: 1;
transition: opacity 100ms linear 100ms;
}
@@ -31,7 +32,22 @@ $hoverScale: 1.05;
background-color: var(--defaultColor);
}
.overlay {
.overlayTitle {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
width: 100%;
height: 100%;
color: var(--offWhite);
text-align: center;
font-size: 20px;
}
.overlayHover {
position: absolute;
top: 0;
left: 0;
@@ -42,10 +58,10 @@ $hoverScale: 1.05;
height: 100%;
}
.overlayTitle {
.overlayHoverTitle {
padding: 5px;
color: var(--offWhite);
text-align: left;
text-align: center;
font-weight: bold;
font-size: 15px;
opacity: 0;

View File

@@ -10,7 +10,8 @@ interface CssExports {
'externalLinks': string;
'link': string;
'monitorToggleButton': string;
'overlay': string;
'overlayHover': string;
'overlayHoverTitle': string;
'overlayTitle': string;
'poster': string;
'posterContainer': string;

View File

@@ -82,6 +82,7 @@ class CollectionMovie extends Component {
} = this.props;
const {
hasPosterError,
isEditMovieModalOpen,
isNewAddMovieModalOpen
} = this.state;
@@ -134,26 +135,31 @@ class CollectionMovie extends Component {
onLoad={this.onPosterLoad}
/>
<div className={styles.overlay}>
<div className={styles.overlayTitle}>
{
hasPosterError &&
<div className={styles.overlayTitle}>
{title}
</div>
}
<div className={styles.overlayHover}>
<div className={styles.overlayHoverTitle}>
{title} {year > 0 ? `(${year})` : ''}
</div>
{
id ?
<div className={styles.overlayStatus}>
<MovieIndexProgressBar
movieId={id}
movieFile={movieFile}
monitored={monitored}
hasFile={hasFile}
status={status}
bottomRadius={true}
width={posterWidth}
detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable}
/>
</div> :
<MovieIndexProgressBar
movieId={id}
movieFile={movieFile}
monitored={monitored}
hasFile={hasFile}
status={status}
bottomRadius={true}
width={posterWidth}
detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable}
/> :
null
}
</div>

View File

@@ -92,15 +92,14 @@ class CollectionOverviews extends Component {
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
this._gridScrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (this._grid && index != null) {
this._grid.scrollToCell({
this._gridScrollToCell({
rowIndex: index,
columnIndex: 0
});
@@ -186,6 +185,19 @@ class CollectionOverviews extends Component {
);
};
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
const scrollOffset = this._grid.getOffsetForCell({
rowIndex,
columnIndex
});
this._gridScrollToPosition(scrollOffset);
};
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
};
//
// Listeners

View File

@@ -1,4 +1,4 @@
import React, { ReactNode, useEffect, useRef } from 'react';
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import { Grid, GridCellProps, WindowScroller } from 'react-virtualized';
import ModelBase from 'App/ModelBase';
import Scroller from 'Components/Scroller/Scroller';
@@ -79,6 +79,39 @@ function VirtualTable<T extends ModelBase>({
position: undefined,
};
const handleScrollToPosition = useCallback(
({
scrollTop = 0,
scrollLeft = 0,
}: {
scrollTop: number;
scrollLeft: number;
}) => {
scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
},
[scroller]
);
const handleScrollToCell = useCallback(
({
rowIndex = 0,
columnIndex = 0,
}: {
rowIndex: number;
columnIndex: number;
}) => {
if (gridRef.current) {
const scrollOffset = gridRef.current.getOffsetForCell({
rowIndex,
columnIndex,
});
handleScrollToPosition(scrollOffset);
}
},
[gridRef, handleScrollToPosition]
);
useEffect(() => {
if (gridRef.current && width > 0) {
gridRef.current.recomputeGridSize();
@@ -97,10 +130,10 @@ function VirtualTable<T extends ModelBase>({
useEffect(() => {
if (gridRef.current && scrollTop && !scrollRestored.current) {
gridRef.current.scrollToPosition({ scrollLeft: 0, scrollTop });
handleScrollToPosition({ scrollLeft: 0, scrollTop });
scrollRestored.current = true;
}
}, [scrollTop]);
}, [scrollTop, handleScrollToPosition]);
useEffect(() => {
if (
@@ -108,12 +141,12 @@ function VirtualTable<T extends ModelBase>({
scrollIndex != null &&
scrollIndex !== previousScrollIndex
) {
gridRef.current.scrollToCell({
handleScrollToCell({
rowIndex: scrollIndex,
columnIndex: 0,
});
}
}, [scrollIndex, previousScrollIndex]);
}, [scrollIndex, previousScrollIndex, handleScrollToCell]);
return (
<WindowScroller scrollElement={isSmallScreen ? undefined : scroller}>

View File

@@ -95,8 +95,7 @@ class DiscoverMovieOverviews extends Component {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (this._grid && index != null) {
this._grid.scrollToCell({
this._gridScrollToCell({
rowIndex: index,
columnIndex: 0
});
@@ -182,6 +181,19 @@ class DiscoverMovieOverviews extends Component {
);
};
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
const scrollOffset = this._grid.getOffsetForCell({
rowIndex,
columnIndex
});
this._gridScrollToPosition(scrollOffset);
};
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
};
//
// Listeners

View File

@@ -170,7 +170,7 @@ class DiscoverMoviePosters extends Component {
if (this._grid && index != null) {
const row = Math.floor(index / columnCount);
this._grid.scrollToCell({
this._gridScrollToCell({
rowIndex: row,
columnIndex: 0
});
@@ -271,6 +271,19 @@ class DiscoverMoviePosters extends Component {
);
};
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
const scrollOffset = this._grid.getOffsetForCell({
rowIndex,
columnIndex
});
this._gridScrollToPosition(scrollOffset);
};
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
};
//
// Listeners

View File

@@ -43,7 +43,8 @@
.physicalRelease,
.digitalRelease,
.releaseDate,
.genres {
.genres,
.keywords {
composes: cell;
flex: 0 0 180px;

View File

@@ -12,6 +12,7 @@ interface CssExports {
'genres': string;
'imdbRating': string;
'inCinemas': string;
'keywords': string;
'minimumAvailability': string;
'movieStatus': string;
'originalLanguage': string;

View File

@@ -72,6 +72,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
minimumAvailability,
path,
genres = [],
keywords = [],
ratings,
popularity,
certification,
@@ -339,6 +340,20 @@ function MovieIndexRow(props: MovieIndexRowProps) {
);
}
if (name === 'keywords') {
const joinedKeywords = keywords.join(', ');
const truncatedKeywords =
keywords.length > 3
? `${keywords.slice(0, 3).join(', ')}...`
: joinedKeywords;
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<span title={joinedKeywords}>{truncatedKeywords}</span>
</VirtualTableRowCell>
);
}
if (name === 'movieStatus') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>

View File

@@ -36,7 +36,8 @@
.physicalRelease,
.digitalRelease,
.releaseDate,
.genres {
.genres,
.keywords {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 180px;

View File

@@ -9,6 +9,7 @@ interface CssExports {
'genres': string;
'imdbRating': string;
'inCinemas': string;
'keywords': string;
'minimumAvailability': string;
'movieStatus': string;
'originalLanguage': string;

View File

@@ -82,6 +82,7 @@ interface Movie extends ModelBase {
minimumAvailability: MovieAvailability;
path: string;
genres: string[];
keywords: string[];
ratings: Ratings;
popularity: number;
certification: string;

View File

@@ -181,6 +181,12 @@ export const defaultState = {
isSortable: false,
isVisible: false
},
{
name: 'keywords',
label: () => translate('Keywords'),
isSortable: false,
isVisible: false
},
{
name: 'movieStatus',
label: () => translate('Status'),
@@ -473,8 +479,8 @@ export const defaultState = {
label: () => translate('Genres'),
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const genreList = items.reduce((acc, movie) => {
movie.genres.forEach((genre) => {
const genreList = items.reduce((acc, { genres = [] }) => {
genres.forEach((genre) => {
acc.push({
id: genre,
name: genre
@@ -487,6 +493,27 @@ export const defaultState = {
return genreList.sort(sortByProp('name'));
}
},
{
name: 'keywords',
label: () => translate('Keywords'),
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const keywordList = items.reduce((acc, { keywords = [] }) => {
keywords.forEach((keyword) => {
if (acc.findIndex((a) => a.id === keyword) === -1) {
acc.push({
id: keyword,
name: keyword
});
}
});
return acc;
}, []);
return keywordList.sort(sortByProp('name'));
}
},
{
name: 'tmdbRating',
label: () => translate('TmdbRating'),

View File

@@ -207,7 +207,7 @@ namespace NzbDrone.Common.EnvironmentInfo
private static bool InternalIsOfficialBuild()
{
// Official builds will never have such a high revision
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000)
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 20000)
{
return false;
}

View File

@@ -141,7 +141,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
throw new WebException("Http request timed out", ex, WebExceptionStatus.Timeout, null);
}
}

View File

@@ -115,6 +115,7 @@ namespace NzbDrone.Core.Test.DiskSpace
[TestCase("/var/lib/docker")]
[TestCase("/some/place/docker/aufs")]
[TestCase("/etc/network")]
[TestCase("/Volumes/.timemachine/ABC123456-A1BC-12A3B45678C9/2025-05-13-181401.backup")]
public void should_not_check_diskspace_for_irrelevant_mounts(string path)
{
var mount = new Mock<IMount>();

View File

@@ -67,7 +67,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 52, Language.Marathi },
new object[] { 53, Language.Tagalog },
new object[] { 54, Language.Urdu },
new object[] { 55, Language.Romansh }
new object[] { 55, Language.Romansh },
new object[] { 56, Language.Mongolian }
};
public static object[] ToIntCases =
@@ -129,7 +130,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Marathi, 52 },
new object[] { Language.Tagalog, 53 },
new object[] { Language.Urdu, 54 },
new object[] { Language.Romansh, 55 }
new object[] { Language.Romansh, 55 },
new object[] { Language.Mongolian, 56 }
};
[Test]

View File

@@ -405,6 +405,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("ice", "IS")]
[TestCase("dut", "NL")]
[TestCase("nor", "NO")]
[TestCase("khk", "MN")]
[TestCase("mvf", "MN")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";

View File

@@ -98,5 +98,16 @@ namespace NzbDrone.Core.Test.ParserTests
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Romansh);
}
[TestCase("mn")]
[TestCase("mon")]
[TestCase("khk")]
[TestCase("mvf")]
[TestCase("mn-Cyrl")]
public void should_return_mongolian(string isoCode)
{
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Mongolian);
}
}
}

View File

@@ -513,6 +513,14 @@ namespace NzbDrone.Core.Test.ParserTests
result.Should().Contain(Language.Romansh);
}
[TestCase("Movie.Title.2025.Mongolian.WEB-DL.h264")]
[TestCase("Movie.Title.2025.Khalkha.WEB-DL.h264")]
public void should_parse_language_mongolian(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
result.Should().Contain(Language.Mongolian);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
{
private static readonly GenreSpecificationValidator Validator = new ();
public override int Order => 1;
public override int Order => 2;
public override string ImplementationName => "Genre";
[FieldDefinition(1, Label = "AutoTaggingSpecificationGenre", Type = FieldType.Tag)]

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class KeywordSpecificationValidator : AbstractValidator<KeywordSpecification>
{
public KeywordSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class KeywordSpecification : AutoTaggingSpecificationBase
{
private static readonly KeywordSpecificationValidator Validator = new ();
public override int Order => 2;
public override string ImplementationName => "Keyword";
[FieldDefinition(1, Label = "AutoTaggingSpecificationKeyword", Type = FieldType.Tag)]
public IEnumerable<string> Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
{
return movie?.MovieMetadata?.Value?.Keywords.Any(keyword => Value.ContainsIgnoreCase(keyword)) ?? false;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -11,6 +11,7 @@ namespace NzbDrone.Core.CustomFormats
{
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue);
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(242)]
public class add_movie_keywords : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("MovieMetadata").AddColumn("Keywords").AsString().Nullable();
}
}
}

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Core.DiskSpace
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
private static readonly Regex _regexSpecialDrive = new Regex(@"^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)|/\.timemachine", RegexOptions.Compiled);
public DiskSpaceService(IMovieService movieService, IRootFolderService rootFolderService, IDiskProvider diskProvider, Logger logger)
{

View File

@@ -172,7 +172,7 @@ namespace NzbDrone.Core.Download
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
{
_logger.Error(ex, "Downloading torrent file for movie '{0}' failed since it no longer exists ({1})", remoteMovie.Release.Title, torrentUrl);
throw new ReleaseUnavailableException(remoteMovie.Release, "Downloading torrent failed", ex);

View File

@@ -48,6 +48,7 @@ namespace NzbDrone.Core.Download
{
var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url);
request.RateLimitKey = remoteMovie?.Release?.IndexerId.ToString();
request.AllowAutoRedirect = true;
var response = await RetryStrategy
.ExecuteAsync(static async (state, _) => await state._httpClient.GetAsync(state.request), (_httpClient, request))
@@ -59,7 +60,7 @@ namespace NzbDrone.Core.Download
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
{
_logger.Error(ex, "Downloading nzb file for movie '{0}' failed since it no longer exists ({1})", remoteMovie.Release.Title, url);
throw new ReleaseUnavailableException(remoteMovie.Release, "Downloading nzb failed", ex);

View File

@@ -40,9 +40,10 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
var excludeCompanyIds = Settings.FilterCriteria.ExcludeCompanyIds;
var languageCode = Settings.FilterCriteria.LanguageCode;
var todaysDate = DateTime.Now.ToString("yyyy-MM-dd");
var threeMonthsAgo = DateTime.Parse(todaysDate).AddMonths(-3).ToString("yyyy-MM-dd");
var threeMonthsFromNow = DateTime.Parse(todaysDate).AddMonths(3).ToString("yyyy-MM-dd");
var now = DateTime.UtcNow;
var todaysDate = now.ToString("yyyy-MM-dd");
var threeMonthsAgo = now.AddMonths(-3).ToString("yyyy-MM-dd");
var threeMonthsFromNow = now.AddMonths(3).ToString("yyyy-MM-dd");
var requestBuilder = RequestBuilder.Create()
.SetSegment("api", "3")
@@ -54,8 +55,9 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
switch (Settings.TMDbListType)
{
case (int)TMDbPopularListType.Theaters:
requestBuilder.AddQueryParam("primary_release_date.gte", threeMonthsAgo)
.AddQueryParam("primary_release_date.lte", todaysDate);
requestBuilder
.AddQueryParam("primary_release_date.gte", threeMonthsAgo)
.AddQueryParam("primary_release_date.lte", todaysDate);
break;
case (int)TMDbPopularListType.Popular:
requestBuilder.AddQueryParam("sort_by", "popularity.desc");
@@ -64,8 +66,9 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
requestBuilder.AddQueryParam("sort_by", "vote_average.desc");
break;
case (int)TMDbPopularListType.Upcoming:
requestBuilder.AddQueryParam("primary_release_date.gte", todaysDate)
.AddQueryParam("primary_release_date.lte", threeMonthsFromNow);
requestBuilder
.AddQueryParam("primary_release_date.gte", todaysDate)
.AddQueryParam("primary_release_date.lte", threeMonthsFromNow);
break;
}

View File

@@ -73,6 +73,8 @@ namespace NzbDrone.Core.ImportLists.TMDb
[FieldOption(Hint = "Urdu")]
ur,
[FieldOption(Hint = "Raeto-Romance")]
rm
rm,
[FieldOption(Hint = "Mongolian")]
mn
}
}

View File

@@ -126,6 +126,7 @@ namespace NzbDrone.Core.Languages
public static Language Tagalog => new Language(53, "Tagalog");
public static Language Urdu => new Language(54, "Urdu");
public static Language Romansh => new Language(55, "Romansh");
public static Language Mongolian => new Language(56, "Mongolian");
public static Language Any => new Language(-1, "Any");
public static Language Original => new Language(-2, "Original");
@@ -191,6 +192,7 @@ namespace NzbDrone.Core.Languages
Tagalog,
Urdu,
Romansh,
Mongolian,
Any,
Original
};

View File

@@ -18,7 +18,7 @@
"Clear": "Vymazat",
"CreateEmptyMovieFolders": "Vytvořte prázdné složky s filmy",
"MovieIsRecommend": "Film se doporučuje na základě nedávného přidání",
"NegateHelpText": "Pokud je zaškrtnuto, vlastní formát se nepoužije, pokud se tato podmínka {0} shoduje.",
"NegateHelpText": "Pokud je zaškrtnuto, vlastní formát se nepoužije, pokud je splněna tato podmínka {implementationName}.",
"OnLatestVersion": "Nejnovější verze aplikace {appName} je již nainstalována",
"QualitySettings": "Nastavení kvality",
"QueueIsEmpty": "Fronta je prázdná",
@@ -71,8 +71,8 @@
"ImportListsSettingsSummary": "Importovat seznamy, vyloučit seznamy",
"LogLevelTraceHelpTextWarning": "Trasování protokolování by mělo být povoleno pouze dočasně",
"MinimumAgeHelpText": "Pouze Usenet: Minimální věk NZB v minutách, než jsou uchopeny. Tímto způsobem dáte novým verzím čas na propagaci u vašeho poskytovatele usenet.",
"MinutesHundredTwenty": "120 minut: {0}",
"MinutesNinety": "90 minut: {0}",
"MinutesHundredTwenty": "120 minut: {hundredTwenty}",
"MinutesNinety": "90 minut: {ninety}",
"NoListRecommendations": "Nebyly nalezeny žádné položky seznamu ani doporučení. Chcete-li začít, budete chtít přidat nový film, importovat některé existující nebo přidat seznam.",
"PreviewRename": "Náhled Přejmenovat",
"PreviewRenameHelpText": "Tip: Chcete-li zobrazit náhled přejmenování ... vyberte možnost Zrušit, klikněte na libovolný název filmu a použijte ikonu",
@@ -107,7 +107,7 @@
"StartImport": "Spusťte import",
"Status": "Postavení",
"Studio": "Studio",
"AddNewMovieRootFolderHelpText": "Podsložka „{0}“ bude vytvořena automaticky",
"AddNewMovieRootFolderHelpText": "Podsložka '{folder}' bude vytvořena automaticky",
"ICalTagsMoviesHelpText": "Platí pro filmy s alespoň jednou shodnou značkou",
"TorrentDelayTime": "Torrent Delay: {0}",
"Trakt": "Trakt",
@@ -222,8 +222,8 @@
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
"CertificationCountry": "Země certifikace",
"ImportErrors": "Chyby importu",
"ImportExtraFilesMovieHelpText": "Po importu filmového souboru importujte odpovídající další soubory (titulky, nfo atd.)",
"ImportFailed": "Import se nezdařil: {0}",
"ImportExtraFilesMovieHelpText": "Importovat odpovídající doplňkové soubory (titulky, nfo atd.) po importu souboru filmu",
"ImportFailed": "Import se nezdařil: {sourceTitle}",
"CreateEmptyMovieFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
"DeleteDownloadClient": "Odstranit staženého klienta",
"DownloadClientStatusCheckAllClientMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
@@ -278,7 +278,7 @@
"MinimumCustomFormatScoreHelpText": "Minimální skóre vlastního formátu povoleno ke stažení",
"MinimumAge": "Minimální věk",
"MoveFiles": "Přesouvat soubory",
"MinutesSixty": "60 minut: {0}",
"MinutesSixty": "60 minut: {sixty}",
"Small": "Malý",
"Calendar": "Kalendář",
"CancelPendingTask": "Opravdu chcete zrušit tento úkol čekající na vyřízení?",
@@ -299,7 +299,7 @@
"Profiles": "Profily",
"ProxyType": "Typ serveru proxy",
"PtpOldSettingsCheckMessage": "Následující indexovače PassThePopcorn mají zastaralá nastavení a měla by být aktualizována: {0}",
"QualitiesHelpText": "Kvality vyšší v seznamu jsou upřednostňovány. Vlastnosti ve stejné skupině jsou stejné. Chtějí se pouze ověřené kvality",
"QualitiesHelpText": "Kvality výše v seznamu jsou více upřednostňovány, i když nejsou zaškrtnuty. Kvality v rámci stejné skupiny jsou si rovny. Pouze zaškrtnuté kvality jsou požadovány",
"QualityProfileInUseMovieListCollection": "Nelze odstranit kvalitní profil připojený k filmu",
"CalendarFeed": "Informační kanál {appName} Calendar",
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
@@ -413,7 +413,7 @@
"PhysicalRelease": "Fyzické uvolnění",
"Port": "Přístav",
"PortNumber": "Číslo portu",
"ProfilesSettingsSummary": "Profily kvality, jazyka a zpoždění",
"ProfilesSettingsSummary": "Profily kvality, jazyka, zpoždění a vydání",
"Progress": "Pokrok",
"Proper": "Správně",
"Protocol": "Protokol",
@@ -422,7 +422,7 @@
"AllowHardcodedSubsHelpText": "Zjištěné pevně zakódované odběry budou automaticky staženy",
"QualityProfile": "Profil kvality",
"QualityProfiles": "Profily kvality",
"QuickImport": "Rychlý import",
"QuickImport": "Přesunout Automaticky",
"SupportedDownloadClients": "{appName} podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.",
"SupportedListsMovie": "{appName} podporuje všechny seznamy filmů RSS i níže uvedené.",
"RadarrTags": "{appName} tagy",
@@ -430,7 +430,7 @@
"RecyclingBinHelpText": "Zde budou filmové soubory odstraněny, místo aby byly trvale odstraněny",
"Source": "Zdroj",
"RefreshAndScan": "Obnovit a skenovat",
"RequiredHelpText": "Aby se mohl použít vlastní formát, musí být splněna tato podmínka {0}. Jinak stačí jedna shoda {1}.",
"RequiredHelpText": "Tato podmínka {implementationName} musí být splněna, aby se použil vlastní formát. Jinak stačí jediná shoda {implementationName}.",
"AddNewRestriction": "Přidat nové omezení",
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možná, aby se zabránilo odstranění AppData při aktualizaci",
"RestartRequiredHelpTextWarning": "Vyžaduje restart, aby se projevilo",
@@ -440,7 +440,7 @@
"RootFolderCheckMultipleMessage": "Chybí více kořenových složek: {rootFolderPaths}",
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
"FileBrowserPlaceholderText": "Začněte psát nebo vyberte cestu níže",
"StartupDirectory": "Spouštěcí adresář",
"StartupDirectory": "Spouštěcí Adresář",
"System": "Systém",
"SystemTimeHealthCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven",
"Posters": "Plakáty",
@@ -519,7 +519,7 @@
"EditMovie": "Upravit film",
"DeleteQualityProfile": "Smažte profil kvality",
"DeleteRestriction": "Odstranit omezení",
"DeleteSelectedMovie": "Odstranit vybrané filmy",
"DeleteSelectedMovie": "Odstranit vybraný film",
"DeleteSelectedMovieFiles": "Odstranit vybrané filmové soubory",
"DeleteMovieFolderConfirmation": "Složka filmu „{0}“ a veškerý její obsah budou smazány.",
"DestinationPath": "Cesta k cíli",
@@ -544,7 +544,7 @@
"Downloading": "Stahování",
"DownloadPropersAndRepacksHelpText": "Zda se má automaticky upgradovat na Propers / Repacks",
"DownloadPropersAndRepacksHelpTextCustomFormat": "Pomocí možnosti „Nepřednostňujte“ můžete třídit podle skóre vlastního formátu přes položky Proppers / Repacks",
"DownloadWarning": "Upozornění na stahování: {0}",
"DownloadWarning": "Varování při stahování: {warningMessage}",
"EditCustomFormat": "Upravit vlastní formát",
"Edition": "Edice",
"EditImportListExclusion": "Upravit vyloučení seznamu",
@@ -650,7 +650,7 @@
"Links": "Odkazy",
"ImportLists": "Seznamy",
"ImportListSettings": "Nastavení seznamu",
"ListSyncLevelHelpText": "Filmy v knihovně budou odstraněny nebo nesledovány, pokud nejsou ve vašem seznamu",
"ListSyncLevelHelpText": "Filmy v knihovně budou zpracovány na základě vašeho výběru, pokud vypadnou nebo se neobjeví na vašich seznamech",
"LogFiles": "Záznam souborů",
"Logging": "Protokolování",
"LogLevel": "Úroveň protokolu",
@@ -702,7 +702,7 @@
"NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky",
"Options": "Možnosti",
"Organize": "Organizovat",
"OrganizeConfirm": "Opravdu chcete uspořádat všechny soubory ve {0} vybraných filmech?",
"OrganizeConfirm": "Opravdu chcete uspořádat všechny soubory ve {count} vybraném filmu(ech)?",
"OrganizeSelectedMovies": "Uspořádejte vybrané filmy",
"Original": "Originál",
"OutputPath": "Výstupní cesta",
@@ -713,7 +713,7 @@
"PreferIndexerFlagsHelpText": "Upřednostněte vydání pomocí speciálních vlajek",
"Preferred": "Upřednostňováno",
"Priority": "Přednost",
"PrioritySettings": "Priorita: {0}",
"PrioritySettings": "Priorita: {priority}",
"ProcessingFolders": "Zpracování složek",
"ProxyCheckBadRequestMessage": "Nepodařilo se otestovat proxy. StatusCode: {statusCode}",
"ProxyCheckResolveIpMessage": "Nepodařilo se vyřešit adresu IP konfigurovaného hostitele proxy {proxyHostName}",
@@ -744,7 +744,7 @@
"RemovedMovieCheckSingleMessage": "Film {movie} byl odebrán z TMDb",
"RemoveFailedDownloadsHelpText": "Odebrat neúspěšná stahování z historie stahování klienta",
"RemoveFilter": "Vyjměte filtr",
"RemoveFromDownloadClient": "Odebrat ze staženého klienta",
"RemoveFromDownloadClient": "Odebrat z Klienta pro stahování",
"RemoveFromQueue": "Odebrat z fronty",
"Renamed": "Přejmenováno",
"RemoveHelpTextWarning": "Odebráním odstraníte stažené soubory a soubory z klienta pro stahování.",
@@ -848,7 +848,7 @@
"Table": "Stůl",
"TableOptions": "Možnosti tabulky",
"TableColumnsHelpText": "Vyberte, které sloupce jsou viditelné a v jakém pořadí se zobrazují",
"TagDetails": "Podrobnosti značky - {0}",
"TagDetails": "Podrobnosti značky - {label}",
"TagIsNotUsedAndCanBeDeleted": "Značka se nepoužívá a lze ji smazat",
"Tags": "Značky",
"TagsSettingsSummary": "Podívejte se na všechny značky a na to, jak se používají. Nepoužité značky lze odstranit",
@@ -945,7 +945,7 @@
"Edit": "Upravit",
"SqliteVersionCheckUpgradeRequiredMessage": "Aktuálně nainstalovaná verze SQLite {0} již není podporována. Upgradujte SQLite alespoň na verzi {1}.",
"ShowReleaseDate": "Zobrazit datum vydání",
"ShowReleaseDateHelpText": "Zobrazit datum vydání pod plakátem",
"ShowReleaseDateHelpText": "Zobrazit datum vydání na základě minimální dostupnosti pod plakátem",
"ShowCinemaRelease": "Zobrazit datum vydání kina",
"OnMovieDelete": "Při mazání filmu",
"OnMovieFileDelete": "Při mazání filmových souborů",
@@ -979,7 +979,7 @@
"DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?",
"DeleteFormatMessageText": "Opravdu chcete smazat značku formátu {0}?",
"RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {selectedCount} položky z fronty?",
"ApplyTagsHelpTextAdd": "Přidat: Přidat štítky do existujícího seznamu štítků",
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít štítky na vybrané indexery",
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané štítky",
@@ -1338,5 +1338,31 @@
"DownloadClientFreeboxAuthenticationError": "Přihlášení k Freebox API se nezdařilo. Důvod: {errorDescription}",
"DownloadClientDownloadStationValidationFolderMissingDetail": "Složka '{downloadDir}' neexistuje, musí být vytvořená ručně ve Sdílené složce '{sharedFolder}'.",
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Přihlaste se do vaší DiskStation jako {username} a ručně to nastavte v nastavení DownloadStation pod BT/HTTP/FTP/NZB -> Umístění.",
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "DiskStation nemá sdílenou složku s názvem '{sharedFolder}', jste si jisti, že jste ji zadali správně?"
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "DiskStation nemá sdílenou složku s názvem '{sharedFolder}', jste si jisti, že jste ji zadali správně?",
"AutoTaggingSpecificationStudio": "Studio (a)",
"BlocklistedAt": "Blocklistováno od {date}",
"DeleteMovieFolderCountWithFilesConfirmation": "Opravdu si přejete smazat {count} vybraných filmů a veškerý obsah?",
"DeleteMovieFolderMovieCount": "{movieFileCount} filmových souborů o celkové velikosti {size}",
"NotificationsAppriseSettingsPasswordHelpText": "Heslo pro základní HTTP autentizaci",
"NotificationsAppriseSettingsServerUrl": "URL serveru Apprise",
"NotificationsAppriseSettingsStatelessUrls": "Bezstavové URL adresy Apprise",
"NotificationsAppriseSettingsStatelessUrlsHelpText": "Jedna nebo více URL adres oddělených čárkami, které určují, kam má být oznámení odesláno. Ponechte prázdné, pokud je použito trvalé úložiště.",
"NotificationsAppriseSettingsTags": "Tagy Apprise",
"NotificationsAppriseSettingsTagsHelpText": "Volitelně upozornit pouze ty, kteří jsou odpovídajícím způsobem označeni.",
"NotificationsAppriseSettingsUsernameHelpText": "Uživatelské jméno pro základní HTTP autentizaci",
"NotificationsCustomScriptSettingsName": "Vlastní skript",
"NotificationsCustomScriptSettingsArguments": "Argumenty",
"NotificationsCustomScriptSettingsArgumentsHelpText": "Argumenty, které se mají předat skriptu",
"NotificationsCustomScriptValidationFileDoesNotExist": "Soubor neexistuje",
"NotificationsCustomScriptSettingsProviderMessage": "Testování spustí skript s EventType nastaveným na {eventTypeTest}, ujistěte se, že váš skript toto správně zpracovává",
"NotificationsAppriseSettingsIncludePosterHelpText": "Zahrnout plakát do zprávy",
"NotificationsAppriseSettingsIncludePoster": "Zahrnout plakát",
"AutoTaggingSpecificationMaximumRuntime": "Maximální délka trvání",
"AutoTaggingSpecificationMinimumRuntime": "Minimální délka trvání",
"DownloadClientFreeboxUnableToReachFreeboxApi": "Nelze se připojit k Freebox API. Ověřte nastavení 'URL API' pro základní URL a verzi.",
"NotificationsAppriseSettingsNotificationType": "Typ oznámení Apprise",
"AnnouncedMovieAvailabilityDescription": "Filmy jsou považovány za dostupné, jakmile jsou přidány do {appName}.",
"CustomFormatsSpecificationQualityModifier": "Modifikátor kvality",
"NotificationsAppriseSettingsServerUrlHelpText": "URL serveru Apprise, včetně http(s):// a portu, pokud je potřeba",
"Disposition": "Dispozice"
}

View File

@@ -119,6 +119,7 @@
"AutoTaggingNegateHelpText": "If checked, the auto tagging rule will not apply if this {implementationName} condition matches.",
"AutoTaggingRequiredHelpText": "This {implementationName} condition must match for the auto tagging rule to apply. Otherwise a single {implementationName} match is sufficient.",
"AutoTaggingSpecificationGenre": "Genre(s)",
"AutoTaggingSpecificationKeyword": "Keyword(s)",
"AutoTaggingSpecificationMaximumRuntime": "Maximum Runtime",
"AutoTaggingSpecificationMaximumYear": "Maximum Year",
"AutoTaggingSpecificationMinimumRuntime": "Minimum Runtime",
@@ -959,6 +960,7 @@
"KeyboardShortcutsMovieIndexScrollTop": "Movie Index: Scroll Top",
"KeyboardShortcutsOpenModal": "Open This Modal",
"KeyboardShortcutsSaveSettings": "Save Settings",
"Keywords": "Keywords",
"Label": "Label",
"LabelIsRequired": "Label is required",
"Language": "Language",

View File

@@ -974,7 +974,7 @@
"RemoveFromBlocklist": "Удалить из черного списка",
"Blocklisted": "Черный список",
"BlocklistReleases": "Релиз из черного списка",
"RemoveFailed": "Удаление не удалось",
"RemoveFailed": "Удалять после ошибки",
"RemoveSelectedItem": "Удалить выбранный элемент",
"RemoveSelectedItems": "Удалить выбранные элементы",
"BypassDelayIfHighestQuality": "Игнорировать при максимальном качестве",
@@ -984,7 +984,7 @@
"NotificationTriggersHelpText": "Выберите события, которые должны вызвать это уведомление",
"RemotePathMappingCheckFileRemoved": "Файл {path} был удален в процессе обработки.",
"RemotePathMappingCheckDockerFolderMissing": "Вы используете docker; загрузчик {downloadClientName} размещает загрузки в {path}, но этот каталог, похоже, не существует внутри контейнера. Проверьте соответствия удаленных путей и настройки тома контейнера.",
"RemoveCompleted": "Удаление завершено",
"RemoveCompleted": "Удалять завершенные",
"RemoveDownloadsAlert": "Параметры удаления перенесены в настройки загрузчиков в таблице выше.",
"TaskUserAgentTooltip": "User-Agent, представленный приложением, который вызывает API",
"IndexerTagMovieHelpText": "Используйте этот индексатор только для фильмов с хотя бы одним совпадающим тегом. Оставьте пустым, чтобы использовать для всех фильмов.",

View File

@@ -2015,5 +2015,8 @@
"MovieEditRootFolderHelpText": "Filmleri aynı kök klasöre taşımak, film klasörlerinin güncellenen başlığa veya adlandırma biçimine uyacak şekilde yeniden adlandırılmasında kullanılabilir",
"UpdateMoviePath": "Film Yolunu Güncelle",
"EditMovieCollectionModalHeader": "Düzenle - {title}",
"NotificationsAppriseSettingsIncludePoster": "Posteri Dahil Et"
"NotificationsAppriseSettingsIncludePoster": "Posteri Dahil Et",
"MovieFolderFormatHelpTextDeprecatedWarning": "Film dosyası özellikleriyle ilişkili token'lar artık kullanımdan kaldırılmıştır ve gelecek ana sürümlerde desteklenmeyecektir.",
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage": "Film Klasör Formatı, artık kullanılmayan dosya ilişkili token'lar içermemelidir: {tokens}",
"NotificationsAppriseSettingsIncludePosterHelpText": "Mesaja poster ekle"
}

View File

@@ -14,5 +14,8 @@
"Yesterday": "昨天",
"Updates": "更新",
"Warn": "警告",
"BackupNow": "立即备份"
"BackupNow": "立即备份",
"AddANewPath": "新建",
"Actions": "Actions",
"AddAutoTagError": "添加"
}

View File

@@ -18,6 +18,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public int? Runtime { get; set; }
public List<ImageResource> Images { get; set; }
public List<string> Genres { get; set; }
public List<string> Keywords { get; set; }
public int Year { get; set; }
public DateTime? Premier { get; set; }

View File

@@ -272,7 +272,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.Ratings = MapRatings(resource.MovieRatings) ?? new Ratings();
movie.TmdbId = resource.TmdbId;
movie.Genres = resource.Genres;
movie.Genres = resource.Genres ?? new List<string>();
movie.Keywords = resource.Keywords ?? new List<string>();
movie.Images = resource.Images.Select(MapImage).ToList();
movie.Recommendations = resource.Recommendations?.Select(r => r.TmdbId).ToList() ?? new List<int>();

View File

@@ -56,6 +56,8 @@ namespace NzbDrone.Core.Movies
_movieMetadataService.Upsert(newMovie.MovieMetadata.Value);
newMovie.MovieMetadataId = newMovie.MovieMetadata.Value.Id;
_movieService.UpdateTags(newMovie);
_movieService.AddMovie(newMovie);
return newMovie;

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Movies
Translations = new List<MovieTranslation>();
Images = new List<MediaCover.MediaCover>();
Genres = new List<string>();
Keywords = new List<string>();
OriginalLanguage = Language.English;
Recommendations = new List<int>();
Ratings = new Ratings();
@@ -24,6 +25,7 @@ namespace NzbDrone.Core.Movies
public List<MediaCover.MediaCover> Images { get; set; }
public List<string> Genres { get; set; }
public List<string> Keywords { get; set; }
public DateTime? InCinemas { get; set; }
public DateTime? PhysicalRelease { get; set; }
public DateTime? DigitalRelease { get; set; }

View File

@@ -114,6 +114,7 @@ namespace NzbDrone.Core.Movies
movieMetadata.Runtime = movieInfo.Runtime;
movieMetadata.Ratings = movieInfo.Ratings;
movieMetadata.Genres = movieInfo.Genres;
movieMetadata.Keywords = movieInfo.Keywords;
movieMetadata.Certification = movieInfo.Certification;
movieMetadata.InCinemas = movieInfo.InCinemas;
movieMetadata.Website = movieInfo.Website;
@@ -205,8 +206,14 @@ namespace NzbDrone.Core.Movies
}
}
private void UpdateTags(Movie movie)
private void UpdateTags(Movie movie, bool isNew)
{
if (isNew)
{
_logger.Trace("Skipping tag update for {0}. Reason: New movie", movie);
return;
}
var tagsUpdated = _movieService.UpdateTags(movie);
if (tagsUpdated)
@@ -230,7 +237,7 @@ namespace NzbDrone.Core.Movies
try
{
movie = RefreshMovieInfo(movieId);
UpdateTags(movie);
UpdateTags(movie, isNew);
RescanMovie(movie, isNew, trigger);
}
catch (MovieNotFoundException)
@@ -240,7 +247,7 @@ namespace NzbDrone.Core.Movies
catch (Exception e)
{
_logger.Error(e, "Couldn't refresh info for {0}", movie);
UpdateTags(movie);
UpdateTags(movie, isNew);
RescanMovie(movie, isNew, trigger);
throw;
}
@@ -277,13 +284,13 @@ namespace NzbDrone.Core.Movies
_logger.Error(e, "Couldn't refresh info for {0}", movieLocal);
}
UpdateTags(movie);
UpdateTags(movie, false);
RescanMovie(movieLocal, false, trigger);
}
else
{
_logger.Debug("Skipping refresh of movie: {0}", movieLocal.Title);
UpdateTags(movie);
UpdateTags(movie, false);
RescanMovie(movieLocal, false, trigger);
}
}

View File

@@ -79,7 +79,9 @@ namespace NzbDrone.Core.Organizer
{ "rum", "ron" },
{ "slo", "slk" },
{ "tib", "bod" },
{ "wel", "cym" }
{ "wel", "cym" },
{ "khk", "mon" },
{ "mvf", "mon" }
}.ToImmutableDictionary();
public static readonly ImmutableArray<string> BadCharacters = ImmutableArray.Create("\\", "/", "<", ">", "?", "*", "|", "\"");

View File

@@ -64,7 +64,8 @@ namespace NzbDrone.Core.Parser
new IsoLanguage("mr", "", "mar", "Marathi", Language.Marathi),
new IsoLanguage("tl", "", "tgl", "Tagalog", Language.Tagalog),
new IsoLanguage("ur", "", "urd", "Urdu", Language.Urdu),
new IsoLanguage("rm", "", "roh", "Romansh", Language.Romansh)
new IsoLanguage("rm", "", "roh", "Romansh", Language.Romansh),
new IsoLanguage("mn", "", "mon", "Mongolian", Language.Mongolian)
};
private static readonly Dictionary<string, Language> AlternateIsoCodeMappings = new ()

View File

@@ -39,6 +39,7 @@ namespace NzbDrone.Core.Parser
(?<korean>\bKOR\b)|
(?<urdu>\burdu\b)|
(?<romansh>\b(?:romansh|rumantsch|romansch)\b)|
(?<mongolian>\b(?:mongolian|khalkha)\b)|
(?<original>\b(?:orig|original)\b)",
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
@@ -432,6 +433,11 @@ namespace NzbDrone.Core.Parser
languages.Add(Language.Romansh);
}
if (match.Groups["mongolian"].Success)
{
languages.Add(Language.Mongolian);
}
if (match.Groups["original"].Success)
{
languages.Add(Language.Original);

View File

@@ -96,6 +96,11 @@ namespace NzbDrone.Core.RemotePathMappings
throw new ArgumentException("Invalid Host");
}
if (mapping.RemotePath.StartsWith(" "))
{
throw new ArgumentException("Remote Path must not start with a space");
}
var remotePath = new OsPath(mapping.RemotePath);
var localPath = new OsPath(mapping.LocalPath);

View File

@@ -3,6 +3,8 @@ using System.IO;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using Radarr.Api.V3.AutoTagging;
using Radarr.Http.ClientSchema;
namespace NzbDrone.Integration.Test.ApiTests
{
@@ -29,6 +31,53 @@ namespace NzbDrone.Integration.Test.ApiTests
result.Tags.Should().Equal(tag.Id);
}
[Test]
[Order(0)]
public void add_movie_should_trigger_autotag()
{
var tag = EnsureTag("autotag-test");
var movie = Movies.Lookup("imdb:tt0110912").Single();
movie.Genres = new List<string> { "Thriller" };
var item = AutoTagging.Post(new AutoTaggingResource
{
Name = "Test",
RemoveTagsAutomatically = false,
Tags = new HashSet<int> { tag.Id },
Specifications = new List<AutoTaggingSpecificationSchema>
{
new AutoTaggingSpecificationSchema
{
Name = "Test",
Implementation = "GenreSpecification",
ImplementationName = "Genre",
Negate = false,
Required = false,
Fields = new List<Field>
{
new Field
{
Name = "value",
Label = "Genre(s)",
Type = "tag",
Value = new List<string> { "Thriller" }
}
}
}
}
});
EnsureNoMovie(680, "Pulp Fiction");
movie.QualityProfileId = 1;
movie.Path = Path.Combine(MovieRootFolder, movie.Title);
var result = Movies.Post(movie);
result.Should().NotBeNull();
result.Tags.Should().Contain(tag.Id);
}
[Test]
[Order(0)]
public void add_movie_without_profileid_should_return_badrequest()

View File

@@ -17,6 +17,7 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Integration.Test.Client;
using NzbDrone.SignalR;
using NzbDrone.Test.Common.Categories;
using Radarr.Api.V3.AutoTagging;
using Radarr.Api.V3.Blocklist;
using Radarr.Api.V3.Config;
using Radarr.Api.V3.DownloadClient;
@@ -36,6 +37,7 @@ namespace NzbDrone.Integration.Test
{
protected RestClient RestClient { get; private set; }
public ClientBase<AutoTaggingResource> AutoTagging;
public ClientBase<BlocklistResource> Blocklist;
public CommandClient Commands;
public ClientBase<TaskResource> Tasks;
@@ -99,6 +101,7 @@ namespace NzbDrone.Integration.Test
RestClient.AddDefaultHeader("Authentication", ApiKey);
RestClient.AddDefaultHeader("X-Api-Key", ApiKey);
AutoTagging = new ClientBase<AutoTaggingResource>(RestClient, ApiKey);
Blocklist = new ClientBase<BlocklistResource>(RestClient, ApiKey);
Commands = new CommandClient(RestClient, ApiKey);
Tasks = new ClientBase<TaskResource>(RestClient, ApiKey, "system/task");

View File

@@ -172,6 +172,11 @@ namespace Radarr.Api.V3.MovieFiles
[Consumes("application/json")]
public object DeleteMovieFiles([FromBody] MovieFileListResource resource)
{
if (!resource.MovieFileIds.Any())
{
throw new BadRequestException("movieFileIds must be provided");
}
var movieFiles = _mediaFileService.GetMovies(resource.MovieFileIds);
var movie = _movieService.GetMovie(movieFiles.First().MovieId);

View File

@@ -6,7 +6,7 @@ namespace Radarr.Api.V3.MovieFiles
{
public class MovieFileListResource
{
public List<int> MovieFileIds { get; set; }
public List<int> MovieFileIds { get; set; } = new ();
public List<Language> Languages { get; set; }
public QualityModel Quality { get; set; }
public string Edition { get; set; }

View File

@@ -76,6 +76,7 @@ namespace Radarr.Api.V3.Movies
public string Folder { get; set; }
public string Certification { get; set; }
public List<string> Genres { get; set; }
public List<string> Keywords { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddMovieOptions AddOptions { get; set; }
@@ -153,6 +154,7 @@ namespace Radarr.Api.V3.Movies
Certification = model.MovieMetadata.Value.Certification,
Website = model.MovieMetadata.Value.Website,
Genres = model.MovieMetadata.Value.Genres,
Keywords = model.MovieMetadata.Value.Keywords,
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation.Paths;
using Radarr.Http;
@@ -21,11 +22,19 @@ namespace Radarr.Api.V3.RemotePathMappings
_remotePathMappingService = remotePathMappingService;
SharedValidator.RuleFor(c => c.Host)
.NotEmpty();
.NotEmpty();
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
SharedValidator.RuleFor(c => c.RemotePath)
.NotEmpty();
.NotEmpty();
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.StartsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not start with a space");
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not end with a space");
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.Stop)

View File

@@ -92,7 +92,7 @@ namespace Radarr.Api.V3.System.Backup
}
[HttpPost("restore/upload")]
[RequestFormLimits(MultipartBodyLengthLimit = 500000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 5000000000)]
public object UploadAndRestore()
{
var files = Request.Form.Files;

View File

@@ -11027,6 +11027,13 @@
},
"nullable": true
},
"keywords": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true
},
"tags": {
"uniqueItems": true,
"type": "array",