aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/media_playback.md32
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Dockerfile2
-rw-r--r--Dockerfile.arm10
-rw-r--r--Dockerfile.arm6411
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs5
-rw-r--r--Emby.Naming/Audio/AlbumParser.cs26
-rw-r--r--Emby.Naming/Audio/MultiPartResult.cs2
-rw-r--r--Emby.Naming/AudioBook/AudioBookFileInfo.cs29
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParser.cs13
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs2
-rw-r--r--Emby.Naming/AudioBook/AudioBookInfo.cs21
-rw-r--r--Emby.Naming/AudioBook/AudioBookListResolver.cs2
-rw-r--r--Emby.Naming/AudioBook/AudioBookResolver.cs11
-rw-r--r--Emby.Naming/Common/EpisodeExpression.cs17
-rw-r--r--Emby.Naming/Common/MediaType.cs2
-rw-r--r--Emby.Naming/Common/NamingOptions.cs75
-rw-r--r--Emby.Naming/Emby.Naming.csproj14
-rw-r--r--Emby.Naming/Extensions/StringExtensions.cs1
-rw-r--r--Emby.Naming/StringExtensions.cs30
-rw-r--r--Emby.Naming/Subtitles/SubtitleInfo.cs3
-rw-r--r--Emby.Naming/TV/EpisodeInfo.cs11
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs51
-rw-r--r--Emby.Naming/TV/EpisodePathParserResult.cs7
-rw-r--r--Emby.Naming/TV/EpisodeResolver.cs12
-rw-r--r--Emby.Naming/TV/SeasonPathParser.cs42
-rw-r--r--Emby.Naming/TV/SeasonPathParserResult.cs2
-rw-r--r--Emby.Naming/Video/CleanDateTimeParser.cs20
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs2
-rw-r--r--Emby.Naming/Video/FileStack.cs4
-rw-r--r--Emby.Naming/Video/Format3DParser.cs10
-rw-r--r--Emby.Naming/Video/Format3DResult.cs12
-rw-r--r--Emby.Naming/Video/StackResolver.cs19
-rw-r--r--Emby.Naming/Video/StubResolver.cs32
-rw-r--r--Emby.Naming/Video/StubResult.cs1
-rw-r--r--Emby.Naming/Video/StubTypeRule.cs1
-rw-r--r--Emby.Naming/Video/VideoFileInfo.cs12
-rw-r--r--Emby.Naming/Video/VideoInfo.cs4
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs40
-rw-r--r--Emby.Naming/Video/VideoResolver.cs12
-rw-r--r--Emby.Photos/PhotoProvider.cs78
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs33
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs16
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs9
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs28
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj9
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs18
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs448
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs14
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs10
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs34
-rw-r--r--Emby.Server.Implementations/Library/InvalidAuthProvider.cs47
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs52
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs38
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs6
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json96
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json156
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json16
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json5
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs4
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs6
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs155
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs6
-rw-r--r--Emby.Server.Implementations/SocketSharp/RequestMono.cs16
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs38
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj18
-rw-r--r--Jellyfin.Server/Program.cs12
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs15
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs44
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs189
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs19
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs40
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs7
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs94
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs2
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs22
-rw-r--r--MediaBrowser.Common/Net/IHttpClient.cs11
-rw-r--r--MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs3
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs91
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs5
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs12
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs58
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs7
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs4
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketListener.cs2
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs3
-rw-r--r--MediaBrowser.Model/Configuration/FanartOptions.cs11
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs7
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs20
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj2
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs334
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs201
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs335
-rw-r--r--MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs205
-rw-r--r--MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs378
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs2
m---------MediaBrowser.WebDashboard/jellyfin-web0
-rw-r--r--RSSDP/HttpParserBase.cs6
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs21
-rw-r--r--SharedVersion.cs4
-rw-r--r--build.yaml2
-rwxr-xr-xdeployment/centos-package-x64/docker-build.sh1
-rwxr-xr-xdeployment/centos-package-x64/package.sh3
-rwxr-xr-xdeployment/debian-package-arm64/docker-build.sh1
-rwxr-xr-xdeployment/debian-package-arm64/package.sh5
-rwxr-xr-xdeployment/debian-package-armhf/docker-build.sh1
-rwxr-xr-xdeployment/debian-package-armhf/package.sh5
-rwxr-xr-xdeployment/debian-package-x64/docker-build.sh1
-rwxr-xr-xdeployment/debian-package-x64/package.sh5
-rw-r--r--deployment/debian-package-x64/pkg-src/changelog18
-rwxr-xr-xdeployment/fedora-package-x64/docker-build.sh1
-rwxr-xr-xdeployment/fedora-package-x64/package.sh5
-rw-r--r--deployment/fedora-package-x64/pkg-src/jellyfin.spec8
-rwxr-xr-xdeployment/ubuntu-package-arm64/docker-build.sh1
-rwxr-xr-xdeployment/ubuntu-package-arm64/package.sh5
-rwxr-xr-xdeployment/ubuntu-package-armhf/docker-build.sh1
-rwxr-xr-xdeployment/ubuntu-package-armhf/package.sh5
-rwxr-xr-xdeployment/ubuntu-package-x64/docker-build.sh1
-rwxr-xr-xdeployment/ubuntu-package-x64/package.sh5
-rw-r--r--jellyfin.ruleset9
128 files changed, 1358 insertions, 2884 deletions
diff --git a/.github/ISSUE_TEMPLATE/media_playback.md b/.github/ISSUE_TEMPLATE/media_playback.md
new file mode 100644
index 000000000..93af33fbf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/media_playback.md
@@ -0,0 +1,32 @@
+---
+name: Media playback issue
+about: Create a media playback issue report
+title: ''
+labels: mediaplayback
+assignees: ''
+
+---
+
+**Media Info of the file**
+<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
+
+**Logs**
+<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
+
+**Stats for Nerds Screenshots**
+<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
+
+**Server System (please complete the following information):**
+ - OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
+ - Jellyfin Version: [e.g. 10.0.1]
+ - Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
+ - Reverse proxy: [e.g. no, nginx, apache, etc.]
+ - Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
+
+**Client System (please complete the following information):**
+ - Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
+ - OS: [e.g. iOS, Android, Windows, macOS]
+ - Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
+ - Browser (if Web client): [e.g. Firefox, Chrome, Safari]
+ - Client and Browser Version: [e.g. 10.3.4 and 68.0]
+
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 81857e57c..c3fcea1e2 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -24,6 +24,7 @@
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
+ - [pjeanjean](https://github.com/pjeanjean)
# Emby Contributors
diff --git a/Dockerfile b/Dockerfile
index f414995f7..c971f1cc1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,7 +21,7 @@ RUN apt-get update \
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
-ARG JELLYFIN_WEB_VERSION=10.3.2
+ARG JELLYFIN_WEB_VERSION=10.3.5
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 66f731354..4847c726b 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -3,11 +3,6 @@
ARG DOTNET_VERSION=3.0
-FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM alpine as qemu_extract
-COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
-RUN tar -xzvf qemu-arm-static.tar.gz
-
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
@@ -21,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
+FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
-COPY --from=qemu_extract qemu-arm-static /usr/bin
+COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
@@ -30,7 +26,7 @@ RUN apt-get update \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
-ARG JELLYFIN_WEB_VERSION=10.3.2
+ARG JELLYFIN_WEB_VERSION=10.3.5
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 690d4b6e7..a26cfc7b3 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -3,12 +3,6 @@
ARG DOTNET_VERSION=3.0
-FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM alpine as qemu_extract
-COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
-RUN tar -xzvf qemu-aarch64-static.tar.gz
-
-
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
@@ -22,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
+FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
-COPY --from=qemu_extract qemu-aarch64-static /usr/bin
+COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
@@ -31,7 +26,7 @@ RUN apt-get update \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
-ARG JELLYFIN_WEB_VERSION=10.3.2
+ARG JELLYFIN_WEB_VERSION=10.3.5
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 1268f3d5c..992a50e67 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -920,8 +920,6 @@ namespace Emby.Dlna.Didl
}
}
- AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
-
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
@@ -930,6 +928,9 @@ namespace Emby.Dlna.Didl
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
}
+
+ AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
+
}
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs
index 7d029a9f4..e8d765552 100644
--- a/Emby.Naming/Audio/AlbumParser.cs
+++ b/Emby.Naming/Audio/AlbumParser.cs
@@ -33,27 +33,29 @@ namespace Emby.Naming.Audio
// Normalize
// Remove whitespace
- filename = filename.Replace("-", " ");
- filename = filename.Replace(".", " ");
- filename = filename.Replace("(", " ");
- filename = filename.Replace(")", " ");
+ filename = filename.Replace('-', ' ');
+ filename = filename.Replace('.', ' ');
+ filename = filename.Replace('(', ' ');
+ filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
filename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{
- if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0)
+ if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
{
- var tmp = filename.Substring(prefix.Length);
+ continue;
+ }
+
+ var tmp = filename.Substring(prefix.Length);
- tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
+ tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
- if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
- {
- result.IsMultiPart = true;
- break;
- }
+ if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
+ {
+ result.IsMultiPart = true;
+ break;
}
}
diff --git a/Emby.Naming/Audio/MultiPartResult.cs b/Emby.Naming/Audio/MultiPartResult.cs
index b1fa6e563..00e4a9eb2 100644
--- a/Emby.Naming/Audio/MultiPartResult.cs
+++ b/Emby.Naming/Audio/MultiPartResult.cs
@@ -7,11 +7,13 @@ namespace Emby.Naming.Audio
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the part.
/// </summary>
/// <value>The part.</value>
public string Part { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is multi part.
/// </summary>
diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
index de66a5402..326ea05ef 100644
--- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
@@ -12,35 +12,56 @@ namespace Emby.Naming.AudioBook
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
+
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
+
/// <summary>
/// Gets or sets the part number.
/// </summary>
/// <value>The part number.</value>
public int? PartNumber { get; set; }
+
/// <summary>
/// Gets or sets the chapter number.
/// </summary>
/// <value>The chapter number.</value>
public int? ChapterNumber { get; set; }
+
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
+ /// <inheritdoc/>
public int CompareTo(AudioBookFileInfo other)
{
- if (ReferenceEquals(this, other)) return 0;
- if (ReferenceEquals(null, other)) return 1;
+ if (ReferenceEquals(this, other))
+ {
+ return 0;
+ }
+
+ if (ReferenceEquals(null, other))
+ {
+ return 1;
+ }
+
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
- if (chapterNumberComparison != 0) return chapterNumberComparison;
+ if (chapterNumberComparison != 0)
+ {
+ return chapterNumberComparison;
+ }
+
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
- if (partNumberComparison != 0) return partNumberComparison;
+ if (partNumberComparison != 0)
+ {
+ return partNumberComparison;
+ }
+
return string.Compare(Path, other.Path, StringComparison.Ordinal);
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 590979794..ea7f06c8c 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
@@ -1,3 +1,4 @@
+using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
@@ -14,14 +15,13 @@ namespace Emby.Naming.AudioBook
_options = options;
}
- public AudioBookFilePathParserResult Parse(string path, bool IsDirectory)
+ public AudioBookFilePathParserResult Parse(string path)
{
- var result = Parse(path);
- return !result.Success ? new AudioBookFilePathParserResult() : result;
- }
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
- private AudioBookFilePathParserResult Parse(string path)
- {
var result = new AudioBookFilePathParserResult();
var fileName = Path.GetFileNameWithoutExtension(path);
foreach (var expression in _options.AudioBookPartsExpressions)
@@ -40,6 +40,7 @@ namespace Emby.Naming.AudioBook
}
}
}
+
if (!result.PartNumber.HasValue)
{
var value = match.Groups["part"];
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
index 3a8e3c31f..f845e8243 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
@@ -3,7 +3,9 @@ namespace Emby.Naming.AudioBook
public class AudioBookFilePathParserResult
{
public int? PartNumber { get; set; }
+
public int? ChapterNumber { get; set; }
+
public bool Success { get; set; }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index f6e1d5be4..600d3f05d 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -7,33 +7,40 @@ namespace Emby.Naming.AudioBook
/// </summary>
public class AudioBookInfo
{
+ public AudioBookInfo()
+ {
+ Files = new List<AudioBookFileInfo>();
+ Extras = new List<AudioBookFileInfo>();
+ AlternateVersions = new List<AudioBookFileInfo>();
+ }
+
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the year.
+ /// </summary>
public int? Year { get; set; }
+
/// <summary>
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<AudioBookFileInfo> Files { get; set; }
+
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<AudioBookFileInfo> Extras { get; set; }
+
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; }
-
- public AudioBookInfo()
- {
- Files = new List<AudioBookFileInfo>();
- Extras = new List<AudioBookFileInfo>();
- AlternateVersions = new List<AudioBookFileInfo>();
- }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index 4e3ad7cac..414ef1183 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
_options = options;
}
- public IEnumerable<AudioBookInfo> Resolve(List<FileSystemMetadata> files)
+ public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{
var audioBookResolver = new AudioBookResolver(_options);
diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs
index 67ab62e80..4a2b516d0 100644
--- a/Emby.Naming/AudioBook/AudioBookResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookResolver.cs
@@ -24,19 +24,21 @@ namespace Emby.Naming.AudioBook
return Resolve(path, true);
}
- public AudioBookFileInfo Resolve(string path, bool IsDirectory = false)
+ public AudioBookFileInfo Resolve(string path, bool isDirectory = false)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
- if (IsDirectory) // TODO
+ // TODO
+ if (isDirectory)
{
return null;
}
var extension = Path.GetExtension(path);
+
// Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
@@ -45,8 +47,7 @@ namespace Emby.Naming.AudioBook
var container = extension.TrimStart('.');
- var parsingResult = new AudioBookFilePathParser(_options)
- .Parse(path, IsDirectory);
+ var parsingResult = new AudioBookFilePathParser(_options).Parse(path);
return new AudioBookFileInfo
{
@@ -54,7 +55,7 @@ namespace Emby.Naming.AudioBook
Container = container,
PartNumber = parsingResult.PartNumber,
ChapterNumber = parsingResult.ChapterNumber,
- IsDirectory = IsDirectory
+ IsDirectory = isDirectory
};
}
}
diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs
index fd85bf76a..136d8189d 100644
--- a/Emby.Naming/Common/EpisodeExpression.cs
+++ b/Emby.Naming/Common/EpisodeExpression.cs
@@ -6,17 +6,28 @@ namespace Emby.Naming.Common
public class EpisodeExpression
{
private string _expression;
- public string Expression { get => _expression;
- set { _expression = value; _regex = null; } }
+ private Regex _regex;
+
+ public string Expression
+ {
+ get => _expression;
+ set
+ {
+ _expression = value;
+ _regex = null;
+ }
+ }
public bool IsByDate { get; set; }
+
public bool IsOptimistic { get; set; }
+
public bool IsNamed { get; set; }
+
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
public string[] DateTimeFormats { get; set; }
- private Regex _regex;
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
public EpisodeExpression(string expression, bool byDate)
diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs
index 49cc9ee39..a7b08bf79 100644
--- a/Emby.Naming/Common/MediaType.cs
+++ b/Emby.Naming/Common/MediaType.cs
@@ -6,10 +6,12 @@ namespace Emby.Naming.Common
/// The audio
/// </summary>
Audio = 0,
+
/// <summary>
/// The photo
/// </summary>
Photo = 1,
+
/// <summary>
/// The video
/// </summary>
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 2ef0208ba..88a9b46e6 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -8,19 +8,25 @@ namespace Emby.Naming.Common
public class NamingOptions
{
public string[] AudioFileExtensions { get; set; }
+
public string[] AlbumStackingPrefixes { get; set; }
public string[] SubtitleFileExtensions { get; set; }
+
public char[] SubtitleFlagDelimiters { get; set; }
public string[] SubtitleForcedFlags { get; set; }
+
public string[] SubtitleDefaultFlags { get; set; }
public EpisodeExpression[] EpisodeExpressions { get; set; }
+
public string[] EpisodeWithoutSeasonExpressions { get; set; }
+
public string[] EpisodeMultiPartExpressions { get; set; }
public string[] VideoFileExtensions { get; set; }
+
public string[] StubFileExtensions { get; set; }
public string[] AudioBookPartsExpressions { get; set; }
@@ -28,12 +34,14 @@ namespace Emby.Naming.Common
public StubTypeRule[] StubTypes { get; set; }
public char[] VideoFlagDelimiters { get; set; }
+
public Format3DRule[] Format3DRules { get; set; }
public string[] VideoFileStackingExpressions { get; set; }
+
public string[] CleanDateTimes { get; set; }
- public string[] CleanStrings { get; set; }
+ public string[] CleanStrings { get; set; }
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
@@ -41,7 +49,7 @@ namespace Emby.Naming.Common
public NamingOptions()
{
- VideoFileExtensions = new string[]
+ VideoFileExtensions = new[]
{
".m4v",
".3gp",
@@ -106,53 +114,53 @@ namespace Emby.Naming.Common
{
new StubTypeRule
{
- StubType = "dvd",
- Token = "dvd"
+ StubType = "dvd",
+ Token = "dvd"
},
new StubTypeRule
{
- StubType = "hddvd",
- Token = "hddvd"
+ StubType = "hddvd",
+ Token = "hddvd"
},
new StubTypeRule
{
- StubType = "bluray",
- Token = "bluray"
+ StubType = "bluray",
+ Token = "bluray"
},
new StubTypeRule
{
- StubType = "bluray",
- Token = "brrip"
+ StubType = "bluray",
+ Token = "brrip"
},
new StubTypeRule
{
- StubType = "bluray",
- Token = "bd25"
+ StubType = "bluray",
+ Token = "bd25"
},
new StubTypeRule
{
- StubType = "bluray",
- Token = "bd50"
+ StubType = "bluray",
+ Token = "bd50"
},
new StubTypeRule
{
- StubType = "vhs",
- Token = "vhs"
+ StubType = "vhs",
+ Token = "vhs"
},
new StubTypeRule
{
- StubType = "tv",
- Token = "HDTV"
+ StubType = "tv",
+ Token = "HDTV"
},
new StubTypeRule
{
- StubType = "tv",
- Token = "PDTV"
+ StubType = "tv",
+ Token = "PDTV"
},
new StubTypeRule
{
- StubType = "tv",
- Token = "DSR"
+ StubType = "tv",
+ Token = "DSR"
}
};
@@ -286,7 +294,7 @@ namespace Emby.Naming.Common
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
{
- DateTimeFormats = new []
+ DateTimeFormats = new[]
{
"yyyy.MM.dd",
"yyyy-MM-dd",
@@ -295,7 +303,7 @@ namespace Emby.Naming.Common
},
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
{
- DateTimeFormats = new []
+ DateTimeFormats = new[]
{
"dd.MM.yyyy",
"dd-MM-yyyy",
@@ -348,9 +356,7 @@ namespace Emby.Naming.Common
},
// "1-12 episode title"
- new EpisodeExpression(@"([0-9]+)-([0-9]+)")
- {
- },
+ new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
// "01 - blah.avi", "01-blah.avi"
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
@@ -427,7 +433,7 @@ namespace Emby.Naming.Common
Token = "_trailer",
MediaType = MediaType.Video
},
- new ExtraRule
+ new ExtraRule
{
ExtraType = "trailer",
RuleType = ExtraRuleType.Suffix,
@@ -462,7 +468,7 @@ namespace Emby.Naming.Common
Token = "_sample",
MediaType = MediaType.Video
},
- new ExtraRule
+ new ExtraRule
{
ExtraType = "sample",
RuleType = ExtraRuleType.Suffix,
@@ -476,7 +482,6 @@ namespace Emby.Naming.Common
Token = "theme",
MediaType = MediaType.Audio
},
-
new ExtraRule
{
ExtraType = "scene",
@@ -526,8 +531,8 @@ namespace Emby.Naming.Common
Token = "-short",
MediaType = MediaType.Video
}
-
};
+
Format3DRules = new[]
{
// Kodi rules:
@@ -648,12 +653,10 @@ namespace Emby.Naming.Common
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
-
}.Select(i => new EpisodeExpression(i)
- {
- IsNamed = true
-
- }).ToArray();
+ {
+ IsNamed = true
+ }).ToArray();
VideoFileExtensions = extensions
.Distinct(StringComparer.OrdinalIgnoreCase)
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index c448ec0ce..9e2a4950f 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@@ -18,6 +18,18 @@
<PackageId>Jellyfin.Naming</PackageId>
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ </PropertyGroup>
+
+ <!-- Code analysers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
diff --git a/Emby.Naming/Extensions/StringExtensions.cs b/Emby.Naming/Extensions/StringExtensions.cs
index 26c09aeb4..5512127a8 100644
--- a/Emby.Naming/Extensions/StringExtensions.cs
+++ b/Emby.Naming/Extensions/StringExtensions.cs
@@ -5,6 +5,7 @@ namespace Emby.Naming.Extensions
{
public static class StringExtensions
{
+ // TODO: @bond remove this when moving to netstandard2.1
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
{
var sb = new StringBuilder();
diff --git a/Emby.Naming/StringExtensions.cs b/Emby.Naming/StringExtensions.cs
deleted file mode 100644
index 7c61922af..000000000
--- a/Emby.Naming/StringExtensions.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Text;
-
-namespace Emby.Naming
-{
- internal static class StringExtensions
- {
- public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
- {
- var sb = new StringBuilder();
-
- var previousIndex = 0;
- var index = str.IndexOf(oldValue, comparison);
-
- while (index != -1)
- {
- sb.Append(str.Substring(previousIndex, index - previousIndex));
- sb.Append(newValue);
- index += oldValue.Length;
-
- previousIndex = index;
- index = str.IndexOf(oldValue, index, comparison);
- }
-
- sb.Append(str.Substring(previousIndex));
-
- return sb.ToString();
- }
- }
-}
diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs
index e4709dfbb..96fce04d7 100644
--- a/Emby.Naming/Subtitles/SubtitleInfo.cs
+++ b/Emby.Naming/Subtitles/SubtitleInfo.cs
@@ -7,16 +7,19 @@ namespace Emby.Naming.Subtitles
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
+
/// <summary>
/// Gets or sets the language.
/// </summary>
/// <value>The language.</value>
public string Language { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is default.
/// </summary>
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
public bool IsDefault { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is forced.
/// </summary>
diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs
index c8aca7a6f..de79b8bba 100644
--- a/Emby.Naming/TV/EpisodeInfo.cs
+++ b/Emby.Naming/TV/EpisodeInfo.cs
@@ -7,31 +7,37 @@ namespace Emby.Naming.TV
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
+
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
+
/// <summary>
/// Gets or sets the name of the series.
/// </summary>
/// <value>The name of the series.</value>
public string SeriesName { get; set; }
+
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is stub.
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
+
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
@@ -39,12 +45,17 @@ namespace Emby.Naming.TV
public string StubType { get; set; }
public int? SeasonNumber { get; set; }
+
public int? EpisodeNumber { get; set; }
+
public int? EndingEpsiodeNumber { get; set; }
public int? Year { get; set; }
+
public int? Month { get; set; }
+
public int? Day { get; set; }
+
public bool IsByDate { get; set; }
}
}
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index a8f81a3b8..a98e8221a 100644
--- a/Emby.Naming/TV/EpisodePathParser.cs
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -15,12 +15,12 @@ namespace Emby.Naming.TV
_options = options;
}
- public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
+ public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
{
// Added to be able to use regex patterns which require a file extension.
// There were no failed tests without this block, but to be safe, we can keep it until
// the regex which require file extensions are modified so that they don't need them.
- if (IsDirectory)
+ if (isDirectory)
{
path += ".mp4";
}
@@ -29,28 +29,20 @@ namespace Emby.Naming.TV
foreach (var expression in _options.EpisodeExpressions)
{
- if (supportsAbsoluteNumbers.HasValue)
+ if (supportsAbsoluteNumbers.HasValue
+ && expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
{
- if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
- {
- continue;
- }
+ continue;
}
- if (isNamed.HasValue)
+ if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
{
- if (expression.IsNamed != isNamed.Value)
- {
- continue;
- }
+ continue;
}
- if (isOptimistic.HasValue)
+ if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
{
- if (expression.IsOptimistic != isOptimistic.Value)
- {
- continue;
- }
+ continue;
}
var currentResult = Parse(path, expression);
@@ -97,7 +89,8 @@ namespace Emby.Naming.TV
DateTime date;
if (expression.DateTimeFormats.Length > 0)
{
- if (DateTime.TryParseExact(match.Groups[0].Value,
+ if (DateTime.TryParseExact(
+ match.Groups[0].Value,
expression.DateTimeFormats,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
@@ -109,15 +102,12 @@ namespace Emby.Naming.TV
result.Success = true;
}
}
- else
+ else if (DateTime.TryParse(match.Groups[0].Value, out date))
{
- if (DateTime.TryParse(match.Groups[0].Value, out date))
- {
- result.Year = date.Year;
- result.Month = date.Month;
- result.Day = date.Day;
- result.Success = true;
- }
+ result.Year = date.Year;
+ result.Month = date.Month;
+ result.Day = date.Day;
+ result.Success = true;
}
// TODO: Only consider success if date successfully parsed?
@@ -142,7 +132,8 @@ namespace Emby.Naming.TV
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
- if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
+ if (nextIndex >= name.Length
+ || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
{
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
@@ -160,6 +151,7 @@ namespace Emby.Naming.TV
{
result.SeasonNumber = num;
}
+
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
result.EpisodeNumber = num;
@@ -171,8 +163,11 @@ namespace Emby.Naming.TV
// Invalidate match when the season is 200 through 1927 or above 2500
// because it is an error unless the TV show is intentionally using false season numbers.
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
- if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500)
+ if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
+ || result.SeasonNumber > 2500)
+ {
result.Success = false;
+ }
result.IsByDate = expression.IsByDate;
}
diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs
index e1a48bfbc..996edfc50 100644
--- a/Emby.Naming/TV/EpisodePathParserResult.cs
+++ b/Emby.Naming/TV/EpisodePathParserResult.cs
@@ -3,14 +3,21 @@ namespace Emby.Naming.TV
public class EpisodePathParserResult
{
public int? SeasonNumber { get; set; }
+
public int? EpisodeNumber { get; set; }
+
public int? EndingEpsiodeNumber { get; set; }
+
public string SeriesName { get; set; }
+
public bool Success { get; set; }
public bool IsByDate { get; set; }
+
public int? Year { get; set; }
+
public int? Month { get; set; }
+
public int? Day { get; set; }
}
}
diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs
index fccf9bdec..2d7bcb638 100644
--- a/Emby.Naming/TV/EpisodeResolver.cs
+++ b/Emby.Naming/TV/EpisodeResolver.cs
@@ -15,7 +15,13 @@ namespace Emby.Naming.TV
_options = options;
}
- public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
+ public EpisodeInfo Resolve(
+ string path,
+ bool isDirectory,
+ bool? isNamed = null,
+ bool? isOptimistic = null,
+ bool? supportsAbsoluteNumbers = null,
+ bool fillExtendedInfo = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -26,7 +32,7 @@ namespace Emby.Naming.TV
string container = null;
string stubType = null;
- if (!IsDirectory)
+ if (!isDirectory)
{
var extension = Path.GetExtension(path);
// Check supported extensions
@@ -52,7 +58,7 @@ namespace Emby.Naming.TV
var format3DResult = new Format3DParser(_options).Parse(flags);
var parsingResult = new EpisodePathParser(_options)
- .Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
+ .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
return new EpisodeInfo
{
diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs
index f1dcc50b8..e81b2bb34 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -3,30 +3,24 @@ using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
+using Emby.Naming.Extensions;
namespace Emby.Naming.TV
{
public class SeasonPathParser
{
- private readonly NamingOptions _options;
-
- public SeasonPathParser(NamingOptions options)
- {
- _options = options;
- }
-
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
{
var result = new SeasonPathParserResult();
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
- result.SeasonNumber = seasonNumberInfo.Item1;
+ result.SeasonNumber = seasonNumberInfo.seasonNumber;
if (result.SeasonNumber.HasValue)
{
result.Success = true;
- result.IsSeasonFolder = seasonNumberInfo.Item2;
+ result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
}
return result;
@@ -35,7 +29,7 @@ namespace Emby.Naming.TV
/// <summary>
/// A season folder must contain one of these somewhere in the name
/// </summary>
- private static readonly string[] SeasonFolderNames =
+ private static readonly string[] _seasonFolderNames =
{
"season",
"sæson",
@@ -54,19 +48,23 @@ namespace Emby.Naming.TV
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private Tuple<int?, bool> GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
+ private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
+ string path,
+ bool supportSpecialAliases,
+ bool supportNumericSeasonFolders)
{
- var filename = Path.GetFileName(path);
+ var filename = Path.GetFileName(path) ?? string.Empty;
if (supportSpecialAliases)
{
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
{
- return new Tuple<int?, bool>(0, true);
+ return (0, true);
}
+
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
{
- return new Tuple<int?, bool>(0, true);
+ return (0, true);
}
}
@@ -74,7 +72,7 @@ namespace Emby.Naming.TV
{
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
- return new Tuple<int?, bool>(val, true);
+ return (val, true);
}
}
@@ -84,12 +82,12 @@ namespace Emby.Naming.TV
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
- return new Tuple<int?, bool>(val, true);
+ return (val, true);
}
}
// Look for one of the season folder names
- foreach (var name in SeasonFolderNames)
+ foreach (var name in _seasonFolderNames)
{
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
@@ -107,10 +105,10 @@ namespace Emby.Naming.TV
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
- return new Tuple<int?, bool>(resultNumber, true);
+ return (resultNumber, true);
}
- private int? GetSeasonNumberFromPart(string part)
+ private static int? GetSeasonNumberFromPart(string part)
{
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
{
@@ -132,7 +130,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private Tuple<int?, bool> GetSeasonNumberFromPathSubstring(string path)
+ private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
{
var numericStart = -1;
var length = 0;
@@ -174,10 +172,10 @@ namespace Emby.Naming.TV
if (numericStart == -1)
{
- return new Tuple<int?, bool>(null, isSeasonFolder);
+ return (null, isSeasonFolder);
}
- return new Tuple<int?, bool>(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
+ return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
}
}
}
diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs
index eab27a4a5..548dbd5d2 100644
--- a/Emby.Naming/TV/SeasonPathParserResult.cs
+++ b/Emby.Naming/TV/SeasonPathParserResult.cs
@@ -7,11 +7,13 @@ namespace Emby.Naming.TV
/// </summary>
/// <value>The season number.</value>
public int? SeasonNumber { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
/// </summary>
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
public bool Success { get; set; }
+
public bool IsSeasonFolder { get; set; }
}
}
diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs
index 74807ef53..25fa09c48 100644
--- a/Emby.Naming/Video/CleanDateTimeParser.cs
+++ b/Emby.Naming/Video/CleanDateTimeParser.cs
@@ -27,8 +27,8 @@ namespace Emby.Naming.Video
{
var extension = Path.GetExtension(name) ?? string.Empty;
// Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) &&
- !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
+ && !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
// Dummy up a file extension because the expressions will fail without one
// This is tricky because we can't just check Path.GetExtension for empty
@@ -38,7 +38,6 @@ namespace Emby.Naming.Video
}
catch (ArgumentException)
{
-
}
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
@@ -69,14 +68,15 @@ namespace Emby.Naming.Video
var match = expression.Match(name);
- if (match.Success && match.Groups.Count == 4)
+ if (match.Success
+ && match.Groups.Count == 4
+ && match.Groups[1].Success
+ && match.Groups[2].Success
+ && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
- if (match.Groups[1].Success && match.Groups[2].Success && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
- {
- name = match.Groups[1].Value;
- result.Year = year;
- result.HasChanged = true;
- }
+ name = match.Groups[1].Value;
+ result.Year = year;
+ result.HasChanged = true;
}
result.Name = name;
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 3459b689a..9f70494d0 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -56,7 +56,6 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
-
else if (rule.RuleType == ExtraRuleType.Suffix)
{
var filename = Path.GetFileNameWithoutExtension(path);
@@ -67,7 +66,6 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
-
else if (rule.RuleType == ExtraRuleType.Regex)
{
var filename = Path.GetFileName(path);
diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs
index 2df1e9aed..584bdf2d2 100644
--- a/Emby.Naming/Video/FileStack.cs
+++ b/Emby.Naming/Video/FileStack.cs
@@ -15,9 +15,9 @@ namespace Emby.Naming.Video
Files = new List<string>();
}
- public bool ContainsFile(string file, bool IsDirectory)
+ public bool ContainsFile(string file, bool isDirectory)
{
- if (IsDirectoryStack == IsDirectory)
+ if (IsDirectoryStack == isDirectory)
{
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
}
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index e6f830c58..333a48641 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -15,10 +15,12 @@ namespace Emby.Naming.Video
public Format3DResult Parse(string path)
{
- var delimeters = _options.VideoFlagDelimiters.ToList();
- delimeters.Add(' ');
+ int oldLen = _options.VideoFlagDelimiters.Length;
+ var delimeters = new char[oldLen + 1];
+ _options.VideoFlagDelimiters.CopyTo(delimeters, 0);
+ delimeters[oldLen] = ' ';
- return Parse(new FlagParser(_options).GetFlags(path, delimeters.ToArray()));
+ return Parse(new FlagParser(_options).GetFlags(path, delimeters));
}
internal Format3DResult Parse(string[] videoFlags)
@@ -66,8 +68,10 @@ namespace Emby.Naming.Video
format = flag;
result.Tokens.Add(rule.Token);
}
+
break;
}
+
foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase);
}
diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs
index e12494079..40fc31e08 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -4,25 +4,27 @@ namespace Emby.Naming.Video
{
public class Format3DResult
{
+ public Format3DResult()
+ {
+ Tokens = new List<string>();
+ }
+
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
+
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
+
/// <summary>
/// Gets or sets the tokens.
/// </summary>
/// <value>The tokens.</value>
public List<string> Tokens { get; set; }
-
- public Format3DResult()
- {
- Tokens = new List<string>();
- }
}
}
diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index 4893002c1..b8ba42da4 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -40,17 +40,24 @@ namespace Emby.Naming.Video
var result = new StackResult();
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
{
- var stack = new FileStack();
- stack.Name = Path.GetFileName(directory.Key);
- stack.IsDirectoryStack = false;
+ var stack = new FileStack()
+ {
+ Name = Path.GetFileName(directory.Key),
+ IsDirectoryStack = false
+ };
foreach (var file in directory)
{
if (file.IsDirectory)
+ {
continue;
+ }
+
stack.Files.Add(file.FullName);
}
+
result.Stacks.Add(stack);
}
+
return result;
}
@@ -114,16 +121,16 @@ namespace Emby.Naming.Video
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
{
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
- //stack.Name = title1 + ignore1 + extension1;
stack.Files.Add(file1.FullName);
}
+
stack.Files.Add(file2.FullName);
}
else
diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs
index f86bcbdf0..b78244cb3 100644
--- a/Emby.Naming/Video/StubResolver.cs
+++ b/Emby.Naming/Video/StubResolver.cs
@@ -9,24 +9,32 @@ namespace Emby.Naming.Video
{
public static StubResult ResolveFile(string path, NamingOptions options)
{
- var result = new StubResult();
- var extension = Path.GetExtension(path) ?? string.Empty;
+ if (path == null)
+ {
+ return default(StubResult);
+ }
+
+ var extension = Path.GetExtension(path);
- if (options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- result.IsStub = true;
+ return default(StubResult);
+ }
- path = Path.GetFileNameWithoutExtension(path);
+ var result = new StubResult()
+ {
+ IsStub = true
+ };
- var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.');
+ path = Path.GetFileNameWithoutExtension(path);
+ var token = Path.GetExtension(path).TrimStart('.');
- foreach (var rule in options.StubTypes)
+ foreach (var rule in options.StubTypes)
+ {
+ if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
- {
- result.StubType = rule.StubType;
- break;
- }
+ result.StubType = rule.StubType;
+ break;
}
}
diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs
index 7f9509ca5..7a62e7b98 100644
--- a/Emby.Naming/Video/StubResult.cs
+++ b/Emby.Naming/Video/StubResult.cs
@@ -7,6 +7,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
+
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs
index b46050085..d76532150 100644
--- a/Emby.Naming/Video/StubTypeRule.cs
+++ b/Emby.Naming/Video/StubTypeRule.cs
@@ -7,6 +7,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The token.</value>
public string Token { get; set; }
+
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 6a29ada7e..78f688ca8 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -1,4 +1,3 @@
-
namespace Emby.Naming.Video
{
/// <summary>
@@ -11,56 +10,67 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
+
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
+
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
+
/// <summary>
/// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
/// </summary>
/// <value>The type of the extra.</value>
public string ExtraType { get; set; }
+
/// <summary>
/// Gets or sets the extra rule.
/// </summary>
/// <value>The extra rule.</value>
public ExtraRule ExtraRule { get; set; }
+
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is stub.
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
+
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
/// <value>The type of the stub.</value>
public string StubType { get; set; }
+
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
+
/// <summary>
/// Gets the file name without extension.
/// </summary>
diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs
index d96d0e757..2e456bda2 100644
--- a/Emby.Naming/Video/VideoInfo.cs
+++ b/Emby.Naming/Video/VideoInfo.cs
@@ -12,21 +12,25 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
+
/// <summary>
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<VideoFileInfo> Files { get; set; }
+
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<VideoFileInfo> Extras { get; set; }
+
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index afedc30ef..5fa0041e0 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -53,7 +53,7 @@ namespace Emby.Naming.Video
Name = stack.Name
};
- info.Year = info.Files.First().Year;
+ info.Year = info.Files[0].Year;
var extraBaseNames = new List<string>
{
@@ -87,7 +87,7 @@ namespace Emby.Naming.Video
Name = media.Name
};
- info.Year = info.Files.First().Year;
+ info.Year = info.Files[0].Year;
var extras = GetExtras(remainingFiles, new List<string> { media.FileNameWithoutExtension });
@@ -115,7 +115,7 @@ namespace Emby.Naming.Video
if (!string.IsNullOrEmpty(parentPath))
{
- var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath));
+ var folderName = Path.GetFileName(parentPath);
if (!string.IsNullOrEmpty(folderName))
{
var extras = GetExtras(remainingFiles, new List<string> { folderName });
@@ -163,9 +163,7 @@ namespace Emby.Naming.Video
Year = i.Year
}));
- var orderedList = list.OrderBy(i => i.Name);
-
- return orderedList;
+ return list.OrderBy(i => i.Name);
}
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
@@ -179,23 +177,21 @@ namespace Emby.Naming.Video
var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
- if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1)
+ if (!string.IsNullOrEmpty(folderName)
+ && folderName.Length > 1
+ && videos.All(i => i.Files.Count == 1
+ && IsEligibleForMultiVersion(folderName, i.Files[0].Path))
+ && HaveSameYear(videos))
{
- if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
- {
- if (HaveSameYear(videos))
- {
- var ordered = videos.OrderBy(i => i.Name).ToList();
+ var ordered = videos.OrderBy(i => i.Name).ToList();
- list.Add(ordered[0]);
+ list.Add(ordered[0]);
- list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
- list[0].Name = folderName;
- list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
+ list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
+ list[0].Name = folderName;
+ list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
- return list;
- }
- }
+ return list;
}
return videos;
@@ -213,9 +209,9 @@ namespace Emby.Naming.Video
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{
testFilename = testFilename.Substring(folderName.Length).Trim();
- return string.IsNullOrEmpty(testFilename) ||
- testFilename.StartsWith("-") ||
- string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ;
+ return string.IsNullOrEmpty(testFilename)
+ || testFilename[0] == '-'
+ || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
}
return false;
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index a67315651..02a25c4b5 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -38,10 +38,11 @@ namespace Emby.Naming.Video
/// Resolves the specified path.
/// </summary>
/// <param name="path">The path.</param>
- /// <param name="IsDirectory">if set to <c>true</c> [is folder].</param>
+ /// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
+ /// <param name="parseName">Whether or not the name should be parsed for info</param>
/// <returns>VideoFileInfo.</returns>
/// <exception cref="ArgumentNullException">path</exception>
- public VideoFileInfo Resolve(string path, bool IsDirectory, bool parseName = true)
+ public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -52,9 +53,10 @@ namespace Emby.Naming.Video
string container = null;
string stubType = null;
- if (!IsDirectory)
+ if (!isDirectory)
{
var extension = Path.GetExtension(path);
+
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
@@ -79,7 +81,7 @@ namespace Emby.Naming.Video
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
- var name = IsDirectory
+ var name = isDirectory
? Path.GetFileName(path)
: Path.GetFileNameWithoutExtension(path);
@@ -108,7 +110,7 @@ namespace Emby.Naming.Video
Is3D = format3DResult.Is3D,
Format3D = format3DResult.Format3D,
ExtraType = extraResult.ExtraType,
- IsDirectory = IsDirectory,
+ IsDirectory = isDirectory,
ExtraRule = extraResult.Rule
};
}
diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs
index a4179e660..99a635e60 100644
--- a/Emby.Photos/PhotoProvider.cs
+++ b/Emby.Photos/PhotoProvider.cs
@@ -20,7 +20,10 @@ namespace Emby.Photos
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{
private readonly ILogger _logger;
- private IImageProcessor _imageProcessor;
+ private readonly IImageProcessor _imageProcessor;
+
+ // These are causing taglib to hang
+ private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" };
public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
{
@@ -28,75 +31,55 @@ namespace Emby.Photos
_imageProcessor = imageProcessor;
}
+ public string Name => "Embedded Information";
+
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
if (item.IsFileProtocol)
{
var file = directoryService.GetFile(item.Path);
- if (file != null && file.LastWriteTimeUtc != item.DateModified)
- {
- return true;
- }
+ return (file != null && file.LastWriteTimeUtc != item.DateModified);
}
return false;
}
- // These are causing taglib to hang
- private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" };
-
public Task<ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
item.SetImagePath(ImageType.Primary, item.Path);
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
- if (_includextensions.Contains(Path.GetExtension(item.Path) ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ if (_includextensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
{
try
{
using (var file = TagLib.File.Create(item.Path))
{
- var image = file as TagLib.Image.File;
-
- var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag;
-
- if (tag != null)
+ if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
{
var structure = tag.Structure;
-
- if (structure != null)
+ if (structure != null
+ && structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
{
- var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry;
-
- if (exif != null)
+ var exifStructure = exif.Structure;
+ if (exifStructure != null)
{
- var exifStructure = exif.Structure;
+ var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
+ if (entry != null)
+ {
+ item.Aperture = (double)entry.Value.Numerator / entry.Value.Denominator;
+ }
- if (exifStructure != null)
+ entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
+ if (entry != null)
{
- var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
-
- if (entry != null)
- {
- double val = entry.Value.Numerator;
- val /= entry.Value.Denominator;
- item.Aperture = val;
- }
-
- entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
-
- if (entry != null)
- {
- double val = entry.Value.Numerator;
- val /= entry.Value.Denominator;
- item.ShutterSpeed = val;
- }
+ item.ShutterSpeed = (double)entry.Value.Numerator / entry.Value.Denominator;
}
}
}
}
- if (image != null)
+ if (file is TagLib.Image.File image)
{
item.CameraMake = image.ImageTag.Make;
item.CameraModel = image.ImageTag.Model;
@@ -116,12 +99,10 @@ namespace Emby.Photos
item.Overview = image.ImageTag.Comment;
- if (!string.IsNullOrWhiteSpace(image.ImageTag.Title))
+ if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
+ && !item.LockedFields.Contains(MetadataFields.Name))
{
- if (!item.LockedFields.Contains(MetadataFields.Name))
- {
- item.Name = image.ImageTag.Title;
- }
+ item.Name = image.ImageTag.Title;
}
var dateTaken = image.ImageTag.DateTime;
@@ -140,12 +121,9 @@ namespace Emby.Photos
{
item.Orientation = null;
}
- else
+ else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
{
- if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
- {
- item.Orientation = orientation;
- }
+ item.Orientation = orientation;
}
item.ExposureTime = image.ImageTag.ExposureTime;
@@ -195,7 +173,5 @@ namespace Emby.Photos
const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
return Task.FromResult(result);
}
-
- public string Name => "Embedded Information";
}
}
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 190e4d55c..0530a251c 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -29,31 +27,39 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityLogEntryPoint : IServerEntryPoint
{
+ private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization;
- private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
- private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost;
private readonly IDeviceManager _deviceManager;
- public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost)
+ public ActivityLogEntryPoint(
+ ILogger<ActivityLogEntryPoint> logger,
+ ISessionManager sessionManager,
+ IDeviceManager deviceManager,
+ ITaskManager taskManager,
+ IActivityManager activityManager,
+ ILocalizationManager localization,
+ IInstallationManager installationManager,
+ ISubtitleManager subManager,
+ IUserManager userManager,
+ IServerApplicationHost appHost)
{
+ _logger = logger;
_sessionManager = sessionManager;
+ _deviceManager = deviceManager;
_taskManager = taskManager;
_activityManager = activityManager;
_localization = localization;
_installationManager = installationManager;
- _libraryManager = libraryManager;
_subManager = subManager;
_userManager = userManager;
- _config = config;
_appHost = appHost;
- _deviceManager = deviceManager;
}
public Task RunAsync()
@@ -122,7 +128,7 @@ namespace Emby.Server.Implementations.Activity
if (item == null)
{
- //_logger.LogWarning("PlaybackStopped reported with null media info.");
+ _logger.LogWarning("PlaybackStopped reported with null media info.");
return;
}
@@ -153,7 +159,7 @@ namespace Emby.Server.Implementations.Activity
if (item == null)
{
- //_logger.LogWarning("PlaybackStart reported with null media info.");
+ _logger.LogWarning("PlaybackStart reported with null media info.");
return;
}
@@ -201,6 +207,7 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlayback.ToString();
}
+
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlayback.ToString();
@@ -215,6 +222,7 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlaybackStopped.ToString();
}
+
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlaybackStopped.ToString();
@@ -403,6 +411,7 @@ namespace Emby.Server.Implementations.Activity
{
vals.Add(e.Result.ErrorMessage);
}
+
if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
{
vals.Add(e.Result.LongErrorMessage);
@@ -412,7 +421,7 @@ namespace Emby.Server.Implementations.Activity
{
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
Type = NotificationType.TaskFailed.ToString(),
- Overview = string.Join(Environment.NewLine, vals.ToArray()),
+ Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime,
Severity = LogLevel.Error
});
@@ -489,6 +498,7 @@ namespace Emby.Server.Implementations.Activity
{
values.Add(CreateValueString(span.Hours, "hour"));
}
+
// Number of minutes
if (span.Minutes >= 1)
{
@@ -512,6 +522,7 @@ namespace Emby.Server.Implementations.Activity
builder.Append(values[i]);
}
+
// Return result
return builder.ToString();
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 53bc85b28..fc4e88878 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -6,6 +6,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -826,10 +827,10 @@ namespace Emby.Server.Implementations
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
serviceCollection.AddSingleton(DtoService);
- ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
+ ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
serviceCollection.AddSingleton(ChannelManager);
- SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
+ SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager);
serviceCollection.AddSingleton(SessionManager);
serviceCollection.AddSingleton<IDlnaManager>(
@@ -1014,7 +1015,6 @@ namespace Emby.Server.Implementations
Video.LiveTvManager = LiveTvManager;
Folder.UserViewManager = UserViewManager;
UserView.TVSeriesManager = TVSeriesManager;
- UserView.PlaylistManager = PlaylistManager;
UserView.CollectionManager = CollectionManager;
BaseItem.MediaSourceManager = MediaSourceManager;
CollectionFolder.XmlSerializer = XmlSerializer;
@@ -1539,12 +1539,12 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false,
LogErrors = false,
LogRequest = false,
- TimeoutMs = 10000,
BufferContent = false,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
- return GetWanApiUrl(response.ReadToEnd().Trim());
+ string res = await response.ReadToEndAsync().ConfigureAwait(false);
+ return GetWanApiUrl(res.Trim());
}
}
catch (Exception ex)
@@ -1697,15 +1697,15 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false,
LogErrors = LogPing,
LogRequest = LogPing,
- TimeoutMs = 5000,
BufferContent = false,
CancellationToken = cancellationToken
- }, "POST").ConfigureAwait(false))
+
+ }, HttpMethod.Post).ConfigureAwait(false))
{
using (var reader = new StreamReader(response.Content))
{
- var result = reader.ReadToEnd();
+ var result = await reader.ReadToEndAsync().ConfigureAwait(false);
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 7e50650d7..e9961e8bd 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -20,7 +19,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
@@ -40,11 +38,8 @@ namespace Emby.Server.Implementations.Channels
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
private readonly IProviderManager _providerManager;
- private readonly ILocalizationManager _localization;
-
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
@@ -54,8 +49,6 @@ namespace Emby.Server.Implementations.Channels
IFileSystem fileSystem,
IUserDataManager userDataManager,
IJsonSerializer jsonSerializer,
- ILocalizationManager localization,
- IHttpClient httpClient,
IProviderManager providerManager)
{
_userManager = userManager;
@@ -66,8 +59,6 @@ namespace Emby.Server.Implementations.Channels
_fileSystem = fileSystem;
_userDataManager = userDataManager;
_jsonSerializer = jsonSerializer;
- _localization = localization;
- _httpClient = httpClient;
_providerManager = providerManager;
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 7b28a22a8..2f1b60be9 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -89,14 +89,11 @@ namespace Emby.Server.Implementations.Dto
var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>>();
var index = 0;
- var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
-
foreach (var item in items)
{
- var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner);
+ var dto = GetBaseItemDtoInternal(item, options, user, owner);
- var tvChannel = item as LiveTvChannel;
- if (tvChannel != null)
+ if (item is LiveTvChannel tvChannel)
{
channelTuples.Add(new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel));
}
@@ -105,9 +102,7 @@ namespace Emby.Server.Implementations.Dto
programTuples.Add(new Tuple<BaseItem, BaseItemDto>(item, dto));
}
- var byName = item as IItemByName;
-
- if (byName != null)
+ if (item is IItemByName byName)
{
if (options.ContainsField(ItemFields.ItemCounts))
{
@@ -130,8 +125,7 @@ namespace Emby.Server.Implementations.Dto
if (programTuples.Count > 0)
{
- var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user);
- Task.WaitAll(task);
+ _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
}
if (channelTuples.Count > 0)
@@ -144,8 +138,7 @@ namespace Emby.Server.Implementations.Dto
public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
{
- var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
- var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner);
+ var dto = GetBaseItemDtoInternal(item, options, user, owner);
var tvChannel = item as LiveTvChannel;
if (tvChannel != null)
{
@@ -188,7 +181,7 @@ namespace Emby.Server.Implementations.Dto
});
}
- private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, List<Folder> allCollectionFolders, User user = null, BaseItem owner = null)
+ private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
{
var dto = new BaseItemDto
{
@@ -312,6 +305,7 @@ namespace Emby.Server.Implementations.Dto
{
path = path.TrimStart('.');
}
+
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
{
fileExtensionContainer = path;
@@ -325,8 +319,7 @@ namespace Emby.Server.Implementations.Dto
public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null)
{
- var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
- var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user);
+ var dto = GetBaseItemDtoInternal(item, options, user);
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
{
@@ -1051,14 +1044,15 @@ namespace Emby.Server.Implementations.Dto
}
else
{
- mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, item.Id.ToString("N"), StringComparison.OrdinalIgnoreCase))
+ string id = item.Id.ToString("N");
+ mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.MediaStreams)
.ToArray();
}
}
else
{
- mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true).First().MediaStreams.ToArray();
+ mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
}
dto.MediaStreams = mediaStreams;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 2c7962452..49015a07e 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -31,10 +31,9 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
- <PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
- <PackageReference Include="sharpcompress" Version="0.22.0" />
+ <PackageReference Include="ServiceStack.Text.Core" Version="5.5.0" />
+ <PackageReference Include="sharpcompress" Version="0.23.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
- <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup>
<ItemGroup>
@@ -52,8 +51,8 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs
deleted file mode 100644
index f747b01b9..000000000
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Net.Http;
-
-namespace Emby.Server.Implementations.HttpClientManager
-{
- /// <summary>
- /// Class HttpClientInfo
- /// </summary>
- public class HttpClientInfo
- {
- /// <summary>
- /// Gets or sets the last timeout.
- /// </summary>
- /// <value>The last timeout.</value>
- public DateTime LastTimeout { get; set; }
- public HttpClient HttpClient { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 1bebdd163..b82d55d0e 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -1,11 +1,10 @@
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
-using System.Net.Cache;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -55,12 +54,13 @@ namespace Emby.Server.Implementations.HttpClientManager
{
throw new ArgumentNullException(nameof(appPaths));
}
+
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
- _logger = loggerFactory.CreateLogger("HttpClient");
+ _logger = loggerFactory.CreateLogger(nameof(HttpClientManager));
_fileSystem = fileSystem;
_appPaths = appPaths;
_defaultUserAgentFn = defaultUserAgentFn;
@@ -74,27 +74,26 @@ namespace Emby.Server.Implementations.HttpClientManager
/// DON'T dispose it after use.
/// </summary>
/// <value>The HTTP clients.</value>
- private readonly ConcurrentDictionary<string, HttpClientInfo> _httpClients = new ConcurrentDictionary<string, HttpClientInfo>();
+ private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
/// <summary>
/// Gets
/// </summary>
- /// <param name="host">The host.</param>
+ /// <param name="url">The host.</param>
/// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param>
/// <returns>HttpClient.</returns>
/// <exception cref="ArgumentNullException">host</exception>
- private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression)
+ private HttpClient GetHttpClient(string url, bool enableHttpCompression)
{
- if (string.IsNullOrEmpty(host))
- {
- throw new ArgumentNullException(nameof(host));
- }
-
- var key = host + enableHttpCompression;
+ var key = GetHostFromUrl(url) + enableHttpCompression;
if (!_httpClients.TryGetValue(key, out var client))
{
- client = new HttpClientInfo();
+
+ client = new HttpClient()
+ {
+ BaseAddress = new Uri(url)
+ };
_httpClients.TryAdd(key, client);
}
@@ -102,110 +101,87 @@ namespace Emby.Server.Implementations.HttpClientManager
return client;
}
- private WebRequest GetRequest(HttpRequestOptions options, string method)
+ private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method)
{
string url = options.Url;
-
var uriAddress = new Uri(url);
string userInfo = uriAddress.UserInfo;
if (!string.IsNullOrWhiteSpace(userInfo))
{
- _logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url);
+ _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
url = url.Replace(userInfo + "@", string.Empty);
}
- var request = WebRequest.Create(url);
+ var request = new HttpRequestMessage(method, url);
- if (request is HttpWebRequest httpWebRequest)
- {
- AddRequestHeaders(httpWebRequest, options);
+ AddRequestHeaders(request, options);
- if (options.EnableHttpCompression)
+ if (options.EnableHttpCompression)
+ {
+ if (options.DecompressionMethod.HasValue
+ && options.DecompressionMethod.Value == CompressionMethod.Gzip)
{
- httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate;
- if (options.DecompressionMethod.HasValue
- && options.DecompressionMethod.Value == CompressionMethod.Gzip)
- {
- httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip;
- }
+ request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
}
else
{
- httpWebRequest.AutomaticDecompression = DecompressionMethods.None;
+ request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
}
+ }
- httpWebRequest.KeepAlive = options.EnableKeepAlive;
+ if (options.EnableKeepAlive)
+ {
+ request.Headers.Add(HeaderNames.Connection, "Keep-Alive");
+ }
- if (!string.IsNullOrEmpty(options.Host))
- {
- httpWebRequest.Host = options.Host;
- }
+ if (!string.IsNullOrEmpty(options.Host))
+ {
+ request.Headers.Add(HeaderNames.Host, options.Host);
+ }
- if (!string.IsNullOrEmpty(options.Referer))
- {
- httpWebRequest.Referer = options.Referer;
- }
+ if (!string.IsNullOrEmpty(options.Referer))
+ {
+ request.Headers.Add(HeaderNames.Referer, options.Referer);
}
- request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
+ //request.Headers.Add(HeaderNames.CacheControl, "no-cache");
- request.Method = method;
- request.Timeout = options.TimeoutMs;
+ //request.Headers.Add(HeaderNames., options.TimeoutMs;
+ /*
if (!string.IsNullOrWhiteSpace(userInfo))
{
var parts = userInfo.Split(':');
if (parts.Length == 2)
{
- request.Credentials = GetCredential(url, parts[0], parts[1]);
- // TODO: .net core ??
- request.PreAuthenticate = true;
+ request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]);
}
}
+ */
return request;
}
- private static CredentialCache GetCredential(string url, string username, string password)
- {
- //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
- var credentialCache = new CredentialCache();
- credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
- return credentialCache;
- }
-
- private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
+ private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options)
{
var hasUserAgent = false;
foreach (var header in options.RequestHeaders)
{
- if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
{
- request.Accept = header.Value;
- }
- else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
- {
- SetUserAgent(request, header.Value);
hasUserAgent = true;
}
- else
- {
- request.Headers.Set(header.Key, header.Value);
- }
+
+ request.Headers.Add(header.Key, header.Value);
}
if (!hasUserAgent && options.EnableDefaultUserAgent)
{
- SetUserAgent(request, _defaultUserAgentFn());
+ request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
}
}
- private static void SetUserAgent(HttpWebRequest request, string userAgent)
- {
- request.UserAgent = userAgent;
- }
-
/// <summary>
/// Gets the response internal.
/// </summary>
@@ -213,7 +189,7 @@ namespace Emby.Server.Implementations.HttpClientManager
/// <returns>Task{HttpResponseInfo}.</returns>
public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
{
- return SendAsync(options, "GET");
+ return SendAsync(options, HttpMethod.Get);
}
/// <summary>
@@ -235,7 +211,21 @@ namespace Emby.Server.Implementations.HttpClientManager
/// <returns>Task{HttpResponseInfo}.</returns>
/// <exception cref="HttpException">
/// </exception>
- public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
+ public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
+ {
+ var httpMethod2 = GetHttpMethod(httpMethod);
+ return SendAsync(options, httpMethod2);
+ }
+
+ /// <summary>
+ /// send as an asynchronous operation.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="httpMethod">The HTTP method.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ /// <exception cref="HttpException">
+ /// </exception>
+ public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod)
{
if (options.CacheMode == CacheMode.None)
{
@@ -263,6 +253,40 @@ namespace Emby.Server.Implementations.HttpClientManager
return response;
}
+ private HttpMethod GetHttpMethod(string httpMethod)
+ {
+ if (httpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Delete;
+ }
+ else if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Get;
+ }
+ else if (httpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Head;
+ }
+ else if (httpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Options;
+ }
+ else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Post;
+ }
+ else if (httpMethod.Equals("PUT", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Put;
+ }
+ else if (httpMethod.Equals("TRACE", StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpMethod.Trace;
+ }
+
+ throw new ArgumentException("Invalid HTTP method", nameof(httpMethod));
+ }
+
private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
{
if (File.Exists(responseCachePath)
@@ -294,31 +318,23 @@ namespace Emby.Server.Implementations.HttpClientManager
}
}
- private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod)
+ private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod)
{
ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested();
- var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
+ var client = GetHttpClient(options.Url, options.EnableHttpCompression);
- if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds)
- {
- throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url))
- {
- IsTimedOut = true
- };
- }
-
- var httpWebRequest = GetRequest(options, httpMethod);
+ var httpWebRequest = GetRequestMessage(options, httpMethod);
if (options.RequestContentBytes != null ||
!string.IsNullOrEmpty(options.RequestContent) ||
- string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
+ httpMethod == HttpMethod.Post)
{
try
{
- var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
+ httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty);
var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
@@ -327,8 +343,8 @@ namespace Emby.Server.Implementations.HttpClientManager
contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\"";
}
- httpWebRequest.ContentType = contentType;
- (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
+ httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);
+ await client.SendAsync(httpWebRequest).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -336,143 +352,96 @@ namespace Emby.Server.Implementations.HttpClientManager
}
}
- if (options.ResourcePool != null)
- {
- await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
- }
-
- if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds)
- {
- options.ResourcePool?.Release();
-
- throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true };
- }
-
if (options.LogRequest)
{
- if (options.LogRequestAsDebug)
- {
- _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
- }
- else
- {
- _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
- }
+ _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url);
}
try
{
options.CancellationToken.ThrowIfCancellationRequested();
- if (!options.BufferContent)
+ /*if (!options.BufferContent)
{
- var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false);
+ var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false);
- var httpResponse = (HttpWebResponse)response;
-
- EnsureSuccessStatusCode(client, httpResponse, options);
+ await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false);
options.CancellationToken.ThrowIfCancellationRequested();
- return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse);
- }
+ return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response);
+ }*/
- using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false))
+ using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false))
{
- var httpResponse = (HttpWebResponse)response;
-
- EnsureSuccessStatusCode(client, httpResponse, options);
+ await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
options.CancellationToken.ThrowIfCancellationRequested();
- using (var stream = httpResponse.GetResponseStream())
+ using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
memoryStream.Position = 0;
- return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null);
+ return GetResponseInfo(response, memoryStream, memoryStream.Length, null);
}
}
}
catch (OperationCanceledException ex)
{
- throw GetCancellationException(options, client, options.CancellationToken, ex);
- }
- catch (Exception ex)
- {
- throw GetException(ex, options, client);
- }
- finally
- {
- options.ResourcePool?.Release();
+ throw GetCancellationException(options, options.CancellationToken, ex);
}
}
- private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
+ private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable)
{
var responseInfo = new HttpResponseInfo(disposable)
{
Content = content,
StatusCode = httpResponse.StatusCode,
- ContentType = httpResponse.ContentType,
+ ContentType = httpResponse.Content.Headers.ContentType?.MediaType,
ContentLength = contentLength,
- ResponseUrl = httpResponse.ResponseUri.ToString()
+ ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString()
};
if (httpResponse.Headers != null)
{
- SetHeaders(httpResponse.Headers, responseInfo);
+ SetHeaders(httpResponse.Content.Headers, responseInfo);
}
return responseInfo;
}
- private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength)
+ private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength)
{
var responseInfo = new HttpResponseInfo
{
TempFilePath = tempFile,
StatusCode = httpResponse.StatusCode,
- ContentType = httpResponse.ContentType,
+ ContentType = httpResponse.Content.Headers.ContentType?.MediaType,
ContentLength = contentLength
};
if (httpResponse.Headers != null)
{
- SetHeaders(httpResponse.Headers, responseInfo);
+ SetHeaders(httpResponse.Content.Headers, responseInfo);
}
return responseInfo;
}
- private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo)
+ private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo)
{
- foreach (var key in headers.AllKeys)
+ foreach (var key in headers)
{
- responseInfo.Headers[key] = headers[key];
+ responseInfo.Headers[key.Key] = string.Join(", ", key.Value);
}
}
public Task<HttpResponseInfo> Post(HttpRequestOptions options)
{
- return SendAsync(options, "POST");
- }
-
- /// <summary>
- /// Performs a POST request
- /// </summary>
- /// <param name="options">The options.</param>
- /// <param name="postData">Params to add to the POST data.</param>
- /// <returns>stream on success, null on failure</returns>
- public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData)
- {
- options.SetPostData(postData);
-
- var response = await Post(options).ConfigureAwait(false);
-
- return response.Content;
+ return SendAsync(options, HttpMethod.Post);
}
/// <summary>
@@ -482,9 +451,10 @@ namespace Emby.Server.Implementations.HttpClientManager
/// <returns>Task{System.String}.</returns>
public async Task<string> GetTempFile(HttpRequestOptions options)
{
- var response = await GetTempFileResponse(options).ConfigureAwait(false);
-
- return response.TempFilePath;
+ using (var response = await GetTempFileResponse(options).ConfigureAwait(false))
+ {
+ return response.TempFilePath;
+ }
}
public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options)
@@ -502,44 +472,28 @@ namespace Emby.Server.Implementations.HttpClientManager
options.CancellationToken.ThrowIfCancellationRequested();
- var httpWebRequest = GetRequest(options, "GET");
-
- if (options.ResourcePool != null)
- {
- await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
- }
+ var httpWebRequest = GetRequestMessage(options, HttpMethod.Get);
options.Progress.Report(0);
if (options.LogRequest)
{
- if (options.LogRequestAsDebug)
- {
- _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
- }
- else
- {
- _logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
- }
+ _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
}
- var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
+ var client = GetHttpClient(options.Url, options.EnableHttpCompression);
try
{
options.CancellationToken.ThrowIfCancellationRequested();
- using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false))
+ using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false)))
{
- var httpResponse = (HttpWebResponse)response;
-
- EnsureSuccessStatusCode(client, httpResponse, options);
+ await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
options.CancellationToken.ThrowIfCancellationRequested();
- var contentLength = GetContentLength(httpResponse);
-
- using (var stream = httpResponse.GetResponseStream())
+ using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
@@ -547,35 +501,22 @@ namespace Emby.Server.Implementations.HttpClientManager
options.Progress.Report(100);
- return GetResponseInfo(httpResponse, tempFile, contentLength);
+ var contentLength = response.Content.Headers.ContentLength;
+ return GetResponseInfo(response, tempFile, contentLength);
}
}
catch (Exception ex)
{
- DeleteTempFile(tempFile);
- throw GetException(ex, options, client);
- }
- finally
- {
- options.ResourcePool?.Release();
- }
- }
-
- private static long? GetContentLength(HttpWebResponse response)
- {
- var length = response.ContentLength;
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
- if (length == 0)
- {
- return null;
+ throw GetException(ex, options);
}
-
- return length;
}
- protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client)
+ private Exception GetException(Exception ex, HttpRequestOptions options)
{
if (ex is HttpException)
{
@@ -599,11 +540,6 @@ namespace Emby.Server.Implementations.HttpClientManager
if (response != null)
{
exception.StatusCode = response.StatusCode;
-
- if ((int)response.StatusCode == 429)
- {
- client.LastTimeout = DateTime.UtcNow;
- }
}
}
@@ -624,7 +560,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (operationCanceledException != null)
{
- return GetCancellationException(options, client, options.CancellationToken, operationCanceledException);
+ return GetCancellationException(options, options.CancellationToken, operationCanceledException);
}
if (options.LogErrors)
@@ -635,18 +571,6 @@ namespace Emby.Server.Implementations.HttpClientManager
return ex;
}
- private void DeleteTempFile(string file)
- {
- try
- {
- _fileSystem.DeleteFile(file);
- }
- catch (IOException)
- {
- // Might not have been created at all. No need to worry.
- }
- }
-
private void ValidateParams(HttpRequestOptions options)
{
if (string.IsNullOrEmpty(options.Url))
@@ -682,11 +606,10 @@ namespace Emby.Server.Implementations.HttpClientManager
/// Throws the cancellation exception.
/// </summary>
/// <param name="options">The options.</param>
- /// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="exception">The exception.</param>
/// <returns>Exception.</returns>
- private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception)
+ private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception)
{
// If the HttpClient's timeout is reached, it will cancel the Task internally
if (!cancellationToken.IsCancellationRequested)
@@ -698,8 +621,6 @@ namespace Emby.Server.Implementations.HttpClientManager
_logger.LogError(msg);
}
- client.LastTimeout = DateTime.UtcNow;
-
// Throw an HttpException so that the caller doesn't think it was cancelled by user code
return new HttpException(msg, exception)
{
@@ -710,91 +631,20 @@ namespace Emby.Server.Implementations.HttpClientManager
return exception;
}
- private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options)
+ private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options)
{
- var statusCode = response.StatusCode;
-
- var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299;
-
- if (isSuccessful)
+ if (response.IsSuccessStatusCode)
{
return;
}
- if (options.LogErrorResponseBody)
- {
- try
- {
- using (var stream = response.GetResponseStream())
- {
- if (stream != null)
- {
- using (var reader = new StreamReader(stream))
- {
- var msg = reader.ReadToEnd();
-
- _logger.LogError(msg);
- }
- }
- }
- }
- catch
- {
-
- }
- }
+ var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ _logger.LogError(msg);
- throw new HttpException(response.StatusDescription)
+ throw new HttpException(response.ReasonPhrase)
{
StatusCode = response.StatusCode
};
}
-
- private static Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout)
- {
- var taskCompletion = new TaskCompletionSource<WebResponse>();
-
- var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null);
-
- ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true);
- var callback = new TaskCallback { taskCompletion = taskCompletion };
- asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted);
-
- // Handle errors
- asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted);
-
- return taskCompletion.Task;
- }
-
- private static void TimeoutCallback(object state, bool timedOut)
- {
- if (timedOut && state != null)
- {
- var request = (WebRequest)state;
- request.Abort();
- }
- }
-
- private class TaskCallback
- {
- public TaskCompletionSource<WebResponse> taskCompletion;
-
- public void OnSuccess(Task<WebResponse> task)
- {
- taskCompletion.TrySetResult(task.Result);
- }
-
- public void OnError(Task<WebResponse> task)
- {
- if (task.Exception == null)
- {
- taskCompletion.TrySetException(Enumerable.Empty<Exception>());
- }
- else
- {
- taskCompletion.TrySetException(task.Exception);
- }
- }
- }
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index b3d2b9cc2..d8938964f 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -805,19 +805,15 @@ namespace Emby.Server.Implementations.HttpServer
Logger.LogDebug("Websocket message received: {0}", result.MessageType);
- var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
+ IEnumerable<Task> GetTasks()
{
- try
- {
- await i.ProcessMessage(result).ConfigureAwait(false);
- }
- catch (Exception ex)
+ foreach (var x in _webSocketListeners)
{
- Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
+ yield return x.ProcessMessageAsync(result);
}
- }));
+ }
- return Task.WhenAll(tasks);
+ return Task.WhenAll(GetTasks());
}
public void Dispose()
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 0dea5041a..7c2ea50e2 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -647,7 +647,6 @@ namespace Emby.Server.Implementations.IO
public virtual bool IsPathFile(string path)
{
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
-
if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
@@ -655,8 +654,6 @@ namespace Emby.Server.Implementations.IO
}
return true;
-
- //return Path.IsPathRooted(path);
}
public virtual void DeleteFile(string path)
@@ -667,13 +664,14 @@ namespace Emby.Server.Implementations.IO
public virtual List<FileSystemMetadata> GetDrives()
{
- // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
- return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
+ // check for ready state to avoid waiting for drives to timeout
+ // some drives on linux have no actual size or are used for other purposes
+ return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram)
+ .Select(d => new FileSystemMetadata
{
Name = d.Name,
FullName = d.RootDirectory.FullName,
IsDirectory = true
-
}).ToList();
}
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index 3d15a8afb..fe09b07ff 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Library
public string Name => "Default";
public bool IsEnabled => true;
-
+
// This is dumb and an artifact of the backwards way auth providers were designed.
// This version of authenticate was never meant to be called, but needs to be here for interface compat
// Only the providers that don't provide local user support use this
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library
{
throw new NotImplementedException();
}
-
+
// This is the verson that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
string hash = user.Password;
user.Password = string.Format("$SHA1${0}", hash);
}
-
+
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
{
string hash = user.EasyPassword;
@@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library
return user.Password;
}
+ public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
+ {
+ ConvertPasswordFormat(user);
+
+ if (newPassword != null)
+ {
+ newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword));
+ }
+
+ if (string.IsNullOrWhiteSpace(newPasswordHash))
+ {
+ throw new ArgumentNullException(nameof(newPasswordHash));
+ }
+
+ user.EasyPassword = newPasswordHash;
+ }
+
+ public string GetEasyPasswordHash(User user)
+ {
+ // This should be removed in the future. This was added to let user login after
+ // Jellyfin 10.3.3 failed to save a well formatted PIN.
+ ConvertPasswordFormat(user);
+
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : (new PasswordHash(user.EasyPassword)).Hash;
+ }
+
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
{
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
new file mode 100644
index 000000000..25d233137
--- /dev/null
+++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Net;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class InvalidAuthProvider : IAuthenticationProvider
+ {
+ public string Name => "InvalidOrMissingAuthenticationProvider";
+
+ public bool IsEnabled => true;
+
+ public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
+ {
+ throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
+ }
+
+ public Task<bool> HasPassword(User user)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task ChangePassword(User user, string newPassword)
+ {
+ return Task.CompletedTask;
+ }
+
+ public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
+ {
+ // Nothing here
+ }
+
+ public string GetPasswordHash(User user)
+ {
+ return string.Empty;
+ }
+
+ public string GetEasyPasswordHash(User user)
+ {
+ return string.Empty;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 1673e3777..4b5063ada 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2368,7 +2368,7 @@ namespace Emby.Server.Implementations.Library
public int? GetSeasonNumberFromPath(string path)
{
- return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber;
+ return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
}
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index db270c398..8171c010b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -14,6 +14,18 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
+ private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "folder",
+ "thumb",
+ "landscape",
+ "fanart",
+ "backdrop",
+ "poster",
+ "cover",
+ "logo",
+ "default"
+ };
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
@@ -31,10 +43,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
if (!args.IsDirectory)
{
// Must be an image file within a photo collection
- var collectionType = args.GetCollectionType();
+ var collectionType = args.CollectionType;
- if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
- (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
{
if (IsImageFile(args.Path, _imageProcessor))
{
@@ -74,43 +86,29 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
+
+ internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
{
- if (imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase))
+ if (path == null)
{
- return true;
+ throw new ArgumentNullException(nameof(path));
}
- return false;
- }
-
- private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
- {
- "folder",
- "thumb",
- "landscape",
- "fanart",
- "backdrop",
- "poster",
- "cover",
- "logo",
- "default"
- };
-
- internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
- {
- var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
+ var filename = Path.GetFileNameWithoutExtension(path);
- if (IgnoreFiles.Contains(filename))
+ if (_ignoreFiles.Contains(filename))
{
return false;
}
- if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
+ if (_ignoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
{
return false;
}
- return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
+ string extension = Path.GetExtension(path).TrimStart('.');
+ return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index ce1386e91..3b9e48d97 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var path = args.Path;
- var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true);
+ var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
var season = new Season
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 5c95534ec..1f873d7c6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -194,9 +194,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
{
- var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
-
- var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber;
+ var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index c33bb7740..ff375e590 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -79,6 +79,8 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
+ private InvalidAuthProvider _invalidAuthProvider;
+
private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
@@ -141,6 +143,8 @@ namespace Emby.Server.Implementations.Library
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
+ _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
+
_passwordResetProviders = passwordResetProviders.ToArray();
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
@@ -307,8 +311,7 @@ namespace Emby.Server.Implementations.Library
user = Users
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
- var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy;
- if (hasNewUserPolicy != null)
+ if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
var policy = hasNewUserPolicy.GetNewUserPolicy();
UpdateUserPolicy(user, policy, true);
@@ -400,7 +403,9 @@ namespace Emby.Server.Implementations.Library
if (providers.Length == 0)
{
- providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider };
+ // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
+ _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId);
+ providers = new IAuthenticationProvider[] { _invalidAuthProvider };
}
return providers;
@@ -471,7 +476,7 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
@@ -497,11 +502,11 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
- success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
}
}
}
@@ -546,13 +551,6 @@ namespace Emby.Server.Implementations.Library
}
}
- private string GetLocalPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? null
- : user.EasyPassword;
- }
-
/// <summary>
/// Loads the users from the repository
/// </summary>
@@ -596,7 +594,7 @@ namespace Emby.Server.Implementations.Library
}
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
- bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user));
+ bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
hasConfiguredEasyPassword :
@@ -884,17 +882,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (newPassword != null)
- {
- newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.EasyPassword = newPasswordHash;
+ GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
UpdateUser(user);
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 4137760d0..f3f747718 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/schedules",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
- // The data can be large so give it some extra time
- TimeoutMs = 60000,
LogErrorResponseBody = true,
RequestContent = requestString
};
@@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/programs",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
- LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
@@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken = cancellationToken,
RequestContent = imageIdString,
LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
};
try
@@ -871,8 +865,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
};
httpOptions.RequestHeaders["token"] = token;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 24b100edd..761275f8f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -138,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds),
BufferContent = false
}, "GET").ConfigureAwait(false))
@@ -191,7 +190,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
}))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index d74cf3be2..e8b34da0c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -47,13 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
CancellationToken = CancellationToken.None,
BufferContent = false,
- // Increase a little bit
- TimeoutMs = 30000,
-
EnableHttpCompression = false,
-
- LogResponse = true,
- LogResponseHeaders = true
};
foreach (var header in mediaSource.RequiredHttpHeaders)
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 2285f2808..003632968 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -18,11 +18,11 @@
"HeaderAlbumArtists": "Artistas del Álbum",
"HeaderCameraUploads": "Subidos desde Camara",
"HeaderContinueWatching": "Continuar Viendo",
- "HeaderFavoriteAlbums": "Álbumes Favoritos",
- "HeaderFavoriteArtists": "Artistas Favoritos",
- "HeaderFavoriteEpisodes": "Episodios Preferidos",
- "HeaderFavoriteShows": "Programas Preferidos",
- "HeaderFavoriteSongs": "Canciones Favoritas",
+ "HeaderFavoriteAlbums": "Álbumes favoritos",
+ "HeaderFavoriteArtists": "Artistas favoritos",
+ "HeaderFavoriteEpisodes": "Episodios favoritos",
+ "HeaderFavoriteShows": "Programas favoritos",
+ "HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en Vivo",
"HeaderNextUp": "A Continuación",
"HeaderRecordingGroups": "Grupos de Grabaciones",
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 1850b8f25..f03184d5b 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -21,7 +21,7 @@
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
- "HeaderFavoriteShows": "Programas favoritos",
+ "HeaderFavoriteShows": "Series favoritas",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en directo",
"HeaderNextUp": "Siguiendo",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
new file mode 100644
index 000000000..53a43a125
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -0,0 +1,96 @@
+{
+ "Albums": "アルバム",
+ "AppDeviceValues": "アプリ: {0}, デバイス: {1}",
+ "Application": "アプリケーション",
+ "Artists": "アーティスト",
+ "AuthenticationSucceededWithUserName": "{0} 認証に成功しました",
+ "Books": "ブック",
+ "CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました",
+ "Channels": "チャンネル",
+ "ChapterNameValue": "チャプター {0}",
+ "Collections": "コレクション",
+ "DeviceOfflineWithName": "{0} が切断されました",
+ "DeviceOnlineWithName": "{0} が接続されました",
+ "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました",
+ "Favorites": "お気に入り",
+ "Folders": "フォルダ",
+ "Genres": "ジャンル",
+ "HeaderAlbumArtists": "アルバムアーティスト",
+ "HeaderCameraUploads": "カメラアップロード",
+ "HeaderContinueWatching": "視聴中",
+ "HeaderFavoriteAlbums": "お気に入りのアルバム",
+ "HeaderFavoriteArtists": "お気に入りのアーティスト",
+ "HeaderFavoriteEpisodes": "お気に入りのエピソード",
+ "HeaderFavoriteShows": "お気に入りの番組",
+ "HeaderFavoriteSongs": "お気に入りの曲",
+ "HeaderLiveTV": "ライブ テレビ",
+ "HeaderNextUp": "次",
+ "HeaderRecordingGroups": "レコーディンググループ",
+ "HomeVideos": "ホームビデオ",
+ "Inherit": "継承",
+ "ItemAddedWithName": "{0} をライブラリに追加しました",
+ "ItemRemovedWithName": "{0} をライブラリから削除しました",
+ "LabelIpAddressValue": "IPアドレス: {0}",
+ "LabelRunningTimeValue": "稼働時間: {0}",
+ "Latest": "最新",
+ "MessageApplicationUpdated": "Jellyfin Server が更新されました",
+ "MessageApplicationUpdatedTo": "Jellyfin Server が {0}に更新されました",
+ "MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました",
+ "MessageServerConfigurationUpdated": "サーバー設定が更新されました",
+ "MixedContent": "ミックスコンテンツ",
+ "Movies": "ムービー",
+ "Music": "ミュージック",
+ "MusicVideos": "ミュージックビデオ",
+ "NameInstallFailed": "{0}のインストールに失敗しました",
+ "NameSeasonNumber": "シーズン {0}",
+ "NameSeasonUnknown": "不明なシーズン",
+ "NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。",
+ "NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
+ "NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
+ "NotificationOptionAudioPlayback": "オーディオの再生を開始",
+ "NotificationOptionAudioPlaybackStopped": "オーディオの再生をストップしました",
+ "NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました",
+ "NotificationOptionInstallationFailed": "インストール失敗",
+ "NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました",
+ "NotificationOptionPluginError": "プラグインに障害が発生しました",
+ "NotificationOptionPluginInstalled": "プラグインがインストールされました",
+ "NotificationOptionPluginUninstalled": "プラグインがアンインストールされました",
+ "NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました",
+ "NotificationOptionServerRestartRequired": "サーバーを再起動してください",
+ "NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗",
+ "NotificationOptionUserLockedOut": "ユーザーはロックされています",
+ "NotificationOptionVideoPlayback": "ビデオの再生を開始しました",
+ "NotificationOptionVideoPlaybackStopped": "ビデオを停止しました",
+ "Photos": "フォト",
+ "Playlists": "プレイリスト",
+ "Plugin": "プラグイン",
+ "PluginInstalledWithName": "{0} がインストールされました",
+ "PluginUninstalledWithName": "{0} がアンインストールされました",
+ "PluginUpdatedWithName": "{0} が更新されました",
+ "ProviderValue": "プロバイダ: {0}",
+ "ScheduledTaskFailedWithName": "{0} が失敗しました",
+ "ScheduledTaskStartedWithName": "{0} が開始されました",
+ "ServerNameNeedsToBeRestarted": "{0} を再起動してください",
+ "Shows": "番組",
+ "Songs": "曲",
+ "StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。",
+ "SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました",
+ "SubtitlesDownloadedForItem": "{0} の字幕がダウンロードされました",
+ "Sync": "同期",
+ "System": "システム",
+ "TvShows": "テレビ番組",
+ "User": "ユーザー",
+ "UserCreatedWithName": "ユーザー {0} が作成されました",
+ "UserDeletedWithName": "User {0} を削除しました",
+ "UserDownloadingItemWithValues": "{0} が {1} をダウンロードしています",
+ "UserLockedOutWithName": "ユーザー {0} はロックされています",
+ "UserOfflineFromDevice": "{0} は {1} から切断しました",
+ "UserOnlineFromDevice": "{0} は {1} からオンラインになりました",
+ "UserPasswordChangedWithName": "ユーザー {0} のパスワードは変更されました",
+ "UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました",
+ "UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています",
+ "UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
+ "ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました",
+ "ValueSpecialEpisodeName": "スペシャル - {0}",
+ "VersionNumber": "バージョン {0}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 21808fd18..5a7ba8ba7 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -1,88 +1,88 @@
{
- "Albums": "Albums",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "Application": "Application",
- "Artists": "Artists",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "Books": "Books",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
- "Channels": "Channels",
- "ChapterNameValue": "Chapter {0}",
- "Collections": "Collections",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
- "Genres": "Genres",
+ "Albums": "앨범",
+ "AppDeviceValues": "앱: {0}, 디바이스: {1}",
+ "Application": "애플리케이션",
+ "Artists": "아티스트",
+ "AuthenticationSucceededWithUserName": "{0} 인증에 성공했습니다.",
+ "Books": "책",
+ "CameraImageUploadedFrom": "새로운 카메라 이미지가 {0}에서 업로드되었습니다.",
+ "Channels": "채널",
+ "ChapterNameValue": "챕터 {0}",
+ "Collections": "컬렉션",
+ "DeviceOfflineWithName": "{0}가 접속이 끊어졌습니다.",
+ "DeviceOnlineWithName": "{0}가 접속되었습니다.",
+ "FailedLoginAttemptWithUserName": "{0}에서 로그인이 실패했습니다.",
+ "Favorites": "즐겨찾기",
+ "Folders": "폴더",
+ "Genres": "장르",
"HeaderAlbumArtists": "앨범 아티스트",
- "HeaderCameraUploads": "Camera Uploads",
+ "HeaderCameraUploads": "카메라 업로드",
"HeaderContinueWatching": "계속 시청하기",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
+ "HeaderFavoriteAlbums": "좋아하는 앨범",
+ "HeaderFavoriteArtists": "좋아하는 아티스트",
"HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteShows": "즐겨찾는 쇼",
- "HeaderFavoriteSongs": "Favorite Songs",
- "HeaderLiveTV": "Live TV",
- "HeaderNextUp": "Next Up",
- "HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
- "Inherit": "Inherit",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
- "Latest": "Latest",
- "MessageApplicationUpdated": "Jellyfin Server has been updated",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MixedContent": "Mixed content",
- "Movies": "Movies",
- "Music": "Music",
- "MusicVideos": "Music videos",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
- "NotificationOptionApplicationUpdateInstalled": "Application update installed",
- "NotificationOptionAudioPlayback": "Audio playback started",
- "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionInstallationFailed": "Installation failure",
- "NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionVideoPlayback": "Video playback started",
- "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
- "Photos": "Photos",
- "Playlists": "Playlists",
- "Plugin": "Plugin",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "PluginUpdatedWithName": "{0} was updated",
- "ProviderValue": "Provider: {0}",
- "ScheduledTaskFailedWithName": "{0} failed",
- "ScheduledTaskStartedWithName": "{0} started",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Shows",
- "Songs": "Songs",
+ "HeaderFavoriteSongs": "좋아하는 노래",
+ "HeaderLiveTV": "TV 방송",
+ "HeaderNextUp": "다음으로",
+ "HeaderRecordingGroups": "녹화 그룹",
+ "HomeVideos": "홈 비디오",
+ "Inherit": "상속",
+ "ItemAddedWithName": "{0} 라이브러리에 추가됨",
+ "ItemRemovedWithName": "{0} 라이브러리에서 제거됨",
+ "LabelIpAddressValue": "IP 주소: {0}",
+ "LabelRunningTimeValue": "상영 시간: {0}",
+ "Latest": "최근",
+ "MessageApplicationUpdated": "Jellyfin 서버 업데이트됨",
+ "MessageApplicationUpdatedTo": "Jellyfin 서버가 {0}로 업데이트됨",
+ "MessageNamedServerConfigurationUpdatedWithValue": "서버 환경 설정 {0} 섹션 업데이트 됨",
+ "MessageServerConfigurationUpdated": "서버 환경 설정 업데이드됨",
+ "MixedContent": "혼합 콘텐츠",
+ "Movies": "영화",
+ "Music": "음악",
+ "MusicVideos": "뮤직 비디오",
+ "NameInstallFailed": "{0} 설치 실패.",
+ "NameSeasonNumber": "시즌 {0}",
+ "NameSeasonUnknown": "알 수 없는 시즌",
+ "NewVersionIsAvailable": "새 버전의 Jellyfin 서버를 사용할 수 있습니다.",
+ "NotificationOptionApplicationUpdateAvailable": "애플리케이션 업데이트 사용 가능",
+ "NotificationOptionApplicationUpdateInstalled": "애플리케이션 업데이트가 설치됨",
+ "NotificationOptionAudioPlayback": "오디오 재생을 시작함",
+ "NotificationOptionAudioPlaybackStopped": "오디오 재생이 중지됨",
+ "NotificationOptionCameraImageUploaded": "카메라 이미지가 업로드됨",
+ "NotificationOptionInstallationFailed": "설치 실패",
+ "NotificationOptionNewLibraryContent": "새 콘텐트가 추가됨",
+ "NotificationOptionPluginError": "플러그인 실패",
+ "NotificationOptionPluginInstalled": "플러그인이 설치됨",
+ "NotificationOptionPluginUninstalled": "플러그인이 설치 제거됨",
+ "NotificationOptionPluginUpdateInstalled": "플러그인 업데이트가 설치됨",
+ "NotificationOptionServerRestartRequired": "서버를 다시 시작하십시오",
+ "NotificationOptionTaskFailed": "예약 작업 실패",
+ "NotificationOptionUserLockedOut": "사용자가 잠겼습니다",
+ "NotificationOptionVideoPlayback": "비디오 재생을 시작함",
+ "NotificationOptionVideoPlaybackStopped": "비디오 재생이 중지됨",
+ "Photos": "사진",
+ "Playlists": "재생목록",
+ "Plugin": "플러그인",
+ "PluginInstalledWithName": "{0} 설치됨",
+ "PluginUninstalledWithName": "{0} 설치 제거됨",
+ "PluginUpdatedWithName": "{0} 업데이트됨",
+ "ProviderValue": "제공자: {0}",
+ "ScheduledTaskFailedWithName": "{0} 실패",
+ "ScheduledTaskStartedWithName": "{0} 시작",
+ "ServerNameNeedsToBeRestarted": "{0} 를 재시작하십시오",
+ "Shows": "프로그램",
+ "Songs": "노래",
"StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시후 다시시도 해주세요.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "Sync": "Sync",
- "System": "System",
- "TvShows": "TV Shows",
- "User": "User",
- "UserCreatedWithName": "User {0} has been created",
- "UserDeletedWithName": "User {0} has been deleted",
+ "SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다",
+ "SubtitlesDownloadedForItem": "{0} 자막을 다운로드했습니다",
+ "Sync": "동기화",
+ "System": "시스템",
+ "TvShows": "TV 쇼",
+ "User": "사용자",
+ "UserCreatedWithName": "사용자 {0} 생성됨",
+ "UserDeletedWithName": "사용자 {0} 삭제됨",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {1}",
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index 558904f06..e2f3ba3dc 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -1,21 +1,21 @@
{
- "Albums": "Albums",
+ "Albums": "Albumai",
"AppDeviceValues": "App: {0}, Device: {1}",
"Application": "Application",
- "Artists": "Artists",
+ "Artists": "Atlikėjai",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "Books": "Books",
+ "Books": "Knygos",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
- "Channels": "Channels",
+ "Channels": "Kanalai",
"ChapterNameValue": "Chapter {0}",
- "Collections": "Collections",
+ "Collections": "Kolekcijos",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
+ "Favorites": "Mėgstami",
+ "Folders": "Katalogai",
"Genres": "Žanrai",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Albumo atlikėjai",
"HeaderCameraUploads": "Camera Uploads",
"HeaderContinueWatching": "Žiūrėti toliau",
"HeaderFavoriteAlbums": "Favorite Albums",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index effff5566..293fc28a8 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -89,5 +89,8 @@
"UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫",
"ValueSpecialEpisodeName": "特典 - {0}",
- "VersionNumber": "版本 {0}"
+ "VersionNumber": "版本 {0}",
+ "HeaderRecordingGroups": "錄製組",
+ "Inherit": "繼承",
+ "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
}
diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
index c81a93767..29b8dfd3d 100644
--- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
@@ -15,13 +15,9 @@ namespace Emby.Server.Implementations.Security
{
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
{
- private readonly IServerConfigurationManager _config;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config)
: base(loggerFactory.CreateLogger(nameof(AuthenticationRepository)))
{
- _config = config;
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
}
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index 6a522fbef..c27eb7686 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Services
foreach (var propertyInfo in RestPath.GetSerializableProperties(type))
{
- var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
+ var propertySetFn = TypeAccessor.GetSetPropertyMethod(propertyInfo);
var propertyType = propertyInfo.PropertyType;
var propertyParseStringFn = GetParseFn(propertyType);
var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType);
@@ -110,9 +110,9 @@ namespace Emby.Server.Implementations.Services
}
}
- internal class TypeAccessor
+ internal static class TypeAccessor
{
- public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
+ public static Action<object, object> GetSetPropertyMethod(PropertyInfo propertyInfo)
{
if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0)
{
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 985748caf..53ed5fc22 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -7,7 +7,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
@@ -25,7 +24,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -53,8 +51,6 @@ namespace Emby.Server.Implementations.Session
private readonly IImageProcessor _imageProcessor;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private readonly IAuthenticationRepository _authRepo;
@@ -96,9 +92,7 @@ namespace Emby.Server.Implementations.Session
IMusicManager musicManager,
IDtoService dtoService,
IImageProcessor imageProcessor,
- IJsonSerializer jsonSerializer,
IServerApplicationHost appHost,
- IHttpClient httpClient,
IAuthenticationRepository authRepo,
IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager)
@@ -110,9 +104,7 @@ namespace Emby.Server.Implementations.Session
_musicManager = musicManager;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _jsonSerializer = jsonSerializer;
_appHost = appHost;
- _httpClient = httpClient;
_authRepo = authRepo;
_deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
@@ -347,8 +339,7 @@ namespace Emby.Server.Implementations.Session
var runtimeTicks = libraryItem.RunTimeTicks;
MediaSourceInfo mediaSource = null;
- var hasMediaSources = libraryItem as IHasMediaSources;
- if (hasMediaSources != null)
+ if (libraryItem is IHasMediaSources hasMediaSources)
{
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
@@ -1046,6 +1037,24 @@ namespace Emby.Server.Implementations.Session
}
}
+ private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, string name, T data, CancellationToken cancellationToken)
+ {
+ IEnumerable<Task> GetTasks()
+ {
+ var messageId = Guid.NewGuid().ToString("N");
+ foreach (var session in sessions)
+ {
+ var controllers = session.SessionControllers;
+ foreach (var controller in controllers)
+ {
+ yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken);
+ }
+ }
+ }
+
+ return Task.WhenAll(GetTasks());
+ }
+
public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1232,12 +1241,13 @@ namespace Emby.Server.Implementations.Session
return SendMessageToSession(session, "Playstate", command, cancellationToken);
}
- private void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
+ private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
{
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
+
if (controllingSession == null)
{
throw new ArgumentNullException(nameof(controllingSession));
@@ -1249,26 +1259,11 @@ namespace Emby.Server.Implementations.Session
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- public async Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+ public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
{
CheckDisposed();
- var sessions = Sessions.ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error in SendRestartRequiredNotification.", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
+ return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken);
}
/// <summary>
@@ -1280,22 +1275,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- var sessions = Sessions.ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error in SendServerShutdownNotification.", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken);
}
/// <summary>
@@ -1309,22 +1289,7 @@ namespace Emby.Server.Implementations.Session
_logger.LogDebug("Beginning SendServerRestartNotification");
- var sessions = Sessions.ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error in SendServerRestartNotification.", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken);
}
/// <summary>
@@ -1841,64 +1806,23 @@ namespace Emby.Server.Implementations.Session
var data = dataFn();
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error sending message", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ return SendMessageToSessions(sessions, name, data, cancellationToken);
}
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken)
{
CheckDisposed();
- var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error sending message", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser));
+ return SendMessageToSessions(sessions, name, data, cancellationToken);
}
public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
{
CheckDisposed();
- var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error sending message", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
+ return SendMessageToSessions(sessions, name, data, cancellationToken);
}
public Task SendMessageToUserDeviceAndAdminSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
@@ -1906,23 +1830,8 @@ namespace Emby.Server.Implementations.Session
CheckDisposed();
var sessions = Sessions
- .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i))
- .ToList();
-
- var tasks = sessions.Select(session => Task.Run(async () =>
- {
- try
- {
- await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error sending message", ex);
- }
-
- }, cancellationToken)).ToArray();
-
- return Task.WhenAll(tasks);
+ .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
+ return SendMessageToSessions(sessions, name, data, cancellationToken);
}
private bool IsAdminSession(SessionInfo s)
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index a551433ed..63ec75762 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -89,10 +89,8 @@ namespace Emby.Server.Implementations.Session
/// </summary>
/// <param name="message">The message.</param>
/// <returns>Task.</returns>
- public Task ProcessMessage(WebSocketMessageInfo message)
- {
- return Task.CompletedTask;
- }
+ public Task ProcessMessageAsync(WebSocketMessageInfo message)
+ => Task.CompletedTask;
private void EnsureController(SessionInfo session, IWebSocketConnection connection)
{
diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs
index 373f6d758..ec637186f 100644
--- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs
+++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs
@@ -86,8 +86,7 @@ namespace Emby.Server.Implementations.SocketSharp
else
{
// We use a substream, as in 2.x we will support large uploads streamed to disk,
- var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
- files[e.Name] = sub;
+ files[e.Name] = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
}
}
}
@@ -374,7 +373,7 @@ namespace Emby.Server.Implementations.SocketSharp
var elem = new Element();
ReadOnlySpan<char> header;
- while ((header = ReadHeaders().AsSpan()) != null)
+ while ((header = ReadLine().AsSpan()).Length != 0)
{
if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
@@ -513,17 +512,6 @@ namespace Emby.Server.Implementations.SocketSharp
return false;
}
- private string ReadHeaders()
- {
- string s = ReadLine();
- if (s.Length == 0)
- {
- return null;
- }
-
- return s;
- }
-
private static bool CompareBytes(byte[] orig, byte[] other)
{
for (int i = orig.Length - 1; i >= 0; i--)
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
index 00465b63e..7a630bf10 100644
--- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Linq;
using System.Text;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Services;
@@ -296,27 +297,28 @@ namespace Emby.Server.Implementations.SocketSharp
{
get
{
- if (httpFiles == null)
+ if (httpFiles != null)
{
- if (files == null)
- {
- return httpFiles = Array.Empty<IHttpFile>();
- }
+ return httpFiles;
+ }
- httpFiles = new IHttpFile[files.Count];
- var i = 0;
- foreach (var pair in files)
+ if (files == null)
+ {
+ return httpFiles = Array.Empty<IHttpFile>();
+ }
+
+ var values = files.Values;
+ httpFiles = new IHttpFile[values.Count];
+ for (int i = 0; i < values.Count; i++)
+ {
+ var reqFile = values.ElementAt(i);
+ httpFiles[i] = new HttpFile
{
- var reqFile = pair.Value;
- httpFiles[i] = new HttpFile
- {
- ContentType = reqFile.ContentType,
- ContentLength = reqFile.ContentLength,
- FileName = reqFile.FileName,
- InputStream = reqFile.InputStream,
- };
- i++;
- }
+ ContentType = reqFile.ContentType,
+ ContentLength = reqFile.ContentLength,
+ FileName = reqFile.FileName,
+ InputStream = reqFile.InputStream,
+ };
}
return httpFiles;
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 9346a2d25..641b3f182 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -12,7 +12,7 @@
<!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion>
<!-- Disable documentation warnings (for now) -->
- <NoWarn>SA1600;SA1601;CS1591</NoWarn>
+ <NoWarn>SA1600;SA1601;SA1629;CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
@@ -26,8 +26,8 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
</ItemGroup>
@@ -36,17 +36,17 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="CommandLineParser" Version="2.4.3" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" />
+ <PackageReference Include="CommandLineParser" Version="2.5.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
- <PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
- <PackageReference Include="Serilog.Sinks.Async" Version="1.3.0" />
+ <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
+ <PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="SkiaSharp" Version="1.68.0" />
- <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.13" />
- <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.13" />
+ <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.14" />
+ <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index fab584bef..049dd761b 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -36,7 +36,7 @@ namespace Jellyfin.Server
private static bool _restartOnShutdown;
private static IConfiguration appConfig;
- public static async Task Main(string[] args)
+ public static Task Main(string[] args)
{
// For backwards compatibility.
// Modify any input arguments now which start with single-hyphen to POSIX standard
@@ -50,8 +50,8 @@ namespace Jellyfin.Server
}
// Parse the command line arguments and either start the app or exit indicating error
- await Parser.Default.ParseArguments<StartupOptions>(args)
- .MapResult(StartApp, _ => Task.CompletedTask).ConfigureAwait(false);
+ return Parser.Default.ParseArguments<StartupOptions>(args)
+ .MapResult(StartApp, _ => Task.CompletedTask);
}
public static void Shutdown()
@@ -122,8 +122,12 @@ namespace Jellyfin.Server
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
+// CA5359: Do Not Disable Certificate Validation
+#pragma warning disable CA5359
+
// Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
+#pragma warning restore CA5359
var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths);
@@ -368,7 +372,7 @@ namespace Jellyfin.Server
}
catch (Exception ex)
{
- _logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}");
+ _logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder.");
}
return new NullImageEncoder();
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 700cbb943..a223a4fe3 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -415,7 +415,7 @@ namespace MediaBrowser.Api
public void OnTranscodeEndRequest(TranscodingJob job)
{
job.ActiveRequestCount--;
- //Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
+ Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
if (job.ActiveRequestCount <= 0)
{
PingTimer(job, false);
@@ -428,7 +428,7 @@ namespace MediaBrowser.Api
throw new ArgumentNullException(nameof(playSessionId));
}
- //Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
+ Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
List<TranscodingJob> jobs;
@@ -443,7 +443,7 @@ namespace MediaBrowser.Api
{
if (isUserPaused.HasValue)
{
- //Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
+ Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
job.IsUserPaused = isUserPaused.Value;
}
PingTimer(job, true);
@@ -601,7 +601,6 @@ namespace MediaBrowser.Api
{
Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
- //process.Kill();
process.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous
@@ -701,7 +700,7 @@ namespace MediaBrowser.Api
{
try
{
- //Logger.LogDebug("Deleting HLS file {0}", file);
+ Logger.LogDebug("Deleting HLS file {0}", file);
_fileSystem.DeleteFile(file);
}
catch (FileNotFoundException)
@@ -840,12 +839,12 @@ namespace MediaBrowser.Api
{
if (KillTimer == null)
{
- //Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
}
else
{
- //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
@@ -864,7 +863,7 @@ namespace MediaBrowser.Api
{
var intervalMs = PingTimeout;
- //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 8eefbdf2c..cee96f7ab 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Api.Movies;
@@ -488,18 +490,6 @@ namespace MediaBrowser.Api.Library
{
return false;
}
- else if (string.Equals(name, "FanArt", StringComparison.OrdinalIgnoreCase))
- {
- if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- return true;
- }
else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
{
return true;
@@ -828,7 +818,16 @@ namespace MediaBrowser.Api.Library
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
if (!string.IsNullOrWhiteSpace(filename))
{
- headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
+ // Kestrel doesn't support non-ASCII characters in headers
+ if (Regex.IsMatch(filename, "[^[:ascii:]]"))
+ {
+ // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
+ headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
+ }
+ else
+ {
+ headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
+ }
}
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
@@ -988,19 +987,16 @@ namespace MediaBrowser.Api.Library
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(RefreshLibrary request)
+ public async Task Post(RefreshLibrary request)
{
- Task.Run(() =>
+ try
{
- try
- {
- _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error refreshing library");
- }
- });
+ await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error refreshing library");
+ }
}
/// <summary>
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index ae259a4f5..399401624 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -8,7 +8,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -32,6 +30,8 @@ namespace MediaBrowser.Api.Playback
/// </summary>
public abstract class BaseStreamingService : BaseApiService
{
+ protected virtual bool EnableOutputInSubFolder => false;
+
/// <summary>
/// Gets or sets the application paths.
/// </summary>
@@ -65,9 +65,13 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; }
protected IDlnaManager DlnaManager { get; private set; }
+
protected IDeviceManager DeviceManager { get; private set; }
+
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
+
protected IMediaSourceManager MediaSourceManager { get; private set; }
+
protected IJsonSerializer JsonSerializer { get; private set; }
protected IAuthorizationContext AuthorizationContext { get; private set; }
@@ -75,6 +79,12 @@ namespace MediaBrowser.Api.Playback
protected EncodingHelper EncodingHelper { get; set; }
/// <summary>
+ /// Gets the type of the transcoding job.
+ /// </summary>
+ /// <value>The type of the transcoding job.</value>
+ protected abstract TranscodingJobType TranscodingJobType { get; }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
protected BaseStreamingService(
@@ -113,12 +123,6 @@ namespace MediaBrowser.Api.Playback
protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
/// <summary>
- /// Gets the type of the transcoding job.
- /// </summary>
- /// <value>The type of the transcoding job.</value>
- protected abstract TranscodingJobType TranscodingJobType { get; }
-
- /// <summary>
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
@@ -133,31 +137,24 @@ namespace MediaBrowser.Api.Playback
/// </summary>
private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
{
- var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
- data += "-" + (state.Request.DeviceId ?? string.Empty);
- data += "-" + (state.Request.PlaySessionId ?? string.Empty);
+ data += "-" + (state.Request.DeviceId ?? string.Empty)
+ + "-" + (state.Request.PlaySessionId ?? string.Empty);
- var dataHash = data.GetMD5().ToString("N");
+ var filename = data.GetMD5().ToString("N");
+ var ext = outputFileExtension.ToLowerInvariant();
+ var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
if (EnableOutputInSubFolder)
{
- return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+ return Path.Combine(folder, filename, filename + ext);
}
- return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+ return Path.Combine(folder, filename + ext);
}
- protected virtual bool EnableOutputInSubFolder => false;
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- protected virtual string GetDefaultH264Preset()
- {
- return "superfast";
- }
+ protected virtual string GetDefaultH264Preset() => "superfast";
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
{
@@ -171,7 +168,6 @@ namespace MediaBrowser.Api.Playback
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
{
OpenToken = state.MediaSource.OpenToken
-
}, cancellationTokenSource.Token).ConfigureAwait(false);
EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
@@ -209,22 +205,16 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (auth.User != null)
+ if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding)
{
- if (!auth.User.Policy.EnableVideoPlaybackTranscoding)
- {
- ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
+ ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
- throw new ArgumentException("User does not have access to video transcoding");
- }
+ throw new ArgumentException("User does not have access to video transcoding");
}
}
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- var transcodingId = Guid.NewGuid().ToString("N");
- var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
-
var process = new Process()
{
StartInfo = new ProcessStartInfo()
@@ -239,7 +229,7 @@ namespace MediaBrowser.Api.Playback
RedirectStandardInput = true,
FileName = MediaEncoder.EncoderPath,
- Arguments = commandLineArgs,
+ Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true),
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
ErrorDialog = false
@@ -250,7 +240,7 @@ namespace MediaBrowser.Api.Playback
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
state.Request.PlaySessionId,
state.MediaSource.LiveStreamId,
- transcodingId,
+ Guid.NewGuid().ToString("N"),
TranscodingJobType,
process,
state.Request.DeviceId,
@@ -261,27 +251,26 @@ namespace MediaBrowser.Api.Playback
Logger.LogInformation(commandLineLogMessage);
var logFilePrefix = "ffmpeg-transcode";
- if (state.VideoRequest != null)
+ if (state.VideoRequest != null
+ && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
logFilePrefix = "ffmpeg-directstream";
}
- else if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ else
{
logFilePrefix = "ffmpeg-remux";
}
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
- Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
+ Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
+ await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
@@ -298,13 +287,10 @@ namespace MediaBrowser.Api.Playback
throw;
}
- // MUST read both stdout and stderr asynchronously or a deadlock may occurr
- //process.BeginOutputReadLine();
-
state.TranscodingJob = transcodingJob;
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
+ _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
// Wait for the file to exist before proceeeding
while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@@ -368,25 +354,16 @@ namespace MediaBrowser.Api.Playback
Logger.LogDebug("Disposing stream resources");
state.Dispose();
- try
+ if (process.ExitCode == 0)
{
- Logger.LogInformation("FFMpeg exited with code {0}", process.ExitCode);
+ Logger.LogInformation("FFMpeg exited with code 0");
}
- catch
+ else
{
- Logger.LogError("FFMpeg exited with an error.");
+ Logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
}
- // This causes on exited to be called twice:
- //try
- //{
- // // Dispose the process
- // process.Dispose();
- //}
- //catch (Exception ex)
- //{
- // Logger.LogError(ex, "Error disposing ffmpeg.");
- //}
+ process.Dispose();
}
/// <summary>
@@ -439,55 +416,55 @@ namespace MediaBrowser.Api.Playback
{
if (videoRequest != null)
{
- videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
+ videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 7)
{
if (videoRequest != null)
{
- videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
+ videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 8)
{
if (videoRequest != null)
{
- videoRequest.VideoBitRate = int.Parse(val, UsCulture);
+ videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 9)
{
- request.AudioBitRate = int.Parse(val, UsCulture);
+ request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 10)
{
- request.MaxAudioChannels = int.Parse(val, UsCulture);
+ request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 11)
{
if (videoRequest != null)
{
- videoRequest.MaxFramerate = float.Parse(val, UsCulture);
+ videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 12)
{
if (videoRequest != null)
{
- videoRequest.MaxWidth = int.Parse(val, UsCulture);
+ videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 13)
{
if (videoRequest != null)
{
- videoRequest.MaxHeight = int.Parse(val, UsCulture);
+ videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 14)
{
- request.StartTimeTicks = long.Parse(val, UsCulture);
+ request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 15)
{
@@ -500,14 +477,14 @@ namespace MediaBrowser.Api.Playback
{
if (videoRequest != null)
{
- videoRequest.MaxRefFrames = int.Parse(val, UsCulture);
+ videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 17)
{
if (videoRequest != null)
{
- videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture);
+ videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 18)
@@ -556,7 +533,7 @@ namespace MediaBrowser.Api.Playback
}
else if (i == 26)
{
- request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
+ request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 27)
{
@@ -643,16 +620,25 @@ namespace MediaBrowser.Api.Playback
return null;
}
- if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
+ const string Npt = "npt=";
+ if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid timeseek header");
}
- value = value.Substring(4).Split(new[] { '-' }, 2)[0];
+ int index = value.IndexOf('-');
+ if (index == -1)
+ {
+ value = value.Substring(Npt.Length);
+ }
+ else
+ {
+ value = value.Substring(Npt.Length, index);
+ }
if (value.IndexOf(':') == -1)
{
// Parses npt times in the format of '417.33'
- if (double.TryParse(value, NumberStyles.Any, UsCulture, out var seconds))
+ if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
{
return TimeSpan.FromSeconds(seconds).Ticks;
}
@@ -667,7 +653,7 @@ namespace MediaBrowser.Api.Playback
foreach (var time in tokens)
{
- if (double.TryParse(time, NumberStyles.Any, UsCulture, out var digit))
+ if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
{
secondsSum += digit * timeFactor;
}
@@ -707,7 +693,7 @@ namespace MediaBrowser.Api.Playback
var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
- var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
+ var state = new StreamState(MediaSourceManager, TranscodingJobType)
{
Request = request,
RequestedUrl = url,
@@ -728,13 +714,10 @@ namespace MediaBrowser.Api.Playback
// state.SegmentLength = 6;
//}
- if (state.VideoRequest != null)
+ if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
{
- if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
- {
- state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
- state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
- }
+ state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+ state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
}
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
@@ -779,12 +762,12 @@ namespace MediaBrowser.Api.Playback
var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
+ ? mediaSources[0]
+ : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
{
- mediaSource = mediaSources.First();
+ mediaSource = mediaSources[0];
}
}
}
@@ -834,11 +817,11 @@ namespace MediaBrowser.Api.Playback
if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
var resolution = ResolutionNormalizer.Normalize(
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
- state.VideoStream == null ? (int?)null : state.VideoStream.Width,
- state.VideoStream == null ? (int?)null : state.VideoStream.Height,
+ state.VideoStream?.BitRate,
+ state.VideoStream?.Width,
+ state.VideoStream?.Height,
state.OutputVideoBitrate.Value,
- state.VideoStream == null ? null : state.VideoStream.Codec,
+ state.VideoStream?.Codec,
state.OutputVideoCodec,
videoRequest.MaxWidth,
videoRequest.MaxHeight);
@@ -846,17 +829,13 @@ namespace MediaBrowser.Api.Playback
videoRequest.MaxWidth = resolution.MaxWidth;
videoRequest.MaxHeight = resolution.MaxHeight;
}
-
- ApplyDeviceProfileSettings(state);
- }
- else
- {
- ApplyDeviceProfileSettings(state);
}
+ ApplyDeviceProfileSettings(state);
+
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state)
- : ("." + state.OutputContainer);
+ : ('.' + state.OutputContainer);
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
@@ -970,18 +949,18 @@ namespace MediaBrowser.Api.Playback
responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
- if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
+ if (state.RunTimeTicks.HasValue)
{
- if (state.RunTimeTicks.HasValue)
+ if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
{
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
}
- }
- if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null)
- {
- AddTimeSeekResponseHeaders(state, responseHeaders);
+ if (!isStaticallyStreamed && profile != null)
+ {
+ AddTimeSeekResponseHeaders(state, responseHeaders);
+ }
}
if (profile == null)
@@ -1046,8 +1025,8 @@ namespace MediaBrowser.Api.Playback
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
{
- var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
- var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
+ var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 1acc42ea5..3c6d33e7e 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
@@ -143,10 +144,10 @@ namespace MediaBrowser.Api.Playback.Hls
text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
- var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture);
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
- text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
return text;
}
@@ -163,7 +164,7 @@ namespace MediaBrowser.Api.Playback.Hls
var paddedBitrate = Convert.ToInt32(bitrate * 1.15);
// Main stream
- builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture));
+ builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(CultureInfo.InvariantCulture));
var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8");
builder.AppendLine(playlistUrl);
@@ -231,7 +232,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var itsOffsetMs = 0;
- var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
+ var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(CultureInfo.InvariantCulture));
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
@@ -240,7 +241,7 @@ namespace MediaBrowser.Api.Playback.Hls
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
// If isEncoding is true we're actually starting ffmpeg
- var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
+ var startNumberParam = isEncoding ? GetStartNumber(state).ToString(CultureInfo.InvariantCulture) : "0";
var baseUrlParam = string.Empty;
@@ -272,7 +273,7 @@ namespace MediaBrowser.Api.Playback.Hls
EncodingHelper.GetMapArgs(state),
GetVideoArguments(state, encodingOptions),
GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
+ state.SegmentLength.ToString(CultureInfo.InvariantCulture),
startNumberParam,
outputPath,
outputTsArg,
@@ -293,9 +294,9 @@ namespace MediaBrowser.Api.Playback.Hls
EncodingHelper.GetMapArgs(state),
GetVideoArguments(state, encodingOptions),
GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
+ state.SegmentLength.ToString(CultureInfo.InvariantCulture),
startNumberParam,
- state.HlsListSize.ToString(UsCulture),
+ state.HlsListSize.ToString(CultureInfo.InvariantCulture),
baseUrlParam,
outputPath
).Trim();
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 45f003cae..fdae59f56 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -177,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
- var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, UsCulture);
+ var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture);
var state = await GetState(request, cancellationToken).ConfigureAwait(false);
@@ -364,7 +364,7 @@ namespace MediaBrowser.Api.Playback.Hls
var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length);
- return int.Parse(indexString, NumberStyles.Integer, UsCulture);
+ return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
@@ -438,7 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
segmentId = segmentRequest.SegmentId;
}
- return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
+ return int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
private string GetSegmentPath(StreamState state, string playlist, int index)
@@ -447,7 +447,7 @@ namespace MediaBrowser.Api.Playback.Hls
var filename = Path.GetFileNameWithoutExtension(playlist);
- return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request));
+ return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request));
}
private async Task<object> GetSegmentResult(StreamState state,
@@ -628,8 +628,8 @@ namespace MediaBrowser.Api.Playback.Hls
private string ReplaceBitrate(string url, int oldValue, int newValue)
{
return url.Replace(
- "videobitrate=" + oldValue.ToString(UsCulture),
- "videobitrate=" + newValue.ToString(UsCulture),
+ "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
+ "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
StringComparison.OrdinalIgnoreCase);
}
@@ -648,8 +648,8 @@ namespace MediaBrowser.Api.Playback.Hls
var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
state.Request.MediaSourceId,
- stream.Index.ToString(UsCulture),
- 30.ToString(UsCulture),
+ stream.Index.ToString(CultureInfo.InvariantCulture),
+ 30.ToString(CultureInfo.InvariantCulture),
AuthorizationContext.GetAuthorizationInfo(Request).Token);
var line = string.Format(format,
@@ -705,7 +705,7 @@ namespace MediaBrowser.Api.Playback.Hls
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
{
- var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(UsCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(UsCulture);
+ var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture);
// tvos wants resolution, codecs, framerate
//if (state.TargetFramerate.HasValue)
@@ -770,7 +770,7 @@ namespace MediaBrowser.Api.Playback.Hls
builder.AppendLine("#EXTM3U");
builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
builder.AppendLine("#EXT-X-VERSION:3");
- builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(UsCulture));
+ builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture));
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
var queryStringIndex = Request.RawUrl.IndexOf('?');
@@ -785,12 +785,12 @@ namespace MediaBrowser.Api.Playback.Hls
foreach (var length in segmentLengths)
{
- builder.AppendLine("#EXTINF:" + length.ToString("0.0000", UsCulture) + ", nodesc");
+ builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc");
builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}",
name,
- index.ToString(UsCulture),
+ index.ToString(CultureInfo.InvariantCulture),
GetSegmentFileExtension(request),
queryString));
@@ -821,17 +821,17 @@ namespace MediaBrowser.Api.Playback.Hls
if (state.OutputAudioBitrate.HasValue)
{
- audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(UsCulture));
+ audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture));
}
if (state.OutputAudioChannels.HasValue)
{
- audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
+ audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
if (state.OutputAudioSampleRate.HasValue)
{
- audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
+ audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
audioTranscodeParams.Add("-vn");
@@ -863,12 +863,12 @@ namespace MediaBrowser.Api.Playback.Hls
if (bitrate.HasValue)
{
- args += " -ab " + bitrate.Value.ToString(UsCulture);
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
if (state.OutputAudioSampleRate.HasValue)
{
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
+ args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
@@ -905,7 +905,7 @@ namespace MediaBrowser.Api.Playback.Hls
else
{
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- state.SegmentLength.ToString(UsCulture));
+ state.SegmentLength.ToString(CultureInfo.InvariantCulture));
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -953,7 +953,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If isEncoding is true we're actually starting ffmpeg
var startNumber = GetStartNumber(state);
- var startNumberParam = isEncoding ? startNumber.ToString(UsCulture) : "0";
+ var startNumberParam = isEncoding ? startNumber.ToString(CultureInfo.InvariantCulture) : "0";
var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty;
@@ -984,7 +984,7 @@ namespace MediaBrowser.Api.Playback.Hls
mapArgs,
GetVideoArguments(state, encodingOptions),
GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
+ state.SegmentLength.ToString(CultureInfo.InvariantCulture),
startNumberParam,
outputPath,
outputTsArg,
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index eb1bbfb74..3c715c5ad 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
@@ -55,12 +56,12 @@ namespace MediaBrowser.Api.Playback.Hls
if (bitrate.HasValue)
{
- args += " -ab " + bitrate.Value.ToString(UsCulture);
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
if (state.OutputAudioSampleRate.HasValue)
{
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
+ args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
@@ -100,7 +101,7 @@ namespace MediaBrowser.Api.Playback.Hls
else
{
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- state.SegmentLength.ToString(UsCulture));
+ state.SegmentLength.ToString(CultureInfo.InvariantCulture));
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 8d4b0cb3d..7396b5c99 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -1,17 +1,14 @@
using System;
-using System.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
public class StreamState : EncodingJobInfo, IDisposable
{
- private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager;
+ private bool _disposed = false;
public string RequestedUrl { get; set; }
@@ -30,11 +27,6 @@ namespace MediaBrowser.Api.Playback
public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
- /// <summary>
- /// Gets or sets the log file stream.
- /// </summary>
- /// <value>The log file stream.</value>
- public Stream LogFileStream { get; set; }
public IDirectStreamProvider DirectStreamProvider { get; set; }
public string WaitForPath { get; set; }
@@ -72,6 +64,7 @@ namespace MediaBrowser.Api.Playback
{
return 3;
}
+
return 6;
}
@@ -94,82 +87,57 @@ namespace MediaBrowser.Api.Playback
public string UserAgent { get; set; }
- public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
- : base(transcodingType)
- {
- _mediaSourceManager = mediaSourceManager;
- _logger = logger;
- }
-
public bool EstimateContentLength { get; set; }
+
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public bool EnableDlnaHeaders { get; set; }
- public override void Dispose()
- {
- DisposeTranscodingThrottler();
- DisposeLogStream();
- DisposeLiveStream();
+ public DeviceProfile DeviceProfile { get; set; }
- TranscodingJob = null;
+ public TranscodingJob TranscodingJob { get; set; }
+
+ public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
+ : base(transcodingType)
+ {
+ _mediaSourceManager = mediaSourceManager;
}
- private void DisposeTranscodingThrottler()
+ public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
{
- if (TranscodingThrottler != null)
- {
- try
- {
- TranscodingThrottler.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing TranscodingThrottler");
- }
+ ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ }
- TranscodingThrottler = null;
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
- private void DisposeLogStream()
+ protected virtual void Dispose(bool disposing)
{
- if (LogFileStream != null)
+ if (_disposed)
{
- try
- {
- LogFileStream.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing log stream");
- }
-
- LogFileStream = null;
+ return;
}
- }
- private async void DisposeLiveStream()
- {
- if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
+ if (disposing)
{
- try
- {
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
+ // REVIEW: Is this the right place for this?
+ if (MediaSource.RequiresClosing
+ && string.IsNullOrWhiteSpace(Request.LiveStreamId)
+ && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{
- _logger.LogError(ex, "Error closing media source");
+ _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
}
+
+ TranscodingThrottler?.Dispose();
}
- }
- public DeviceProfile DeviceProfile { get; set; }
+ TranscodingThrottler = null;
+ TranscodingJob = null;
- public TranscodingJob TranscodingJob;
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
- {
- ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ _disposed = true;
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 3c7ad1d0a..f1ae48492 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -224,7 +224,7 @@ namespace MediaBrowser.Api.UserLibrary
request.IncludeItemTypes = "Playlist";
}
- if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id))
+ if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id))
{
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
return new QueryResult<BaseItem>
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index bea178517..38e0ff0f5 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -28,6 +28,7 @@ namespace MediaBrowser.Common.Net
get => GetHeaderValue(HeaderNames.Accept);
set => RequestHeaders[HeaderNames.Accept] = value;
}
+
/// <summary>
/// Gets or sets the cancellation token.
/// </summary>
@@ -35,12 +36,6 @@ namespace MediaBrowser.Common.Net
public CancellationToken CancellationToken { get; set; }
/// <summary>
- /// Gets or sets the resource pool.
- /// </summary>
- /// <value>The resource pool.</value>
- public SemaphoreSlim ResourcePool { get; set; }
-
- /// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
@@ -86,8 +81,6 @@ namespace MediaBrowser.Common.Net
public bool LogRequest { get; set; }
public bool LogRequestAsDebug { get; set; }
public bool LogErrors { get; set; }
- public bool LogResponse { get; set; }
- public bool LogResponseHeaders { get; set; }
public bool LogErrorResponseBody { get; set; }
public bool EnableKeepAlive { get; set; }
@@ -95,11 +88,9 @@ namespace MediaBrowser.Common.Net
public CacheMode CacheMode { get; set; }
public TimeSpan CacheLength { get; set; }
- public int TimeoutMs { get; set; }
public bool EnableDefaultUserAgent { get; set; }
public bool AppendCharsetToMimeType { get; set; }
- public string DownloadFilePath { get; set; }
private string GetHeaderValue(string name)
{
@@ -120,17 +111,6 @@ namespace MediaBrowser.Common.Net
LogRequest = true;
LogErrors = true;
CacheMode = CacheMode.None;
-
- TimeoutMs = 20000;
- }
-
- public void SetPostData(IDictionary<string, string> values)
- {
- var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key]));
- var postContent = string.Join("&", strings.ToArray());
-
- RequestContent = postContent;
- RequestContentType = "application/x-www-form-urlencoded";
}
}
diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs
index 5aaf7e0be..db69c6f2c 100644
--- a/MediaBrowser.Common/Net/IHttpClient.cs
+++ b/MediaBrowser.Common/Net/IHttpClient.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
+using System.Net.Http;
namespace MediaBrowser.Common.Net
{
@@ -23,6 +24,8 @@ namespace MediaBrowser.Common.Net
Task<Stream> Get(HttpRequestOptions options);
/// <summary>
+ /// Warning: Deprecated function,
+ /// use 'Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead
/// Sends the asynchronous.
/// </summary>
/// <param name="options">The options.</param>
@@ -31,6 +34,14 @@ namespace MediaBrowser.Common.Net
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod);
/// <summary>
+ /// Sends the asynchronous.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="httpMethod">The HTTP method.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);
+
+ /// <summary>
/// Posts the specified options.
/// </summary>
/// <param name="options">The options.</param>
diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
index b9f282bd2..2cf531eed 100644
--- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
+++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
@@ -11,6 +11,9 @@ namespace MediaBrowser.Controller.Authentication
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
Task<bool> HasPassword(User user);
Task ChangePassword(User user, string newPassword);
+ void ChangeEasyPassword(User user, string newPassword, string newPasswordHash);
+ string GetPasswordHash(User user);
+ string GetEasyPasswordHash(User user);
}
public interface IRequiresResolvedUser
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index e20641c99..10a603e42 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -78,10 +78,25 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// The trailer folder name
/// </summary>
- public static string TrailerFolderName = "trailers";
- public static string ThemeSongsFolderName = "theme-music";
- public static string ThemeSongFilename = "theme";
- public static string ThemeVideosFolderName = "backdrops";
+ public const string TrailerFolderName = "trailers";
+ public const string ThemeSongsFolderName = "theme-music";
+ public const string ThemeSongFilename = "theme";
+ public const string ThemeVideosFolderName = "backdrops";
+ public const string ExtrasFolderName = "extras";
+ public const string BehindTheScenesFolderName = "behind the scenes";
+ public const string DeletedScenesFolderName = "deleted scenes";
+ public const string InterviewFolderName = "interviews";
+ public const string SceneFolderName = "scenes";
+ public const string SampleFolderName = "samples";
+
+ public static readonly string[] AllExtrasTypesFolderNames = {
+ ExtrasFolderName,
+ BehindTheScenesFolderName,
+ DeletedScenesFolderName,
+ InterviewFolderName,
+ SceneFolderName,
+ SampleFolderName
+ };
[IgnoreDataMember]
public Guid[] ThemeSongIds { get; set; }
@@ -1276,16 +1291,15 @@ namespace MediaBrowser.Controller.Entities
.Select(item =>
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
- var dbItem = LibraryManager.GetItemById(item.Id) as Video;
- if (dbItem != null)
+ if (LibraryManager.GetItemById(item.Id) is Video dbItem)
{
item = dbItem;
}
else
{
// item is new
- item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo;
+ item.ExtraType = Model.Entities.ExtraType.ThemeVideo;
}
return item;
@@ -1296,32 +1310,37 @@ namespace MediaBrowser.Controller.Entities
protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
- var files = fileSystemChildren.Where(i => i.IsDirectory)
- .SelectMany(i => FileSystem.GetFiles(i.FullName));
+ var extras = new List<Video>();
- return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
- .OfType<Video>()
- .Select(item =>
- {
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- var dbItem = LibraryManager.GetItemById(item.Id) as Video;
+ var folders = fileSystemChildren.Where(i => i.IsDirectory).ToArray();
+ foreach (var extraFolderName in AllExtrasTypesFolderNames)
+ {
+ var files = folders
+ .Where(i => string.Equals(i.Name, extraFolderName, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => FileSystem.GetFiles(i.FullName));
- if (dbItem != null)
- {
- item = dbItem;
- }
- else
+ extras.AddRange(LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
+ .OfType<Video>()
+ .Select(item =>
{
- // item is new
- item.ExtraType = MediaBrowser.Model.Entities.ExtraType.Clip;
- }
+ // Try to retrieve it from the db. If we don't find it, use the resolved version
+ if (LibraryManager.GetItemById(item.Id) is Video dbItem)
+ {
+ item = dbItem;
+ }
- return item;
+ // Use some hackery to get the extra type based on foldername
+ Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
+ item.ExtraType = extraType;
- // Sort them so that the list can be easily compared for changes
- }).OrderBy(i => i.Path).ToArray();
- }
+ return item;
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path));
+ }
+ return extras.ToArray();
+ }
public Task RefreshMetadata(CancellationToken cancellationToken)
{
@@ -1481,7 +1500,13 @@ namespace MediaBrowser.Controller.Entities
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
- var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService).Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)).Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService));
+ var extras = LoadExtras(fileSystemChildren, options.DirectoryService);
+ var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
+ var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
+ var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length];
+ extras.CopyTo(newExtras, 0);
+ themeVideos.CopyTo(newExtras, extras.Length);
+ themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length);
var newExtraIds = newExtras.Select(i => i.Id).ToArray();
@@ -1493,7 +1518,15 @@ namespace MediaBrowser.Controller.Entities
var tasks = newExtras.Select(i =>
{
- return RefreshMetadataForOwnedItem(i, true, new MetadataRefreshOptions(options), cancellationToken);
+ var subOptions = new MetadataRefreshOptions(options);
+ if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
+ {
+ i.OwnerId = ownerId;
+ i.ParentId = Guid.Empty;
+ subOptions.ForceSave = true;
+ }
+
+ return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
});
await Task.WhenAll(tasks).ConfigureAwait(false);
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 3e2191376..4a6d32dce 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
@@ -17,7 +16,6 @@ namespace MediaBrowser.Controller.Entities
public Guid? UserId { get; set; }
public static ITVSeriesManager TVSeriesManager;
- public static IPlaylistManager PlaylistManager;
[IgnoreDataMember]
public string CollectionType => ViewType;
@@ -38,6 +36,7 @@ namespace MediaBrowser.Controller.Entities
{
list.Add(Id);
}
+
return list;
}
@@ -65,7 +64,7 @@ namespace MediaBrowser.Controller.Entities
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
}
- return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager)
+ return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager)
.GetUserItems(parent, this, CollectionType, query);
}
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 683218a9e..e483c8f34 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -21,9 +20,14 @@ namespace MediaBrowser.Controller.Entities
private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager;
private readonly IServerConfigurationManager _config;
- private readonly IPlaylistManager _playlistManager;
- public UserViewBuilder(IUserViewManager userViewManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager)
+ public UserViewBuilder(
+ IUserViewManager userViewManager,
+ ILibraryManager libraryManager,
+ ILogger logger,
+ IUserDataManager userDataManager,
+ ITVSeriesManager tvSeriesManager,
+ IServerConfigurationManager config)
{
_userViewManager = userViewManager;
_libraryManager = libraryManager;
@@ -31,7 +35,6 @@ namespace MediaBrowser.Controller.Entities
_userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager;
_config = config;
- _playlistManager = playlistManager;
}
public QueryResult<BaseItem> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query)
@@ -110,6 +113,7 @@ namespace MediaBrowser.Controller.Entities
{
return GetResult(GetMediaFolders(user).OfType<Folder>().SelectMany(i => i.GetChildren(user, true)), queryParent, query);
}
+
return queryParent.GetItems(query);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 916d691b8..34af3b156 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -374,14 +374,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (AudioStream != null)
{
return AudioStream.SampleRate;
}
}
-
else if (BaseRequest.AudioSampleRate.HasValue)
{
// Don't exceed what the encoder supports
@@ -397,7 +397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (AudioStream != null)
{
@@ -405,13 +406,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- //else if (BaseRequest.AudioSampleRate.HasValue)
- //{
- // // Don't exceed what the encoder supports
- // // Seeing issues of attempting to encode to 88200
- // return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
- //}
-
return null;
}
}
@@ -446,7 +440,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.BitDepth;
}
@@ -463,7 +458,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.RefFrames;
}
@@ -479,7 +475,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
}
@@ -545,7 +542,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.CodecTag;
}
@@ -558,7 +556,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.IsAnamorphic;
}
@@ -571,14 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- var codec = OutputVideoCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.Codec;
}
- return codec;
+ return OutputVideoCodec;
}
}
@@ -586,14 +583,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- var codec = OutputAudioCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return AudioStream?.Codec;
}
- return codec;
+ return OutputAudioCodec;
}
}
@@ -601,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.IsInterlaced;
}
@@ -636,6 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
@@ -648,17 +645,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
}
- public int HlsListSize
- {
- get
- {
- return 0;
- }
- }
+ public int HlsListSize => 0;
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
@@ -677,10 +669,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
Progress.Report(percentComplete.Value);
}
-
- public virtual void Dispose()
- {
- }
}
/// <summary>
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 2755bf581..ac989f6ba 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
@@ -18,10 +19,11 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger;
}
- public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
+ public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
{
try
{
+ using (target)
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream && reader.BaseStream.CanRead)
@@ -97,8 +99,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var currentMs = startMs + val.TotalMilliseconds;
- var percentVal = currentMs / totalMs;
- percent = 100 * percentVal;
+ percent = 100.0 * currentMs / totalMs;
transcodingPosition = val;
}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 844412546..ee5c1a165 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Net
/// </summary>
/// <param name="message">The message.</param>
/// <returns>Task.</returns>
- public Task ProcessMessage(WebSocketMessageInfo message)
+ public Task ProcessMessageAsync(WebSocketMessageInfo message)
{
if (message == null)
{
@@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Net
Stop(message);
}
- return Task.FromResult(true);
+ return Task.CompletedTask;
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs
index e38f0e259..0f472a2bc 100644
--- a/MediaBrowser.Controller/Net/IWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs
@@ -12,6 +12,6 @@ namespace MediaBrowser.Controller.Net
/// </summary>
/// <param name="message">The message.</param>
/// <returns>Task.</returns>
- Task ProcessMessage(WebSocketMessageInfo message);
+ Task ProcessMessageAsync(WebSocketMessageInfo message);
}
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index e4757543e..c0f92ac4a 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -18,7 +18,7 @@
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
- <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
+ <PackageReference Include="UTF.Unknown" Version="1.0.0" />
</ItemGroup>
</Project>
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index d978359c7..8677b363f 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -126,8 +126,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException(nameof(mediaSourceId));
}
- // TODO network path substition useful ?
- var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, true, cancellationToken).ConfigureAwait(false);
+ var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Model/Configuration/FanartOptions.cs b/MediaBrowser.Model/Configuration/FanartOptions.cs
deleted file mode 100644
index 9c8be39be..000000000
--- a/MediaBrowser.Model/Configuration/FanartOptions.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace MediaBrowser.Model.Configuration
-{
- public class FanartOptions
- {
- /// <summary>
- /// Gets or sets the user API key.
- /// </summary>
- /// <value>The user API key.</value>
- public string UserApiKey { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 0ba36b4b9..2673597ca 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -49,9 +49,9 @@ namespace MediaBrowser.Model.Configuration
public bool EnableNormalizedItemByNameIds { get; set; }
/// <summary>
- /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
+ /// Gets or sets the value pointing to the file system where the ssl certificate is located..
/// </summary>
- /// <value>The value pointing to the file system where the ssl certiifcate is located..</value>
+ /// <value>The value pointing to the file system where the ssl certificate is located..</value>
public string CertificatePath { get; set; }
public string CertificatePassword { get; set; }
@@ -259,7 +259,7 @@ namespace MediaBrowser.Model.Configuration
{
ItemType = "MusicVideo",
DisabledMetadataFetchers = new [] { "The Open Movie Database" },
- DisabledImageFetchers = new [] { "The Open Movie Database", "FanArt" }
+ DisabledImageFetchers = new [] { "The Open Movie Database" }
},
new MetadataOptions
{
@@ -285,7 +285,6 @@ namespace MediaBrowser.Model.Configuration
{
ItemType = "Season",
DisabledMetadataFetchers = new [] { "TheMovieDb" },
- DisabledImageFetchers = new [] { "FanArt" }
},
new MetadataOptions
{
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 901d81c5f..e52951dd0 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -13,7 +13,8 @@ namespace MediaBrowser.Model.Dlna
_profile = profile;
}
- public string BuildImageHeader(string container,
+ public string BuildImageHeader(
+ string container,
int? width,
int? height,
bool isDirectStream,
@@ -28,8 +29,7 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15;
- string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
- DlnaMaps.FlagsToString(flagValue));
+ string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue));
ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container,
width,
@@ -37,7 +37,7 @@ namespace MediaBrowser.Model.Dlna
if (string.IsNullOrEmpty(orgPn))
{
- orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+ orgPn = mediaProfile?.OrgPn;
}
if (string.IsNullOrEmpty(orgPn))
@@ -50,7 +50,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
}
- public string BuildAudioHeader(string container,
+ public string BuildAudioHeader(
+ string container,
string audioCodec,
int? audioBitrate,
int? audioSampleRate,
@@ -102,7 +103,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
}
- public List<string> BuildVideoHeader(string container,
+ public List<string> BuildVideoHeader(
+ string container,
string videoCodec,
string audioCodec,
int? width,
@@ -206,7 +208,7 @@ namespace MediaBrowser.Model.Dlna
return contentFeatureList;
}
- private string GetImageOrgPnValue(string container, int? width, int? height)
+ private static string GetImageOrgPnValue(string container, int? width, int? height)
{
MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveImageFormat(container,
@@ -216,7 +218,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null;
}
- private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
+ private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
{
MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveAudioFormat(container,
@@ -227,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null;
}
- private string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
+ private static string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
{
return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp);
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index cfbb85ea6..5941ed436 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
- <PackageReference Include="PlaylistsNET" Version="1.0.2" />
+ <PackageReference Include="PlaylistsNET" Version="1.0.4" />
<PackageReference Include="TvDbSharper" Version="2.0.0" />
</ItemGroup>
diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
deleted file mode 100644
index 70d187bf5..000000000
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ /dev/null
@@ -1,334 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Music;
-using MediaBrowser.Providers.TV;
-using MediaBrowser.Providers.TV.FanArt;
-
-namespace MediaBrowser.Providers.Movies
-{
- public class FanartMovieImageProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _json;
-
- private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3/movies/{1}?api_key={0}";
-
- internal static FanartMovieImageProvider Current;
-
- public FanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer json)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- _json = json;
-
- Current = this;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "FanArt";
-
- public bool Supports(BaseItem item)
- {
- return item is Movie || item is BoxSet || item is MusicVideo;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Primary,
- ImageType.Thumb,
- ImageType.Art,
- ImageType.Logo,
- ImageType.Disc,
- ImageType.Banner,
- ImageType.Backdrop
- };
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var baseItem = item;
- var list = new List<RemoteImageInfo>();
-
- var movieId = baseItem.GetProviderId(MetadataProviders.Tmdb);
-
- if (!string.IsNullOrEmpty(movieId))
- {
- // Bad id entered
- try
- {
- await EnsureMovieJson(movieId, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- var path = GetFanartJsonPath(movieId);
-
- try
- {
- AddImages(list, path, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // No biggie. Don't blow up
- }
- catch (IOException)
- {
- // No biggie. Don't blow up
- }
- }
-
- var language = item.GetPreferredMetadataLanguage();
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
- // Sort first by width to prioritize HD versions
- return list.OrderByDescending(i => i.Width ?? 0)
- .ThenByDescending(i =>
- {
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0);
- }
-
- private void AddImages(List<RemoteImageInfo> list, string path, CancellationToken cancellationToken)
- {
- var root = _json.DeserializeFromFile<RootObject>(path);
-
- AddImages(list, root, cancellationToken);
- }
-
- private void AddImages(List<RemoteImageInfo> list, RootObject obj, CancellationToken cancellationToken)
- {
- PopulateImages(list, obj.hdmovieclearart, ImageType.Art, 1000, 562);
- PopulateImages(list, obj.hdmovielogo, ImageType.Logo, 800, 310);
- PopulateImages(list, obj.moviedisc, ImageType.Disc, 1000, 1000);
- PopulateImages(list, obj.movieposter, ImageType.Primary, 1000, 1426);
- PopulateImages(list, obj.movielogo, ImageType.Logo, 400, 155);
- PopulateImages(list, obj.movieart, ImageType.Art, 500, 281);
- PopulateImages(list, obj.moviethumb, ImageType.Thumb, 1000, 562);
- PopulateImages(list, obj.moviebanner, ImageType.Banner, 1000, 185);
- PopulateImages(list, obj.moviebackground, ImageType.Backdrop, 1920, 1080);
- }
-
- private void PopulateImages(List<RemoteImageInfo> list, List<Image> images, ImageType type, int width, int height)
- {
- if (images == null)
- {
- return;
- }
-
- list.AddRange(images.Select(i =>
- {
- var url = i.url;
-
- if (!string.IsNullOrEmpty(url))
- {
- var likesString = i.likes;
-
- var info = new RemoteImageInfo
- {
- RatingType = RatingType.Likes,
- Type = type,
- Width = width,
- Height = height,
- ProviderName = Name,
- Url = url,
- Language = i.lang
- };
-
- if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out var likes))
- {
- info.CommunityRating = likes;
- }
-
- return info;
- }
-
- return null;
- }).Where(i => i != null));
- }
-
- public int Order => 1;
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
- }
-
- /// <summary>
- /// Gets the movie data path.
- /// </summary>
- /// <param name="appPaths">The application paths.</param>
- /// <param name="id">The identifier.</param>
- /// <returns>System.String.</returns>
- internal static string GetMovieDataPath(IApplicationPaths appPaths, string id)
- {
- var dataPath = Path.Combine(GetMoviesDataPath(appPaths), id);
-
- return dataPath;
- }
-
- /// <summary>
- /// Gets the movie data path.
- /// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <returns>System.String.</returns>
- internal static string GetMoviesDataPath(IApplicationPaths appPaths)
- {
- var dataPath = Path.Combine(appPaths.CachePath, "fanart-movies");
-
- return dataPath;
- }
-
- public string GetFanartJsonPath(string id)
- {
- var movieDataPath = GetMovieDataPath(_config.ApplicationPaths, id);
- return Path.Combine(movieDataPath, "fanart.json");
- }
-
- /// <summary>
- /// Downloads the movie json.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- internal async Task DownloadMovieJson(string id, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, id);
-
- var clientKey = FanartSeriesProvider.Current.GetFanartOptions().UserApiKey;
- if (!string.IsNullOrWhiteSpace(clientKey))
- {
- url += "&client_key=" + clientKey;
- }
-
- var path = GetFanartJsonPath(id);
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- try
- {
- using (var httpResponse = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = true
-
- }, "GET").ConfigureAwait(false))
- {
- using (var response = httpResponse.Content)
- {
- using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await response.CopyToAsync(fileStream).ConfigureAwait(false);
- }
- }
- }
- }
- catch (HttpException exception)
- {
- if (exception.StatusCode.HasValue && exception.StatusCode.Value == HttpStatusCode.NotFound)
- {
- // If the user has automatic updates enabled, save a dummy object to prevent repeated download attempts
- _json.SerializeToFile(new RootObject(), path);
-
- return;
- }
-
- throw;
- }
- }
-
- internal Task EnsureMovieJson(string id, CancellationToken cancellationToken)
- {
- var path = GetFanartJsonPath(id);
-
- var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
- if (fileInfo.Exists)
- {
- if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
- {
- return Task.CompletedTask;
- }
- }
-
- return DownloadMovieJson(id, cancellationToken);
- }
-
- public class Image
- {
- public string id { get; set; }
- public string url { get; set; }
- public string lang { get; set; }
- public string likes { get; set; }
- }
-
- public class RootObject
- {
- public string name { get; set; }
- public string tmdb_id { get; set; }
- public string imdb_id { get; set; }
- public List<Image> hdmovielogo { get; set; }
- public List<Image> moviedisc { get; set; }
- public List<Image> movielogo { get; set; }
- public List<Image> movieposter { get; set; }
- public List<Image> hdmovieclearart { get; set; }
- public List<Image> movieart { get; set; }
- public List<Image> moviebackground { get; set; }
- public List<Image> moviebanner { get; set; }
- public List<Image> moviethumb { get; set; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
deleted file mode 100644
index ebb740ffe..000000000
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ /dev/null
@@ -1,201 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-
-namespace MediaBrowser.Providers.Music
-{
- public class FanartAlbumProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _jsonSerializer;
-
- public FanartAlbumProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- _jsonSerializer = jsonSerializer;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "FanArt";
-
- public bool Supports(BaseItem item)
- {
- return item is MusicAlbum;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Primary,
- ImageType.Disc
- };
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var album = (MusicAlbum)item;
-
- var list = new List<RemoteImageInfo>();
-
- var musicArtist = album.MusicArtist;
-
- if (musicArtist == null)
- {
- return list;
- }
-
- var artistMusicBrainzId = musicArtist.GetProviderId(MetadataProviders.MusicBrainzArtist);
-
- if (!string.IsNullOrEmpty(artistMusicBrainzId))
- {
- await FanartArtistProvider.Current.EnsureArtistJson(artistMusicBrainzId, cancellationToken).ConfigureAwait(false);
-
- var artistJsonPath = FanartArtistProvider.GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-
- var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
-
- var musicBrainzId = album.GetProviderId(MetadataProviders.MusicBrainzAlbum);
-
- try
- {
- AddImages(list, artistJsonPath, musicBrainzId, musicBrainzReleaseGroupId, cancellationToken);
- }
- catch (FileNotFoundException)
- {
-
- }
- catch (IOException)
- {
-
- }
- }
-
- var language = item.GetPreferredMetadataLanguage();
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
- // Sort first by width to prioritize HD versions
- return list.OrderByDescending(i => i.Width ?? 0)
- .ThenByDescending(i =>
- {
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- /// <summary>
- /// Adds the images.
- /// </summary>
- /// <param name="list">The list.</param>
- /// <param name="path">The path.</param>
- /// <param name="releaseId">The release identifier.</param>
- /// <param name="releaseGroupId">The release group identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- private void AddImages(List<RemoteImageInfo> list, string path, string releaseId, string releaseGroupId, CancellationToken cancellationToken)
- {
- var obj = _jsonSerializer.DeserializeFromFile<FanartArtistProvider.FanartArtistResponse>(path);
-
- if (obj.albums != null)
- {
- var album = obj.albums.FirstOrDefault(i => string.Equals(i.release_group_id, releaseGroupId, StringComparison.OrdinalIgnoreCase));
-
- if (album != null)
- {
- PopulateImages(list, album.albumcover, ImageType.Primary, 1000, 1000);
- PopulateImages(list, album.cdart, ImageType.Disc, 1000, 1000);
- }
- }
- }
-
- private void PopulateImages(List<RemoteImageInfo> list,
- List<FanartArtistProvider.FanartArtistImage> images,
- ImageType type,
- int width,
- int height)
- {
- if (images == null)
- {
- return;
- }
-
- list.AddRange(images.Select(i =>
- {
- var url = i.url;
-
- if (!string.IsNullOrEmpty(url))
- {
- var likesString = i.likes;
-
- var info = new RemoteImageInfo
- {
- RatingType = RatingType.Likes,
- Type = type,
- Width = width,
- Height = height,
- ProviderName = Name,
- Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase),
- Language = i.lang
- };
-
- if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out var likes))
- {
- info.CommunityRating = likes;
- }
-
- return info;
- }
-
- return null;
- }).Where(i => i != null));
- }
- // After embedded provider
- public int Order => 1;
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
- }
- }
-}
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
deleted file mode 100644
index 75b4213c5..000000000
--- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
+++ /dev/null
@@ -1,335 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.TV;
-using MediaBrowser.Providers.TV.FanArt;
-
-namespace MediaBrowser.Providers.Music
-{
- public class FanartArtistProvider : IRemoteImageProvider, IHasOrder
- {
- internal const string ApiKey = "184e1a2b1fe3b94935365411f919f638";
- private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3.1/music/{1}?api_key={0}";
-
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _jsonSerializer;
-
- internal static FanartArtistProvider Current;
-
- public FanartArtistProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- _jsonSerializer = jsonSerializer;
-
- Current = this;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "FanArt";
-
- public bool Supports(BaseItem item)
- {
- return item is MusicArtist;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Primary,
- ImageType.Logo,
- ImageType.Art,
- ImageType.Banner,
- ImageType.Backdrop
- };
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var artist = (MusicArtist)item;
-
- var list = new List<RemoteImageInfo>();
-
- var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.MusicBrainzArtist);
-
- if (!string.IsNullOrEmpty(artistMusicBrainzId))
- {
- await EnsureArtistJson(artistMusicBrainzId, cancellationToken).ConfigureAwait(false);
-
- var artistJsonPath = GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-
- try
- {
- AddImages(list, artistJsonPath, cancellationToken);
- }
- catch (FileNotFoundException)
- {
-
- }
- catch (IOException)
- {
-
- }
- }
-
- var language = item.GetPreferredMetadataLanguage();
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
- // Sort first by width to prioritize HD versions
- return list.OrderByDescending(i => i.Width ?? 0)
- .ThenByDescending(i =>
- {
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- /// <summary>
- /// Adds the images.
- /// </summary>
- /// <param name="list">The list.</param>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- private void AddImages(List<RemoteImageInfo> list, string path, CancellationToken cancellationToken)
- {
- var obj = _jsonSerializer.DeserializeFromFile<FanartArtistResponse>(path);
-
- PopulateImages(list, obj.artistbackground, ImageType.Backdrop, 1920, 1080);
- PopulateImages(list, obj.artistthumb, ImageType.Primary, 500, 281);
- PopulateImages(list, obj.hdmusiclogo, ImageType.Logo, 800, 310);
- PopulateImages(list, obj.musicbanner, ImageType.Banner, 1000, 185);
- PopulateImages(list, obj.musiclogo, ImageType.Logo, 400, 155);
- PopulateImages(list, obj.hdmusicarts, ImageType.Art, 1000, 562);
- PopulateImages(list, obj.musicarts, ImageType.Art, 500, 281);
- }
-
- private void PopulateImages(List<RemoteImageInfo> list,
- List<FanartArtistImage> images,
- ImageType type,
- int width,
- int height)
- {
- if (images == null)
- {
- return;
- }
-
- list.AddRange(images.Select(i =>
- {
- var url = i.url;
-
- if (!string.IsNullOrEmpty(url))
- {
- var likesString = i.likes;
-
- var info = new RemoteImageInfo
- {
- RatingType = RatingType.Likes,
- Type = type,
- Width = width,
- Height = height,
- ProviderName = Name,
- Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase),
- Language = i.lang
- };
-
- if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out var likes))
- {
- info.CommunityRating = likes;
- }
-
- return info;
- }
-
- return null;
- }).Where(i => i != null));
- }
-
- public int Order => 0;
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
- }
-
- internal Task EnsureArtistJson(string musicBrainzId, CancellationToken cancellationToken)
- {
- var jsonPath = GetArtistJsonPath(_config.ApplicationPaths, musicBrainzId);
-
- var fileInfo = _fileSystem.GetFileSystemInfo(jsonPath);
-
- if (fileInfo.Exists)
- {
- if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
- {
- return Task.CompletedTask;
- }
- }
-
- return DownloadArtistJson(musicBrainzId, cancellationToken);
- }
-
- /// <summary>
- /// Downloads the artist data.
- /// </summary>
- /// <param name="musicBrainzId">The music brainz id.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- internal async Task DownloadArtistJson(string musicBrainzId, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var url = string.Format(FanArtBaseUrl, ApiKey, musicBrainzId);
-
- var clientKey = FanartSeriesProvider.Current.GetFanartOptions().UserApiKey;
- if (!string.IsNullOrWhiteSpace(clientKey))
- {
- url += "&client_key=" + clientKey;
- }
-
- var jsonPath = GetArtistJsonPath(_config.ApplicationPaths, musicBrainzId);
-
- Directory.CreateDirectory(Path.GetDirectoryName(jsonPath));
-
- try
- {
- using (var httpResponse = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = true
-
- }, "GET").ConfigureAwait(false))
- {
- using (var response = httpResponse.Content)
- {
- using (var saveFileStream = _fileSystem.GetFileStream(jsonPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await response.CopyToAsync(saveFileStream).ConfigureAwait(false);
- }
- }
- }
- }
- catch (HttpException ex)
- {
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
- {
- _jsonSerializer.SerializeToFile(new FanartArtistResponse(), jsonPath);
- }
- else
- {
- throw;
- }
- }
- }
-
- /// <summary>
- /// Gets the artist data path.
- /// </summary>
- /// <param name="appPaths">The application paths.</param>
- /// <param name="musicBrainzArtistId">The music brainz artist identifier.</param>
- /// <returns>System.String.</returns>
- private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId)
- {
- var dataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId);
-
- return dataPath;
- }
-
- /// <summary>
- /// Gets the artist data path.
- /// </summary>
- /// <param name="appPaths">The application paths.</param>
- /// <returns>System.String.</returns>
- internal static string GetArtistDataPath(IApplicationPaths appPaths)
- {
- var dataPath = Path.Combine(appPaths.CachePath, "fanart-music");
-
- return dataPath;
- }
-
- internal static string GetArtistJsonPath(IApplicationPaths appPaths, string musicBrainzArtistId)
- {
- var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId);
-
- return Path.Combine(dataPath, "fanart.json");
- }
-
-
- public class FanartArtistImage
- {
- public string id { get; set; }
- public string url { get; set; }
- public string likes { get; set; }
- public string disc { get; set; }
- public string size { get; set; }
- public string lang { get; set; }
- }
-
- public class Album
- {
- public string release_group_id { get; set; }
- public List<FanartArtistImage> cdart { get; set; }
- public List<FanartArtistImage> albumcover { get; set; }
- }
-
- public class FanartArtistResponse
- {
- public string name { get; set; }
- public string mbid_id { get; set; }
- public List<FanartArtistImage> artistthumb { get; set; }
- public List<FanartArtistImage> artistbackground { get; set; }
- public List<FanartArtistImage> hdmusiclogo { get; set; }
- public List<FanartArtistImage> musicbanner { get; set; }
- public List<FanartArtistImage> musiclogo { get; set; }
- public List<FanartArtistImage> musicarts { get; set; }
- public List<FanartArtistImage> hdmusicarts { get; set; }
- public List<Album> albums { get; set; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs
deleted file mode 100644
index 58356910f..000000000
--- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs
+++ /dev/null
@@ -1,205 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-
-namespace MediaBrowser.Providers.TV.FanArt
-{
- public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _json;
-
- public FanArtSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer json)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- _json = json;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "FanArt";
-
- public bool Supports(BaseItem item)
- {
- return item is Season;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Backdrop,
- ImageType.Thumb,
- ImageType.Banner,
- ImageType.Primary
- };
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var list = new List<RemoteImageInfo>();
-
- var season = (Season)item;
- var series = season.Series;
-
- if (series != null)
- {
- var id = series.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
- {
- // Bad id entered
- try
- {
- await FanartSeriesProvider.Current.EnsureSeriesJson(id, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- var path = FanartSeriesProvider.Current.GetFanartJsonPath(id);
-
- try
- {
- AddImages(list, season.IndexNumber.Value, path, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // No biggie. Don't blow up
- }
- catch (IOException)
- {
- // No biggie. Don't blow up
- }
- }
- }
-
- var language = item.GetPreferredMetadataLanguage();
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
- // Sort first by width to prioritize HD versions
- return list.OrderByDescending(i => i.Width ?? 0)
- .ThenByDescending(i =>
- {
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- private void AddImages(List<RemoteImageInfo> list, int seasonNumber, string path, CancellationToken cancellationToken)
- {
- var root = _json.DeserializeFromFile<FanartSeriesProvider.RootObject>(path);
-
- AddImages(list, root, seasonNumber, cancellationToken);
- }
-
- private void AddImages(List<RemoteImageInfo> list, FanartSeriesProvider.RootObject obj, int seasonNumber, CancellationToken cancellationToken)
- {
- PopulateImages(list, obj.seasonposter, ImageType.Primary, 1000, 1426, seasonNumber);
- PopulateImages(list, obj.seasonbanner, ImageType.Banner, 1000, 185, seasonNumber);
- PopulateImages(list, obj.seasonthumb, ImageType.Thumb, 500, 281, seasonNumber);
- PopulateImages(list, obj.showbackground, ImageType.Backdrop, 1920, 1080, seasonNumber);
- }
-
- private void PopulateImages(List<RemoteImageInfo> list,
- List<FanartSeriesProvider.Image> images,
- ImageType type,
- int width,
- int height,
- int seasonNumber)
- {
- if (images == null)
- {
- return;
- }
-
- list.AddRange(images.Select(i =>
- {
- var url = i.url;
- var season = i.season;
-
- if (!string.IsNullOrEmpty(url) &&
- !string.IsNullOrEmpty(season) &&
- int.TryParse(season, NumberStyles.Integer, _usCulture, out var imageSeasonNumber) &&
- seasonNumber == imageSeasonNumber)
- {
- var likesString = i.likes;
-
- var info = new RemoteImageInfo
- {
- RatingType = RatingType.Likes,
- Type = type,
- Width = width,
- Height = height,
- ProviderName = Name,
- Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase),
- Language = i.lang
- };
-
- if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out var likes))
- {
- info.CommunityRating = likes;
- }
-
- return info;
- }
-
- return null;
- }).Where(i => i != null));
- }
-
- public int Order => 1;
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs
deleted file mode 100644
index 49cd9596e..000000000
--- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs
+++ /dev/null
@@ -1,378 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Music;
-
-namespace MediaBrowser.Providers.TV.FanArt
-{
- public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _json;
-
- private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3/tv/{1}?api_key={0}";
-
- internal static FanartSeriesProvider Current { get; private set; }
-
- public FanartSeriesProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer json)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- _json = json;
-
- Current = this;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "FanArt";
-
- public bool Supports(BaseItem item)
- {
- return item is Series;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Primary,
- ImageType.Thumb,
- ImageType.Art,
- ImageType.Logo,
- ImageType.Backdrop,
- ImageType.Banner
- };
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var list = new List<RemoteImageInfo>();
-
- var series = (Series)item;
-
- var id = series.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(id))
- {
- // Bad id entered
- try
- {
- await EnsureSeriesJson(id, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- var path = GetFanartJsonPath(id);
-
- try
- {
- AddImages(list, path, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // No biggie. Don't blow up
- }
- catch (IOException)
- {
- // No biggie. Don't blow up
- }
- }
-
- var language = item.GetPreferredMetadataLanguage();
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
- // Sort first by width to prioritize HD versions
- return list.OrderByDescending(i => i.Width ?? 0)
- .ThenByDescending(i =>
- {
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- private void AddImages(List<RemoteImageInfo> list, string path, CancellationToken cancellationToken)
- {
- var root = _json.DeserializeFromFile<RootObject>(path);
-
- AddImages(list, root, cancellationToken);
- }
-
- private void AddImages(List<RemoteImageInfo> list, RootObject obj, CancellationToken cancellationToken)
- {
- PopulateImages(list, obj.hdtvlogo, ImageType.Logo, 800, 310);
- PopulateImages(list, obj.hdclearart, ImageType.Art, 1000, 562);
- PopulateImages(list, obj.clearlogo, ImageType.Logo, 400, 155);
- PopulateImages(list, obj.clearart, ImageType.Art, 500, 281);
- PopulateImages(list, obj.showbackground, ImageType.Backdrop, 1920, 1080, true);
- PopulateImages(list, obj.seasonthumb, ImageType.Thumb, 500, 281);
- PopulateImages(list, obj.tvthumb, ImageType.Thumb, 500, 281);
- PopulateImages(list, obj.tvbanner, ImageType.Banner, 1000, 185);
- PopulateImages(list, obj.tvposter, ImageType.Primary, 1000, 1426);
- }
-
- private void PopulateImages(List<RemoteImageInfo> list,
- List<Image> images,
- ImageType type,
- int width,
- int height,
- bool allowSeasonAll = false)
- {
- if (images == null)
- {
- return;
- }
-
- list.AddRange(images.Select(i =>
- {
- var url = i.url;
- var season = i.season;
-
- var isSeasonValid = string.IsNullOrEmpty(season) ||
- (allowSeasonAll && string.Equals(season, "all", StringComparison.OrdinalIgnoreCase));
-
- if (!string.IsNullOrEmpty(url) && isSeasonValid)
- {
- var likesString = i.likes;
-
- var info = new RemoteImageInfo
- {
- RatingType = RatingType.Likes,
- Type = type,
- Width = width,
- Height = height,
- ProviderName = Name,
- Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase),
- Language = i.lang
- };
-
- if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out var likes))
- {
- info.CommunityRating = likes;
- }
-
- return info;
- }
-
- return null;
- }).Where(i => i != null));
- }
-
- public int Order => 1;
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
- }
-
- /// <summary>
- /// Gets the series data path.
- /// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <param name="seriesId">The series id.</param>
- /// <returns>System.String.</returns>
- internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
- {
- var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
-
- return seriesDataPath;
- }
-
- /// <summary>
- /// Gets the series data path.
- /// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <returns>System.String.</returns>
- internal static string GetSeriesDataPath(IApplicationPaths appPaths)
- {
- var dataPath = Path.Combine(appPaths.CachePath, "fanart-tv");
-
- return dataPath;
- }
-
- public string GetFanartJsonPath(string tvdbId)
- {
- var dataPath = GetSeriesDataPath(_config.ApplicationPaths, tvdbId);
- return Path.Combine(dataPath, "fanart.json");
- }
-
- private readonly SemaphoreSlim _ensureSemaphore = new SemaphoreSlim(1, 1);
- internal async Task EnsureSeriesJson(string tvdbId, CancellationToken cancellationToken)
- {
- var path = GetFanartJsonPath(tvdbId);
-
- // Only allow one thread in here at a time since every season will be calling this method, possibly concurrently
- await _ensureSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
- if (fileInfo.Exists)
- {
- if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
- {
- return;
- }
- }
-
- await DownloadSeriesJson(tvdbId, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- _ensureSemaphore.Release();
- }
- }
-
- public FanartOptions GetFanartOptions()
- {
- return _config.GetConfiguration<FanartOptions>("fanart");
- }
-
- /// <summary>
- /// Downloads the series json.
- /// </summary>
- /// <param name="tvdbId">The TVDB identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- internal async Task DownloadSeriesJson(string tvdbId, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
-
- var clientKey = GetFanartOptions().UserApiKey;
- if (!string.IsNullOrWhiteSpace(clientKey))
- {
- url += "&client_key=" + clientKey;
- }
-
- var path = GetFanartJsonPath(tvdbId);
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- try
- {
- using (var httpResponse = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = true
-
- }, "GET").ConfigureAwait(false))
- {
- using (var response = httpResponse.Content)
- {
- using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await response.CopyToAsync(fileStream).ConfigureAwait(false);
- }
- }
- }
- }
- catch (HttpException exception)
- {
- if (exception.StatusCode.HasValue && exception.StatusCode.Value == HttpStatusCode.NotFound)
- {
- // If the user has automatic updates enabled, save a dummy object to prevent repeated download attempts
- _json.SerializeToFile(new RootObject(), path);
-
- return;
- }
-
- throw;
- }
- }
-
- public class Image
- {
- public string id { get; set; }
- public string url { get; set; }
- public string lang { get; set; }
- public string likes { get; set; }
- public string season { get; set; }
- }
-
- public class RootObject
- {
- public string name { get; set; }
- public string thetvdb_id { get; set; }
- public List<Image> clearlogo { get; set; }
- public List<Image> hdtvlogo { get; set; }
- public List<Image> clearart { get; set; }
- public List<Image> showbackground { get; set; }
- public List<Image> tvthumb { get; set; }
- public List<Image> seasonposter { get; set; }
- public List<Image> seasonthumb { get; set; }
- public List<Image> hdclearart { get; set; }
- public List<Image> tvbanner { get; set; }
- public List<Image> characterart { get; set; }
- public List<Image> tvposter { get; set; }
- public List<Image> seasonbanner { get; set; }
- }
- }
-
- public class FanartConfigStore : IConfigurationFactory
- {
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- return new ConfigurationStore[]
- {
- new ConfigurationStore
- {
- Key = "fanart",
- ConfigurationType = typeof(FanartOptions)
- }
- };
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
index efb8a0fe8..1d1fbd00f 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
get
{
// Refresh if necessary
- if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20)))
+ if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20)))
{
try
{
diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web
-Subproject 1ba58b06b3dc28e07abae124cff78aa656fcb7e
+Subproject 37636dae5c6c0b0711dfc7612f843b864dd5946
diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs
index 18712470d..76d816e7b 100644
--- a/RSSDP/HttpParserBase.cs
+++ b/RSSDP/HttpParserBase.cs
@@ -23,8 +23,6 @@ namespace Rssdp.Infrastructure
#region Public Methods
- private static byte[] EmptyByteArray = new byte[]{};
-
/// <summary>
/// Parses the <paramref name="data"/> provided into either a <see cref="HttpRequestMessage"/> or <see cref="HttpResponseMessage"/> object.
/// </summary>
@@ -46,7 +44,7 @@ namespace Rssdp.Infrastructure
if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", nameof(data));
if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data));
- using (var retVal = new ByteArrayContent(EmptyByteArray))
+ using (var retVal = new ByteArrayContent(Array.Empty<byte>()))
{
var lines = data.Split(LineTerminators, StringSplitOptions.None);
@@ -209,4 +207,4 @@ namespace Rssdp.Infrastructure
#endregion
}
-} \ No newline at end of file
+}
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index d9a4b6ac0..5d2afc37a 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -355,7 +355,7 @@ namespace Rssdp.Infrastructure
{
var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort);
- ListenToSocket(socket);
+ _ = ListenToSocketInternal(socket);
return socket;
}
@@ -389,19 +389,12 @@ namespace Rssdp.Infrastructure
foreach (var socket in sockets)
{
- ListenToSocket(socket);
+ _ = ListenToSocketInternal(socket);
}
return sockets;
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")]
- private void ListenToSocket(ISocket socket)
- {
- // Tasks are captured to local variables even if we don't use them just to avoid compiler warnings.
- var t = Task.Run(() => ListenToSocketInternal(socket));
- }
-
private async Task ListenToSocketInternal(ISocket socket)
{
var cancelled = false;
@@ -448,10 +441,10 @@ namespace Rssdp.Infrastructure
private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress)
{
- //Responses start with the HTTP version, prefixed with HTTP/ while
- //requests start with a method which can vary and might be one we haven't
- //seen/don't know. We'll check if this message is a request or a response
- //by checking for the HTTP/ prefix on the start of the message.
+ // Responses start with the HTTP version, prefixed with HTTP/ while
+ // requests start with a method which can vary and might be one we haven't
+ // seen/don't know. We'll check if this message is a request or a response
+ // by checking for the HTTP/ prefix on the start of the message.
if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase))
{
HttpResponseMessage responseMessage = null;
@@ -465,7 +458,9 @@ namespace Rssdp.Infrastructure
}
if (responseMessage != null)
+ {
OnResponseReceived(responseMessage, endPoint, receivedOnLocalIpAddress);
+ }
}
else
{
diff --git a/SharedVersion.cs b/SharedVersion.cs
index 700ef4549..27ba1cf2c 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("10.3.2")]
-[assembly: AssemblyFileVersion("10.3.2")]
+[assembly: AssemblyVersion("10.3.5")]
+[assembly: AssemblyFileVersion("10.3.5")]
diff --git a/build.yaml b/build.yaml
index 8ce6a00a8..cb010ed59 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin"
-version: "10.3.2"
+version: "10.3.5"
packages:
- debian-package-x64
- debian-package-armhf
diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh
index 3acf1ec0d..cefb1652e 100755
--- a/deployment/centos-package-x64/docker-build.sh
+++ b/deployment/centos-package-x64/docker-build.sh
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh
index 27d686e46..df5a66580 100755
--- a/deployment/centos-package-x64/package.sh
+++ b/deployment/centos-package-x64/package.sh
@@ -72,9 +72,6 @@ fi
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the RPMs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" \
- || sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}"
diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh
index 308f3df15..cee96e136 100755
--- a/deployment/debian-package-arm64/docker-build.sh
+++ b/deployment/debian-package-arm64/docker-build.sh
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-arm64/package.sh b/deployment/debian-package-arm64/package.sh
index 19f70d7f6..ce02b1af5 100755
--- a/deployment/debian-package-arm64/package.sh
+++ b/deployment/debian-package-arm64/package.sh
@@ -30,13 +30,12 @@ case $ARCH in
;;
esac
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh
index 45e68f0c6..56227b588 100755
--- a/deployment/debian-package-armhf/docker-build.sh
+++ b/deployment/debian-package-armhf/docker-build.sh
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-armhf/package.sh b/deployment/debian-package-armhf/package.sh
index 0ec0dc95c..4393fb834 100755
--- a/deployment/debian-package-armhf/package.sh
+++ b/deployment/debian-package-armhf/package.sh
@@ -30,13 +30,12 @@ case $ARCH in
;;
esac
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh
index 0590be097..07f726dcc 100755
--- a/deployment/debian-package-x64/docker-build.sh
+++ b/deployment/debian-package-x64/docker-build.sh
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh
index d7c3f5809..2530e253b 100755
--- a/deployment/debian-package-x64/package.sh
+++ b/deployment/debian-package-x64/package.sh
@@ -19,13 +19,12 @@ else
docker_sudo=""
fi
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog
index 61977c4e0..94d0c87df 100644
--- a/deployment/debian-package-x64/pkg-src/changelog
+++ b/deployment/debian-package-x64/pkg-src/changelog
@@ -1,3 +1,21 @@
+jellyfin (10.3.5-1) unstable; urgency=medium
+
+ * New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 09 Jun 2019 21:47:35 -0400
+
+jellyfin (10.3.4-1) unstable; urgency=medium
+
+ * New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 06 Jun 2019 22:45:31 -0400
+
+jellyfin (10.3.3-1) unstable; urgency=medium
+
+ * New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 17 May 2019 23:12:08 -0400
+
jellyfin (10.3.2-1) unstable; urgency=medium
* New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh
index 3acf1ec0d..cefb1652e 100755
--- a/deployment/fedora-package-x64/docker-build.sh
+++ b/deployment/fedora-package-x64/docker-build.sh
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh
index eed29aef3..e659ee5e9 100755
--- a/deployment/fedora-package-x64/package.sh
+++ b/deployment/fedora-package-x64/package.sh
@@ -23,13 +23,12 @@ fi
./create_tarball.sh
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the RPMs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" \
- || sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}"
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec
index 58d643569..aeea7ecd0 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec
+++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec
@@ -7,7 +7,7 @@
%endif
Name: jellyfin
-Version: 10.3.2
+Version: 10.3.5
Release: 1%{?dist}
Summary: The Free Software Media Browser
License: GPLv2
@@ -140,6 +140,12 @@ fi
%systemd_postun_with_restart jellyfin.service
%changelog
+* Sun Jun 09 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
+- New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5
+* Thu Jun 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
+- New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
+* Fri May 17 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
+- New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
* Tue Apr 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
* Sat Apr 20 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh
index 308f3df15..cee96e136 100755
--- a/deployment/ubuntu-package-arm64/docker-build.sh
+++ b/deployment/ubuntu-package-arm64/docker-build.sh
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-arm64/package.sh b/deployment/ubuntu-package-arm64/package.sh
index 54fc38750..5a2bf61c8 100755
--- a/deployment/ubuntu-package-arm64/package.sh
+++ b/deployment/ubuntu-package-arm64/package.sh
@@ -30,13 +30,12 @@ case $ARCH in
;;
esac
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh
index 45e68f0c6..56227b588 100755
--- a/deployment/ubuntu-package-armhf/docker-build.sh
+++ b/deployment/ubuntu-package-armhf/docker-build.sh
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh
index fb03652cd..15f55bff2 100755
--- a/deployment/ubuntu-package-armhf/package.sh
+++ b/deployment/ubuntu-package-armhf/package.sh
@@ -30,13 +30,12 @@ case $ARCH in
;;
esac
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh
index 0590be097..07f726dcc 100755
--- a/deployment/ubuntu-package-x64/docker-build.sh
+++ b/deployment/ubuntu-package-x64/docker-build.sh
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
+chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh
index 6d4625a19..32e6d4fd6 100755
--- a/deployment/ubuntu-package-x64/package.sh
+++ b/deployment/ubuntu-package-x64/package.sh
@@ -19,13 +19,12 @@ else
docker_sudo=""
fi
+# Prepare temporary package dir
+mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
-# Correct ownership on the DEBs (as current user, then as root if that fails)
-chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
- || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 262121a32..1249a60c0 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -14,16 +14,25 @@
<Rule Id="SA1200" Action="None" />
<!-- disable warning SA1309: Fields must not begin with an underscore -->
<Rule Id="SA1309" Action="None" />
+ <!-- disable warning SA1413: Use trailing comma in multi-line initializers -->
+ <Rule Id="SA1413" Action="None" />
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
<Rule Id="SA1512" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
+
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
+ <!-- disable warning CA1031: Do not catch general exception types -->
+ <Rule Id="CA1031" Action="Info" />
+ <!-- disable warning CA1062: Validate arguments of public methods -->
+ <Rule Id="CA1062" Action="Info" />
<!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
<Rule Id="CA1822" Action="Info" />
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
<Rule Id="CA1054" Action="None" />
+ <!-- disable warning CA1303: Do not pass literals as localized parameters -->
+ <Rule Id="CA1303" Action="None" />
</Rules>
</RuleSet>