1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-10 15:10:09 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Mark McDowall
720371c1fd Upgraded SixLabors.ImageSharp to 3.1.11 2025-08-01 09:20:12 -07:00
69 changed files with 350 additions and 2025 deletions

View File

@@ -7,7 +7,6 @@
### Version V1.0.2 2024-01-03 - markus101 - Get user input from /dev/tty
### Version V1.0.3 2024-01-06 - StevieTV - exit script when it is ran from install directory
### Version V1.0.4 2025-04-05 - kaecyra - Allow user/group to be supplied via CLI, add unattended mode
### Version V1.0.5 2025-07-08 - bparkin1283 - use systemctl instead of service for stopping app
### Boilerplate Warning
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
@@ -168,10 +167,11 @@ if ! getent group "$app_guid" | grep -qw "$app_uid"; then
echo "Added User [$app_uid] to Group [$app_guid]"
fi
# Stop and disable the App if running
if [ $(systemctl is-active "$app") = "active" ]; then
systemctl disable --now -q "$app"
echo "Stopped and disabled existing $app"
# Stop the App if running
if service --status-all | grep -Fq "$app"; then
systemctl stop "$app"
systemctl disable "$app".service
echo "Stopped existing $app"
fi
# Create Appdata Directory

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames';
import React, { SyntheticEvent, useCallback, useState } from 'react';
import React, { useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
@@ -123,31 +122,8 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
setIsDeleteSeriesModalOpen(false);
}, [setIsDeleteSeriesModalOpen]);
const [selectState, selectDispatch] = useSelect();
const onSelectPress = useCallback(
(event: SyntheticEvent<HTMLElement, MouseEvent>) => {
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
window.open(`/series/${titleSlug}`, '_blank');
return;
}
const shiftKey = event.nativeEvent.shiftKey;
selectDispatch({
type: 'toggleSelected',
id: seriesId,
isSelected: !selectState.selectedState[seriesId],
shiftKey,
});
},
[seriesId, selectState.selectedState, selectDispatch, titleSlug]
);
const link = `/series/${titleSlug}`;
const linkProps = isSelectMode ? { onPress: onSelectPress } : { to: link };
const elementStyle = {
width: `${posterWidth}px`,
height: `${posterHeight}px`,
@@ -199,7 +175,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
/>
) : null}
<Link className={styles.link} style={elementStyle} {...linkProps}>
<Link className={styles.link} style={elementStyle} to={link}>
<SeriesPoster
style={elementStyle}
images={images}

View File

@@ -4,8 +4,6 @@ namespace NzbDrone.Common.Extensions
{
public static class DateTimeExtensions
{
public static readonly DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static bool InNextDays(this DateTime dateTime, int days)
{
return InNext(dateTime, new TimeSpan(days, 0, 0, 0));
@@ -45,10 +43,5 @@ namespace NzbDrone.Common.Extensions
{
return dateTime.AddTicks(-(dateTime.Ticks % TimeSpan.TicksPerSecond));
}
public static DateTime WithTicksFrom(this DateTime dateTime, DateTime other)
{
return dateTime.WithoutTicks().AddTicks(other.Ticks % TimeSpan.TicksPerSecond);
}
}
}

View File

@@ -390,12 +390,5 @@ namespace NzbDrone.Common.Http
return this;
}
public virtual HttpRequestBuilder AllowRedirect(bool allowAutoRedirect = true)
{
AllowAutoRedirect = allowAutoRedirect;
return this;
}
}
}

View File

@@ -1,88 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class nzb_su_url_to_nzb_lifeFixture : MigrationTest<nzb_su_url_to_nzb_life>
{
[TestCase("Newznab", "https://api.nzb.su")]
[TestCase("Newznab", "http://api.nzb.su")]
public void should_replace_old_url(string impl, string baseUrl)
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Nzb.su",
Implementation = impl,
Settings = new NewznabSettings219
{
BaseUrl = baseUrl,
ApiPath = "/api"
}.ToJson(),
ConfigContract = impl + "Settings",
EnableInteractiveSearch = false
});
});
var items = db.Query<IndexerDefinition219>("SELECT * FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Settings.ToObject<NewznabSettings219>().BaseUrl.Should().Be(baseUrl.Replace("su", "life"));
}
[TestCase("Newznab", "https://api.indexer.com")]
public void should_not_replace_different_url(string impl, string baseUrl)
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Indexer.com",
Implementation = impl,
Settings = new NewznabSettings219
{
BaseUrl = baseUrl,
ApiPath = "/api"
}.ToJson(),
ConfigContract = impl + "Settings",
EnableInteractiveSearch = false
});
});
var items = db.Query<IndexerDefinition219>("SELECT * FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Settings.ToObject<NewznabSettings219>().BaseUrl.Should().Be(baseUrl);
}
}
internal class IndexerDefinition219
{
public int Id { get; set; }
public string Name { get; set; }
public JObject Settings { get; set; }
public int Priority { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; }
public HashSet<int> Tags { get; set; }
public int DownloadClientId { get; set; }
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
}
internal class NewznabSettings219
{
public string BaseUrl { get; set; }
public string ApiPath { get; set; }
}
}

View File

@@ -27,7 +27,6 @@
"TvrageID":"4055",
"ImdbID":"0320037",
"InfoHash":"123",
"Tags": ["Subtitles"],
"DownloadURL":"https:\/\/broadcasthe.net\/torrents.php?action=download&id=123&authkey=123&torrent_pass=123"
},
"1234":{
@@ -55,9 +54,8 @@
"TvrageID":"38472",
"ImdbID":"2377081",
"InfoHash":"1234",
"Tags": [],
"DownloadURL":"https:\/\/broadcasthe.net\/torrents.php?action=download&id=1234&authkey=1234&torrent_pass=1234"
}},
"results":"117927"
}
}
}

View File

@@ -124,34 +124,5 @@
<newznab:attr name="nuked" value="0"/>
</item>
<item>
<title>title</title>
<guid isPermaLink="true">subs=eng</guid>
<link>link</link>
<comments>comments</comments>
<pubDate>Sat, 31 Aug 2024 12:28:40 +0300</pubDate>
<category>category</category>
<description>description</description>
<enclosure url="url" length="500" type="application/x-nzb"/>
<newznab:attr name="haspretime" value="0"/>
<newznab:attr name="nuked" value="0"/>
<newznab:attr name="subs" value="Eng"/>
</item>
<item>
<title>title</title>
<guid isPermaLink="true">subs=''</guid>
<link>link</link>
<comments>comments</comments>
<pubDate>Sat, 31 Aug 2024 12:28:40 +0300</pubDate>
<category>category</category>
<description>description</description>
<enclosure url="url" length="500" type="application/x-nzb"/>
<newznab:attr name="haspretime" value="0"/>
<newznab:attr name="nuked" value="0"/>
<newznab:attr name="subs" value=""/>
</item>
</channel>
</rss>

View File

@@ -64,8 +64,6 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
torrentInfo.Container.Should().Be("MP4");
torrentInfo.Codec.Should().Be("x264");
torrentInfo.Resolution.Should().Be("SD");
torrentInfo.IndexerFlags.Should().HaveFlag(IndexerFlags.Subtitles);
}
private void VerifyBackOff()

View File

@@ -165,8 +165,6 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[TestCase("nuked=0 attribute")]
[TestCase("prematch=1 and nuked=1 attributes", IndexerFlags.Scene, IndexerFlags.Nuked)]
[TestCase("haspretime=0 and nuked=0 attributes")]
[TestCase("subs=eng", IndexerFlags.Subtitles)]
[TestCase("subs=''")]
public async Task should_parse_indexer_flags(string releaseGuid, params IndexerFlags[] indexerFlags)
{
var feed = ReadAllText(@"Files/Indexers/Newznab/newznab_indexerflags.xml");

View File

@@ -59,7 +59,6 @@ namespace NzbDrone.Core.Test.IndexerTests
public void should_return_season_time_for_season_packs()
{
var settings = new TorznabSettings();
settings.SeedCriteria.SeasonPackSeedGoal = (int)SeasonPackSeedGoal.UseSeasonPackSeedGoal;
settings.SeedCriteria.SeasonPackSeedTime = 10;
Mocker.GetMock<ICachedIndexerSettingsProvider>()
@@ -86,71 +85,5 @@ namespace NzbDrone.Core.Test.IndexerTests
result.Should().NotBeNull();
result.SeedTime.Should().Be(TimeSpan.FromMinutes(10));
}
[Test]
public void should_return_season_ratio_for_season_packs_when_set()
{
var settings = new TorznabSettings();
settings.SeedCriteria.SeasonPackSeedGoal = (int)SeasonPackSeedGoal.UseSeasonPackSeedGoal;
settings.SeedCriteria.SeedRatio = 1.0;
settings.SeedCriteria.SeasonPackSeedRatio = 10.0;
Mocker.GetMock<ICachedIndexerSettingsProvider>()
.Setup(v => v.GetSettings(It.IsAny<int>()))
.Returns(new CachedIndexerSettings
{
FailDownloads = new HashSet<FailDownloads> { FailDownloads.Executables },
SeedCriteriaSettings = settings.SeedCriteria
});
var result = Subject.GetSeedConfiguration(new RemoteEpisode
{
Release = new ReleaseInfo
{
DownloadProtocol = DownloadProtocol.Torrent,
IndexerId = 1
},
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
FullSeason = true
}
});
result.Should().NotBeNull();
result.Ratio.Should().Be(10.0);
}
[Test]
public void should_return_standard_ratio_for_season_packs_when_not_set()
{
var settings = new TorznabSettings();
settings.SeedCriteria.SeasonPackSeedGoal = (int)SeasonPackSeedGoal.UseStandardSeedGoal;
settings.SeedCriteria.SeedRatio = 1.0;
settings.SeedCriteria.SeasonPackSeedRatio = 10.0;
Mocker.GetMock<ICachedIndexerSettingsProvider>()
.Setup(v => v.GetSettings(It.IsAny<int>()))
.Returns(new CachedIndexerSettings
{
FailDownloads = new HashSet<FailDownloads> { FailDownloads.Executables },
SeedCriteriaSettings = settings.SeedCriteria
});
var result = Subject.GetSeedConfiguration(new RemoteEpisode
{
Release = new ReleaseInfo
{
DownloadProtocol = DownloadProtocol.Torrent,
IndexerId = 1
},
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
FullSeason = true
}
});
result.Should().NotBeNull();
result.Ratio.Should().Be(1.0);
}
}
}

View File

@@ -60,8 +60,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 48, Language.Uzbek },
new object[] { 49, Language.Malay },
new object[] { 50, Language.Urdu },
new object[] { 51, Language.Romansh },
new object[] { 52, Language.Georgian }
new object[] { 51, Language.Romansh }
};
public static object[] ToIntCases =
@@ -116,8 +115,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Uzbek, 48 },
new object[] { Language.Malay, 49 },
new object[] { Language.Urdu, 50 },
new object[] { Language.Romansh, 51 },
new object[] { Language.Georgian, 52 }
new object[] { Language.Romansh, 51 }
};
[Test]

View File

@@ -1,118 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.UpdateEpisodeFileServiceTests
{
[TestFixture]
public class ChangeFileDateForFileFixture : CoreTest<UpdateEpisodeFileService>
{
private readonly DateTime _veryOldAirDateUtc = new(1965, 01, 01, 0, 0, 0, 512, 512, DateTimeKind.Utc);
private DateTime _lastWrite = new(2025, 07, 27, 12, 0, 0, 512, 512, DateTimeKind.Utc);
private Series _series;
private EpisodeFile _episodeFile;
private string _seriesFolder;
private List<Episode> _episodes;
[SetUp]
public void Setup()
{
_seriesFolder = @"C:\Test\TV\Series Title".AsOsAgnostic();
_series = Builder<Series>.CreateNew()
.With(s => s.Path = _seriesFolder)
.Build();
_episodes = Builder<Episode>.CreateListOfSize(1)
.All()
.With(e => e.AirDateUtc = _lastWrite.AddDays(2))
.Build()
.ToList();
_episodeFile = Builder<EpisodeFile>.CreateNew()
.With(f => f.Path = Path.Combine(_series.Path, "Season 1", "Series Title - S01E01.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Season 1\Series Title - S01E01.mkv".AsOsAgnostic())
.Build();
Mocker.GetMock<IDiskProvider>()
.Setup(x => x.FileGetLastWrite(_episodeFile.Path))
.Returns(() => _lastWrite);
Mocker.GetMock<IDiskProvider>()
.Setup(x => x.FileSetLastWriteTime(_episodeFile.Path, It.IsAny<DateTime>()))
.Callback<string, DateTime>((path, dateTime) =>
{
_lastWrite = dateTime.Kind == DateTimeKind.Utc
? dateTime
: dateTime.ToUniversalTime();
});
Mocker.GetMock<IConfigService>()
.Setup(x => x.FileDate)
.Returns(FileDateType.LocalAirDate);
}
[Test]
public void should_change_date_once_only()
{
var previousWrite = new DateTime(_lastWrite.Ticks, _lastWrite.Kind);
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.FileSetLastWriteTime(_episodeFile.Path, It.IsAny<DateTime>()), Times.Once());
var actualWriteTime = Mocker.GetMock<IDiskProvider>().Object.FileGetLastWrite(_episodeFile.Path).ToLocalTime();
actualWriteTime.Should().Be(_episodes[0].AirDateUtc.Value.ToLocalTime().WithTicksFrom(previousWrite));
}
[Test]
public void should_clamp_mtime_on_posix()
{
PosixOnly();
var previousWrite = new DateTime(_lastWrite.Ticks, _lastWrite.Kind);
_episodes[0].AirDateUtc = _veryOldAirDateUtc;
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.FileSetLastWriteTime(_episodeFile.Path, It.IsAny<DateTime>()), Times.Once());
var actualWriteTime = Mocker.GetMock<IDiskProvider>().Object.FileGetLastWrite(_episodeFile.Path).ToLocalTime();
actualWriteTime.Should().Be(DateTimeExtensions.EpochTime.ToLocalTime().WithTicksFrom(previousWrite));
}
[Test]
public void should_not_clamp_mtime_on_windows()
{
WindowsOnly();
var previousWrite = new DateTime(_lastWrite.Ticks, _lastWrite.Kind);
_episodes[0].AirDateUtc = _veryOldAirDateUtc;
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Subject.ChangeFileDateForFile(_episodeFile, _series, _episodes);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.FileSetLastWriteTime(_episodeFile.Path, It.IsAny<DateTime>()), Times.Once());
var actualWriteTime = Mocker.GetMock<IDiskProvider>().Object.FileGetLastWrite(_episodeFile.Path).ToLocalTime();
actualWriteTime.Should().Be(_episodes[0].AirDateUtc.Value.ToLocalTime().WithTicksFrom(previousWrite));
}
}
}

View File

@@ -580,8 +580,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("ice", "IS")]
[TestCase("dut", "NL")]
[TestCase("nor", "NO")]
[TestCase("geo", "KA")]
[TestCase("kat", "KA")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";

View File

@@ -69,15 +69,5 @@ namespace NzbDrone.Core.Test.ParserTests
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Romansh);
}
[TestCase("ka")]
[TestCase("geo")]
[TestCase("kat")]
[TestCase("ka-GE")]
public void should_return_georgian(string isoCode)
{
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Georgian);
}
}
}

View File

@@ -86,8 +86,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title S01 1080p Eng Fra [mkvonly]")]
[TestCase("Series Title S01 Eng Fre Multi Subs 720p [H264 mp4]")]
[TestCase("Series-Title-S01-[DVDRip]-H264-Fra-Ac3-2-0-Eng-5-1")]
[TestCase("Series Title S01 1080p FR ENG [mkvonly]")]
[TestCase("Series Title S01 1080p ENG FR [mkvonly]")]
public void should_parse_language_french_english(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
@@ -147,7 +145,6 @@ namespace NzbDrone.Core.Test.ParserTests
}
[TestCase("Title.the.Series.2009.S01E14.Japanese.HDTV.XviD-LOL")]
[TestCase("[Erai-raws] To Be Series - 14 (JA) [1080p CR WEB-DL AVC AAC][MultiSub]")]
public void should_parse_language_japanese(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
@@ -529,22 +526,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.Should().Contain(Language.Romansh);
}
[TestCase("Title.the.Series.2025.S01.Georgian.1080p.WEB-DL.h264-RlsGrp")]
[TestCase("Title.the.Series.2025.S01.Geo.1080p.WEB-DL.h264-RlsGrp")]
[TestCase("Title.the.Series.2025.S01.KA.1080p.WEB-DL.h264-RlsGrp")]
public void should_parse_language_georgian(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
result.Should().Contain(Language.Georgian);
}
[TestCase("Title.the.Series.2025.S01.RU-KA.1080p.WEB-DL.h264-RlsGrp")]
public void should_parse_language_russian_and_georgian(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
result.Should().BeEquivalentTo(new[] { Language.Russian, Language.Georgian });
}
[TestCase("Name (2020) - S01E20 - [AAC 2.0].testtitle.default.eng.forced.ass", new[] { "default", "forced" }, "testtitle", "English")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].eng.default.testtitle.forced.ass", new[] { "default", "forced" }, "testtitle", "English")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].default.eng.testtitle.forced.ass", new[] { "default", "forced" }, "testtitle", "English")]

View File

@@ -50,17 +50,17 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title S01 REMUX Dual Audio AVC 1080p 8-Bit-ZR-", "ZR")]
public void should_parse_release_group(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Show.Name.2009.S01.1080p.BluRay.DTS5.1.x264-D-Z0N3", "D-Z0N3")]
[TestCase("Show.Name.S01E01.1080p.WEB-DL.H264.Fight-BB.mkv", "Fight-BB")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 5.1 Tigole) [QxR]", "QxR")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 2.0 afm72) [QxR]", "QxR")]
[TestCase("Show Name (2021) Season 1 S01 (1080p DSNP WEB-DL x265 HEVC 10bit EAC3 5.1 Silence) [QxR]", "QxR")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 2.0 Panda) [QxR]", "QxR")]
[TestCase("Show Name (2020) Season 1 S01 (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 2.0 Ghost) [QxR]", "QxR")]
[TestCase("Show Name (2020) Season 1 S01 (1080p WEB-DL x265 HEVC 10bit AC3 5.1 MONOLITH) [QxR]", "QxR")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 5.1 Tigole) [QxR]", "Tigole")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 2.0 afm72) [QxR]", "afm72")]
[TestCase("Show Name (2021) Season 1 S01 (1080p DSNP WEB-DL x265 HEVC 10bit EAC3 5.1 Silence) [QxR]", "Silence")]
[TestCase("Show Name (2021) Season 1 S01 (1080p BluRay x265 HEVC 10bit AAC 2.0 Panda) [QxR]", "Panda")]
[TestCase("Show Name (2020) Season 1 S01 (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 2.0 Ghost) [QxR]", "Ghost")]
[TestCase("Show Name (2020) Season 1 S01 (1080p WEB-DL x265 HEVC 10bit AC3 5.1 MONOLITH) [QxR]", "MONOLITH")]
[TestCase("The Show S08E09 The Series.1080p.AMZN.WEB-DL.x265.10bit.EAC3.6.0-Qman[UTR]", "UTR")]
[TestCase("The Show S03E07 Fire and Series[1080p x265 10bit S87 Joy]", "Joy")]
[TestCase("The Show (2016) - S02E01 - Soul Series #1 (1080p NF WEBRip x265 ImE)", "ImE")]
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title (2012) - S01E01 - Episode 1 (1080p BluRay x265 r00t).mkv", "r00t")]
[TestCase("Series Title - S01E01 - Girls Gone Wild Exposed (720p x265 EDGE2020).mkv", "EDGE2020")]
[TestCase("Series.Title.S01E02.1080p.BluRay.Remux.AVC.FLAC.2.0-E.N.D", "E.N.D")]
[TestCase("Show Name (2016) Season 1 S01 (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "QxR")]
[TestCase("Show Name (2016) Season 1 S01 (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "RZeroX")]
[TestCase("Series Title S01 1080p Blu-ray Remux AVC FLAC 2.0 - KRaLiMaRKo", "KRaLiMaRKo")]
[TestCase("Series Title S01 1080p Blu-ray Remux AVC DTS-HD MA 2.0 - BluDragon", "BluDragon")]
[TestCase("Example (2013) S01E01 (1080p iP WEBRip x265 SDR AAC 2.0 English - DarQ)", "DarQ")]
@@ -95,30 +95,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series.S01E05.1080p.WEB-DL.DDP5.1.H264-BEN.THE.MEN", "BEN.THE.MEN")]
[TestCase("Series (2022) S01 (1080p BluRay x265 SDR DDP 5.1 English - JBENT TAoE)", "TAoE")]
[TestCase("Series (2005) S21E12 (1080p AMZN WEB-DL x265 SDR DDP 5.1 English - Goki TAoE)", "TAoE")]
[TestCase("Series (2022) S03E12 (1080p AMZN Webrip x265 10 bit EAC3 5 1 - Ainz)[TAoE]", "TAoE")]
[TestCase("Series Things (2016) S04 Part 1 (1080p Webrip NF x265 10bit EAC3 5 1 - AJJMIN) [TAoE]", "TAoE")]
[TestCase("Series Soup (2024) S01 (1080p NF Webrip x265 10bit EAC3 5 1 Multi - ANONAZ)[TAoE]", "TAoE")]
[TestCase("Series (2022) S01 (1080p NF Webrip x265 10bit EAC3 5 1 Atmos - ArcX)[TAoE]", "TAoE")]
[TestCase("Series - King of Titles (2021) S01 (1080p HMAX Webrip x265 10bit AC3 5 1 - bccornfo) [TAoE]", "TAoE")]
[TestCase("Welcome to Series (2022) S04 (1080p AMZN Webrip x265 10bit EAC3 5 1 - DNU)[TAoE]", "TAoE")]
[TestCase("Series Who (2005) S01 (1080p BDRip x265 10bit AC3 5 1 - DrainedDay)[TAoE]", "TAoE")]
[TestCase("Series Down (2019) (1080p AMZN Webrip x265 10bit EAC3 5 1 - DUHiT)[TAoE]", "TAoE")]
[TestCase("Series (2016) S09 (1080p CRAV Webrip x265 10bit EAC3 5 1 - Erie) [TAoE]", "TAoE")]
[TestCase("Common Series Effects (2025) S01 (1080p AMZN Webrip x265 10bit EAC3 2 0 - Frys) [TAoE]", "TAoE")]
[TestCase("Murderbot (2025) S01 (2160p HDR10 DV Hybrid ATVP Webrip x265 10bit EAC3 5 1 Atmos - Goki)[TAoE]", "TAoE")]
[TestCase("Series In Real Life (2019) S01 REPACK (1080p DSNP Webrip x265 10bit AAC 2 0 - HxD)[TAoE]", "TAoE")]
[TestCase("Series Discovery (2017) S02 (1080p BDRip x265 10bit DTS-HD MA 5 1 - jb2049) [TAoE]", "TAoE")]
[TestCase("Series (2021) S03 (1080p DS4K NF Webrip x265 10bit EAC3 5 1 Atmos English - JBENT)[TAoE]", "TAoE")]
[TestCase("SuSeriespergirl (2015) S04 (1080p BDRip x265 10bit AC3 5 1 - Nostradamus)[TAoE]", "TAoE")]
[TestCase("Series (2019) S02 (4Kto1080p ATVP Webrip x265 10bit AC3 5 1 - r0b0t) [TAoE]", "TAoE")]
[TestCase("v (1970) S01 (2160p AIUS HDR10 DV Hybrid BDRip x265 10bit DTS-HD MA 5 1 - Species180) [TAoE]", "TAoE")]
[TestCase("Series (2024) S02 (1080p ATVP Webrip x265 10bit EAC3 5 1 - TheSickle)[TAoE]", "TAoE")]
[TestCase("Series (2016) S05 Part 02 (1080p NF Webrip x265 10bit EAC3 5 1 - xtrem3x) [TAoE]", "TAoE")]
[TestCase("Series (2013) S01 (1080p BDRip x265 10bit DTS-HD MA 5 1 - WEM)[TAoE]", "TAoE")]
[TestCase("The.Series.1989.S00E65.1080p.DSNP.Webrip.x265.10bit.EAC3.5.1.Goki.TAoE", "TAoE")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
[Test]
@@ -136,7 +115,7 @@ namespace NzbDrone.Core.Test.ParserTests
// [TestCase("", "")]
public void should_not_include_language_in_release_group(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Series.Title.S02E04.720p.WEB-DL.AAC2.0.H.264-EVL-RP", "EVL")]
@@ -167,7 +146,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series.Title.S04E06.Episode.Name.720p.WEB-DL.DD5.1.H.264-HarrHD-RePACKPOST", "HarrHD")]
public void should_not_include_repost_in_release_group(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("[FFF] Series Title!! - S01E11 - Someday, With Sonarr", "FFF")]
@@ -180,13 +159,13 @@ namespace NzbDrone.Core.Test.ParserTests
// [TestCase("", "")]
public void should_parse_anime_release_groups(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Terrible.Anime.Title.001.DBOX.480p.x264-iKaos [v3] [6AFFEF6B]")]
public void should_not_parse_anime_hash_as_release_group(string title)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().BeNull();
Parser.Parser.ParseReleaseGroup(title).Should().BeNull();
}
}
}

View File

@@ -1,19 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class SubGroupParserFixture : CoreTest
{
[TestCase("[GHOST][1080p] Series - 25 [BD HEVC 10bit Dual Audio AC3][AE0ADDBA]", "GHOST")]
public void should_parse_sub_group_from_title_as_release_group(string title, string expected)
{
var result = Parser.Parser.ParseTitle(title);
result.Should().NotBeNull();
result.ReleaseGroup.Should().Be(expected);
}
}
}

View File

@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
public void should_not_parse_url_in_group(string title, string expected)
{
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
}
}

View File

@@ -12,7 +12,6 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Datastore.Migration
@@ -810,7 +809,7 @@ namespace NzbDrone.Core.Datastore.Migration
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
{
sceneName = sceneName.IsNotNullOrWhiteSpace() ? FileExtensions.RemoveFileExtension(sceneName) : string.Empty;
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
foreach (var token in tokens)
{

View File

@@ -1,16 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(219)]
public class nzb_su_url_to_nzb_life : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.Sql("UPDATE \"Indexers\" SET \"Settings\" = replace(\"Settings\", '//api.nzb.su', '//api.nzb.life')" +
"WHERE \"Implementation\" = 'Newznab'" +
"AND \"Settings\" LIKE '%//api.nzb.su%'");
}
}
}

View File

@@ -1,66 +0,0 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(229)]
public class enable_season_pack_seeding_goal : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(SetSeasonPackSeedingGoal);
}
private void SetSeasonPackSeedingGoal(IDbConnection conn, IDbTransaction tran)
{
var updatedIndexers = new List<object>();
using var selectCommand = conn.CreateCommand();
selectCommand.Transaction = tran;
selectCommand.CommandText = "SELECT * FROM \"Indexers\"";
using var reader = selectCommand.ExecuteReader();
while (reader.Read())
{
var idIndex = reader.GetOrdinal("Id");
var settingsIndex = reader.GetOrdinal("Settings");
var id = reader.GetInt32(idIndex);
var settings = Json.Deserialize<Dictionary<string, object>>(reader.GetString(settingsIndex));
if (settings.TryGetValue("seedCriteria", out var seedCriteriaToken) && seedCriteriaToken is JObject seedCriteria)
{
if (seedCriteria?["seasonPackSeedTime"] != null)
{
seedCriteria["seasonPackSeedGoal"] = 1;
if (seedCriteria["seedRatio"] != null)
{
seedCriteria["seasonPackSeedRatio"] = seedCriteria["seedRatio"];
}
updatedIndexers.Add(new
{
Settings = settings.ToJson(),
Id = id,
});
}
}
}
if (updatedIndexers.Any())
{
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updatedIndexers, transaction: tran);
}
}
}
}

View File

@@ -1,155 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace NzbDrone.Core.Download.Clients.Tribler
{
public enum DownloadStatus
{
[EnumMember(Value = @"WAITING4HASHCHECK")]
Waiting4HashCheck = 0,
[EnumMember(Value = @"HASHCHECKING")]
Hashchecking = 1,
[EnumMember(Value = @"METADATA")]
Metadata = 2,
[EnumMember(Value = @"DOWNLOADING")]
Downloading = 3,
[EnumMember(Value = @"SEEDING")]
Seeding = 4,
[EnumMember(Value = @"STOPPED")]
Stopped = 5,
[EnumMember(Value = @"ALLOCATING_DISKSPACE")]
AllocatingDiskspace = 6,
[EnumMember(Value = @"EXIT_NODES")]
Exitnodes = 7,
[EnumMember(Value = @"CIRCUITS")]
Circuits = 8,
[EnumMember(Value = @"STOPPED_ON_ERROR")]
StoppedOnError = 9,
[EnumMember(Value = @"LOADING")]
Loading = 10,
}
public class Trackers
{
public string Url { get; set; }
[JsonProperty("peers")]
public object Peers { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
}
public class Download
{
public string Name { get; set; }
public float? Progress { get; set; }
public string Infohash { get; set; }
public bool? AnonDownload { get; set; }
public float? Availability { get; set; }
public double? Eta { get; set; }
public long? TotalPieces { get; set; }
public long? NumSeeds { get; set; }
public long? AllTimeUpload { get; set; }
public long? AllTimeDownload { get; set; }
[JsonProperty("status")]
[JsonConverter(typeof(StringEnumConverter))]
public DownloadStatus? Status { get; set; }
public int? StatusCode { get; set; }
public float? AllTimeRatio { get; set; }
public long? TimeAdded { get; set; }
public long? MaxUploadSpeed { get; set; }
public long? MaxDownloadSpeed { get; set; }
public long? Hops { get; set; }
public bool? SafeSeeding { get; set; }
public string Error { get; set; }
public long? TotalDown { get; set; }
public long? Size { get; set; }
public string Destination { get; set; }
public float? SpeedDown { get; set; }
public float? SpeedUp { get; set; }
public long? NumPeers { get; set; }
public List<Trackers> Trackers { get; set; }
}
public class DownloadsResponse
{
public List<Download> Downloads { get; set; }
}
public class AddDownloadRequest
{
[JsonProperty("anon_hops")]
public long? AnonymityHops { get; set; }
[JsonProperty("safe_seeding")]
public bool? SafeSeeding { get; set; }
public string Destination { get; set; }
[JsonProperty("uri", Required = Newtonsoft.Json.Required.Always)]
[Required(AllowEmptyStrings = true)]
public string Uri { get; set; }
}
public class AddDownloadResponse
{
public string Infohash { get; set; }
public bool? Started { get; set; }
}
public class RemoveDownloadRequest
{
[JsonProperty("remove_data")]
public bool? RemoveData { get; set; }
}
public class DeleteDownloadResponse
{
public bool? Removed { get; set; }
public string Infohash { get; set; }
}
public class UpdateDownloadRequest
{
[JsonProperty("anon_hops")]
public long? AnonHops { get; set; }
[JsonProperty("selected_files")]
public List<int> Selected_files { get; set; }
public string State { get; set; }
}
public class UpdateDownloadResponse
{
public bool? Modified { get; set; }
public string Infohash { get; set; }
}
public class File
{
public long? Size { get; set; }
public long? Index { get; set; }
public string Name { get; set; }
public float? Progress { get; set; }
public bool? Included { get; set; }
}
public class GetFilesResponse
{
public List<File> Files { get; set; }
}
}

View File

@@ -1,189 +0,0 @@
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace NzbDrone.Core.Indexers.Tribler
{
public class TriblerSettingsResponse
{
public Settings Settings { get; set; }
}
public class Settings
{
public Api Api { get; set; }
public bool Statistics { get; set; }
[JsonProperty("content_discovery_community")]
public ContentDiscoveryCommunity ContentDiscoveryCommunity { get; set; }
public Database Database { get; set; }
[JsonProperty("dht_discovery")]
public DHTDiscovery DHTDiscovery { get; set; }
[JsonProperty("knowledge_community")]
public KnowledgeCommunity KnowledgeCommunity { get; set; }
public LibTorrent LibTorrent { get; set; }
public Recommender Recommender { get; set; }
public Rendezvous RecoRendezvousmmender { get; set; }
[JsonProperty("torrent_checker")]
public TorrentChecker TorrentChecker { get; set; }
[JsonProperty("tunnel_community")]
public TunnelCommunity TunnelCommunity { get; set; }
public Versioning Versioning { get; set; }
[JsonProperty("watch_folder")]
public WatchFolder WatchFolder { get; set; }
[JsonProperty("state_dir")]
public string StateDir { get; set; }
[JsonProperty("memory_db")]
public bool? MemoryDB { get; set; }
}
public class Api
{
[JsonProperty("http_enabled")]
public bool HttpEnabled { get; set; }
[JsonProperty("http_port")]
public int HttpPort { get; set; }
[JsonProperty("http_host")]
public string HttpHost { get; set; }
[JsonProperty("https_enabled")]
public bool HttpsEnabled { get; set; }
[JsonProperty("https_port")]
public int HttpsPort { get; set; }
[JsonProperty("https_host")]
public string HttpsHost { get; set; }
[JsonProperty("https_certfile")]
public string HttpsCertFile { get; set; }
[JsonProperty("http_port_running")]
public int HttpPortRunning { get; set; }
[JsonProperty("https_port_running")]
public int HttpsPortRunning { get; set; }
}
public class ContentDiscoveryCommunity
{
public bool? Enabled { get; set; }
}
public class Database
{
public bool? Enabled { get; set; }
}
public class DHTDiscovery
{
public bool? Enabled { get; set; }
}
public class KnowledgeCommunity
{
public bool? Enabled { get; set; }
}
public class LibTorrent
{
[JsonProperty("download_defaults")]
public LibTorrentDownloadDefaults DownloadDefaults { get; set; }
// contains a lot more data, but it's not needed currently
}
public class Recommender
{
public bool? Enabled { get; set; }
}
public class Rendezvous
{
public bool? Enabled { get; set; }
}
public class TorrentChecker
{
[JsonProperty("enabled")]
public bool? Enabled { get; set; }
}
public class TunnelCommunity
{
[JsonProperty("enabled")]
public bool? Enabled { get; set; }
[JsonProperty("min_circuits")]
public int? MinCircuits { get; set; }
[JsonProperty("max_circuits")]
public int? MaxCircuits { get; set; }
}
public class Versioning
{
[JsonProperty("enabled")]
public bool? Enabled { get; set; }
}
public class WatchFolder
{
[JsonProperty("enabled")]
public bool? Enabled { get; set; }
[JsonProperty("directory")]
public string Directory { get; set; }
[JsonProperty("check_interval")]
public int? CheckInterval { get; set; }
}
public class LibTorrentDownloadDefaults
{
[JsonProperty("anonymity_enabled")]
public bool? AnonymityEnabled { get; set; }
[JsonProperty("number_hops")]
public int? NumberHops { get; set; }
[JsonProperty("safeseeding_enabled")]
public bool? SafeSeedingEnabled { get; set; }
[JsonProperty("saveas")]
public string SaveAS { get; set; }
[JsonProperty("seeding_mode")]
[JsonConverter(typeof(StringEnumConverter))]
public DownloadDefaultsSeedingMode? SeedingMode { get; set; }
[JsonProperty("seeding_ratio")]
public double? SeedingRatio { get; set; }
[JsonProperty("seeding_time")]
public double? SeedingTime { get; set; }
}
public enum DownloadDefaultsSeedingMode
{
[EnumMember(Value = @"ratio")]
Ratio = 0,
[EnumMember(Value = @"forever")]
Forever = 1,
[EnumMember(Value = @"time")]
Time = 2,
[EnumMember(Value = @"never")]
Never = 3,
}
}

View File

@@ -1,298 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Tribler;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.Tribler
{
public class TriblerDownloadClient : TorrentClientBase<TriblerDownloadSettings>
{
private readonly ITriblerDownloadClientProxy _proxy;
public TriblerDownloadClient(
ITriblerDownloadClientProxy triblerDownloadClientProxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = triblerDownloadClientProxy;
}
public override string Name => "Tribler";
public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientTriblerProviderMessage", new Dictionary<string, object> { { "clientName", Name }, { "clientVersionRange", "8.0.7" } }), ProviderMessageType.Warning);
public override bool PreferTorrentFile => false;
public override IEnumerable<DownloadClientItem> GetItems()
{
var configAsync = _proxy.GetConfig(Settings);
var items = new List<DownloadClientItem>();
var downloads = _proxy.GetDownloads(Settings);
foreach (var download in downloads)
{
// If totalsize == 0 the torrent is a magnet downloading metadata
if (download.Size == null || download.Size == 0)
{
continue;
}
var item = new DownloadClientItem
{
DownloadId = download.Infohash,
Title = download.Name,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false)
};
// some concurrency could make this faster.
var files = _proxy.GetDownloadFiles(Settings, download);
item.OutputPath = new OsPath(download.Destination);
if (files.Count == 1)
{
item.OutputPath += files.First().Name;
}
else
{
item.OutputPath += item.Title;
}
item.TotalSize = (long)download.Size;
item.RemainingSize = (long)(download.Size * (1 - download.Progress));
item.SeedRatio = download.AllTimeRatio;
if (download.Eta.HasValue)
{
if (download.Eta.Value >= TimeSpan.FromDays(365).TotalSeconds)
{
item.RemainingTime = TimeSpan.FromDays(365);
}
else if (download.Eta.Value < 0)
{
item.RemainingTime = TimeSpan.FromSeconds(0);
}
else
{
item.RemainingTime = TimeSpan.FromSeconds(download.Eta.Value);
}
}
item.Message = download.Error;
// tribler always saves files unencrypted to disk.
item.IsEncrypted = false;
switch (download.Status)
{
case DownloadStatus.Hashchecking:
case DownloadStatus.Waiting4HashCheck:
case DownloadStatus.Circuits:
case DownloadStatus.Exitnodes:
case DownloadStatus.Downloading:
item.Status = DownloadItemStatus.Downloading;
break;
case DownloadStatus.Metadata:
case DownloadStatus.AllocatingDiskspace:
item.Status = DownloadItemStatus.Queued;
break;
case DownloadStatus.Seeding:
case DownloadStatus.Stopped:
item.Status = DownloadItemStatus.Completed;
break;
case DownloadStatus.StoppedOnError:
item.Status = DownloadItemStatus.Failed;
break;
case DownloadStatus.Loading:
default: // new status in API? default to downloading
item.Message = "Unknown download state: " + download.Status;
_logger.Info(item.Message);
item.Status = DownloadItemStatus.Downloading;
break;
}
// Override status if completed, but not finished downloading
if (download.Status == DownloadStatus.Stopped && download.Progress < 1)
{
item.Status = DownloadItemStatus.Paused;
}
if (download.Error != null && download.Error.Length > 0)
{
item.Status = DownloadItemStatus.Warning;
item.Message = download.Error;
}
item.CanBeRemoved = item.CanMoveFiles = HasReachedSeedLimit(download, configAsync);
items.Add(item);
}
return items;
}
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.RemoveDownload(Settings, item, deleteData);
}
public override DownloadClientInfo GetStatus()
{
var config = _proxy.GetConfig(Settings);
var destDir = config.Settings.LibTorrent.DownloadDefaults.SaveAS;
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory);
}
return new DownloadClientInfo
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
};
}
protected static bool HasReachedSeedLimit(Download torrent, TriblerSettingsResponse config)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}
if (torrent == null)
{
throw new ArgumentNullException(nameof(torrent));
}
// if download is still running then it's not finished.
if (torrent.Status != DownloadStatus.Stopped)
{
return false;
}
switch (config.Settings.LibTorrent.DownloadDefaults.SeedingMode)
{
case DownloadDefaultsSeedingMode.Ratio:
return torrent.AllTimeRatio.HasValue
&& torrent.AllTimeRatio >= config.Settings.LibTorrent.DownloadDefaults.SeedingRatio;
case DownloadDefaultsSeedingMode.Time:
var downloadStarted = DateTimeOffset.FromUnixTimeSeconds(torrent.TimeAdded.Value);
var maxSeedingTime = TimeSpan.FromSeconds(config.Settings.LibTorrent.DownloadDefaults.SeedingTime ?? 0);
return torrent.TimeAdded.HasValue
&& downloadStarted.Add(maxSeedingTime) < DateTimeOffset.Now;
case DownloadDefaultsSeedingMode.Never:
return true;
case DownloadDefaultsSeedingMode.Forever:
default:
return false;
}
}
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
{
var addDownloadRequestObject = new AddDownloadRequest
{
Destination = GetDownloadDirectory(),
Uri = magnetLink,
SafeSeeding = Settings.SafeSeeding,
AnonymityHops = Settings.AnonymityLevel
};
return _proxy.AddFromMagnetLink(Settings, addDownloadRequestObject);
}
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
{
// TODO: Tribler 8.x does support adding from a torrent file, but it's not a simple put command.
throw new NotSupportedException("Tribler does not support torrent files, only magnet links");
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
if (failures.HasErrors())
{
return;
}
}
protected string GetDownloadDirectory()
{
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
return Settings.TvDirectory;
}
if (!Settings.TvCategory.IsNotNullOrWhiteSpace())
{
return null;
}
var config = _proxy.GetConfig(Settings);
var destDir = config.Settings.LibTorrent.DownloadDefaults.SaveAS;
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
}
protected ValidationFailure TestConnection()
{
try
{
var downloads = GetItems();
return null;
}
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("DownloadClientValidationApiKeyIncorrect"));
}
catch (DownloadClientUnavailableException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } }))
{
DetailedDescription = ex.Message
};
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to test");
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } }));
}
}
}
}

View File

@@ -1,119 +0,0 @@
using System.Collections.Generic;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Tribler;
namespace NzbDrone.Core.Download.Clients.Tribler
{
public interface ITriblerDownloadClientProxy
{
List<Download> GetDownloads(TriblerDownloadSettings settings);
List<File> GetDownloadFiles(TriblerDownloadSettings settings, Download downloadItem);
TriblerSettingsResponse GetConfig(TriblerDownloadSettings settings);
void RemoveDownload(TriblerDownloadSettings settings, DownloadClientItem item, bool deleteData);
string AddFromMagnetLink(TriblerDownloadSettings settings, AddDownloadRequest downloadRequest);
}
public class TriblerDownloadClientProxy : ITriblerDownloadClientProxy
{
protected readonly IHttpClient _httpClient;
private readonly Logger _logger;
public TriblerDownloadClientProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
private HttpRequestBuilder GetRequestBuilder(TriblerDownloadSettings settings, string relativePath = null)
{
var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);
baseUrl = HttpUri.CombinePath(baseUrl, relativePath);
var requestBuilder = new HttpRequestBuilder(baseUrl)
.Accept(HttpAccept.Json);
requestBuilder.Headers.Add("X-Api-Key", settings.ApiKey);
requestBuilder.LogResponseContent = true;
return requestBuilder;
}
private T ProcessRequest<T>(HttpRequestBuilder requestBuilder)
where T : new()
{
return ProcessRequest<T>(requestBuilder.Build());
}
private T ProcessRequest<T>(HttpRequest requestBuilder)
where T : new()
{
var httpRequest = requestBuilder;
_logger.Debug("Url: {0}", httpRequest.Url);
try
{
var response = _httpClient.Execute(httpRequest);
return Json.Deserialize<T>(response.Content);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new DownloadClientAuthenticationException("Unauthorized - AuthToken is invalid", ex);
}
throw new DownloadClientUnavailableException("Unable to connect to Tribler. Status Code: {0}", ex.Response.StatusCode, ex);
}
}
public TriblerSettingsResponse GetConfig(TriblerDownloadSettings settings)
{
var configRequest = GetRequestBuilder(settings, "api/settings");
return ProcessRequest<TriblerSettingsResponse>(configRequest);
}
public List<File> GetDownloadFiles(TriblerDownloadSettings settings, Download downloadItem)
{
var filesRequest = GetRequestBuilder(settings, "api/downloads/" + downloadItem.Infohash + "/files");
return ProcessRequest<GetFilesResponse>(filesRequest).Files;
}
public List<Download> GetDownloads(TriblerDownloadSettings settings)
{
var downloadRequest = GetRequestBuilder(settings, "api/downloads");
var downloads = ProcessRequest<DownloadsResponse>(downloadRequest);
return downloads.Downloads;
}
public void RemoveDownload(TriblerDownloadSettings settings, DownloadClientItem item, bool deleteData)
{
var deleteDownloadRequestObject = new RemoveDownloadRequest
{
RemoveData = deleteData
};
var deleteRequestBuilder = GetRequestBuilder(settings, "api/downloads/" + item.DownloadId.ToLower());
deleteRequestBuilder.Method = HttpMethod.Delete;
var deleteRequest = deleteRequestBuilder.Build();
deleteRequest.SetContent(Json.ToJson(deleteDownloadRequestObject));
ProcessRequest<DeleteDownloadResponse>(deleteRequest);
}
public string AddFromMagnetLink(TriblerDownloadSettings settings, AddDownloadRequest downloadRequest)
{
var addDownloadRequestBuilder = GetRequestBuilder(settings, "api/downloads");
addDownloadRequestBuilder.Method = HttpMethod.Put;
var addDownloadRequest = addDownloadRequestBuilder.Build();
addDownloadRequest.SetContent(Json.ToJson(downloadRequest));
return ProcessRequest<AddDownloadResponse>(addDownloadRequest).Infohash;
}
}
}

View File

@@ -1,74 +0,0 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.Tribler
{
public class TriblerSettingsValidator : AbstractValidator<TriblerDownloadSettings>
{
public TriblerSettingsValidator()
{
RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.UrlBase).ValidUrlBase();
RuleFor(c => c.ApiKey).NotEmpty();
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
RuleFor(c => c.TvCategory).Empty()
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
.WithMessage("Cannot use Category and Directory");
RuleFor(c => c.AnonymityLevel).GreaterThanOrEqualTo(0);
}
}
public class TriblerDownloadSettings : IProviderConfig
{
private static readonly TriblerSettingsValidator Validator = new TriblerSettingsValidator();
public TriblerDownloadSettings()
{
Host = "localhost";
Port = 20100;
UrlBase = "";
AnonymityLevel = 1;
SafeSeeding = true;
}
[FieldDefinition(1, Label = "Host", Type = FieldType.Textbox)]
public string Host { get; set; }
[FieldDefinition(2, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Tribler")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]")]
public string UrlBase { get; set; }
[FieldDefinition(5, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, HelpText = "DownloadClientTriblerSettingsApiKeyHelpText")]
public string ApiKey { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")]
public string TvCategory { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTriblerSettingsDirectoryHelpText")]
public string TvDirectory { get; set; }
[FieldDefinition(8, Label = "DownloadClientTriblerSettingsAnonymityLevel", Type = FieldType.Number, HelpText = "DownloadClientTriblerSettingsAnonymityLevelHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientTriblerSettingsAnonymityLevel", "url", "https://www.tribler.org/anonymity.html")]
public int AnonymityLevel { get; set; }
[FieldDefinition(9, Label = "DownloadClientTriblerSettingsSafeSeeding", Type = FieldType.Checkbox, HelpText = "DownloadClientTriblerSettingsSafeSeedingHelpText")]
public bool SafeSeeding { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Core.ImportLists.Custom
}
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Accept(HttpAccept.Json).AllowRedirect().Build();
var request = new HttpRequestBuilder(baseUrl).Accept(HttpAccept.Json).Build();
var response = _httpClient.Get(request);
var results = JsonConvert.DeserializeObject<List<TResource>>(response.Content);

View File

@@ -121,11 +121,6 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
break;
}
if (item.Tags?.Contains("Subtitles") == true)
{
flags |= IndexerFlags.Subtitles;
}
return flags;
}

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrent
@@ -28,7 +26,6 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public int? TvrageID { get; set; }
public string ImdbID { get; set; }
public string InfoHash { get; set; }
public List<string> Tags { get; set; }
public string DownloadURL { get; set; }
}
}

View File

@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr"));
yield return GetDefinition("DrunkenSlug", GetSettings("https://drunkenslug.com"));
yield return GetDefinition("Nzb.life", GetSettings("https://api.nzb.life"));
yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"));
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws", categories: new[] { 5030, 5040, 5045 }));
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));

View File

@@ -210,11 +210,6 @@ namespace NzbDrone.Core.Indexers.Newznab
flags |= IndexerFlags.Nuked;
}
if (TryGetNewznabAttribute(item, "subs").IsNotNullOrWhiteSpace())
{
flags |= IndexerFlags.Subtitles;
}
return flags;
}

View File

@@ -13,10 +13,10 @@ namespace NzbDrone.Core.Indexers.Newznab
{
public class NewznabSettingsValidator : AbstractValidator<NewznabSettings>
{
private static readonly string[] ApiKeyAllowList =
private static readonly string[] ApiKeyWhiteList =
{
"nzbs.org",
"nzb.life",
"nzb.su",
"dognzb.cr",
"nzbplanet.net",
"nzbid.org",
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Newznab
private static bool ShouldHaveApiKey(NewznabSettings settings)
{
return settings.BaseUrl != null && ApiKeyAllowList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
return settings.BaseUrl != null && ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
}
private static readonly Regex AdditionalParametersRegex = new(@"(&.+?\=.+?)+", RegexOptions.Compiled);

View File

@@ -1,11 +0,0 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Indexers;
public enum SeasonPackSeedGoal
{
[FieldOption(Label = "IndexerSettingsSeasonPackSeedGoalUseStandardGoals")]
UseStandardSeedGoal = 0,
[FieldOption(Label = "IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals")]
UseSeasonPackSeedGoal = 1
}

View File

@@ -49,16 +49,12 @@ namespace NzbDrone.Core.Indexers
return null;
}
var useSeasonPackSeedGoal = (SeasonPackSeedGoal)seedCriteria.SeasonPackSeedGoal == SeasonPackSeedGoal.UseSeasonPackSeedGoal;
var seedConfig = new TorrentSeedConfiguration
{
Ratio = (fullSeason && useSeasonPackSeedGoal)
? seedCriteria.SeasonPackSeedRatio
: seedCriteria.SeedRatio
Ratio = seedCriteria.SeedRatio
};
var seedTime = (fullSeason && useSeasonPackSeedGoal) ? seedCriteria.SeasonPackSeedTime : seedCriteria.SeedTime;
var seedTime = fullSeason ? seedCriteria.SeasonPackSeedTime : seedCriteria.SeedTime;
if (seedTime.HasValue)
{
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);

View File

@@ -17,10 +17,6 @@ namespace NzbDrone.Core.Indexers
.When(c => c.SeedTime.HasValue)
.AsWarning().WithMessage("Should be greater than zero");
RuleFor(c => c.SeasonPackSeedRatio).GreaterThan(0.0)
.When(c => c.SeasonPackSeedRatio.HasValue)
.AsWarning().WithMessage("Should be greater than zero");
RuleFor(c => c.SeasonPackSeedTime).GreaterThan(0)
.When(c => c.SeasonPackSeedTime.HasValue)
.AsWarning().WithMessage("Should be greater than zero");
@@ -31,11 +27,6 @@ namespace NzbDrone.Core.Indexers
.When(c => c.SeedRatio > 0.0)
.AsWarning()
.WithMessage($"Under {seedRatioMinimum} leads to H&R");
RuleFor(c => c.SeasonPackSeedRatio).GreaterThanOrEqualTo(seedRatioMinimum)
.When(c => c.SeasonPackSeedRatio > 0.0)
.AsWarning()
.WithMessage($"Under {seedRatioMinimum} leads to H&R");
}
if (seedTimeMinimum != 0)
@@ -64,13 +55,7 @@ namespace NzbDrone.Core.Indexers
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
public int? SeedTime { get; set; }
[FieldDefinition(2, Type = FieldType.Select, Label = "IndexerSettingsSeasonPackSeedGoal", SelectOptions = typeof(SeasonPackSeedGoal), HelpText = "IndexerSettingsSeasonPackSeedGoalHelpText", Advanced = true)]
public int SeasonPackSeedGoal { get; set; }
[FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsSeasonPackSeedRatio", HelpText = "IndexerSettingsSeasonPackSeedRatioHelpText", Advanced = true)]
public double? SeasonPackSeedRatio { get; set; }
[FieldDefinition(4, Type = FieldType.Number, Label = "IndexerSettingsSeasonPackSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeasonPackSeedTimeHelpText", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Number, Label = "Season-Pack Seed Time", Unit = "minutes", HelpText = "IndexerSettingsSeasonPackSeedTimeHelpText", Advanced = true)]
public int? SeasonPackSeedTime { get; set; }
}
}

View File

@@ -12,11 +12,11 @@ namespace NzbDrone.Core.Indexers.Torznab
{
public class TorznabSettingsValidator : AbstractValidator<TorznabSettings>
{
private static readonly string[] ApiKeyAllowList = Array.Empty<string>();
private static readonly string[] ApiKeyWhiteList = Array.Empty<string>();
private static bool ShouldHaveApiKey(TorznabSettings settings)
{
return settings.BaseUrl != null && ApiKeyAllowList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
return settings.BaseUrl != null && ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
}
private static readonly Regex AdditionalParametersRegex = new(@"(&.+?\=.+?)+", RegexOptions.Compiled);

View File

@@ -122,7 +122,6 @@ namespace NzbDrone.Core.Languages
public static Language Malay => new Language(49, "Malay");
public static Language Urdu => new Language(50, "Urdu");
public static Language Romansh => new Language(51, "Romansh");
public static Language Georgian => new Language(52, "Georgian");
public static Language Original => new Language(-2, "Original");
public static List<Language> All
@@ -183,7 +182,6 @@ namespace NzbDrone.Core.Languages
Malay,
Urdu,
Romansh,
Georgian,
Original
};
}

View File

@@ -91,7 +91,7 @@
"SeasonDetails": "Детайли за сезона",
"SeasonCount": "Брой сезони",
"SslPort": "SSL порт",
"AppDataLocationHealthCheckMessage": "Актуализацията няма да бъде възможна, за да се предотврати изтриването на AppData при обновяване",
"AppDataLocationHealthCheckMessage": "Актуализирането няма да бъде възможно, за да се предотврати изтриването на папката на приложението по време на актуализацията",
"AppUpdated": "{appName} Актуализиран",
"ApplyTagsHelpTextReplace": "Замяна: Заменете таговете с въведените тагове (не въвеждайте тагове, за да изчистите всички тагове)",
"AudioLanguages": "Аудио езици",
@@ -139,53 +139,5 @@
"StandardEpisodeFormat": "Формат на епизода ( Стандартен )",
"SslCertPathHelpText": "Път до \"pfx\" файл",
"EpisodeNaming": "Именуване на епизоди",
"Close": "Затвори",
"AutoTaggingSpecificationMaximumYear": "Максимална година",
"AutoTaggingSpecificationMinimumYear": "Минимална година",
"AutoTaggingSpecificationTag": "Етикет",
"AutoTaggingSpecificationNetwork": "Мрежа(и)",
"Airs": "Излъчва се",
"BranchUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
"BranchUpdate": "Клон, който да се използва за актуализиране на {appName}",
"BindAddressHelpText": "Валиден IP адрес, localhost или '*' за всички интерфейси",
"BlackholeWatchFolder": "Наблюдавана папка",
"BlackholeWatchFolderHelpText": "Папка, от която {appName} трябва да импортира завършените изтегляния",
"BlackholeFolderHelpText": "Папка, в която {appName} ще съхранява файла {extension}",
"BlocklistAndSearchHint": "Започнете търсене на заместител след блокиране",
"BlocklistAndSearchMultipleHint": "Започнете търсене на заместители след блокиране",
"BlocklistAndSearch": "Списък за блокиране и търсене",
"Backups": "Архиви",
"AutoTaggingSpecificationRootFolder": "Основна директория",
"BlocklistFilterHasNoItems": "Избраният филтър за блокиране не съдържа елементи",
"Branch": "Клон",
"BuiltIn": "Вграден",
"BeforeUpdate": "Преди актуализация",
"BlocklistOnly": "Само списък за блокиране",
"BlocklistMultipleOnlyHint": "Списък за блокиране без търсене на заместители",
"BlocklistReleases": "Освобождаване на черния списък",
"BlocklistRelease": "Освобождаване на черния списък",
"Automatic": "Автоматично",
"BackupsLoadError": "Архивите не могат да се заредят",
"BindAddress": "Адрес за обвързване",
"BlocklistLoadError": "Неуспешно зареждане на списъка за блокиране",
"BackupIntervalHelpText": "Интервал между автоматичните резервни копия",
"AutoTaggingSpecificationQualityProfile": "Профил за качество",
"AutoTaggingSpecificationSeriesType": "Тип сериал",
"AutoTaggingSpecificationGenre": "Жанр(ове)",
"AutoTaggingSpecificationOriginalLanguage": "Език",
"AutoTaggingSpecificationStatus": "Статус",
"Backup": "Архивиране",
"BackupFolderHelpText": "Вариант 1: Относителните пътища ще се намират в директорията AppData на {appName}",
"BackupNow": "Архивиране сега",
"AutoTaggingRequiredHelpText": "Условието {implementationName} трябва да съвпада, за да се приложи правилото за автоматично тагване. В противен случай е достатъчно едно съвпадение на {implementationName}.",
"AutomaticAdd": "Автоматично добавяне",
"AutomaticUpdatesDisabledDocker": "Автоматичните актуализации не се поддържат директно при използване на механизма за актуализация на Docker. Ще трябва да актуализирате Image-a на контейнера извън {appName} или да използвате скрипт",
"AutomaticSearch": "Автоматично търсене",
"BackupRetentionHelpText": "Автоматичните резервни копия, по-стари от зададения период на съхранение, ще бъдат изтрити автоматично",
"BypassDelayIfAboveCustomFormatScore": "Пропусни, ако е над рейтинга на персонализирания формат",
"AbsoluteEpisodeNumbers": "Абсолютен номер на епизод(и)",
"BlocklistReleaseHelpText": "Блокира този релийз, така че {appName} да не го изтегля повторно чрез RSS или автоматично търсене",
"Blocklist": "Списък за блокиране",
"BrowserReloadRequired": "Необходимо е презареждане на браузъра",
"BlocklistOnlyHint": "Списък за блокиране без търсене на заместител"
"Close": "Затвори"
}

View File

@@ -1505,7 +1505,7 @@
"IndexerSettingsCookieHelpText": "Si el vostre lloc requereix una galeta d'inici de sessió per accedir als rss, haureu de recuperar-la a través d'un navegador.",
"IndexerSettingsMultiLanguageRelease": "Multiidiomes",
"IndexerSettingsFailDownloadsHelpText": "Mentre es processen les descàrregues completades, {appName} tractarà aquests tipus d'arxiu seleccionats com a descàrregues fallides.",
"IndexerSettingsSeasonPackSeedTimeHelpText": "La quantitat de temps que un torrent de paquet de temporada s'ha de sembrar abans d'aturar-se, deixeu en blanc per utilitzar el valor per defecte del client de baixada",
"IndexerSettingsSeasonPackSeedTimeHelpText": "La quantitat de temps que un torrent de pack de temporada s'ha de sembrar abans d'aturar-se, deixeu en blanc per utilitzar el valor per defecte del client de baixada",
"IndexerValidationCloudFlareCaptchaRequired": "Lloc protegit per CloudFlare CAPTCHA. Cal un testimoni CAPTCHA vàlid.",
"IndexerValidationFeedNotSupported": "No s'admet el canal Indexer: {exceptionMessage}",
"IndexerValidationInvalidApiKey": "Clau API no vàlida",
@@ -1689,7 +1689,7 @@
"UpgradeUntilThisQualityIsMetOrExceeded": "Actualitza fins que aquesta qualitat es compleixi o superi",
"UpgradesAllowedHelpText": "Si es desactiven les qualitats no s'actualitzaran",
"IndexerSettingsFailDownloads": "Baixades fallides",
"IndexerSettingsSeasonPackSeedTime": "Hora de la llavor dels paquets de temporada",
"IndexerSettingsSeasonPackSeedTime": "Hora de la llavor de les packs de temporada",
"IndexerValidationNoRssFeedQueryAvailable": "No hi ha disponible cap consulta de fonts RSS. Aquest pot ser un problema amb l'indexador o la configuració de la categoria de l'indexador.",
"LibraryImportTipsSeriesUseRootFolder": "Apunteu {appName} a la carpeta que conté totes les sèries, no una específica. ex. \"`{goodFolderExample}`\" i no eg. \"`{badFolderExample}`\". A més, cada sèrie ha d'estar en la seva pròpia carpeta dins de la carpeta arrel/biblioteca.",
"LocalAirDate": "Data d'emissió local",
@@ -1874,6 +1874,7 @@
"DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "La cua de torrent no està activada a la configuració del qBittorrent. Activeu-lo a qBittorrent o seleccioneu 'Last' com a prioritat.",
"DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent està configurat per a eliminar els torrents quan arribin al límit de la relació de compartició",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Afegeix un prefix a l'URL rpc de {clientName}, ex. {url}, per defecte a {defaultUrl}",
"DownloadClientUTorrentProviderMessage": "uTorrent té un historial d'inclusió de criptominers, programari maliciós i anuncis, us animem a triar un client diferent.",
"DownloadClientValidationAuthenticationFailureDetail": "Verifiqueu el vostre nom d'usuari i contrasenya. Verifiqueu també si el servidor que executa {appName} no està bloquejat per accedir a {clientName} per les limitacions de WhiteList a la configuració {clientName}.",
"DownloadClientValidationCategoryMissingDetail": "La categoria que heu introduït no existeix a {clientName}. Primer creeu-lo a {clientName}.",
"DownloadClientValidationSslConnectFailure": "No s'ha pogut connectar a través de SSL",
@@ -2165,24 +2166,5 @@
"NotificationsAppriseSettingsIncludePosterHelpText": "Inclou el cartell al missatge",
"MonitorEpisodesModalInfo": "Aquesta opció només ajustarà quins episodis o temporades són monitorats en les sèries. Seleccionar Cap deixarà de monitorar les sèries",
"EpisodeMonitoring": "Monitoratge d'episodis",
"NotificationsAppriseSettingsIncludePoster": "Inclou el cartell",
"UserRejectedExtensions": "Extensions addicionals d'arxiu rebutjades",
"UserRejectedExtensionsHelpText": "Llista d'extensions d'arxiu a fallar separades per coma (Descàrregues fallides també necessita ser activat per indexador)",
"UserRejectedExtensionsTextsExamples": "Exemples: '.ext, .xyz' o 'ext,xyz'",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Afegeix etiquetes de sèries",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Afegeix etiquetes de sèries als nous torrents afegits al client de descàrrega (qBittorrent 4.1.0+)",
"IndexerSettingsSeasonPackSeedGoal": "Objectiu de compartició per als paquets de temporada",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Trieu si utilitzar diferents objectius de llavor per als paquets de temporada",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Utilitzeu els objectius estàndard",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Utilitzeu els objectius del paquet de temporada",
"IndexerSettingsSeasonPackSeedRatio": "Ràtio de la llavor del paquet de temporada",
"IndexerSettingsSeasonPackSeedRatioHelpText": "La relació a la qual ha d'arribar el torrent d'un paquet de temporada abans d'aturar-se, buida usa el valor predeterminat del client de baixada. La relació ha de ser com a mínim 1.0 i seguir les regles dels indexadors",
"RemoveRootFolderWithSeriesMessageText": "Esteu segur que voleu eliminar la carpeta arrel '{path}'? Els fitxers i carpetes no s'eliminaran del disc, i les sèries d'aquesta carpeta arrel no s'eliminaran de {appName}.",
"DownloadClientTriblerSettingsAnonymityLevel": "Nivell d'anonimat",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Nombre de proxies a usar quan es descarrega contingut. Establir a 0 per a desactivar-ho. Els proxies redueixen la velocitat de descàrrega/pujada. Vegeu {url}",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key de triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Ubicació opcional per a desar les baixades, deixeu-ho en blanc per utilitzar la ubicació predeterminada del Tribler",
"DownloadClientTriblerSettingsSafeSeeding": "Compartició segura",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Quan està activat, només es comparteix a través de proxies.",
"DownloadClientTriblerProviderMessage": "La integració tribler és altament experimental. S'ha provat amb {clientName} versió {clientVersionRange}."
"NotificationsAppriseSettingsIncludePoster": "Inclou el cartell"
}

View File

@@ -178,7 +178,7 @@
"Any": "Jakákoliv",
"ApiKey": "Klíč API",
"AppUpdated": "{appName} aktualizován",
"AppUpdatedVersion": "{appName} byl aktualizován na verzi `{version}`. Abyste získali nejnovější změny, musíte znovu načíst {appName} ",
"AppUpdatedVersion": "{appName} byla aktualizována na verzi `{version}`, abyste získali nejnovější změny, musíte znovu načíst {appName}. ",
"ChooseImportMode": "Vyberte mód importu",
"ClickToChangeEpisode": "Kliknutím změníte epizodu",
"ClickToChangeLanguage": "Kliknutím změníte jazyk",
@@ -316,7 +316,7 @@
"EditSelectedImportLists": "Upravit vybrané seznamy k importu",
"FormatDateTime": "{formattedDate} {formattedTime}",
"AddRootFolderError": "Nepodařilo se přidat kořenový adresář",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient pro stahování {downloadClientName} je nastaven, aby odstraňoval dokončené stahování. To může vést k tomu, že stažená data budou z klienta odstraněna dříve, než je {appName} bude moci importovat.",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {appName}.",
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
"CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena",
"CustomFormatsSpecificationFlag": "Značka",
@@ -357,13 +357,13 @@
"DeleteSpecification": "Smaž specifikace",
"MappedNetworkDrivesWindowsService": "Mapované síťové jednotky nejsou k dispozici, když běží jako služba Windows. Další informace najdete v [FAQ]({url}).",
"DeletedSeriesDescription": "Seriál byl smazán z TheTVDB",
"RecycleBinUnableToWriteHealthCheckMessage": "Nelze zapisovat do nakonfigurované složky koše: {path}. Ujistěte se, že tato cesta existuje a že do ní může zapisovat uživatel, pod kterým běží {appName}",
"RecycleBinUnableToWriteHealthCheckMessage": "Nelze zapisovat do nakonfigurované složky koše: {path}. Ujistěte se, že tato cesta existuje a že do ní může zapisovat uživatel se spuštěnou {appName}",
"DeleteSelectedImportListExclusionsMessageText": "Opravdu smazat vybraný importovaný seznam vyjímek?",
"DoNotUpgradeAutomatically": "Neupgradovat automaticky",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Počáteční stav torrentů přidaných do qBittorrentu. Pamatujte, že vynucené torrenty nedodržují omezení týkající se seedů",
"DeleteSelectedCustomFormats": "Smazat vlastní formát(y)",
"ClickToChangeReleaseType": "Kliknutím změníte typ verze",
"CollapseAll": "Sbalit vše",
"CollapseAll": "Sbal Všechno",
"CutoffUnmetNoItems": "Žádné neodpovídající nesplněné položky",
"CutoffUnmetLoadError": "Chybné načítání nesplněných položek",
"AddDelayProfileError": "Nelze přidat nový profil zpoždění, zkuste to prosím znovu.",
@@ -401,7 +401,7 @@
"DoneEditingGroups": "Úpravy skupin dokončeny",
"DeleteSeriesModalHeader": "Smazat - {title}",
"DeleteSelectedCustomFormatsMessageText": "Opravdu odstranit {count} vybraný vlastní formát(y)?",
"DownloadClientCheckNoneAvailableHealthCheckMessage": "Nedostupný žádný klient pro stahování",
"DownloadClientCheckNoneAvailableHealthCheckMessage": "Nedostupný klient pro stahování",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Nelze komunikovat s {downloadClientName}. {errorMessage}",
"DownloadClientFreeboxSettingsAppTokenHelpText": "Token aplikace získaný při vytváření přístupu k Freebox API (tj. app_token)",
"DownloadClientFreeboxSettingsAppToken": "Token aplikace",
@@ -537,6 +537,7 @@
"DownloadClientFreeboxUnableToReachFreebox": "Nelze se připojit k Freebox API. Zkontrolujte nastavení 'Host', 'Port' nebo 'Použít SSL'. (Chyba: {exceptionMessage})",
"IndexerHDBitsSettingsCodecsHelpText": "Pokud nespecifikováno, použijí se všechny možnosti.",
"DownloadClientDownloadStationValidationSharedFolderMissing": "Sdílená složka neexistuje",
"DownloadClientUTorrentProviderMessage": "uTorrent je známý tím, že zahrnuje cryptominery, malware a reklamy, důrazně vám doporučujeme zvolit jiného klienta.",
"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í.",
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexery nedostupné z důvodu selhání delším než 6 hodin: {indexerNames}",
"DownloadClientFloodSettingsRemovalInfo": "{appName} se postará o automatické mazání torrentů podle aktuálních kritérií seedování v Nastavení -> Indexery",
@@ -560,19 +561,5 @@
"Path": "Cesta",
"RegularExpressionsCanBeTested": "Regulární výrazy lze testovat [zde]({url}).",
"NotificationStatusSingleClientHealthCheckMessage": "Oznámení nedostupná z důvodu selhání: {notificationNames}",
"NotificationTriggersHelpText": "Vyber, které události mají vyvolat toto upozornění",
"MonitorSelected": "Monitorovat vybrané",
"ShowBannersHelpText": "Zobrazit bannery místo jmen",
"ListRootFolderHelpText": "Kořenová složka, do které budou přidány položky seznamu",
"UnmonitorSelected": "Nemonitorovat vybrané",
"ErrorLoadingItem": "Nastala chyba při načítání této položky",
"IndexerJackettAllHealthCheckMessage": "Indexery, které používají nepodporovaný Jackett endpoint 'all': {indexerNames}",
"ReleaseProfileIndexerHelpText": "Výběr, jakých indexerů se profil týká",
"SkipRedownloadHelpText": "Zabraňuje {appName} zkoušet stahovat alternativní vydání pro odebrané položky",
"ErrorLoadingPage": "Nastala chyba při načítání této stránky",
"ExpandAll": "Rozbalit vše",
"ImportListsPlexSettingsAuthenticateWithPlex": "Autentizovat s Plex.tv",
"MissingNoItems": "Žádné chybějící položky",
"RegularExpression": "Regulární výraz",
"IndexerSearchNoInteractiveHealthCheckMessage": "Nejsou dostupné žádné indexery s povoleným interaktivním vyhledáváním, {appName} nemůže poskytnout žádné výsledky interaktivního hledání"
"NotificationTriggersHelpText": "Vyber, které události mají vyvolat toto upozornění"
}

View File

@@ -549,13 +549,7 @@
"DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {downloadClientNames}",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Transmission location",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} rpc url, eg {url}, defaults to '{defaultUrl}'",
"DownloadClientTriblerSettingsAnonymityLevel": "Anonymity level",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Number of proxies to use when downloading content. To disable set to 0. Proxies reduce download/upload speed. See {url}",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key from triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Tribler location",
"DownloadClientTriblerSettingsSafeSeeding": "Safe Seeding",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "When enabled, only seed through proxies.",
"DownloadClientTriblerProviderMessage": "The tribler integration is highly experimental. Tested against {clientName} version {clientVersionRange}.",
"DownloadClientUTorrentProviderMessage": "uTorrent has a history of including cryptominers, malware and ads, we strongly encourage you to choose a different client.",
"DownloadClientUTorrentTorrentStateError": "uTorrent is reporting an error",
"DownloadClientUnavailable": "Download Client Unavailable",
"DownloadClientValidationApiKeyIncorrect": "API Key Incorrect",
@@ -1025,14 +1019,8 @@
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.",
"IndexerSettingsRssUrl": "RSS URL",
"IndexerSettingsRssUrlHelpText": "Enter to URL to an {indexer} compatible RSS feed",
"IndexerSettingsSeasonPackSeedGoal": "Seeding Goal for Season Packs",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Choose whether to use different seeding goals for season packs",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Use Standard Goals",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Use Season Pack Goals",
"IndexerSettingsSeasonPackSeedRatio": "Season Pack Seed Ratio",
"IndexerSettingsSeasonPackSeedRatioHelpText": "The ratio a season pack torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules",
"IndexerSettingsSeasonPackSeedTime": "Season Pack Seed Time",
"IndexerSettingsSeasonPackSeedTimeHelpText": "The time a season pack torrent should be seeded before stopping, empty uses the download client's default",
"IndexerSettingsSeasonPackSeedTime": "Season-Pack Seed Time",
"IndexerSettingsSeasonPackSeedTimeHelpText": "The time a season-pack torrent should be seeded before stopping, empty uses the download client's default",
"IndexerSettingsSeedRatio": "Seed Ratio",
"IndexerSettingsSeedRatioHelpText": "The ratio a torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules",
"IndexerSettingsSeedTime": "Seed Time",

View File

@@ -474,7 +474,7 @@
"DelayProfiles": "Perfiles de retraso",
"DeleteCustomFormatMessageText": "¿Estás seguro que quieres eliminar el formato personalizado '{name}'?",
"DeleteBackup": "Eliminar copia de seguridad",
"CopyUsingHardlinksSeriesHelpText": "Los enlaces físicos permiten a {appName} importar los torrents que se estén sembrando a la carpeta de la serie sin usar espacio adicional en el disco o copiar el contenido completo del archivo. Los enlaces físicos solo funcionarán si el origen y el destino están en el mismo volumen",
"CopyUsingHardlinksSeriesHelpText": "Los hardlinks permiten a {appName} a importar los torrents que se estén compartiendo a la carpeta de la serie sin usar espacio adicional en el disco o sin copiar el contenido completo del archivo. Los hardlinks solo funcionarán si el origen y el destino están en el mismo volumen",
"DefaultDelayProfileSeries": "Este es el perfil por defecto. Aplica a todas las series que no tienen un perfil explícito.",
"DelayProfileSeriesTagsHelpText": "Aplica a series con al menos una etiqueta coincidente",
"DeleteCustomFormat": "Eliminar formato personalizado",
@@ -526,7 +526,7 @@
"DeleteEpisodesFilesHelpText": "Eliminar archivos de episodios y directorio de series",
"DoNotPrefer": "No preferir",
"DoNotUpgradeAutomatically": "No actualizar automáticamente",
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de detenerse, dejar vacío utiliza el valor predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de parar, vacío usa el predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
"Download": "Descargar",
"Donate": "Donar",
"DownloadClientDelugeValidationLabelPluginFailure": "La configuración de etiqueta falló",
@@ -730,7 +730,7 @@
"DownloadFailedEpisodeTooltip": "La descarga del episodio falló",
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Descarga primero las primeras y últimas piezas (qBittorrent 4.1.0+)",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Primeras y últimas primero",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Estado inicial para los torrents añadidos a qBittorrent. Ten en cuenta que Forzar torrents no cumple las restricciones de sembrado",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Estado inicial para los torrents añadidos a qBittorrent. Ten en cuenta que Forzar torrents no cumple las restricciones de semilla",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Descarga en orden secuencial (qBittorrent 4.1.0+)",
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "El Diskstation no tiene una carpeta compartida con el nombre '{sharedFolder}'. ¿Estás seguro que lo has especificado correctamente?",
"EnableCompletedDownloadHandlingHelpText": "Importa automáticamente las descargas completas del gestor de descargas",
@@ -771,7 +771,7 @@
"EpisodeIsNotMonitored": "El episodio no está monitorizado",
"EpisodesLoadError": "No se puede cargar los episodios",
"File": "Archivo",
"HardlinkCopyFiles": "Enlace físico/Copiar archivos",
"HardlinkCopyFiles": "Enlace permanente/Copiar archivos",
"EpisodeDownloaded": "Episodio descargado",
"FileBrowser": "Explorador de archivos",
"FilterDoesNotStartWith": "no empieza con",
@@ -1075,11 +1075,11 @@
"StartupDirectory": "Directorio de Arranque",
"IndexerSettingsAdditionalParametersNyaa": "Parámetros Adicionales",
"IndexerSettingsPasskey": "Clave de acceso",
"IndexerSettingsSeasonPackSeedTime": "Tiempo de sembrado de los pack de temporada",
"IndexerSettingsSeasonPackSeedTime": "Tiempo de Semillado de los Pack de Temporada",
"IndexerSettingsAnimeStandardFormatSearch": "Formato Estándar de Búsqueda de Anime",
"IndexerSettingsAnimeStandardFormatSearchHelpText": "Buscar también anime utilizando la numeración estándar",
"IndexerSettingsApiPathHelpText": "Ruta a la API, usualmente {url}",
"IndexerSettingsSeasonPackSeedTimeHelpText": "El tiempo que un torrent de pack de temporada debe ser sembrado antes de detenerse, si se deja vacío utiliza el valor predeterminado del cliente de descarga",
"IndexerSettingsSeasonPackSeedTimeHelpText": "La cantidad de tiempo que un torrent de pack de temporada debe ser compartido antes de que se detenga, dejar vacío utiliza el valor por defecto del cliente de descarga",
"IndexerSettingsSeedTime": "Tiempo de sembrado",
"IndexerStatusAllUnavailableHealthCheckMessage": "Todos los indexadores no están disponibles debido a errores",
"IndexerValidationCloudFlareCaptchaExpired": "El token CAPTCHA de CloudFlare ha caducado, actualícelo.",
@@ -1098,7 +1098,7 @@
"IndexerSettingsRssUrlHelpText": "Introduzca la URL de un canal RSS compatible con {indexer}",
"IndexerStatusUnavailableHealthCheckMessage": "Indexadores no disponibles debido a errores: {indexerNames}",
"IndexerHDBitsSettingsMediums": "Medios",
"IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser sembrado antes de detenerse, dejar vacío utiliza el valor predeterminado del cliente de descarga",
"IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser sembrado antes de parar, vacío usa el predeterminado del cliente de descarga",
"IndexerValidationCloudFlareCaptchaRequired": "Sitio protegido por CloudFlare CAPTCHA. Se requiere un token CAPTCHA válido.",
"NotificationsEmailSettingsUseEncryption": "Usar Cifrado",
"LastDuration": "Última Duración",
@@ -1706,7 +1706,7 @@
"UpgradeUntil": "Actualizar hasta",
"UpdaterLogFiles": "Actualizador de archivos de registro",
"UseSeasonFolder": "Usar carpeta de temporada",
"UseHardlinksInsteadOfCopy": "Utilizar enlaces físicos en lugar de copiar",
"UseHardlinksInsteadOfCopy": "Utilizar enlaces directos en lugar de copiar",
"View": "Vista",
"VisitTheWikiForMoreDetails": "Visita la wiki para más detalles: ",
"WaitingToProcess": "Esperar al proceso",
@@ -1875,7 +1875,7 @@
"SmartReplace": "Reemplazo inteligente",
"SupportedDownloadClientsMoreInfo": "Para más información en los clientes de descarga individuales, haz clic en los botones de más información.",
"SupportedImportListsMoreInfo": "Para más información de los listas de importación individuales, haz clic en los botones de más información.",
"TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "En lugar de mover archivos, esto indicará a {appName} que copie o haga un enlace físico (dependiendo de los ajustes/configuración del sistema)",
"TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "En lugar de mover archivos esto indicará a {appName} que copie o enlace (dependiendo de los ajustes/configuración del sistema)",
"TorrentDelay": "Retraso de torrent",
"ToggleMonitoredToUnmonitored": "Monitorizado, haz clic para dejar de monitorizar",
"TorrentBlackholeSaveMagnetFilesHelpText": "Guarda el enlace magnet si no hay ningún archivo .torrent disponible (útil solo si el cliente de descarga soporta magnets guardados en un archivo)",
@@ -2151,6 +2151,7 @@
"NotificationsTelegramSettingsLinkPreviewHelpText": "Determina qué enlaces se previsualizarán en las notificaciones de Telegram. Elige 'Ninguno' para deshabilitarlo",
"MediaInfoFootNote2": "MediaInfo AudioLanguages excluye el inglés si es el único idioma. Usa MediaInfo AudioLanguagesAll para incluir solo el inglés",
"ReleaseSource": "Fuente de lanzamiento",
"DownloadClientUTorrentProviderMessage": "uTorrent tiene un amplio historial de incluir criptomineros, malware y publicidad, por lo que recomendamos encarecidamente que elijas un cliente diferente.",
"NotificationsPushcutSettingsIncludePoster": "Incluir póster",
"NotificationsPushcutSettingsIncludePosterHelpText": "Incluir póster con notificación",
"NotificationsPushcutSettingsMetadataLinks": "Enlaces de metadatos",
@@ -2165,24 +2166,5 @@
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir póster en el mensaje",
"EpisodeMonitoring": "Monitorización de episodios",
"MonitorEpisodes": "Monitorizar episodios",
"MonitorEpisodesModalInfo": "Esta opción solo ajustará qué episodios o temporadas son monitorizados en las series. Seleccionar Ninguno dejará de monitorizar las series",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Añadir etiquetas de series",
"UserRejectedExtensions": "Extensiones adicionales de archivo rechazadas",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Añade etiquetas de series a los nuevos torrents añadidos al cliente de descarga (qBittorrent 4.1.0+)",
"UserRejectedExtensionsTextsExamples": "Ejemplos: '.ext, .xyz' o 'ext,xyz'",
"UserRejectedExtensionsHelpText": "Lista de extensiones de archivo a fallar separadas por coma (Descargas fallidas también necesita ser activado por indexador)",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Usar objetivos estándar",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Usar objetivos de pack de temporada",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Elige si usar diferentes objetivos de sembrado para packs de temporada",
"IndexerSettingsSeasonPackSeedRatio": "Ratio de siembra del pack de temporada",
"RemoveRootFolderWithSeriesMessageText": "¿Estás seguro que quieres eliminar la carpeta raíz '{path}'? Los archivos y carpetas no serán borrados del disco, y las series en esta carpeta raíz no serán eliminadas de {appName}.",
"IndexerSettingsSeasonPackSeedGoal": "Objetivo de sembrado para packs de temporada",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key de triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Localización opcional en la que poner las descargas, dejar en blanco para usar la localización predeterminada de Tribler",
"IndexerSettingsSeasonPackSeedRatioHelpText": "El ratio que un torrent de pack de temporada debería alcanzar antes de detenerse, si se deja vacío usa el valor predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
"DownloadClientTriblerSettingsAnonymityLevel": "Nivel de anonimato",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Número de proxies a usar cuando se descarga contenido. Establecer a 0 para deshabilitarlo. Los proxies reducen la velocidad de descarga/subida. Ver {url}",
"DownloadClientTriblerProviderMessage": "La integración con Tribler es altamente experimental. Probado con {clientName} versión {clientVersionRange}.",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Cuando se habilita, solo se siembra a través de los proxies.",
"DownloadClientTriblerSettingsSafeSeeding": "Sembrado seguro"
"MonitorEpisodesModalInfo": "Esta opción solo ajustará qué episodios o temporadas son monitorizados en las series. Seleccionar Ninguno dejará de monitorizar las series"
}

View File

@@ -816,7 +816,7 @@
"CollapseMultipleEpisodesHelpText": "Tiivistä useat samana päivänä esitettävät jaksot.",
"CalendarLegendSeriesFinaleTooltip": "Sarjan tai kauden päätösjakso",
"CalendarLegendSeriesPremiereTooltip": "Sarjan tai kauden pilottijakso",
"ClickToChangeSeries": "Vaihda sarja klikkaamalla",
"ClickToChangeSeries": "Muuta sarjaa klikkaamalla",
"CloneIndexer": "Monista hakupalvelu",
"Close": "Sulje",
"ClearBlocklist": "Tyhjennä estolista",
@@ -1190,7 +1190,7 @@
"AddedDate": "Lisätty: {date}",
"Anime": "Anime",
"Any": "Mikä tahansa",
"ClickToChangeSeason": "Vaihda tuotantokausi klikkaamalla",
"ClickToChangeSeason": "Vaihda tuotantokautta painamalla tästä",
"CountSelectedFile": "{selectedCount} tiedosto on valittu",
"SingleEpisodeInvalidFormat": "Yksittäinen jakso: virheellinen kaava",
"Underscore": "Alaviiva",
@@ -1242,7 +1242,7 @@
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Vahvista uusi salasana",
"Category": "Kategoria",
"ChownGroup": "chown-ryhmä",
"ClickToChangeEpisode": "Vaihda jakso klikkaamalla",
"ClickToChangeEpisode": "Vaihda jaksoa painamalla tästä",
"CompletedDownloadHandling": "Valmistuneiden latausten käsittely",
"Condition": "Ehto",
"Continuing": "Jatkuu",
@@ -1834,13 +1834,13 @@
"ImportListsMyAnimeListSettingsListStatus": "Listan tila",
"ImportListStatusAllUnavailableHealthCheckMessage": "Mitkään listat eivät ole virheiden vuoksi käytettävissä",
"MetadataKometaDeprecatedSetting": "Poistunut",
"NotificationsTelegramSettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit median metatietoihin.",
"NotificationsTelegramSettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit sarjojen metatietoihin.",
"OnFileImport": "Kun tiedosto tuodaan",
"OnFileUpgrade": "Kun tiedosto päivitetään",
"ReleaseProfile": "Julkaisuprofiili",
"ShowTags": "Näytä tunnisteet",
"TodayAt": "Tänään klo {time}",
"ClickToChangeReleaseType": "Vaihda julkaisun tyyppi klikkaamalla",
"ClickToChangeReleaseType": "Vaihda julkaisun tyyppiä painamalla tästä",
"CustomFormatsSpecificationSource": "Lähde",
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent ilmoittaa puuttuvista tiedostoista",
"DownloadClientSabnzbdValidationCheckBeforeDownload": "Poista SABnbzd:n \"Tarkista ennen lataamista\" -asetus käytöstä",
@@ -1862,7 +1862,7 @@
"ReleaseGroupFootNote": "Vaihtoehtoisesti voit hallita lyhennystä tavujen enimmäismäärän perusteella, ellipsi (...) mukaan lukien. Sekä lyhennystä lopusta (esim. \"{Julkaisuryhmä:30}\"), että alusta (esim. \"{Julkaisuryhmä:-30}\") tuetaan.",
"InstallMajorVersionUpdateMessage": "Tämä päivitys asentaa uuden pääversion, joka ei välttämättä ole yhteensopiva laitteistosi kanssa. Haluatko varmasti asentaa päivityksen?",
"MinimumCustomFormatScoreIncrementHelpText": "Pienin vaadittu olemassa olevien ja uusien julkaisujen välinen mukautetun muodon pisteytyksen korotus ennen kuin {appName} tulkitsee julkaisun päivitykseksi.",
"NotificationsGotifySettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit median metatietoihin.",
"NotificationsGotifySettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit sarjojen metatietoihin.",
"NotificationsPlexSettingsServerHelpText": "Valitse tunnistautumisen jälkeen palvelin Plex.tv-tililtä.",
"EpisodeTitleFootNote": "Vaihtoehtoisesti voit hallita lyhennystä tavujen enimmäismäärän perusteella, ellipsi (...) mukaan lukien. Sekä lyhennystä lopusta (esim. \"{jakson nimi:30}\"), että alusta (esim. \"{jakson nimi:-30}\") tuetaan. Tarvittaessa jaksojen nimet lyhennetään automaattisesti järjestelmän rajoitukseen.",
"SeriesFootNote": "Vaihtoehtoisesti voit hallita lyhennystä tavujen enimmäismäärän perusteella, ellipsi (...) mukaan lukien. Sekä lyhennystä lopusta (esim. \"{Sarjan nimi:30}\"), että alusta (esim. \"{Sarjan nimi:-30}\") tuetaan.",
@@ -2152,23 +2152,16 @@
"QualityDefinitionsSizeNotice": "Kokorajoitukset on siirretty laatuprofiileihin",
"NotificationsTelegramSettingsLinkPreview": "Linkin esikatselu",
"NotificationsTelegramSettingsLinkPreviewHelpText": "Määrittää minkä linkin esikatselu Telegram-ilmoituksessa näytetään. Poista käytöstä valitsemalla \"Ei mitään\".",
"DownloadClientUTorrentProviderMessage": "Koska uTorrent on tunnettu crypto-, haitta- and mainossisällöstä ja sovelluksista, suosittelemme qBittorrentin, Delugen ja ruTorrentin kaltaisten vaihtoehtojen käyttämistä.",
"NotificationsPushcutSettingsIncludePoster": "Sisällytä juliste",
"NotificationsPushcutSettingsIncludePosterHelpText": "Näytä juliste ilmoituksessa.",
"NotificationsPushcutSettingsMetadataLinks": "Metatietolinkit",
"NotificationsPushcutSettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit median metatietoihin.",
"NotificationsPushcutSettingsMetadataLinksHelpText": "Lisää lähetettäviin ilmoituksiin linkit sarjojen metatietoihin.",
"AutoTaggingSpecificationNetwork": "Verkot",
"DownloadClientItemErrorMessage": "{clientName} ilmoittaa virheestä: {message}",
"EpisodesInSeason": "Tuotantokaudessa on {episodeCount} jaksoa",
"CloneImportList": "Monista tuontilista",
"DefaultNameCopiedImportList": "{name} (kopio)",
"EpisodeMonitoring": "Jaksojen valvonta",
"MonitorEpisodes": "Valvo jaksoja",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Merkitse uudet latauspalveluun lisätyt torrentit sarjatunnisteilla (aBittorrent 4.1.0+).",
"MonitorEpisodesModalInfo": "Tämä määrittää vain mitä jaksoja tai kausia sarjasta valvotaan. Valinta \"Ei mitään\" lopettaa sarjan valvonnan.",
"NotificationsAppriseSettingsIncludePoster": "Sisällytä juliste",
"NotificationsAppriseSettingsIncludePosterHelpText": "Sisällytä julisteet viesteihin.",
"UserRejectedExtensions": "Lisää estettyjä tiedostopäätteitä",
"UserRejectedExtensionsHelpText": "Pilkuin eroteltu listaus hylättävistä tiedostopäätteistä. Lisäksi \"Hylättävät lautaukset\"-asetuksen tulee olla käytössä hakupalvelukohtaisesti.",
"UserRejectedExtensionsTextsExamples": "Esimerkiksi: \".ext, .xyz\" tai \"ext,xyz\".",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Lisää sarjan tunnisteet"
"EpisodeMonitoring": "Jakson Valvonta",
"MonitorEpisodes": "Valvo Jaksoja"
}

View File

@@ -444,7 +444,7 @@
"NoSeriesFoundImportOrAdd": "Aucune série trouvée. Pour commencer, vous souhaiterez importer votre série existante ou ajouter une nouvelle série.",
"ICalFeedHelpText": "Copiez cette URL dans votre/vos client(s) ou cliquez pour abonner si votre navigateur est compatible avec webcal",
"SeasonFolderFormat": "Format du dossier de saison",
"QualitiesHelpText": "Les qualités placées en haut de la liste sont privilégiées même si elles ne sont pas cochées. Les qualités d'un même groupe sont égales. Seules les qualités cochées sont recherchées",
"QualitiesHelpText": "Les qualités plus élevées dans la liste sont plus préférées. Les qualités au sein dun même groupe sont égales. Seules les qualités vérifiées sont recherchées",
"PrioritySettings": "Priorité: {priority}",
"ImportExistingSeries": "Importer une série existante",
"RootFolderSelectFreeSpace": "{freeSpace} Libre",
@@ -800,7 +800,7 @@
"UpdaterLogFiles": "Journaux du programme de mise à jour",
"UpgradeUntil": "Mise à niveau jusqu'à",
"UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé",
"UpgradeUntilCustomFormatScoreEpisodeHelpText": "Une fois que la qualité minimum est atteinte ou dépassée et que le score de format personnalisé est atteint, {appName} ne récupérera plus les sorties d'épisodes",
"UpgradeUntilCustomFormatScoreEpisodeHelpText": "Une fois ce score de format personnalisé atteint, {appName} ne récupérera plus les sorties d'épisodes",
"UrlBase": "URL de base",
"UseHardlinksInsteadOfCopy": "Utiliser les liens durs au lieu de copier",
"UseSeasonFolder": "Utiliser le dossier de la saison",
@@ -904,7 +904,7 @@
"UnmappedFilesOnly": "Fichiers non mappés uniquement",
"UnmonitorSpecialsEpisodesDescription": "Annulez la surveillance de tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes",
"UpdateUiNotWritableHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de l'interface utilisateur « {uiFolder} » n'est pas accessible en écriture par l'utilisateur « {userName} ».",
"UpgradeUntilEpisodeHelpText": "Une fois cette qualité atteinte, {appName} ne téléchargera plus d'épisodes une fois le que le score du format personnalisé est atteint ou dépassé",
"UpgradeUntilEpisodeHelpText": "Une fois cette qualité atteinte, {appName} ne téléchargera plus d'épisodes",
"UpgradeUntilThisQualityIsMetOrExceeded": "Mise à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée",
"UseProxy": "Utiliser le proxy",
"WaitingToImport": "En attente d'import",
@@ -1191,7 +1191,7 @@
"PackageVersion": "Version du paquet",
"PackageVersionInfo": "{packageVersion} par {packageAuthor}",
"QuickSearch": "Recherche rapide",
"ReleaseRejected": "Version rejetée",
"ReleaseRejected": "Libération rejetée",
"ReleaseSceneIndicatorAssumingScene": "En supposant la numérotation des scènes.",
"ReleaseSceneIndicatorAssumingTvdb": "En supposant la numérotation TVDB.",
"ReleaseSceneIndicatorMappedNotRequested": "L'épisode mappé n'a pas été demandé dans cette recherche.",
@@ -2017,7 +2017,7 @@
"ImportListsTraktSettingsPopularListTypeTrendingShows": "Spectacles en vogue",
"ImportListsTraktSettingsPopularName": "Liste populaire de Trakt",
"ImportListsTraktSettingsRating": "Evaluation",
"ImportListsTraktSettingsRatingSeriesHelpText": "Filtrer les séries par plage de classement (0-100)",
"ImportListsTraktSettingsRatingSeriesHelpText": "Série de filtres par plage de valeurs nominales (0-100)",
"ImportListsTraktSettingsWatchedListFilterSeriesHelpText": "Si le type de liste est surveillé, sélectionnez le type de série que vous souhaitez importer",
"ImportListsTraktSettingsWatchListSorting": "Tri de la liste de surveillance",
"ImportListsTraktSettingsWatchListSortingHelpText": "Si le type de liste est surveillé, sélectionnez l'ordre de tri de la liste",
@@ -2123,66 +2123,5 @@
"LastSearched": "Dernière recherche",
"FolderNameTokens": "Jetons de nom de dossier",
"ManageCustomFormats": "Gérer les formats personnalisés",
"Menu": "Menu",
"Fallback": "Alternative",
"MetadataKometaDeprecatedSetting": "Obsolète",
"AutoTaggingSpecificationNetwork": "Réseau(x)",
"DefaultNameCopiedImportList": "{name} - Copie",
"DownloadClientItemErrorMessage": "{clientName} a rapporté une erreur : {message}",
"EditSizes": "Modifier les dimensions",
"NotificationsGotifySettingsPreferredMetadataLink": "Lien de métadonnées préféré",
"NotificationsPushcutSettingsMetadataLinksHelpText": "Ajouter un lien vers les métadonnées de la série lors de l'envoie d'une notification",
"NotificationsTelegramSettingsLinkPreviewHelpText": "Détermine quel lien sera aperçu dans la notification Telegram. Choisir 'Aucun' pour désactiver",
"DoneEditingSizes": "Terminer la modification des dimensions",
"EpisodeMonitoring": "Suivi des épisodes",
"ManageFormats": "Gérer les formats",
"MinuteShorthand": "m",
"MonitorEpisodes": "Surveiller les épisodes",
"NotificationsGotifySettingsPreferredMetadataLinkHelpText": "Lien de métadonnées pour les clients qui ne peuvent avoir qu'un seul lien",
"NotificationsSettingsWebhookHeaders": "En-têtes",
"NotificationsTelegramSettingsIncludeInstanceNameHelpText": "Inclure le nom de l'instance dans la notification de façon facultative",
"EpisodesInSeason": "{episodeCount} épisodes dans la saison",
"FileSize": "Taille de fichier",
"Maximum": "Maximum",
"Minimum": "Minimum",
"MinimumCustomFormatScoreIncrement": "Incrément minimal du score du format personnalisé",
"Minute": "minute",
"NotificationsPushcutSettingsIncludePoster": "Inclure l'affiche",
"NotificationsPushcutSettingsIncludePosterHelpText": "Inclure l'affiche avec les notifications",
"NotificationsTelegramSettingsLinkPreview": "Aperçu du lien",
"FavoriteFolderAdd": "Ajouter un dossier favori",
"FavoriteFolderRemove": "Supprimer le dossier favori",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Ajouter des tags de séries",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Ajouter des tags de séries aux nouveaux torrents ajoutés au client de téléchargement (qBittorrent 4.1.0+)",
"FavoriteFolders": "Dossier favori",
"MinimumCustomFormatScoreIncrementHelpText": "Amélioration minimale requise du score de format personnalisé entre les versions existantes et nouvelles avant que {appName} ne le considère comme une mise à niveau",
"MonitorEpisodesModalInfo": "Ce paramètre n'ajustera que les épisodes ou saisons qui seront surveillés dans une série. Sélectionner Aucun retirera la surveillance de la série",
"NotificationsTelegramSettingsIncludeInstanceName": "Inclure le nom de l'instance dans le titre",
"NotificationsPushcutSettingsMetadataLinks": "Lien de métadonnées",
"UserRejectedExtensions": "Extensions de fichiers rejetées supplémentaires",
"UserRejectedExtensionsHelpText": "Liste séparée par des virgules des extensions de fichiers à échouer (“Échouer les téléchargements” doit également être activé dans lindexeur)",
"UserRejectedExtensionsTextsExamples": "Examples : '.ext, .xyz' or 'ext,xyz'",
"Warning": "Avertissement",
"QualityDefinitionsSizeNotice": "Les restrictions de taille sont maintenant dans les profils de qualité",
"UserInvokedSearch": "Recherche invoquée par lutilisateur",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Nombre de proxy à utiliser pour télécharger. Pour désactiver, mettre à 0. Les proxy réduisent la vitesse d'envoi et de réception. Voir {url}",
"DownloadClientTriblerSettingsDirectoryHelpText": "Emplacement optionnel où mettre les téléchargements, laisser vide pour utiliser l'emplacement Tribler par défaut",
"CloneImportList": "Cloner la liste d'importation",
"IndexerSettingsSeasonPackSeedGoal": "Objective de partage pour les packs de saison",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Sélectionné si des objectifs différent devraient être utilisés pour les packs de saison",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Utiliser les objectifs standards",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Utiliser les objectifs pour les packs de saison",
"IndexerSettingsSeasonPackSeedRatio": "Ratio de partage pour les packs de saison",
"MediaInfoFootNote2": "MediaInfo AudioLanguages exclue langlais sil sagit de la seule langue. Utiliser MediaInfo AudioLanguagesAll pour inclure ceux seulement en anglais",
"NotificationsAppriseSettingsIncludePosterHelpText": "Inclure le poster dans le message",
"ReleasePush": "Délai de version",
"ReleaseSource": "Source de version",
"RemoveRootFolderWithSeriesMessageText": "Êtes-vous sûr de vouloir supprimer le dossier racine '{path}' ? Les fichiers et les dossiers ne seront pas supprimés du disque, et les séries dans ce dossier racine ne seront pas supprimées de {appName}.",
"IndexerSettingsSeasonPackSeedRatioHelpText": "Le ratio quun torrent de pack de saison devrait atteindre avant darrêter. Laisser à vide utilise le default du client de téléchargement. Le ratio devrait être au moins 1.0 et sur les règles de lindexeur",
"DownloadClientTriblerSettingsAnonymityLevel": "Niveau danonymat",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key de triblerd.conf",
"DownloadClientTriblerSettingsSafeSeeding": "Partage protégé",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Lorsque activé, seulement partager via un proxy.",
"DownloadClientTriblerProviderMessage": "Lintégration avec tribler est hautement expérimental. Tester sur {clientName} version {clientVersionRange}.",
"NotificationsAppriseSettingsIncludePoster": "Inclure le poster"
"Menu": "Menu"
}

View File

@@ -61,7 +61,7 @@
"AddDelayProfile": "Voeg vertragingsprofiel toe",
"AddCustomFormatError": "Kan geen nieuw aangepast formaat toevoegen. Probeer het opnieuw.",
"AddDownloadClientError": "Kan geen nieuwe downloadclient toevoegen. Probeer het opnieuw.",
"AddDownloadClient": "Downloadprogramma Toevoegen",
"AddDownloadClient": "Download Client Toevoegen",
"AddIndexerError": "Kon geen nieuwe indexeerder toevoegen, Probeer het opnieuw.",
"AddList": "Lijst Toevoegen",
"AddListError": "Kon geen nieuwe lijst toevoegen, probeer het opnieuw.",
@@ -266,36 +266,5 @@
"CertificateValidationHelpText": "Verander hoe strikt HTTPS-certificaatvalidatie is. Verander dit niet als je de risico's niet begrijpt.",
"ChmodFolderHelpText": "Octaal, toegepast tijdens importeren/hernoemen van mediamappen en -bestanden (zonder uitvoeringsbits)",
"CalendarFeed": "{appName} kalenderfeed",
"BypassDelayIfHighestQualityHelpText": "Omzeil uitstel wanneer de uitgave de hoogst mogelijke kwaliteit heeft uit het kwaliteitsprofiel van het geprefereerde protocol",
"DownloadClientValidationSslConnectFailure": "Kan niet verbinden via SSL",
"DownloadClientValidationGroupMissing": "Groep bestaat niet",
"DownloadClientValidationUnknownException": "Onbekende fout: {exception}",
"DownloadClientValidationVerifySsl": "Verifieer SSL instellingen",
"DownloadClients": "Downloadprogramma's",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Aantal proxies om te gebruiken bij het downloaden van content. Zet dit op 0 om uit te schakelen. Proxies vertragen download- en uploadsnelheid. Zie {url}",
"DownloadClientTriblerProviderMessage": "De Tribler-integratie is heel experimenteel. Getest met {clientName} versie {clientVersionRange}.",
"DownloadClientValidationAuthenticationFailureDetail": "Gelieve uw gebruikersnaam en wachtwoord te verifiëren. Verifieer ook of de host waar {appName} op draait niet geblokkeerd is voor toegang tot {clientName} door Whitelist limitaties in de {clientName} instellingen.",
"DownloadClientValidationSslConnectFailureDetail": "{appName} kan niet verbinden met {clientName} via SSL. Dit probleem kan computergerelateerd zijn. Probeer alstublieft om {appName} en {clientName} te configureren om geen SSL te gebruiken.",
"DownloadClientValidationAuthenticationFailure": "Authenticatiefout",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key uit triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Optionele locatie om downloads in te plaatsen, laat leeg om de standaard Tribler locatie te gebruiken",
"DownloadClientTriblerSettingsSafeSeeding": "Veilig Seeden",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Wanneer ingeschakeld alleen maar seeden via proxies.",
"DownloadClientUTorrentTorrentStateError": "uTorrent rapporteert een fout",
"DownloadClientUnavailable": "Download Client niet beschikbaar",
"DownloadClientValidationApiKeyIncorrect": "API-sleutel Incorrect",
"DownloadClientValidationApiKeyRequired": "API-sleutel Nodig",
"DownloadClientValidationCategoryMissing": "Categorie bestaat niet",
"DownloadClientValidationCategoryMissingDetail": "De ingevoerde categorie bestaat niet in {clientName}. Voeg deze eerst toe in {clientName}.",
"DownloadClientValidationErrorVersion": "{clientName} versie moet tenminste {requiredVersion} zijn. Gerapporteerde versie is {reportedVersion}",
"DownloadClientValidationGroupMissingDetail": "De ingevoerde groep bestaat niet in {clientName}. Voeg deze eerst toe in {clientName}.",
"DownloadClientValidationTestNzbs": "Kon de lijst van NZBs niet verkrijgen: {exceptionMessage}",
"DownloadClientValidationTestTorrents": "Kon de lijst van torrents niet verkrijgen: {exceptionMessage}",
"DownloadClientValidationUnableToConnect": "Kon niet verbinden met {clientName}",
"DownloadClientValidationUnableToConnectDetail": "Gelieve de hostnaam en poort te verifiëren.",
"DownloadClientValidationVerifySslDetail": "Gelieve uw SSL-configuratie te verifiëren in zowel {clientName} als {appName}",
"DownloadClientVuzeValidationErrorVersion": "Protocolversie niet ondersteund, gebruik Vuze 5.0.0.0 of hoger met de Vuze Web Remote plugin.",
"DownloadClientsLoadError": "Kan downloadprogramma's niet laden",
"GeneralSettingsLoadError": "Algemene instellingen konden niet worden geladen",
"GeneralSettingsSummary": "Poort, SSL, gebruikersnaam/wachtwoord, proxy, analytics en updates"
"BypassDelayIfHighestQualityHelpText": "Omzeil uitstel wanneer de uitgave de hoogst mogelijke kwaliteit heeft uit het kwaliteitsprofiel van het geprefereerde protocol"
}

View File

@@ -55,7 +55,7 @@
"Added": "Adicionado",
"ApiKeyValidationHealthCheckMessage": "Atualize sua chave de API para ter pelo menos {length} caracteres. Você pode fazer isso através das configurações ou do arquivo de configuração",
"RemoveCompletedDownloads": "Remover downloads concluídos",
"AppDataLocationHealthCheckMessage": "A atualização não será possível para impedir a exclusão de AppData na Atualização",
"AppDataLocationHealthCheckMessage": "Não será possível atualizar para evitar a exclusão de AppData na Atualização",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {downloadClientName}. {errorMessage}",
"DownloadClientRootFolderHealthCheckMessage": "O cliente de download {downloadClientName} coloca os downloads na pasta raiz {rootFolderPath}. Você não deve baixar para uma pasta raiz.",
"DownloadClientSortingHealthCheckMessage": "O cliente de download {downloadClientName} tem classificação {sortingMode} habilitada para a categoria do {appName}. Você deve desabilitar essa classificação em seu cliente de download para evitar problemas de importação.",
@@ -1632,7 +1632,7 @@
"IndexerSettingsMinimumSeedersHelpText": "Quantidade mínima de semeadores necessária.",
"IndexerSettingsPasskey": "Chave de acesso",
"IndexerSettingsRssUrl": "URL do RSS",
"IndexerSettingsSeasonPackSeedTime": "Tempo de Semeadura para Pacotes de Temporada",
"IndexerSettingsSeasonPackSeedTime": "Tempo de semeadura para pacotes de temporada",
"IndexerSettingsSeedRatio": "Proporção de semeadura",
"IndexerSettingsSeedTime": "Tempo de semeadura",
"IndexerSettingsSeedTimeHelpText": "Quanto tempo um torrent deve ser semeado antes de parar, deixe vazio para usar o padrão do cliente de download",
@@ -2152,6 +2152,7 @@
"QualityDefinitionsSizeNotice": "As restrições de tamanho foram transferidas para Perfis de Qualidade",
"NotificationsTelegramSettingsLinkPreview": "Prévia do Link",
"NotificationsTelegramSettingsLinkPreviewHelpText": "Determina qual link será visualizado na notificação do Telegram. Escolha 'Nenhum' para desativar",
"DownloadClientUTorrentProviderMessage": "O uTorrent tem um histórico de incluir criptomineradores, malware e anúncios, recomendamos fortemente que você escolha um cliente diferente.",
"NotificationsPushcutSettingsIncludePoster": "Incluir pôster",
"NotificationsPushcutSettingsMetadataLinks": "Links de metadados",
"NotificationsPushcutSettingsIncludePosterHelpText": "Incluir pôster com notificação",
@@ -2165,24 +2166,5 @@
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir pôster na mensagem",
"EpisodeMonitoring": "Monitoramento do Episódio",
"MonitorEpisodes": "Monitorar Episódios",
"MonitorEpisodesModalInfo": "Esta configuração ajustará apenas quais episódios ou temporadas serão monitorados dentro de uma série. Selecionar Nenhum desativará o monitoramento da série",
"UserRejectedExtensions": "Extensões de Arquivos Rejeitadas Adicionais",
"UserRejectedExtensionsHelpText": "Lista separada por vírgulas de extensões de arquivos para falhar (Falha em downloads também precisa ser habilitado por indexador)",
"UserRejectedExtensionsTextsExamples": "Exemplos: '.ext, .xyz' or 'ext,xyz'",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Adicionar etiquetas das séries a novos torrents adicionados ao cliente de download (qBittorrent 4.1.0+)",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Adicionar Etiquetas das Séries",
"IndexerSettingsSeasonPackSeedGoal": "Meta de Semeadura para Pacotes de Temporada",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Usar Metas Padrões",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Usar as Metas de Pacote de Temporada",
"IndexerSettingsSeasonPackSeedRatio": "Proporção de Semeadura do Pacote de Temporada",
"IndexerSettingsSeasonPackSeedRatioHelpText": "A proporção que um torrent de pacote de temporada deve atingir antes de parar, vazio usa o padrão do cliente de download. A proporção deve ser de pelo menos 1,0 e seguir as regras dos indexadores",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Escolha se deseja usar metas de semeadura diferentes para pacotes de temporada",
"RemoveRootFolderWithSeriesMessageText": "Tem certeza de que deseja remover a pasta raiz '{path}'? Arquivos e pastas não serão excluídos do disco e as séries nesta pasta raiz não serão removidas de {appName}.",
"DownloadClientTriblerSettingsAnonymityLevel": "Nível de anonimato",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Número de proxies a serem usados ao baixar conteúdo. Para desativar, defina como 0. Os proxies reduzem a velocidade de download/upload. Veja {url}",
"DownloadClientTriblerSettingsApiKeyHelpText": "[API]. chave de triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Local opcional para colocar downloads, deixe em branco para usar o local padrão do Tribler",
"DownloadClientTriblerSettingsSafeSeeding": "Semeadura Segura",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Quando ativado, apenas semeia por meio de proxies.",
"DownloadClientTriblerProviderMessage": "A integração tribler é altamente experimental. Testado em {clientName} versão {clientVersionRange}."
"MonitorEpisodesModalInfo": "Esta configuração ajustará apenas quais episódios ou temporadas serão monitorados dentro de uma série. Selecionar Nenhum desativará o monitoramento da série"
}

View File

@@ -269,7 +269,7 @@
"AnimeEpisodeFormat": "Формат аниме-эпизода",
"AuthBasic": "Базовый (Всплывающее окно браузера)",
"AuthForm": "Формы (Страница авторизации)",
"Authentication": "Аутентификация",
"Authentication": "Авторизация",
"AuthenticationRequired": "Требуется авторизация",
"BackupIntervalHelpText": "Периодичность автоматического резервного копирования",
"BackupRetentionHelpText": "Автоматические резервные копии старше указанного периода будут автоматически удалены",
@@ -1371,7 +1371,7 @@
"Hostname": "Имя хоста",
"Host": "Хост",
"IndexerSettingsRssUrlHelpText": "Введите URL-адрес RSS-канала, совместимого с {indexer}",
"IndexerSettingsSeasonPackSeedTimeHelpText": "Время, когда торрент полного сезона должен быть на раздаче, перед остановкой, при пустом значении используется значение загрузчика по умолчанию",
"IndexerSettingsSeasonPackSeedTimeHelpText": "Время, когда торрент сезонного пакета должен быть на раздаче перед остановкой, при пустом значении используется значение по умолчанию клиента загрузки",
"IndexerSettingsSeedRatioHelpText": "Рейтинг, которого должен достичь торрент перед остановкой, пусто — используется значение по умолчанию клиента загрузки. Рейтинг должен быть не менее 1,0 и соответствовать правилам индексаторов",
"IndexerHDBitsSettingsMediumsHelpText": "Если не указано, используются все параметры.",
"IndexerSettingsApiUrlHelpText": "Не меняйте это, если вы не знаете, что делаете. Поскольку ваш ключ API будет отправлен на этот хост.",
@@ -1500,7 +1500,7 @@
"RejectionCount": "Количество отказов",
"Release": "Релиз",
"ReleaseGroup": "Релиз группа",
"ReleaseGroupFootNote": "При необходимости можно управлять обрезкой до максимального количества байтов, включая многоточие (`...`). Поддерживается обрезка как с конца (например, `{Release Group:30}`), так и с начала (например, `{Release Group:-30}`).",
"ReleaseGroupFootNote": "При необходимости можно управлять обрезкой до максимального количества байтов, включая многоточие (`...`). Поддерживается обрезка как с конца (например, `{Release Group:30}`), так и с начала (например, `{Release Group:-30}`).`).",
"ReleaseProfileIndexerHelpText": "Укажите, к какому индексатору применяется профиль",
"ReleaseProfileIndexerHelpTextWarning": "Установка определенного индексатора в профиле релиза приведет к тому, что этот профиль будет применяться только к релизам из этого индексатора.",
"ReleaseProfiles": "Профили релизов",
@@ -1681,7 +1681,7 @@
"ImportListsPlexSettingsWatchlistRSSName": "Список наблюдения Plex RSS",
"IndexerSettingsAnimeCategoriesHelpText": "Выпадающий список, оставьте пустым, чтобы отключить аниме",
"IndexerSettingsMultiLanguageRelease": "Несколько языков",
"IndexerSettingsSeasonPackSeedTime": "Время сидирования полного сезона",
"IndexerSettingsSeasonPackSeedTime": "Время сидирования сезон-пака",
"IndexerValidationUnableToConnectResolutionFailure": "Невозможно подключиться к индексатору. Проверьте подключение к серверу индексатора и DNS. {exceptionMessage}.",
"IndexerValidationUnableToConnectHttpError": "Невозможно подключиться к индексатору. Проверьте настройки DNS и убедитесь, что IPv6 работает или отключен. {exceptionMessage}.",
"IndexerTagSeriesHelpText": "Используйте этот индексатор только для сериалов, имеющих хотя бы один соответствующий тег. Оставьте поле пустым, чтобы использовать его для всех.",
@@ -1696,7 +1696,7 @@
"MetadataSettings": "Настройки метаданных",
"NotificationsAppriseSettingsNotificationType": "Тип информирования об уведомлении",
"NotificationsEmailSettingsFromAddress": "С адреса",
"NotificationsEmailSettingsUseEncryptionHelpText": "Выбрать режим шифрования: предпочитать шифрование, если оно настроено на сервере; всегда использовать шифрование через SSL (только порт 465) или StartTLS (любой другой порт); никогда не использовать шифрование",
"NotificationsEmailSettingsUseEncryptionHelpText": "Выбрать режим шифрования: предпочитать шифрование, если оно настроено на сервере; всегда использовать шифрование через SSL (только порт 465) или StartTLS (любой другой порт); никогда не использовать шифрование.",
"NotificationsCustomScriptSettingsProviderMessage": "При тестировании будет выполняться сценарий с типом события, установленным на {eventTypeTest}. Убедитесь, что ваш сценарий обрабатывает это правильно",
"NotificationsJoinSettingsApiKeyHelpText": "Ключ API из настроек вашей учетной записи присоединения (нажмите кнопку «Присоединиться к API»).",
"NotificationsGotifySettingsServerHelpText": "URL-адрес сервера Gotify, включая http(s):// и порт, если необходимо",
@@ -2016,7 +2016,7 @@
"Search": "Поиск",
"RestartReloadNote": "Примечание: {appName} автоматически перезапустится и перезагрузит интерфейс пользователя во время процесса восстановления.",
"HealthMessagesInfoBox": "Дополнительную информацию о причине появления этих сообщений о проверке работоспособности можно найти, перейдя по ссылке wiki (значок книги) в конце строки или проверить [журналы]({link}). Если у вас возникли трудности с пониманием этих сообщений, вы можете обратиться в нашу службу поддержки по ссылкам ниже.",
"MaintenanceRelease": "Технический релиз: исправление ошибок и другие улучшения. Подробнее см. в истории коммитов Github",
"MaintenanceRelease": "Технический релиз: исправление ошибок и другие улучшения. Подробнее см. в истории коммитов Github.",
"Space": "Пробел",
"SslCertPasswordHelpText": "Пароль для файла pfx",
"SpecialEpisode": "Спец. эпизод",
@@ -2155,6 +2155,7 @@
"UpdatePath": "Обновить путь",
"UpdateSeriesPath": "Обновить путь до сериала",
"ReleasePush": "Через API",
"DownloadClientUTorrentProviderMessage": "Мы настоятельно советуем не использовать uTorrent, т.к. он известен как программа-шифровальщик и в целом вредоносное ПО.",
"CloneImportList": "Копировать список импорта",
"EpisodesInSeason": "{episodeCount} эпизодов в сезоне",
"DefaultNameCopiedImportList": "{name} - копировать",
@@ -2164,25 +2165,5 @@
"NotificationsAppriseSettingsIncludePosterHelpText": "Добавлять постер в сообщение",
"EpisodeMonitoring": "Отслеживание эпизода",
"MonitorEpisodes": "Отслеживать эпизоды",
"MonitorEpisodesModalInfo": "Эта настройка влияет только на отслеживание эпизодов или сезонов внутри сериала. Выбор ничего приведёт к остановке отслеживания сериала",
"ImportListsSimklSettingsUserListTypeHold": "Оставить",
"UserRejectedExtensions": "Дополнительные запрещенные расширения файлов",
"UserRejectedExtensionsTextsExamples": "Примеры: '.ext, .xyz' или 'ext,xyz'",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Добавлять теги сериалов",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Добавлять теги сериалов к новым торрентам, добавляемым в загрузчик (qBittorrent 4.1.0+)",
"UserRejectedExtensionsHelpText": "Список запрещенных расширений файлов, разделенных запятой (так же нужно включить настройку Считать загрузки неуспешными в настройках индексаторов)",
"IndexerSettingsSeasonPackSeedGoal": "Целевая раздача для полных сезонов",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Следует ли использовать другое значение целевой раздачи для полных сезонов",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Использовать стандартные значения",
"IndexerSettingsSeasonPackSeedRatio": "Соотношение раздачи для полных сезонов",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Использовать целевые значения для полных сезонов",
"IndexerSettingsSeasonPackSeedRatioHelpText": "Соотношение, которое полные сезоны должны достигнуть перед остановкой. Пустое значение приведёт к использованию значения загрузчика по умолчанию. Соотношение должно быть не менее 1.0 и соответствовать правилам индексаторов",
"RemoveRootFolderWithSeriesMessageText": "Вы уверены, что хотите удалить корневой каталог '{path}'? Файлов и папки не будут удалены с диска, а сериалы в этом корневом каталоге останутся в {appName}.",
"DownloadClientTriblerSettingsAnonymityLevel": "Уровень анонимности",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "Количество используемых прокси-серверов для скачивания контента. Чтобы выключить, укажите 0. Прокси-серверы уменьшают скорость скачивания/загрузки. Доп. инфо: {url}",
"DownloadClientTriblerSettingsApiKeyHelpText": "[api].key из triblerd.conf",
"DownloadClientTriblerSettingsDirectoryHelpText": "Не обязательный путь для сохранения загрузок. Оставьте пустым, чтобы использовать путь Tribler по умолчанию",
"DownloadClientTriblerSettingsSafeSeeding": "Безопасная раздача",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Если включено, раздача будет вестись только через прокси-серверы.",
"DownloadClientTriblerProviderMessage": "Интеграция с Tribler находится в ранней экспериментальной стадии. Тестировалось на {clientName} версии {clientVersionRange}."
"MonitorEpisodesModalInfo": "Эта настройка влияет только на отслеживание эпизодов или сезонов внутри сериала. Выбор ничего приведёт к остановке отслеживания сериала"
}

View File

@@ -43,6 +43,5 @@
"DownloadStationStatusExtracting": "Packar upp: {progress}%",
"Duplicate": "Dubblett",
"Yesterday": "Igår",
"EditCustomFormat": "Redigera anpassat format",
"AbsoluteEpisodeNumber": "Fullständigt Avsnitt Nummer"
"EditCustomFormat": "Redigera anpassat format"
}

View File

@@ -1780,18 +1780,18 @@
"ImportListsTraktSettingsWatchListSortingHelpText": "Liste Türü İzlenen ise, listeyi sıralamak için sırayı seçin",
"IndexerSearchNoAutomaticHealthCheckMessage": "Otomatik Arama etkinleştirildiğinde hiçbir indeksleyici kullanılamaz, {appName} herhangi bir otomatik arama sonucu sağlamayacaktır",
"IndexerSettingsApiUrlHelpText": "Ne yaptığınızı bilmiyorsanız bunu değiştirmeyin. API anahtarınız ana sunucuya gönderilecektir.",
"IndexerSettingsSeasonPackSeedTimeHelpText": "Bir sezon paketi torrentinin durmadan önce paylaşılması gereken süre. Boş bırakılırsa indirme istemcisinin varsayılanı kullanılır",
"IndexerSettingsSeasonPackSeedTimeHelpText": "Bir sezon paketi torrentinin durdurulmadan önce başlatılması gereken süre, boş bırakıldığında indirme istemcisinin varsayılanı kullanılır",
"IndexerValidationCloudFlareCaptchaRequired": "Site CloudFlare CAPTCHA tarafından korunmaktadır. Geçerli CAPTCHA belirteci gereklidir.",
"IndexerValidationUnableToConnectServerUnavailable": "İndeksleyiciye bağlanılamıyor, indeksleyicinin sunucusu kullanılamıyor. Daha sonra tekrar deneyin. {exceptionMessage}.",
"ImportListsTraktSettingsUserListUsernameHelpText": "İçe aktarılacak Liste için Kullanıcı Adı (Yetkili Kullanıcı için boş bırakın)",
"ImportListsTraktSettingsYearsSeriesHelpText": "Diziyi yıla veya yıl aralığına göre filtreleyin",
"IndexerHDBitsSettingsMediums": "Ortamlar",
"IndexerSearchNoAvailableIndexersHealthCheckMessage": "Son zamanlardaki indeksleyici hataları nedeniyle tüm arama yeteneğine sahip indeksleyiciler geçici olarak kullanılamıyor",
"IndexerSettingsSeasonPackSeedTime": "Sezon Paketi Gönderme (Seed) Süresi",
"IndexerSettingsSeasonPackSeedTime": "Sezon Paketi Seed Süresi",
"IndexerValidationJackettAllNotSupportedHelpText": "Jackett'in tüm uç noktaları desteklenmiyor, lütfen indeksleyicileri tek tek ekleyin",
"IndexerValidationNoRssFeedQueryAvailable": "RSS besleme sorgusu mevcut değil. Bu, indeksleyici veya indeksleyici kategori ayarlarınızdan kaynaklı bir sorun olabilir.",
"IndexerValidationUnableToConnectResolutionFailure": "İndeksleyiciye bağlanılamıyor bağlantı hatası. İndeksleyicinin sunucusuna ve DNS'ine olan bağlantınızı kontrol edin. {exceptionMessage}.",
"IndexerSettingsFailDownloads": "İndirmeleri Başarısız Say",
"IndexerSettingsFailDownloads": "Başarısız İndirmeler",
"IndexerSettingsFailDownloadsHelpText": "Tamamlanan indirmeler işlenirken {appName} bu seçili dosya türlerini başarısız indirmeler olarak değerlendirecektir.",
"IndexerSettingsMinimumSeeders": "Minimum Seeder",
"IndexerSettingsRssUrl": "RSS URL",
@@ -2152,6 +2152,7 @@
"QualityDefinitionsSizeNotice": "Boyut kısıtlamaları Kalite Profillerine taşındı",
"NotificationsTelegramSettingsLinkPreview": "Bağlantı Önizlemesi",
"NotificationsTelegramSettingsLinkPreviewHelpText": "Telegram bildiriminde hangi bağlantının önizleneceğini belirler. Devre dışı bırakmak için 'Hiçbiri'ni seçin",
"DownloadClientUTorrentProviderMessage": "uTorrent'in kripto para madenciliği, kötü amaçlı yazılım ve reklam içerme geçmişi vardır, bu nedenle farklı bir istemci seçmenizi önemle tavsiye ederiz.",
"NotificationsPushcutSettingsIncludePoster": "Posteri Dahil Et",
"NotificationsPushcutSettingsMetadataLinks": "Meta Veri Bağlantıları",
"NotificationsPushcutSettingsMetadataLinksHelpText": "Bildirim içeriğine meta verilerin bağlantılarını ekleyin",
@@ -2162,27 +2163,5 @@
"EpisodesInSeason": "Sezondaki {episodeCount} bölüm",
"AutoTaggingSpecificationNetwork": "Ağ(lar)",
"NotificationsAppriseSettingsIncludePoster": "Poster'i ekle",
"NotificationsAppriseSettingsIncludePosterHelpText": "Mesaja poster ekle",
"DownloadClientQbittorrentSettingsAddSeriesTags": "Dizilere Etiket Ekle",
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "İndirme istemcisine (qBittorrent 4.1.0+) eklenen yeni torrentlere dizi etiketleri ekle",
"UserRejectedExtensionsTextsExamples": "Örneğin: '.ext, .xyz' veya 'ext,xyz'",
"MonitorEpisodes": "Bölümleri Takip Et",
"MonitorEpisodesModalInfo": "Bu ayar, bir dizide hangi bölüm veya sezonların takip edileceğini kontrol eder. \"Hiçbiri\" seçilirse, dizi takip edilmeyecektir",
"UserRejectedExtensions": "Ek Olarak Reddedilen Dosya Uzantıları",
"UserRejectedExtensionsHelpText": "Başarısız sayılacak dosya uzantılarını virgülle ayırarak girin (Ayrıca, her dizinleyici için \"İndirmeleri Başarısız Say\" seçeneği etkin olmalıdır)",
"EpisodeMonitoring": "Bölüm Takibi",
"IndexerSettingsSeasonPackSeedGoalUseStandardGoals": "Standart Hedefleri Kullanın",
"IndexerSettingsSeasonPackSeedGoalUseSeasonPackGoals": "Sezon Paketi Hedeflerini Kullan",
"IndexerSettingsSeasonPackSeedRatio": "Sezon Paketi Paylaşım(Seed) Oranı",
"RemoveRootFolderWithSeriesMessageText": "Kök klasörü silmek istediğinizden emin misiniz? {path} klasörü silinecek. Diskten dosya ve klasörler silinmeyecek, ve bu kök klasördeki diziler {appName} içinden kaldırılmayacak.",
"IndexerSettingsSeasonPackSeedGoal": "Sezon Paketleri için Paylaşım(seed) Hedefi",
"IndexerSettingsSeasonPackSeedGoalHelpText": "Sezon paketleri için farklı paylaşım hedefleri kullanıp kullanmamayı seçin",
"IndexerSettingsSeasonPackSeedRatioHelpText": "Bir sezon paketi torrentinin durmadan önce ulaşması gereken oran. Boş bırakılırsa indirme istemcisinin varsayılanı kullanılır. Oran en az 1.0 olmalı ve indeksleyicinin kurallarına uymalıdır",
"DownloadClientTriblerSettingsAnonymityLevel": "Anonimlik seviyesi",
"DownloadClientTriblerSettingsAnonymityLevelHelpText": "İçerik indirirken kullanılacak proxy sayısı. Devre dışı bırakmak için 0 olarak ayarlayın. Proxyler indirme/yükleme hızını azaltır. Bkz. {url}",
"DownloadClientTriblerSettingsApiKeyHelpText": "triblerd.conf dosyasından [api].key",
"DownloadClientTriblerSettingsDirectoryHelpText": "İsteğe bağlı olarak indirmelerin kaydedileceği konum. Varsayılan Tribler konumunu kullanmak için boş bırakın",
"DownloadClientTriblerSettingsSafeSeeding": "Güvenli Paylaşım",
"DownloadClientTriblerSettingsSafeSeedingHelpText": "Etkinleştirildiğinde, yalnızca proxyler üzerinden paylaşım(seed) yapar.",
"DownloadClientTriblerProviderMessage": "Tribler entegrasyonu oldukça deneyseldir. {clientName} istemcisinin {clientVersionRange} sürüm aralığında test edilmiştir."
"NotificationsAppriseSettingsIncludePosterHelpText": "Mesaja poster ekle"
}

View File

@@ -2123,6 +2123,7 @@
"Airs": "Ефіри",
"DoneEditingSizes": "Редагування розмірів завершено",
"IndexerSettingsFailDownloads": "Не вдалося завантажити",
"DownloadClientUTorrentProviderMessage": "uTorrent має історію включення криптомайнерів, шкідливого програмного забезпечення та реклами. Ми наполегливо рекомендуємо вибрати інший клієнт.",
"EditSelectedCustomFormats": "Змінити вибрані власні формати",
"EditSizes": "Змінити розміри",
"FailedToFetchSettings": "Не вдалося отримати налаштування",

View File

@@ -156,7 +156,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var downloadClientItem = GetTrackedDownload(downloadId)?.DownloadItem;
var episodes = _episodeService.GetEpisodes(episodeIds);
var finalReleaseGroup = releaseGroup.IsNullOrWhiteSpace()
? Parser.ReleaseGroupParser.ParseReleaseGroup(path)
? Parser.Parser.ParseReleaseGroup(path)
: releaseGroup;
var finalQuality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality;
var finalLanguges =
@@ -218,7 +218,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
SceneSource = SceneSource(series, rootFolder),
ExistingFile = series.Path.IsParentPath(path),
Size = _diskProvider.GetFileSize(path),
ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.ReleaseGroupParser.ParseReleaseGroup(path) : releaseGroup,
ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup,
Languages = languages?.Count <= 1 && (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? LanguageParser.ParseLanguages(path) : languages,
Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality,
IndexerFlags = (IndexerFlags)indexerFlags,
@@ -331,7 +331,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
localEpisode.ReleaseGroup = Parser.ReleaseGroupParser.ParseReleaseGroup(file);
localEpisode.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file);
localEpisode.Quality = QualityParser.ParseQuality(file);
localEpisode.Languages = LanguageParser.ParseLanguages(file);
localEpisode.Size = _diskProvider.GetFileSize(file);

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (!otherVideoFiles && downloadClientInfo != null && !downloadClientInfo.FullSeason)
{
return FileExtensions.RemoveFileExtension(downloadClientInfo.ReleaseTitle);
return Parser.Parser.RemoveFileExtension(downloadClientInfo.ReleaseTitle);
}
var fileName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());

View File

@@ -1,21 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.MediaFiles
{
public static class FileExtensions
{
private static readonly Regex FileExtensionRegex = new(@"\.[a-z0-9]{2,4}$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly HashSet<string> UsenetExtensions = new HashSet<string>()
{
".par2",
".nzb"
};
public static HashSet<string> ArchiveExtensions => new(StringComparer.OrdinalIgnoreCase)
private static List<string> _archiveExtensions = new List<string>
{
".7z",
".bz2",
@@ -30,7 +20,8 @@ namespace NzbDrone.Core.MediaFiles
".tgz",
".zip"
};
public static HashSet<string> DangerousExtensions => new(StringComparer.OrdinalIgnoreCase)
private static List<string> _dangerousExtensions = new List<string>
{
".arj",
".lnk",
@@ -40,7 +31,8 @@ namespace NzbDrone.Core.MediaFiles
".vbs",
".zipx"
};
public static HashSet<string> ExecutableExtensions => new(StringComparer.OrdinalIgnoreCase)
private static List<string> _executableExtensions = new List<string>
{
".bat",
".cmd",
@@ -48,20 +40,8 @@ namespace NzbDrone.Core.MediaFiles
".sh"
};
public static string RemoveFileExtension(string title)
{
title = FileExtensionRegex.Replace(title, m =>
{
var extension = m.Value.ToLower();
if (MediaFileExtensions.Extensions.Contains(extension) || UsenetExtensions.Contains(extension))
{
return string.Empty;
}
return m.Value;
});
return title;
}
public static HashSet<string> ArchiveExtensions => new HashSet<string>(_archiveExtensions, StringComparer.OrdinalIgnoreCase);
public static HashSet<string> DangerousExtensions => new HashSet<string>(_dangerousExtensions, StringComparer.OrdinalIgnoreCase);
public static HashSet<string> ExecutableExtensions => new HashSet<string>(_executableExtensions, StringComparer.OrdinalIgnoreCase);
}
}

View File

@@ -293,7 +293,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
{
sceneName = sceneName.IsNotNullOrWhiteSpace() ? FileExtensions.RemoveFileExtension(sceneName) : string.Empty;
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
foreach (var token in tokens)
{

View File

@@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IConfigService _configService;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
private static readonly DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public UpdateEpisodeFileService(IDiskProvider diskProvider,
IConfigService configService,
@@ -46,48 +47,90 @@ namespace NzbDrone.Core.MediaFiles
private bool ChangeFileDate(EpisodeFile episodeFile, Series series, List<Episode> episodes)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
var airDateUtc = episodes.First().AirDateUtc;
if (!airDateUtc.HasValue)
switch (_configService.FileDate)
{
return false;
case FileDateType.LocalAirDate:
{
var airDate = episodes.First().AirDate;
var airTime = series.AirTime;
if (airDate.IsNullOrWhiteSpace() || airTime.IsNullOrWhiteSpace())
{
return false;
}
return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime);
}
case FileDateType.UtcAirDate:
{
var airDateUtc = episodes.First().AirDateUtc;
if (!airDateUtc.HasValue)
{
return false;
}
return ChangeFileDateToUtcAirDate(episodeFilePath, airDateUtc.Value);
}
}
return _configService.FileDate switch
{
FileDateType.LocalAirDate =>
ChangeFileDateToLocalDate(episodeFilePath, airDateUtc.Value.ToLocalTime()),
// Intentionally pass UTC as local per user preference
FileDateType.UtcAirDate =>
ChangeFileDateToLocalDate(
episodeFilePath,
DateTime.SpecifyKind(airDateUtc.Value, DateTimeKind.Local)),
_ => false,
};
return false;
}
private bool ChangeFileDateToLocalDate(string filePath, DateTime localDate)
private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime)
{
// FileGetLastWrite returns UTC; convert to local to compare
var oldLastWrite = _diskProvider.FileGetLastWrite(filePath).ToLocalTime();
if (OsInfo.IsNotWindows && localDate.ToUniversalTime() < DateTimeExtensions.EpochTime)
if (DateTime.TryParse(fileDate + ' ' + fileTime, out var airDate))
{
_logger.Debug("Setting date of file to 1970-01-01 as actual airdate is before that time and will not be set properly");
localDate = DateTimeExtensions.EpochTime.ToLocalTime();
// avoiding false +ve checks and set date skewing by not using UTC (Windows)
var oldLastWrite = _diskProvider.FileGetLastWrite(filePath);
if (OsInfo.IsNotWindows && airDate < EpochTime)
{
_logger.Debug("Setting date of file to 1970-01-01 as actual airdate is before that time and will not be set properly");
airDate = EpochTime;
}
if (!DateTime.Equals(airDate.WithoutTicks(), oldLastWrite.WithoutTicks()))
{
try
{
_diskProvider.FileSetLastWriteTime(filePath, airDate);
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, airDate);
return true;
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
}
}
}
else
{
_logger.Debug("Could not create valid date to change file [{0}]", filePath);
}
if (!DateTime.Equals(localDate.WithoutTicks(), oldLastWrite.WithoutTicks()))
return false;
}
private bool ChangeFileDateToUtcAirDate(string filePath, DateTime airDateUtc)
{
var oldLastWrite = _diskProvider.FileGetLastWrite(filePath);
if (OsInfo.IsNotWindows && airDateUtc < EpochTime)
{
_logger.Debug("Setting date of file to 1970-01-01 as actual airdate is before that time and will not be set properly");
airDateUtc = EpochTime;
}
if (!DateTime.Equals(airDateUtc.WithoutTicks(), oldLastWrite.WithoutTicks()))
{
try
{
// Preserve prior mtime subseconds per https://github.com/Sonarr/Sonarr/issues/7228
var mtime = localDate.WithTicksFrom(oldLastWrite);
_diskProvider.FileSetLastWriteTime(filePath, mtime);
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, mtime);
_diskProvider.FileSetLastWriteTime(filePath, airDateUtc.AddMilliseconds(oldLastWrite.Millisecond));
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, airDateUtc);
return true;
}

View File

@@ -136,7 +136,6 @@ namespace NzbDrone.Core.Notifications.CustomScript
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", episodeFile.Episodes.Value.Select(e => e.AirDateUtc)));
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", episodeFile.Episodes.Value.Select(e => e.Title)));
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeOverviews", string.Join("|", episodeFile.Episodes.Value.Select(e => e.Overview)));
environmentVariables.Add("Sonarr_EpisodeFile_FinaleTypes", string.Join("|", episodeFile.Episodes.Value.Select(e => e.FinaleType)));
environmentVariables.Add("Sonarr_EpisodeFile_Quality", episodeFile.Quality.Quality.Name);
environmentVariables.Add("Sonarr_EpisodeFile_QualityVersion", episodeFile.Quality.Revision.Version.ToString());
environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroup", episodeFile.ReleaseGroup ?? string.Empty);
@@ -208,7 +207,6 @@ namespace NzbDrone.Core.Notifications.CustomScript
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", episodes.Select(e => e.AirDateUtc)));
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", episodes.Select(e => e.Title)));
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeOverviews", string.Join("|", episodes.Select(e => e.Overview)));
environmentVariables.Add("Sonarr_EpisodeFile_FinaleTypes", string.Join("|", episodes.Select(e => e.FinaleType)));
environmentVariables.Add("Sonarr_EpisodeFile_Qualities", string.Join("|", episodeFiles.Select(f => f.Quality.Quality.Name)));
environmentVariables.Add("Sonarr_EpisodeFile_QualityVersions", string.Join("|", episodeFiles.Select(f => f.Quality.Revision.Version)));
environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroups", string.Join("|", episodeFiles.Select(f => f.ReleaseGroup)));

View File

@@ -20,7 +20,6 @@ namespace NzbDrone.Core.Notifications.Webhook
AirDateUtc = episode.AirDateUtc;
SeriesId = episode.SeriesId;
TvdbId = episode.TvdbId;
FinaleType = episode.FinaleType;
}
public int Id { get; set; }
@@ -32,6 +31,5 @@ namespace NzbDrone.Core.Notifications.Webhook
public DateTime? AirDateUtc { get; set; }
public int SeriesId { get; set; }
public int TvdbId { get; set; }
public string FinaleType { get; set; }
}
}

View File

@@ -15,8 +15,6 @@ namespace NzbDrone.Core.Notifications.Webhook
public int TvMazeId { get; set; }
public int TmdbId { get; set; }
public string ImdbId { get; set; }
public HashSet<int> MalIds { get; set; }
public HashSet<int> AniListIds { get; set; }
public SeriesTypes Type { get; set; }
public int Year { get; set; }
public List<string> Genres { get; set; }
@@ -38,8 +36,6 @@ namespace NzbDrone.Core.Notifications.Webhook
TvMazeId = series.TvMazeId;
TmdbId = series.TmdbId;
ImdbId = series.ImdbId;
MalIds = series.MalIds;
AniListIds = series.AniListIds;
Type = series.SeriesType;
Year = series.Year;
Genres = series.Genres;

View File

@@ -61,8 +61,7 @@ namespace NzbDrone.Core.Parser
new IsoLanguage("uz", "", "uzb", Language.Uzbek),
new IsoLanguage("ms", "", "msa", Language.Malay),
new IsoLanguage("ur", "", "urd", Language.Urdu),
new IsoLanguage("rm", "", "roh", Language.Romansh),
new IsoLanguage("ka", "", "kat", Language.Georgian)
new IsoLanguage("rm", "", "roh", Language.Romansh)
};
private static readonly Dictionary<string, Language> AlternateIsoCodeMappings = new Dictionary<string, Language>

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Parser
new RegexReplace(@".*?[_. ](S\d{2}(?:E\d{2,4})*[_. ].*)", "$1", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex LanguageRegex = new Regex(@"(?<english>\b(?:ing|eng)\b)|(?<italian>\b(?:ita|italian)\b)|(?<german>(?:swiss)?german\b|videomann|ger[. ]dub|\bger\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_|\b)(?:FR|VF|VF2|VFF|VFI|VFQ|TRUEFRENCH|FRENCH|FRE|FRA)(?:\W|_|\b))|(?<russian>\b(?:rus|ru)\b)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<hebrew>\bHebDub\b)|(?<polish>\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?<chinese>\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?<bulgarian>\bbgaudio\b)|(?<spanish>\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?<ukrainian>\b(?:\dx?)?(?:ukr))|(?<thai>\b(?:THAI)\b)|(?<romanian>\b(?:RoDubbed|ROMANIAN)\b)|(?<catalan>[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)|(?<latvian>\b(?:lat|lav|lv)\b)|(?<turkish>\b(?:tur)\b)|(?<urdu>\burdu\b)|(?<romansh>\b(?:romansh|rumantsch|romansch)\b)|(?<georgian>\b(?:geo|ka|kat|georgian)\b)|(?<japanese>\(JA\))|(?<original>\b(?:orig|original)\b)",
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<english>\b(?:ing|eng)\b)|(?<italian>\b(?:ita|italian)\b)|(?<german>(?:swiss)?german\b|videomann|ger[. ]dub|\bger\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_|\b)(?:FR|VF|VF2|VFF|VFI|VFQ|TRUEFRENCH|FRENCH|FRE|FRA)(?:\W|_|\b))|(?<russian>\b(?:rus|ru)\b)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<hebrew>\bHebDub\b)|(?<polish>\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?<chinese>\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?<bulgarian>\bbgaudio\b)|(?<spanish>\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?<ukrainian>\b(?:\dx?)?(?:ukr))|(?<thai>\b(?:THAI)\b)|(?<romanian>\b(?:RoDubbed|ROMANIAN)\b)|(?<catalan>[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)|(?<latvian>\b(?:lat|lav|lv)\b)|(?<turkish>\b(?:tur)\b)|(?<urdu>\burdu\b)|(?<romansh>\b(?:romansh|rumantsch|romansch)\b)|(?<original>\b(?:orig|original)\b)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CaseSensitiveLanguageRegex = new Regex(@"(?:(?i)(?<!SUB[\W|_|^]))(?:(?<lithuanian>\bLT\b)|(?<czech>\bCZ\b)|(?<polish>\bPL\b)|(?<bulgarian>\bBG\b)|(?<slovak>\bSK\b)|(?<german>\bDE\b))(?:(?i)(?![\W|_|^]SUB))",
@@ -496,16 +496,6 @@ namespace NzbDrone.Core.Parser
languages.Add(Language.Romansh);
}
if (match.Groups["georgian"].Success)
{
languages.Add(Language.Georgian);
}
if (match.Groups["japanese"].Success)
{
languages.Add(Language.Japanese);
}
if (match.Groups["original"].Success)
{
languages.Add(Language.Original);

View File

@@ -43,11 +43,6 @@ namespace NzbDrone.Core.Parser.Model
/// <summary>
/// The release is nuked
/// </summary>
Nuked = 128,
/// <summary>
/// The release contains subtitles
/// </summary>
Subtitles = 256
Nuked = 128
}
}

View File

@@ -8,7 +8,6 @@ using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@@ -18,6 +17,45 @@ namespace NzbDrone.Core.Parser
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Parser));
private static readonly RegexReplace[] PreSubstitutionRegex = new[]
{
// Korean series without season number, replace with S01Exxx and remove airdate
new RegexReplace(@"\.E(\d{2,4})\.\d{6}\.(.*-NEXT)$", ".S01E$1.$2", RegexOptions.Compiled),
// Some Chinese anime releases contain both English and Chinese titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?:(?<subgroup>[^\]]+?)(?:[\u4E00-\u9FCC]+)?)\]\[(?<title>[^\]]+?)(?:\s(?<chinesetitle>[\u4E00-\u9FCC][^\]]*?))\]\[(?:(?:[\u4E00-\u9FCC]+?)?(?<episode>\d{1,4})(?:[\u4E00-\u9FCC]+?)?)\]", "[${subgroup}] ${title} - ${episode} - ", RegexOptions.Compiled),
// Chinese LoliHouse/ZERO/Lilith-Raws/Skymoon-Raws/orion origin releases don't use the expected brackets, normalize using brackets
new RegexReplace(@"^\[(?<subgroup>[^\]]*?(?:LoliHouse|ZERO|Lilith-Raws|Skymoon-Raws|orion origin)[^\]]*?)\](?<title>[^\[\]]+?)(?: - (?<episode>[0-9-]+)\s*|\[第?(?<episode>[0-9]+(?:-[0-9]+)?)话?(?:END|完)?\])\[", "[${subgroup}][${title}][${episode}][", RegexOptions.Compiled),
// Most Chinese anime releases contain additional brackets/separators for chinese and non-chinese titles, remove junk first and if it has S0x as season number, convert it to Sx
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s?★[^\[ -]+\s?)?\[?(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\]\[|\s*[_/·]\s*)){0,2}(?<title>[^\[\]]+?)(?:\s(?:S?(?<!\d+)((0)(?<season>\d)|(?<season>[1-9]\d))(?!\d+)))\]?(?:\[\d{4}\])?\[第?(?<episode>[0-9]+(?:-[0-9]+)?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} S${season} - ${episode} ", RegexOptions.Compiled),
// Some Chinese releases don't include a separation between Chinese and English titles within the same bracketed group
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\]\[(?<chinesetitle>(?<![^a-zA-Z0-9])[^a-zA-Z0-9]+)(?<title>[^\]]+?)\](?:\[\d{4}\])?\[第?(?<episode>[0-9]+(?:-[0-9]+)?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Most Chinese anime releases contain additional brackets/separators for chinese and non-chinese titles, remove junk and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s?★[^\[ -]+\s?)?\[?(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\]\[|\s*[_/·]\s*)){0,2}(?<title>[^\]]+?)\]?(?:\[\d{4}\])?\[第?(?<episode>[0-9]{1,4}(?:-[0-9]{1,4})?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles, remove the Chinese title first and if it has S0x as season number, convert it to Sx
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s/\s))(?<title>[^\[\]]+?)(?:\s(?:S?(?<!\d+)((0)(?<season>\d)|(?<season>[1-9]\d))(?!\d+)))(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?)话?(?:END|完)?", "[${subgroup}] ${title} S${season} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both English and Chinese titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<title>[^\]]+?)(?:\s/\s))(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s/\s))(?<title>[^\]]+?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// GM-Team releases with lots of square brackets
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:(?<chinesubgroup>\[(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*\])+)\[(?<title>[^\]]+?)\](?<junk>\[[^\]]+\])*\[(?<episode>[0-9]+(?:-[0-9]+)?)( END| Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles separated by | instead of /, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s\|\s))(?<title>[^\]]+?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Spanish releases with information in brackets
new RegexReplace(@"^(?<title>.+?(?=[ ._-]\()).+?\((?<year>\d{4})\/(?<info>S[^\/]+)", "${title} (${year}) - ${info} ", RegexOptions.Compiled),
};
private static readonly Regex[] ReportTitleRegex = new[]
{
// Anime - Absolute Episode Number + Title + Season+Episode
@@ -86,7 +124,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Anime - [SubGroup] Title with trailing 3-digit number and sub title - Absolute Episode Number
new Regex(@"^\[(?<subgroup>[^\]]+?)\][-_. ]?(?<title>[^]]+?)(?:[-_. ]{3}?(?<absoluteepisode>\d{2}(\.\d{1,2})?(?!-?\d+|-[a-z]+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.mkv)",
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^]]+?)(?:[-_. ]{3}?(?<absoluteepisode>\d{2}(\.\d{1,2})?(?!-?\d+|-[a-z]+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Anime - [SubGroup] Title with trailing number Absolute Episode Number
@@ -502,18 +540,52 @@ namespace NzbDrone.Core.Parser
private static readonly Regex PercentRegex = new Regex(@"(?<=\b\d+)%", RegexOptions.Compiled);
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|540|576|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(?<![a-f0-9])(8|10)[ -]?(b(?![a-z0-9])|bit))\s*?",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
// Valid TLDs http://data.iana.org/TLD/tlds-alpha-by-domain.txt
private static readonly RegexReplace WebsitePrefixRegex = new RegexReplace(@"^(?:(?:\[|\()\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*(?:\]|\))|[ -]{2,})[ -]*",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace WebsitePostfixRegex = new RegexReplace(@"(?:\[\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?:xn--[a-z0-9-]{4,}|[a-z]{2,6})\b(?:\s*\])$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SixDigitAirDateRegex = new Regex(@"(?<=[_.-])(?<airdate>(?<!\d)(?<airyear>[1-9]\d{1})(?<airmonth>[0-1][0-9])(?<airday>[0-3][0-9]))(?=[_.-])",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace CleanReleaseGroupRegex = new RegexReplace(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|Scrambled|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet|AlteZachen|RePACKPOST))+$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace CleanTorrentSuffixRegex = new RegexReplace(@"\[(?:ettv|rartv|rarbg|cttv|publichd)\]$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CleanQualityBracketsRegex = new Regex(@"\[[a-z0-9 ._-]+\]$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-DL|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-GER|-FRA|-FRE|-ITA|\d{1,2}-bit|[ ._]\d{4}-\d{2}|-\d{2})(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex InvalidReleaseGroupRegex = new Regex(@"^([se]\d+|[0-9a-f]{8})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
// Handle Exception Release Groups that don't follow -RlsGrp; Manual List
// name only...be very careful with this last; high chance of false positives
private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"(?<releasegroup>(?:D\-Z0N3|Fight-BB|VARYG|E\.N\.D|KRaLiMaRKo|BluDragon|DarQ|KCRT|BEN[_. ]THE[_. ]MEN)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
// groups whose releases end with RlsGroup) or RlsGroup]
private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<=[._ \[])(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV|Garshasp|Kappa|Natty|RCVR|SAMPA|YOGI|r00t|EDGE2020|RZeroX|TAoE)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)[-_. ]+?[\(\[]?(?<year>\d{4})[\]\)]?",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
@@ -632,7 +704,7 @@ namespace NzbDrone.Core.Parser
if (ReversedTitleRegex.IsMatch(title))
{
var titleWithoutExtension = FileExtensions.RemoveFileExtension(title).ToCharArray();
var titleWithoutExtension = RemoveFileExtension(title).ToCharArray();
Array.Reverse(titleWithoutExtension);
title = string.Concat(new string(titleWithoutExtension), title.AsSpan(titleWithoutExtension.Length));
@@ -642,9 +714,10 @@ namespace NzbDrone.Core.Parser
var simpleTitle = title;
simpleTitle = ParserCommon.WebsitePrefixRegex.Replace(simpleTitle);
simpleTitle = ParserCommon.WebsitePostfixRegex.Replace(simpleTitle);
simpleTitle = ParserCommon.CleanTorrentSuffixRegex.Replace(simpleTitle);
simpleTitle = WebsitePrefixRegex.Replace(simpleTitle);
simpleTitle = WebsitePostfixRegex.Replace(simpleTitle);
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
return simpleTitle;
}
@@ -662,7 +735,7 @@ namespace NzbDrone.Core.Parser
if (ReversedTitleRegex.IsMatch(title))
{
var titleWithoutExtension = FileExtensions.RemoveFileExtension(title).ToCharArray();
var titleWithoutExtension = RemoveFileExtension(title).ToCharArray();
Array.Reverse(titleWithoutExtension);
title = string.Concat(new string(titleWithoutExtension), title.AsSpan(titleWithoutExtension.Length));
@@ -670,11 +743,11 @@ namespace NzbDrone.Core.Parser
Logger.Debug("Reversed name detected. Converted to '{0}'", title);
}
var releaseTitle = FileExtensions.RemoveFileExtension(title);
var releaseTitle = RemoveFileExtension(title);
releaseTitle = releaseTitle.Replace("【", "[").Replace("】", "]");
foreach (var replace in ParserCommon.PreSubstitutionRegex)
foreach (var replace in PreSubstitutionRegex)
{
if (replace.TryReplace(ref releaseTitle))
{
@@ -686,9 +759,10 @@ namespace NzbDrone.Core.Parser
var simpleTitle = SimpleTitleRegex.Replace(releaseTitle);
// TODO: Quick fix stripping [url] - prefixes and postfixes.
simpleTitle = ParserCommon.WebsitePrefixRegex.Replace(simpleTitle);
simpleTitle = ParserCommon.WebsitePostfixRegex.Replace(simpleTitle);
simpleTitle = ParserCommon.CleanTorrentSuffixRegex.Replace(simpleTitle);
simpleTitle = WebsitePrefixRegex.Replace(simpleTitle);
simpleTitle = WebsitePostfixRegex.Replace(simpleTitle);
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
simpleTitle = CleanQualityBracketsRegex.Replace(simpleTitle, m =>
{
@@ -740,7 +814,7 @@ namespace NzbDrone.Core.Parser
result.Quality = QualityParser.ParseQuality(title);
Logger.Debug("Quality parsed: {0}", result.Quality);
result.ReleaseGroup = ReleaseGroupParser.ParseReleaseGroup(releaseTitle);
result.ReleaseGroup = ParseReleaseGroup(releaseTitle);
var subGroup = GetSubGroup(match);
if (!subGroup.IsNullOrWhiteSpace())
@@ -860,9 +934,80 @@ namespace NzbDrone.Core.Parser
return null;
}
public static string ParseReleaseGroup(string title)
{
title = title.Trim();
title = RemoveFileExtension(title);
foreach (var replace in PreSubstitutionRegex)
{
if (replace.TryReplace(ref title))
{
break;
}
}
title = WebsitePrefixRegex.Replace(title);
title = CleanTorrentSuffixRegex.Replace(title);
var animeMatch = AnimeReleaseGroupRegex.Match(title);
if (animeMatch.Success)
{
return animeMatch.Groups["subgroup"].Value;
}
title = CleanReleaseGroupRegex.Replace(title);
var exceptionReleaseGroupRegex = ExceptionReleaseGroupRegex.Matches(title);
if (exceptionReleaseGroupRegex.Count != 0)
{
return exceptionReleaseGroupRegex.OfType<Match>().Last().Groups["releasegroup"].Value;
}
var exceptionExactMatch = ExceptionReleaseGroupRegexExact.Matches(title);
if (exceptionExactMatch.Count != 0)
{
return exceptionExactMatch.OfType<Match>().Last().Groups["releasegroup"].Value;
}
var matches = ReleaseGroupRegex.Matches(title);
if (matches.Count != 0)
{
var group = matches.OfType<Match>().Last().Groups["releasegroup"].Value;
if (int.TryParse(group, out _))
{
return null;
}
if (InvalidReleaseGroupRegex.IsMatch(group))
{
return null;
}
return group;
}
return null;
}
public static string RemoveFileExtension(string title)
{
return FileExtensions.RemoveFileExtension(title);
title = FileExtensionRegex.Replace(title, m =>
{
var extension = m.Value.ToLower();
if (MediaFiles.MediaFileExtensions.Extensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension))
{
return string.Empty;
}
return m.Value;
});
return title;
}
public static bool HasMultipleLanguages(string title)
@@ -1168,7 +1313,7 @@ namespace NzbDrone.Core.Parser
return false;
}
var titleWithoutExtension = FileExtensions.RemoveFileExtension(title);
var titleWithoutExtension = RemoveFileExtension(title);
if (RejectHashedReleasesRegexes.Any(v => v.IsMatch(titleWithoutExtension)))
{

View File

@@ -1,59 +0,0 @@
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Parser;
// These are functions shared between different parser functions
// they are not intended to be used outside of them parsing.
internal static class ParserCommon
{
internal static readonly RegexReplace[] PreSubstitutionRegex = new[]
{
// Korean series without season number, replace with S01Exxx and remove airdate
new RegexReplace(@"\.E(\d{2,4})\.\d{6}\.(.*-NEXT)$", ".S01E$1.$2", RegexOptions.Compiled),
// Some Chinese anime releases contain both English and Chinese titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?:(?<subgroup>[^\]]+?)(?:[\u4E00-\u9FCC]+)?)\]\[(?<title>[^\]]+?)(?:\s(?<chinesetitle>[\u4E00-\u9FCC][^\]]*?))\]\[(?:(?:[\u4E00-\u9FCC]+?)?(?<episode>\d{1,4})(?:[\u4E00-\u9FCC]+?)?)\]", "[${subgroup}] ${title} - ${episode} - ", RegexOptions.Compiled),
// Chinese LoliHouse/ZERO/Lilith-Raws/Skymoon-Raws/orion origin releases don't use the expected brackets, normalize using brackets
new RegexReplace(@"^\[(?<subgroup>[^\]]*?(?:LoliHouse|ZERO|Lilith-Raws|Skymoon-Raws|orion origin)[^\]]*?)\](?<title>[^\[\]]+?)(?: - (?<episode>[0-9-]+)\s*|\[第?(?<episode>[0-9]+(?:-[0-9]+)?)话?(?:END|完)?\])\[", "[${subgroup}][${title}][${episode}][", RegexOptions.Compiled),
// Most Chinese anime releases contain additional brackets/separators for chinese and non-chinese titles, remove junk first and if it has S0x as season number, convert it to Sx
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s?★[^\[ -]+\s?)?\[?(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\]\[|\s*[_/·]\s*)){0,2}(?<title>[^\[\]]+?)(?:\s(?:S?(?<!\d+)((0)(?<season>\d)|(?<season>[1-9]\d))(?!\d+)))\]?(?:\[\d{4}\])?\[第?(?<episode>[0-9]+(?:-[0-9]+)?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} S${season} - ${episode} ", RegexOptions.Compiled),
// Some Chinese releases don't include a separation between Chinese and English titles within the same bracketed group
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\]\[(?<chinesetitle>(?<![^a-zA-Z0-9])[^a-zA-Z0-9]+)(?<title>[^\]]+?)\](?:\[\d{4}\])?\[第?(?<episode>[0-9]+(?:-[0-9]+)?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Most Chinese anime releases contain additional brackets/separators for chinese and non-chinese titles, remove junk and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s?★[^\[ -]+\s?)?\[?(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\]\[|\s*[_/·]\s*)){0,2}(?<title>[^\]]+?)\]?(?:\[\d{4}\])?\[第?(?<episode>[0-9]{1,4}(?:-[0-9]{1,4})?)(?:话|集)?(?: ?END|完| ?Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles, remove the Chinese title first and if it has S0x as season number, convert it to Sx
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s/\s))(?<title>[^\[\]]+?)(?:\s(?:S?(?<!\d+)((0)(?<season>\d)|(?<season>[1-9]\d))(?!\d+)))(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?)话?(?:END|完)?", "[${subgroup}] ${title} S${season} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both English and Chinese titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<title>[^\]]+?)(?:\s/\s))(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s/\s))(?<title>[^\]]+?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// GM-Team releases with lots of square brackets
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:(?<chinesubgroup>\[(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*\])+)\[(?<title>[^\]]+?)\](?<junk>\[[^\]]+\])*\[(?<episode>[0-9]+(?:-[0-9]+)?)( END| Fin)?\]", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Some Chinese anime releases contain both Chinese and English titles separated by | instead of /, remove the Chinese title and replace with normal anime pattern
new RegexReplace(@"^\[(?<subgroup>[^\]]+)\](?:\s)(?:(?<chinesetitle>(?=[^\]]*?[\u4E00-\u9FCC])[^\]]*?)(?:\s\|\s))(?<title>[^\]]+?)(?:[- ]+)(?<episode>[0-9]+(?:-[0-9]+)?(?![a-z]))话?(?:END|完)?", "[${subgroup}] ${title} - ${episode} ", RegexOptions.Compiled),
// Spanish releases with information in brackets
new RegexReplace(@"^(?<title>.+?(?=[ ._-]\()).+?\((?<year>\d{4})\/(?<info>S[^\/]+)", "${title} (${year}) - ${info} ", RegexOptions.Compiled),
};
internal static readonly RegexReplace WebsitePrefixRegex = new(@"^(?:(?:\[|\()\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*(?:\]|\))|[ -]{2,})[ -]*",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
internal static readonly RegexReplace WebsitePostfixRegex = new(@"(?:\[\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?:xn--[a-z0-9-]{4,}|[a-z]{2,6})\b(?:\s*\])$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
internal static readonly RegexReplace CleanTorrentSuffixRegex = new(@"\[(?:ettv|rartv|rarbg|cttv|publichd)\]$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
}

View File

@@ -353,7 +353,7 @@ namespace NzbDrone.Core.Parser
EpisodeNumbers = new int[1] { episode.EpisodeNumber },
FullSeason = false,
Quality = QualityParser.ParseQuality(releaseTitle),
ReleaseGroup = ReleaseGroupParser.ParseReleaseGroup(releaseTitle),
ReleaseGroup = Parser.ParseReleaseGroup(releaseTitle),
Languages = LanguageParser.ParseLanguages(releaseTitle),
Special = true
};

View File

@@ -1,87 +0,0 @@
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Parser;
public static class ReleaseGroupParser
{
private static readonly Regex ReleaseGroupRegex = new(@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-DL|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-GER|-FRA|-FRE|-ITA|\d{1,2}-bit|[ ._]\d{4}-\d{2}|-\d{2})(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex InvalidReleaseGroupRegex = new(@"^([se]\d+|[0-9a-f]{8})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AnimeReleaseGroupRegex = new(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
// Handle Exception Release Groups that don't follow -RlsGrp; Manual List
// name only...be very careful with this last; high chance of false positives
private static readonly Regex ExceptionReleaseGroupRegexExact = new(@"(?<releasegroup>(?:D\-Z0N3|Fight-BB|VARYG|E\.N\.D|KRaLiMaRKo|BluDragon|DarQ|KCRT|BEN[_. ]THE[_. ]MEN|TAoE|QxR)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
// groups whose releases end with RlsGroup) or RlsGroup]
private static readonly Regex ExceptionReleaseGroupRegex = new(@"(?<=[._ \[])(?<releasegroup>(Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV|Garshasp|Kappa|Natty|RCVR|SAMPA|YOGI|r00t|EDGE2020)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace CleanReleaseGroupRegex = new(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|Scrambled|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet|AlteZachen|RePACKPOST))+$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static string ParseReleaseGroup(string title)
{
title = title.Trim();
title = FileExtensions.RemoveFileExtension(title);
foreach (var replace in ParserCommon.PreSubstitutionRegex)
{
if (replace.TryReplace(ref title))
{
break;
}
}
title = ParserCommon.WebsitePrefixRegex.Replace(title);
title = ParserCommon.CleanTorrentSuffixRegex.Replace(title);
var animeMatch = AnimeReleaseGroupRegex.Match(title);
if (animeMatch.Success)
{
return animeMatch.Groups["subgroup"].Value;
}
title = CleanReleaseGroupRegex.Replace(title);
var exceptionReleaseGroupRegex = ExceptionReleaseGroupRegex.Matches(title);
if (exceptionReleaseGroupRegex.Count != 0)
{
return exceptionReleaseGroupRegex.OfType<Match>().Last().Groups["releasegroup"].Value;
}
var exceptionExactMatch = ExceptionReleaseGroupRegexExact.Matches(title);
if (exceptionExactMatch.Count != 0)
{
return exceptionExactMatch.OfType<Match>().Last().Groups["releasegroup"].Value;
}
var matches = ReleaseGroupRegex.Matches(title);
if (matches.Count != 0)
{
var group = matches.OfType<Match>().Last().Groups["releasegroup"].Value;
if (int.TryParse(group, out _))
{
return null;
}
if (InvalidReleaseGroupRegex.IsMatch(group))
{
return null;
}
return group;
}
return null;
}
}

View File

@@ -4,7 +4,6 @@ using System.Linq;
using NzbDrone.Common.Crypto;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
@@ -66,7 +65,7 @@ namespace NzbDrone.Core.Queue
Episode = episode,
Languages = trackedDownload.RemoteEpisode?.Languages ?? new List<Language> { Language.Unknown },
Quality = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Quality ?? new QualityModel(Quality.Unknown),
Title = FileExtensions.RemoveFileExtension(trackedDownload.DownloadItem.Title),
Title = Parser.Parser.RemoveFileExtension(trackedDownload.DownloadItem.Title),
Size = trackedDownload.DownloadItem.TotalSize,
SizeLeft = trackedDownload.DownloadItem.RemainingSize,
TimeLeft = trackedDownload.DownloadItem.RemainingTime,

View File

@@ -35,8 +35,6 @@ public class SeriesResource : RestResource
public int TvRageId { get; set; }
public int TvMazeId { get; set; }
public int TmdbId { get; set; }
public HashSet<int>? MalIds { get; set; }
public HashSet<int>? AniListIds { get; set; }
public DateTime? FirstAired { get; set; }
public DateTime? LastAired { get; set; }
public SeriesTypes SeriesType { get; set; }
@@ -83,8 +81,6 @@ public static class SeriesResourceMapper
TvRageId = model.TvRageId,
TvMazeId = model.TvMazeId,
TmdbId = model.TmdbId,
MalIds = model.MalIds,
AniListIds = model.AniListIds,
FirstAired = model.FirstAired,
LastAired = model.LastAired,
SeriesType = model.SeriesType,
@@ -126,8 +122,6 @@ public static class SeriesResourceMapper
TvRageId = resource.TvRageId,
TvMazeId = resource.TvMazeId,
TmdbId = resource.TmdbId,
MalIds = resource.MalIds,
AniListIds = resource.AniListIds,
FirstAired = resource.FirstAired,
SeriesType = resource.SeriesType,
CleanTitle = resource.CleanTitle,

View File

@@ -1062,24 +1062,6 @@
"type": "integer",
"format": "int32"
},
"malIds": {
"uniqueItems": true,
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"aniListIds": {
"uniqueItems": true,
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"firstAired": {
"type": "string",
"format": "date-time",

View File

@@ -77,7 +77,7 @@ namespace Sonarr.Http.Authentication
private void LogSuccess(HttpRequest context, string username)
{
_authLogger.Debug("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
_authLogger.Info("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
}
private void LogLogout(HttpRequest context, string username)