Compare commits

..

No commits in common. "develop" and "v4.0.14.2938" have entirely different histories.

12 changed files with 211 additions and 262 deletions

View file

@ -22,7 +22,7 @@ env:
FRAMEWORK: net6.0 FRAMEWORK: net6.0
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }} RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SONARR_MAJOR_VERSION: 4 SONARR_MAJOR_VERSION: 4
VERSION: 4.0.16 VERSION: 4.0.14
jobs: jobs:
backend: backend:

View file

@ -19,7 +19,6 @@
.modal { .modal {
position: relative; position: relative;
display: flex; display: flex;
max-width: 90%;
max-height: 90%; max-height: 90%;
border-radius: 6px; border-radius: 6px;
opacity: 1; opacity: 1;

View file

@ -390,12 +390,5 @@ public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, by
return this; return this;
} }
public virtual HttpRequestBuilder AllowRedirect(bool allowAutoRedirect = true)
{
AllowAutoRedirect = allowAutoRedirect;
return this;
}
} }
} }

View file

@ -1,9 +0,0 @@
using System.IO;
using System.Text;
namespace NzbDrone.Common;
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}

View file

@ -213,16 +213,5 @@ public void should_use_runtime_from_episode_over_series()
Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample); Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample);
} }
[Test]
public void should_default_to_45_minutes_if_runtime_is_zero()
{
GivenRuntime(120);
_localEpisode.Series.Runtime = 0;
_localEpisode.Episodes.First().Runtime = 0;
Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample);
}
} }
} }

View file

@ -425,8 +425,8 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
} }
catch (HttpException ex) catch (HttpException ex)
{ {
_logger.Debug(ex, "qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
{ {
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
} }
@ -438,7 +438,7 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex); throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
} }
if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.") if (response.Content != "Ok.")
{ {
// returns "Fails." on bad login // returns "Fails." on bad login
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");

View file

@ -8,7 +8,6 @@
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using NLog; using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
@ -150,116 +149,110 @@ public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataR
if (Settings.SeriesMetadata) if (Settings.SeriesMetadata)
{ {
_logger.Debug("Generating Series Metadata for: {0}", series.Title); _logger.Debug("Generating Series Metadata for: {0}", series.Title);
var tvShow = new XElement("tvshow");
tvShow.Add(new XElement("title", series.Title));
if (series.Ratings != null && series.Ratings.Votes > 0)
{
tvShow.Add(new XElement("rating", series.Ratings.Value));
}
tvShow.Add(new XElement("plot", series.Overview));
tvShow.Add(new XElement("mpaa", series.Certification));
tvShow.Add(new XElement("id", series.TvdbId));
var uniqueId = new XElement("uniqueid", series.TvdbId);
uniqueId.SetAttributeValue("type", "tvdb");
uniqueId.SetAttributeValue("default", true);
tvShow.Add(uniqueId);
if (series.ImdbId.IsNotNullOrWhiteSpace())
{
var imdbId = new XElement("uniqueid", series.ImdbId);
imdbId.SetAttributeValue("type", "imdb");
tvShow.Add(imdbId);
}
if (series.TmdbId > 0)
{
var tmdbId = new XElement("uniqueid", series.TmdbId);
tmdbId.SetAttributeValue("type", "tmdb");
tvShow.Add(tmdbId);
}
if (series.TvMazeId > 0)
{
var tvMazeId = new XElement("uniqueid", series.TvMazeId);
tvMazeId.SetAttributeValue("type", "tvmaze");
tvShow.Add(tvMazeId);
}
foreach (var genre in series.Genres)
{
tvShow.Add(new XElement("genre", genre));
}
if (series.Tags.Any())
{
var tags = _tagRepo.GetTags(series.Tags);
foreach (var tag in tags)
{
tvShow.Add(new XElement("tag", tag.Label));
}
}
tvShow.Add(new XElement("status", series.Status));
if (series.FirstAired.HasValue)
{
tvShow.Add(new XElement("premiered", series.FirstAired.Value.ToString("yyyy-MM-dd")));
}
// Add support for Jellyfin's "enddate" tag
if (series.Status == SeriesStatusType.Ended && series.LastAired.HasValue)
{
tvShow.Add(new XElement("enddate", series.LastAired.Value.ToString("yyyy-MM-dd")));
}
tvShow.Add(new XElement("studio", series.Network));
foreach (var actor in series.Actors)
{
var xmlActor = new XElement("actor",
new XElement("name", actor.Name),
new XElement("role", actor.Character));
if (actor.Images.Any())
{
xmlActor.Add(new XElement("thumb", actor.Images.First().RemoteUrl));
}
tvShow.Add(xmlActor);
}
if (Settings.SeriesMetadataEpisodeGuide)
{
var episodeGuide = new KodiEpisodeGuide(series);
var serializerSettings = STJson.GetSerializerSettings();
serializerSettings.WriteIndented = false;
tvShow.Add(new XElement("episodeguide", JsonSerializer.Serialize(episodeGuide, serializerSettings)));
}
var doc = new XDocument(tvShow)
{
Declaration = new XDeclaration("1.0", "UTF-8", "yes"),
};
var sb = new StringBuilder(); var sb = new StringBuilder();
using var sw = new Utf8StringWriter(); var xws = new XmlWriterSettings();
using var xw = XmlWriter.Create(sw, new XmlWriterSettings xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{ {
Encoding = Encoding.UTF8, var tvShow = new XElement("tvshow");
Indent = true
});
doc.Save(xw); tvShow.Add(new XElement("title", series.Title));
xw.Flush();
xmlResult += sw.ToString(); if (series.Ratings != null && series.Ratings.Votes > 0)
{
tvShow.Add(new XElement("rating", series.Ratings.Value));
}
tvShow.Add(new XElement("plot", series.Overview));
tvShow.Add(new XElement("mpaa", series.Certification));
tvShow.Add(new XElement("id", series.TvdbId));
var uniqueId = new XElement("uniqueid", series.TvdbId);
uniqueId.SetAttributeValue("type", "tvdb");
uniqueId.SetAttributeValue("default", true);
tvShow.Add(uniqueId);
if (series.ImdbId.IsNotNullOrWhiteSpace())
{
var imdbId = new XElement("uniqueid", series.ImdbId);
imdbId.SetAttributeValue("type", "imdb");
tvShow.Add(imdbId);
}
if (series.TmdbId > 0)
{
var tmdbId = new XElement("uniqueid", series.TmdbId);
tmdbId.SetAttributeValue("type", "tmdb");
tvShow.Add(tmdbId);
}
if (series.TvMazeId > 0)
{
var tvMazeId = new XElement("uniqueid", series.TvMazeId);
tvMazeId.SetAttributeValue("type", "tvmaze");
tvShow.Add(tvMazeId);
}
foreach (var genre in series.Genres)
{
tvShow.Add(new XElement("genre", genre));
}
if (series.Tags.Any())
{
var tags = _tagRepo.GetTags(series.Tags);
foreach (var tag in tags)
{
tvShow.Add(new XElement("tag", tag.Label));
}
}
tvShow.Add(new XElement("status", series.Status));
if (series.FirstAired.HasValue)
{
tvShow.Add(new XElement("premiered", series.FirstAired.Value.ToString("yyyy-MM-dd")));
}
// Add support for Jellyfin's "enddate" tag
if (series.Status == SeriesStatusType.Ended && series.LastAired.HasValue)
{
tvShow.Add(new XElement("enddate", series.LastAired.Value.ToString("yyyy-MM-dd")));
}
tvShow.Add(new XElement("studio", series.Network));
foreach (var actor in series.Actors)
{
var xmlActor = new XElement("actor",
new XElement("name", actor.Name),
new XElement("role", actor.Character));
if (actor.Images.Any())
{
xmlActor.Add(new XElement("thumb", actor.Images.First().RemoteUrl));
}
tvShow.Add(xmlActor);
}
if (Settings.SeriesMetadataEpisodeGuide)
{
var episodeGuide = new KodiEpisodeGuide(series);
var serializerSettings = STJson.GetSerializerSettings();
serializerSettings.WriteIndented = false;
tvShow.Add(new XElement("episodeguide", JsonSerializer.Serialize(episodeGuide, serializerSettings)));
}
var doc = new XDocument(tvShow);
doc.Save(xw);
xmlResult += doc.ToString();
}
} }
if (Settings.SeriesMetadataUrl) if (Settings.SeriesMetadataUrl)
@ -287,119 +280,113 @@ public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile ep
var watched = GetExistingWatchedStatus(series, episodeFile.RelativePath); var watched = GetExistingWatchedStatus(series, episodeFile.RelativePath);
var xmlResult = string.Empty; var xmlResult = string.Empty;
var xws = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true
};
foreach (var episode in episodeFile.Episodes.Value) foreach (var episode in episodeFile.Episodes.Value)
{ {
var doc = new XDocument var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{ {
Declaration = new XDeclaration("1.0", "UTF-8", "yes") var doc = new XDocument();
}; var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); var details = new XElement("episodedetails");
details.Add(new XElement("title", episode.Title));
details.Add(new XElement("season", episode.SeasonNumber));
details.Add(new XElement("episode", episode.EpisodeNumber));
details.Add(new XElement("aired", episode.AirDate));
details.Add(new XElement("plot", episode.Overview));
var details = new XElement("episodedetails"); if (episode.SeasonNumber == 0 && episode.AiredAfterSeasonNumber.HasValue)
details.Add(new XElement("title", episode.Title));
details.Add(new XElement("season", episode.SeasonNumber));
details.Add(new XElement("episode", episode.EpisodeNumber));
details.Add(new XElement("aired", episode.AirDate));
details.Add(new XElement("plot", episode.Overview));
if (episode.SeasonNumber == 0 && episode.AiredAfterSeasonNumber.HasValue)
{
details.Add(new XElement("displayafterseason", episode.AiredAfterSeasonNumber));
}
else if (episode.SeasonNumber == 0 && episode.AiredBeforeSeasonNumber.HasValue)
{
details.Add(new XElement("displayseason", episode.AiredBeforeSeasonNumber));
details.Add(new XElement("displayepisode", episode.AiredBeforeEpisodeNumber ?? -1));
}
var tvdbId = new XElement("uniqueid", episode.TvdbId);
tvdbId.SetAttributeValue("type", "tvdb");
tvdbId.SetAttributeValue("default", true);
details.Add(tvdbId);
var sonarrId = new XElement("uniqueid", episode.Id);
sonarrId.SetAttributeValue("type", "sonarr");
details.Add(sonarrId);
if (image == null)
{
details.Add(new XElement("thumb"));
}
else if (Settings.EpisodeImageThumb)
{
details.Add(new XElement("thumb", image.RemoteUrl));
}
details.Add(new XElement("watched", watched));
if (episode.Ratings != null && episode.Ratings.Votes > 0)
{
details.Add(new XElement("rating", episode.Ratings.Value));
}
if (episodeFile.MediaInfo != null)
{
var sceneName = episodeFile.GetSceneOrFileName();
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float)episodeFile.MediaInfo.Width / (float)episodeFile.MediaInfo.Height));
video.Add(new XElement("bitrate", episodeFile.MediaInfo.VideoBitrate));
video.Add(new XElement("codec", MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName)));
video.Add(new XElement("framerate", episodeFile.MediaInfo.VideoFps));
video.Add(new XElement("height", episodeFile.MediaInfo.Height));
video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType));
video.Add(new XElement("width", episodeFile.MediaInfo.Width));
video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", Math.Round(episodeFile.MediaInfo.RunTime.TotalSeconds)));
streamDetails.Add(video);
var audio = new XElement("audio");
var audioChannelCount = episodeFile.MediaInfo.AudioChannels;
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", audioChannelCount));
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName)));
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Count > 0)
{ {
foreach (var s in episodeFile.MediaInfo.Subtitles) details.Add(new XElement("displayafterseason", episode.AiredAfterSeasonNumber));
{ }
var subtitle = new XElement("subtitle"); else if (episode.SeasonNumber == 0 && episode.AiredBeforeSeasonNumber.HasValue)
subtitle.Add(new XElement("language", s)); {
streamDetails.Add(subtitle); details.Add(new XElement("displayseason", episode.AiredBeforeSeasonNumber));
} details.Add(new XElement("displayepisode", episode.AiredBeforeEpisodeNumber ?? -1));
} }
fileInfo.Add(streamDetails); var tvdbId = new XElement("uniqueid", episode.TvdbId);
details.Add(fileInfo); tvdbId.SetAttributeValue("type", "tvdb");
tvdbId.SetAttributeValue("default", true);
details.Add(tvdbId);
var sonarrId = new XElement("uniqueid", episode.Id);
sonarrId.SetAttributeValue("type", "sonarr");
details.Add(sonarrId);
if (image == null)
{
details.Add(new XElement("thumb"));
}
else if (Settings.EpisodeImageThumb)
{
details.Add(new XElement("thumb", image.RemoteUrl));
}
details.Add(new XElement("watched", watched));
if (episode.Ratings != null && episode.Ratings.Votes > 0)
{
details.Add(new XElement("rating", episode.Ratings.Value));
}
if (episodeFile.MediaInfo != null)
{
var sceneName = episodeFile.GetSceneOrFileName();
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float)episodeFile.MediaInfo.Width / (float)episodeFile.MediaInfo.Height));
video.Add(new XElement("bitrate", episodeFile.MediaInfo.VideoBitrate));
video.Add(new XElement("codec", MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName)));
video.Add(new XElement("framerate", episodeFile.MediaInfo.VideoFps));
video.Add(new XElement("height", episodeFile.MediaInfo.Height));
video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType));
video.Add(new XElement("width", episodeFile.MediaInfo.Width));
video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", Math.Round(episodeFile.MediaInfo.RunTime.TotalSeconds)));
streamDetails.Add(video);
var audio = new XElement("audio");
var audioChannelCount = episodeFile.MediaInfo.AudioChannels;
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", audioChannelCount));
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName)));
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Count > 0)
{
foreach (var s in episodeFile.MediaInfo.Subtitles)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", s));
streamDetails.Add(subtitle);
}
}
fileInfo.Add(streamDetails);
details.Add(fileInfo);
}
// Todo: get guest stars, writer and director
// details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
// details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
doc.Add(details);
doc.Save(xw);
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
} }
// Todo: get guest stars, writer and director
// details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
// details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
using var sw = new Utf8StringWriter();
using var xw = XmlWriter.Create(sw, xws);
doc.Add(details);
doc.Save(xw);
xw.Flush();
xmlResult += sw.ToString();
xmlResult += Environment.NewLine;
} }
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray())); return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));

View file

@ -72,7 +72,7 @@ private List<TResource> Execute<TResource>(CustomSettings settings)
} }
var baseUrl = settings.BaseUrl.TrimEnd('/'); 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 response = _httpClient.Get(request);
var results = JsonConvert.DeserializeObject<List<TResource>>(response.Content); var results = JsonConvert.DeserializeObject<List<TResource>>(response.Content);

View file

@ -305,7 +305,7 @@ private void CleanLibrary()
{ {
var seriesExists = allListItems.Where(l => var seriesExists = allListItems.Where(l =>
l.TvdbId == series.TvdbId || l.TvdbId == series.TvdbId ||
(l.ImdbId.IsNotNullOrWhiteSpace() && series.ImdbId.IsNotNullOrWhiteSpace() && l.ImdbId == series.ImdbId) || l.ImdbId == series.ImdbId ||
l.TmdbId == series.TmdbId || l.TmdbId == series.TmdbId ||
series.MalIds.Contains(l.MalId) || series.MalIds.Contains(l.MalId) ||
series.AniListIds.Contains(l.AniListId)).ToList(); series.AniListIds.Contains(l.AniListId)).ToList();

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using System.Text.Json.Serialization;
namespace NzbDrone.Core.ImportLists.Trakt namespace NzbDrone.Core.ImportLists.Trakt
{ {
@ -17,7 +17,7 @@ public class TraktSeriesResource
public string Title { get; set; } public string Title { get; set; }
public int? Year { get; set; } public int? Year { get; set; }
public TraktSeriesIdsResource Ids { get; set; } public TraktSeriesIdsResource Ids { get; set; }
[JsonProperty("aired_episodes")] [JsonPropertyName("aired_episodes")]
public int AiredEpisodes { get; set; } public int AiredEpisodes { get; set; }
} }
@ -44,11 +44,11 @@ public class TraktWatchedResponse : TraktResponse
public class RefreshRequestResponse public class RefreshRequestResponse
{ {
[JsonProperty("access_token")] [JsonPropertyName("access_token")]
public string AccessToken { get; set; } public string AccessToken { get; set; }
[JsonProperty("expires_in")] [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; } public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")] [JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; } public string RefreshToken { get; set; }
} }

View file

@ -66,12 +66,6 @@ public DetectSampleResult IsSample(LocalEpisode localEpisode)
return DetectSampleResult.Indeterminate; return DetectSampleResult.Indeterminate;
} }
if (runtime == 0)
{
_logger.Debug("Series runtime is 0, defaulting runtime to 45 minutes");
runtime = 45;
}
return IsSample(localEpisode.Path, localEpisode.MediaInfo.RunTime, runtime); return IsSample(localEpisode.Path, localEpisode.MediaInfo.RunTime, runtime);
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using DryIoc; using DryIoc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -60,11 +59,8 @@ public void ConfigureServices(IServiceCollection services)
services.Configure<ForwardedHeadersOptions>(options => services.Configure<ForwardedHeadersOptions>(options =>
{ {
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); options.KnownNetworks.Clear();
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); options.KnownProxies.Clear();
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));
}); });
services.AddRouting(options => options.LowercaseUrls = true); services.AddRouting(options => options.LowercaseUrls = true);