Merge branch 'master' into tonemap

This commit is contained in:
Nyanmisaka 2020-07-27 13:57:40 +08:00 committed by GitHub
commit df6b303da7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 493 additions and 621 deletions

View File

@ -2776,82 +2776,82 @@ namespace Emby.Server.Implementations.Data
private string FixUnicodeChars(string buffer) private string FixUnicodeChars(string buffer)
{ {
if (buffer.IndexOf('\u2013') > -1) if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2013', '-'); // en dash buffer = buffer.Replace('\u2013', '-'); // en dash
} }
if (buffer.IndexOf('\u2014') > -1) if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2014', '-'); // em dash buffer = buffer.Replace('\u2014', '-'); // em dash
} }
if (buffer.IndexOf('\u2015') > -1) if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2015', '-'); // horizontal bar buffer = buffer.Replace('\u2015', '-'); // horizontal bar
} }
if (buffer.IndexOf('\u2017') > -1) if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2017', '_'); // double low line buffer = buffer.Replace('\u2017', '_'); // double low line
} }
if (buffer.IndexOf('\u2018') > -1) if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2018', '\''); // left single quotation mark buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
} }
if (buffer.IndexOf('\u2019') > -1) if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2019', '\''); // right single quotation mark buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
} }
if (buffer.IndexOf('\u201a') > -1) if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
} }
if (buffer.IndexOf('\u201b') > -1) if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
} }
if (buffer.IndexOf('\u201c') > -1) if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
} }
if (buffer.IndexOf('\u201d') > -1) if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
} }
if (buffer.IndexOf('\u201e') > -1) if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
} }
if (buffer.IndexOf('\u2026') > -1) if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
} }
if (buffer.IndexOf('\u2032') > -1) if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2032', '\''); // prime buffer = buffer.Replace('\u2032', '\''); // prime
} }
if (buffer.IndexOf('\u2033') > -1) if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u2033', '\"'); // double prime buffer = buffer.Replace('\u2033', '\"'); // double prime
} }
if (buffer.IndexOf('\u0060') > -1) if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u0060', '\''); // grave accent buffer = buffer.Replace('\u0060', '\''); // grave accent
} }
if (buffer.IndexOf('\u00B4') > -1) if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1)
{ {
buffer = buffer.Replace('\u00B4', '\''); // acute accent buffer = buffer.Replace('\u00B4', '\''); // acute accent
} }
@ -3000,7 +3000,6 @@ namespace Emby.Server.Implementations.Data
{ {
connection.RunInTransaction(db => connection.RunInTransaction(db =>
{ {
var statements = PrepareAll(db, statementTexts).ToList(); var statements = PrepareAll(db, statementTexts).ToList();
if (!isReturningZeroItems) if (!isReturningZeroItems)
@ -4670,8 +4669,12 @@ namespace Emby.Server.Implementations.Data
if (query.BlockUnratedItems.Length > 1) if (query.BlockUnratedItems.Length > 1)
{ {
var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
whereClauses.Add(string.Format("(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", inClause)); whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))",
inClause));
} }
if (query.ExcludeInheritedTags.Length > 0) if (query.ExcludeInheritedTags.Length > 0)
@ -4680,7 +4683,7 @@ namespace Emby.Server.Implementations.Data
if (statement == null) if (statement == null)
{ {
int index = 0; int index = 0;
string excludedTags = string.Join(",", query.ExcludeInheritedTags.Select(t => paramName + index++)); string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(t => paramName + index++));
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)"); whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
} }
else else

View File

@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.HttpServer
var handler = GetServiceHandler(httpReq); var handler = GetServiceHandler(httpReq);
if (handler != null) if (handler != null)
{ {
await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false); await handler.ProcessRequestAsync(this, httpReq, httpRes, cancellationToken).ConfigureAwait(false);
} }
else else
{ {

View File

@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<string> _affectedPaths = new List<string>(); private readonly List<string> _affectedPaths = new List<string>();
private readonly object _timerLock = new object(); private readonly object _timerLock = new object();
private Timer _timer; private Timer _timer;
private bool _disposed;
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{ {
@ -213,11 +214,11 @@ namespace Emby.Server.Implementations.IO
} }
} }
private bool _disposed;
public void Dispose() public void Dispose()
{ {
_disposed = true; _disposed = true;
DisposeTimer(); DisposeTimer();
GC.SuppressFinalize(this);
} }
} }
} }

View File

@ -18,7 +18,21 @@ namespace Emby.Server.Implementations.Library
{ {
"**/small.jpg", "**/small.jpg",
"**/albumart.jpg", "**/albumart.jpg",
"**/*sample*",
// We have neither non-greedy matching or character group repetitions, working around that here.
// https://github.com/dazinator/DotNet.Glob#patterns
// .*/sample\..{1,5}
"**/sample.?",
"**/sample.??",
"**/sample.???", // Matches sample.mkv
"**/sample.????", // Matches sample.webm
"**/sample.?????",
"**/*.sample.?",
"**/*.sample.??",
"**/*.sample.???",
"**/*.sample.????",
"**/*.sample.?????",
"**/sample/*",
// Directories // Directories
"**/metadata/**", "**/metadata/**",
@ -64,10 +78,13 @@ namespace Emby.Server.Implementations.Library
"**/.grab/**", "**/.grab/**",
"**/.grab", "**/.grab",
// Unix hidden files and directories // Unix hidden files
"**/.*/**",
"**/.*", "**/.*",
// Mac - if you ever remove the above.
// "**/._*",
// "**/.DS_Store",
// thumbs.db // thumbs.db
"**/thumbs.db", "**/thumbs.db",

View File

@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.Library
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
private readonly object _disposeLock = new object();
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
public MediaSourceManager( public MediaSourceManager(
@ -623,12 +621,14 @@ namespace Emby.Server.Implementations.Library
if (liveStreamInfo is IDirectStreamProvider) if (liveStreamInfo is IDirectStreamProvider)
{ {
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest var info = await _mediaEncoder.GetMediaInfo(
new MediaInfoRequest
{ {
MediaSource = mediaSource, MediaSource = mediaSource,
ExtractChapters = false, ExtractChapters = false,
MediaType = DlnaProfileType.Video MediaType = DlnaProfileType.Video
}, cancellationToken).ConfigureAwait(false); },
cancellationToken).ConfigureAwait(false);
mediaSource.MediaStreams = info.MediaStreams; mediaSource.MediaStreams = info.MediaStreams;
mediaSource.Container = info.Container; mediaSource.Container = info.Container;
@ -859,11 +859,11 @@ namespace Emby.Server.Implementations.Library
} }
} }
private Tuple<IMediaSourceProvider, string> GetProvider(string key) private (IMediaSourceProvider, string) GetProvider(string key)
{ {
if (string.IsNullOrEmpty(key)) if (string.IsNullOrEmpty(key))
{ {
throw new ArgumentException("key"); throw new ArgumentException("Key can't be empty.", nameof(key));
} }
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
@ -873,7 +873,7 @@ namespace Emby.Server.Implementations.Library
var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal); var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal);
var keyId = key.Substring(splitIndex + 1); var keyId = key.Substring(splitIndex + 1);
return new Tuple<IMediaSourceProvider, string>(provider, keyId); return (provider, keyId);
} }
/// <summary> /// <summary>
@ -892,16 +892,13 @@ namespace Emby.Server.Implementations.Library
protected virtual void Dispose(bool dispose) protected virtual void Dispose(bool dispose)
{ {
if (dispose) if (dispose)
{
lock (_disposeLock)
{ {
foreach (var key in _openStreams.Keys.ToList()) foreach (var key in _openStreams.Keys.ToList())
{ {
var task = CloseLiveStream(key); CloseLiveStream(key).GetAwaiter().GetResult();
}
Task.WaitAll(task); _liveStreamSemaphore.Dispose();
}
}
} }
} }
} }

View File

@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
public class HdHomerunManager : IDisposable public sealed class HdHomerunManager : IDisposable
{ {
public const int HdHomeRunPort = 65001; public const int HdHomeRunPort = 65001;
@ -105,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
StopStreaming(socket).GetAwaiter().GetResult(); StopStreaming(socket).GetAwaiter().GetResult();
} }
} }
GC.SuppressFinalize(this);
} }
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
@ -162,7 +164,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
_activeTuner = i; _activeTuner = i;
var lockKeyString = string.Format("{0:d}", lockKeyValue); var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
@ -173,8 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
continue; continue;
} }
var commandList = commands.GetCommands(); foreach (var command in commands.GetCommands())
foreach (var command in commandList)
{ {
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort); var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);

View File

@ -92,7 +92,7 @@
"HeaderRecordingGroups": "錄製組", "HeaderRecordingGroups": "錄製組",
"Inherit": "繼承", "Inherit": "繼承",
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕", "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
"TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。", "TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。",
"TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道", "TaskRefreshChannels": "重新整理頻道",
"TaskUpdatePlugins": "更新插件", "TaskUpdatePlugins": "更新插件",

View File

@ -15,13 +15,11 @@ namespace Emby.Server.Implementations.Net
public sealed class UdpSocket : ISocket, IDisposable public sealed class UdpSocket : ISocket, IDisposable
{ {
private Socket _socket; private Socket _socket;
private int _localPort; private readonly int _localPort;
private bool _disposed = false; private bool _disposed = false;
public Socket Socket => _socket; public Socket Socket => _socket;
public IPAddress LocalIPAddress { get; }
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
{ {
SocketFlags = SocketFlags.None SocketFlags = SocketFlags.None
@ -51,18 +49,33 @@ namespace Emby.Server.Implementations.Net
InitReceiveSocketAsyncEventArgs(); InitReceiveSocketAsyncEventArgs();
} }
public UdpSocket(Socket socket, IPEndPoint endPoint)
{
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
_socket = socket;
_socket.Connect(endPoint);
InitReceiveSocketAsyncEventArgs();
}
public IPAddress LocalIPAddress { get; }
private void InitReceiveSocketAsyncEventArgs() private void InitReceiveSocketAsyncEventArgs()
{ {
var receiveBuffer = new byte[8192]; var receiveBuffer = new byte[8192];
_receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
_receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; _receiveSocketAsyncEventArgs.Completed += OnReceiveSocketAsyncEventArgsCompleted;
var sendBuffer = new byte[8192]; var sendBuffer = new byte[8192];
_sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
_sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed; _sendSocketAsyncEventArgs.Completed += OnSendSocketAsyncEventArgsCompleted;
} }
private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) private void OnReceiveSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e)
{ {
var tcs = _currentReceiveTaskCompletionSource; var tcs = _currentReceiveTaskCompletionSource;
if (tcs != null) if (tcs != null)
@ -86,7 +99,7 @@ namespace Emby.Server.Implementations.Net
} }
} }
private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) private void OnSendSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e)
{ {
var tcs = _currentSendTaskCompletionSource; var tcs = _currentSendTaskCompletionSource;
if (tcs != null) if (tcs != null)
@ -104,19 +117,6 @@ namespace Emby.Server.Implementations.Net
} }
} }
public UdpSocket(Socket socket, IPEndPoint endPoint)
{
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
_socket = socket;
_socket.Connect(endPoint);
InitReceiveSocketAsyncEventArgs();
}
public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
@ -247,6 +247,7 @@ namespace Emby.Server.Implementations.Net
} }
} }
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)
@ -255,6 +256,8 @@ namespace Emby.Server.Implementations.Net
} }
_socket?.Dispose(); _socket?.Dispose();
_receiveSocketAsyncEventArgs.Dispose();
_sendSocketAsyncEventArgs.Dispose();
_currentReceiveTaskCompletionSource?.TrySetCanceled(); _currentReceiveTaskCompletionSource?.TrySetCanceled();
_currentSendTaskCompletionSource?.TrySetCanceled(); _currentSendTaskCompletionSource?.TrySetCanceled();

View File

@ -349,16 +349,14 @@ namespace Emby.Server.Implementations.Playlists
AlbumTitle = child.Album AlbumTitle = child.Album
}; };
var hasAlbumArtist = child as IHasAlbumArtist; if (child is IHasAlbumArtist hasAlbumArtist)
if (hasAlbumArtist != null)
{ {
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
} }
var hasArtist = child as IHasArtist; if (child is IHasArtist hasArtist)
if (hasArtist != null)
{ {
entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
} }
if (child.RunTimeTicks.HasValue) if (child.RunTimeTicks.HasValue)
@ -385,16 +383,14 @@ namespace Emby.Server.Implementations.Playlists
AlbumTitle = child.Album AlbumTitle = child.Album
}; };
var hasAlbumArtist = child as IHasAlbumArtist; if (child is IHasAlbumArtist hasAlbumArtist)
if (hasAlbumArtist != null)
{ {
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
} }
var hasArtist = child as IHasArtist; if (child is IHasArtist hasArtist)
if (hasArtist != null)
{ {
entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
} }
if (child.RunTimeTicks.HasValue) if (child.RunTimeTicks.HasValue)
@ -411,8 +407,10 @@ namespace Emby.Server.Implementations.Playlists
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
{ {
var playlist = new M3uPlaylist(); var playlist = new M3uPlaylist
playlist.IsExtended = true; {
IsExtended = true
};
foreach (var child in item.GetLinkedChildren()) foreach (var child in item.GetLinkedChildren())
{ {
var entry = new M3uPlaylistEntry() var entry = new M3uPlaylistEntry()
@ -422,10 +420,9 @@ namespace Emby.Server.Implementations.Playlists
Album = child.Album Album = child.Album
}; };
var hasAlbumArtist = child as IHasAlbumArtist; if (child is IHasAlbumArtist hasAlbumArtist)
if (hasAlbumArtist != null)
{ {
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
} }
if (child.RunTimeTicks.HasValue) if (child.RunTimeTicks.HasValue)
@ -453,10 +450,9 @@ namespace Emby.Server.Implementations.Playlists
Album = child.Album Album = child.Album
}; };
var hasAlbumArtist = child as IHasAlbumArtist; if (child is IHasAlbumArtist hasAlbumArtist)
if (hasAlbumArtist != null)
{ {
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
} }
if (child.RunTimeTicks.HasValue) if (child.RunTimeTicks.HasValue)
@ -514,7 +510,7 @@ namespace Emby.Server.Implementations.Playlists
if (!folderPath.EndsWith(Path.DirectorySeparatorChar)) if (!folderPath.EndsWith(Path.DirectorySeparatorChar))
{ {
folderPath = folderPath + Path.DirectorySeparatorChar; folderPath += Path.DirectorySeparatorChar;
} }
var folderUri = new Uri(folderPath); var folderUri = new Uri(folderPath);
@ -537,32 +533,12 @@ namespace Emby.Server.Implementations.Playlists
return relativePath; return relativePath;
} }
private static string UnEscape(string content)
{
if (content == null)
{
return content;
}
return content.Replace("&amp;", "&").Replace("&apos;", "'").Replace("&quot;", "\"").Replace("&gt;", ">").Replace("&lt;", "<");
}
private static string Escape(string content)
{
if (content == null)
{
return null;
}
return content.Replace("&", "&amp;").Replace("'", "&apos;").Replace("\"", "&quot;").Replace(">", "&gt;").Replace("<", "&lt;");
}
public Folder GetPlaylistsFolder(Guid userId) public Folder GetPlaylistsFolder(Guid userId)
{ {
var typeName = "PlaylistsFolder"; const string TypeName = "PlaylistsFolder";
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ?? return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ??
_libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)); _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal));
} }
} }
} }

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
@ -91,12 +92,22 @@ namespace Emby.Server.Implementations.Services
{ {
if (restPath.Path[0] != '/') if (restPath.Path[0] != '/')
{ {
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"Route '{0}' on '{1}' must start with a '/'",
restPath.Path,
restPath.RequestType.GetMethodName()));
} }
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
{ {
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"Route '{0}' on '{1}' contains invalid chars. ",
restPath.Path,
restPath.RequestType.GetMethodName()));
} }
if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch)) if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch))
@ -179,8 +190,7 @@ namespace Emby.Server.Implementations.Services
var service = httpHost.CreateInstance(serviceType); var service = httpHost.CreateInstance(serviceType);
var serviceRequiresContext = service as IRequiresRequest; if (service is IRequiresRequest serviceRequiresContext)
if (serviceRequiresContext != null)
{ {
serviceRequiresContext.Request = req; serviceRequiresContext.Request = req;
} }

View File

@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.Services
return null; return null;
} }
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken) public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, CancellationToken cancellationToken)
{ {
httpReq.Items["__route"] = _restPath; httpReq.Items["__route"] = _restPath;
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Services
httpReq.ResponseContentType = _responseContentType; httpReq.ResponseContentType = _responseContentType;
} }
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false); var request = await CreateRequest(httpHost, httpReq, _restPath).ConfigureAwait(false);
httpHost.ApplyRequestFilters(httpReq, httpRes, request); httpHost.ApplyRequestFilters(httpReq, httpRes, request);
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Services
await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false);
} }
public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath)
{ {
var requestType = restPath.RequestType; var requestType = restPath.RequestType;

View File

@ -848,8 +848,8 @@ namespace Emby.Server.Implementations.Session
/// </summary> /// </summary>
/// <param name="info">The info.</param> /// <param name="info">The info.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">info</exception> /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">positionTicks</exception> /// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception>
public async Task OnPlaybackStopped(PlaybackStopInfo info) public async Task OnPlaybackStopped(PlaybackStopInfo info)
{ {
CheckDisposed(); CheckDisposed();

View File

@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Session
if (session != null) if (session != null)
{ {
EnsureController(session, e.Argument); EnsureController(session, e.Argument);
await KeepAliveWebSocket(e.Argument); await KeepAliveWebSocket(e.Argument).ConfigureAwait(false);
} }
else else
{ {
@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Session
// Notify WebSocket about timeout // Notify WebSocket about timeout
try try
{ {
await SendForceKeepAlive(webSocket); await SendForceKeepAlive(webSocket).ConfigureAwait(false);
} }
catch (WebSocketException exception) catch (WebSocketException exception)
{ {
@ -233,6 +233,7 @@ namespace Emby.Server.Implementations.Session
if (_keepAliveCancellationToken != null) if (_keepAliveCancellationToken != null)
{ {
_keepAliveCancellationToken.Cancel(); _keepAliveCancellationToken.Cancel();
_keepAliveCancellationToken.Dispose();
_keepAliveCancellationToken = null; _keepAliveCancellationToken = null;
} }
} }
@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Session
lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList();
} }
if (inactive.Any()) if (inactive.Count > 0)
{ {
_logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count);
} }
@ -277,7 +278,7 @@ namespace Emby.Server.Implementations.Session
{ {
try try
{ {
await SendForceKeepAlive(webSocket); await SendForceKeepAlive(webSocket).ConfigureAwait(false);
} }
catch (WebSocketException exception) catch (WebSocketException exception)
{ {
@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session
lock (_webSocketsLock) lock (_webSocketsLock)
{ {
if (lost.Any()) if (lost.Count > 0)
{ {
_logger.LogInformation("Lost {0} WebSockets.", lost.Count); _logger.LogInformation("Lost {0} WebSockets.", lost.Count);
foreach (var webSocket in lost) foreach (var webSocket in lost)
@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.Session
} }
} }
if (!_webSockets.Any()) if (_webSockets.Count == 0)
{ {
StopKeepAlive(); StopKeepAlive();
} }
@ -312,11 +313,13 @@ namespace Emby.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
private Task SendForceKeepAlive(IWebSocketConnection webSocket) private Task SendForceKeepAlive(IWebSocketConnection webSocket)
{ {
return webSocket.SendAsync(new WebSocketMessage<int> return webSocket.SendAsync(
new WebSocketMessage<int>
{ {
MessageType = "ForceKeepAlive", MessageType = "ForceKeepAlive",
Data = WebSocketLostTimeout Data = WebSocketLostTimeout
}, CancellationToken.None); },
CancellationToken.None);
} }
/// <summary> /// <summary>
@ -330,12 +333,11 @@ namespace Emby.Server.Implementations.Session
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await callback(); await callback().ConfigureAwait(false);
Task task = Task.Delay(interval, cancellationToken);
try try
{ {
await task; await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {

View File

@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Sorting
private static int CompareEpisodes(Episode x, Episode y) private static int CompareEpisodes(Episode x, Episode y)
{ {
var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); var xValue = ((x.ParentIndexNumber ?? -1) * 1000) + (x.IndexNumber ?? -1);
var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); var yValue = ((y.ParentIndexNumber ?? -1) * 1000) + (y.IndexNumber ?? -1);
return xValue.CompareTo(yValue); return xValue.CompareTo(yValue);
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -27,14 +28,17 @@ namespace Emby.Server.Implementations.SyncPlay
/// All sessions will receive the message. /// All sessions will receive the message.
/// </summary> /// </summary>
AllGroup = 0, AllGroup = 0,
/// <summary> /// <summary>
/// Only the specified session will receive the message. /// Only the specified session will receive the message.
/// </summary> /// </summary>
CurrentSession = 1, CurrentSession = 1,
/// <summary> /// <summary>
/// All sessions, except the current one, will receive the message. /// All sessions, except the current one, will receive the message.
/// </summary> /// </summary>
AllExceptCurrentSession = 2, AllExceptCurrentSession = 2,
/// <summary> /// <summary>
/// Only sessions that are not buffering will receive the message. /// Only sessions that are not buffering will receive the message.
/// </summary> /// </summary>
@ -56,15 +60,6 @@ namespace Emby.Server.Implementations.SyncPlay
/// </summary> /// </summary>
private readonly GroupInfo _group = new GroupInfo(); private readonly GroupInfo _group = new GroupInfo();
/// <inheritdoc />
public Guid GetGroupId() => _group.GroupId;
/// <inheritdoc />
public Guid GetPlayingItemId() => _group.PlayingItem.Id;
/// <inheritdoc />
public bool IsGroupEmpty() => _group.IsEmpty();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SyncPlayController" /> class. /// Initializes a new instance of the <see cref="SyncPlayController" /> class.
/// </summary> /// </summary>
@ -78,6 +73,15 @@ namespace Emby.Server.Implementations.SyncPlay
_syncPlayManager = syncPlayManager; _syncPlayManager = syncPlayManager;
} }
/// <inheritdoc />
public Guid GetGroupId() => _group.GroupId;
/// <inheritdoc />
public Guid GetPlayingItemId() => _group.PlayingItem.Id;
/// <inheritdoc />
public bool IsGroupEmpty() => _group.IsEmpty();
/// <summary> /// <summary>
/// Converts DateTime to UTC string. /// Converts DateTime to UTC string.
/// </summary> /// </summary>
@ -85,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay
/// <value>The UTC string.</value> /// <value>The UTC string.</value>
private string DateToUTCString(DateTime date) private string DateToUTCString(DateTime date)
{ {
return date.ToUniversalTime().ToString("o"); return date.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
} }
/// <summary> /// <summary>
@ -94,23 +98,23 @@ namespace Emby.Server.Implementations.SyncPlay
/// <param name="from">The current session.</param> /// <param name="from">The current session.</param>
/// <param name="type">The filtering type.</param> /// <param name="type">The filtering type.</param>
/// <value>The array of sessions matching the filter.</value> /// <value>The array of sessions matching the filter.</value>
private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, BroadcastType type)
{ {
switch (type) switch (type)
{ {
case BroadcastType.CurrentSession: case BroadcastType.CurrentSession:
return new SessionInfo[] { from }; return new SessionInfo[] { from };
case BroadcastType.AllGroup: case BroadcastType.AllGroup:
return _group.Participants.Values.Select( return _group.Participants.Values
session => session.Session).ToArray(); .Select(session => session.Session);
case BroadcastType.AllExceptCurrentSession: case BroadcastType.AllExceptCurrentSession:
return _group.Participants.Values.Select( return _group.Participants.Values
session => session.Session).Where( .Select(session => session.Session)
session => !session.Id.Equals(from.Id)).ToArray(); .Where(session => !session.Id.Equals(from.Id, StringComparison.Ordinal));
case BroadcastType.AllReady: case BroadcastType.AllReady:
return _group.Participants.Values.Where( return _group.Participants.Values
session => !session.IsBuffering).Select( .Where(session => !session.IsBuffering)
session => session.Session).ToArray(); .Select(session => session.Session);
default: default:
return Array.Empty<SessionInfo>(); return Array.Empty<SessionInfo>();
} }
@ -128,10 +132,9 @@ namespace Emby.Server.Implementations.SyncPlay
{ {
IEnumerable<Task> GetTasks() IEnumerable<Task> GetTasks()
{ {
SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in FilterSessions(from, type))
foreach (var session in sessions)
{ {
yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id, message, cancellationToken);
} }
} }
@ -150,10 +153,9 @@ namespace Emby.Server.Implementations.SyncPlay
{ {
IEnumerable<Task> GetTasks() IEnumerable<Task> GetTasks()
{ {
SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in FilterSessions(from, type))
foreach (var session in sessions)
{ {
yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); yield return _sessionManager.SendSyncPlayCommand(session.Id, message, cancellationToken);
} }
} }
@ -236,9 +238,11 @@ namespace Emby.Server.Implementations.SyncPlay
} }
else else
{ {
var playRequest = new PlayRequest(); var playRequest = new PlayRequest
playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; {
playRequest.StartPositionTicks = _group.PositionTicks; ItemIds = new Guid[] { _group.PlayingItem.Id },
StartPositionTicks = _group.PositionTicks
};
var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest);
SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken);
} }

View File

@ -19,22 +19,18 @@ namespace Jellyfin.Drawing.Skia
/// <param name="percent">The percentage played to display with the indicator.</param> /// <param name="percent">The percentage played to display with the indicator.</param>
public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent) public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
{ {
using (var paint = new SKPaint()) using var paint = new SKPaint();
{
var endX = imageSize.Width - 1; var endX = imageSize.Width - 1;
var endY = imageSize.Height - 1; var endY = imageSize.Height - 1;
paint.Color = SKColor.Parse("#99000000"); paint.Color = SKColor.Parse("#99000000");
paint.Style = SKPaintStyle.Fill; paint.Style = SKPaintStyle.Fill;
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, (float)endX, (float)endY), paint); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, endX, endY), paint);
double foregroundWidth = endX; double foregroundWidth = (endX * percent) / 100;
foregroundWidth *= percent;
foregroundWidth /= 100;
paint.Color = SKColor.Parse("#FF00A4DC"); paint.Color = SKColor.Parse("#FF00A4DC");
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), endY), paint);
}
} }
} }
} }

View File

@ -22,18 +22,15 @@ namespace Jellyfin.Drawing.Skia
{ {
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
using (var paint = new SKPaint()) using var paint = new SKPaint
{ {
paint.Color = SKColor.Parse("#CC00A4DC"); Color = SKColor.Parse("#CC00A4DC"),
paint.Style = SKPaintStyle.Fill; Style = SKPaintStyle.Fill
};
canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
}
using (var paint = new SKPaint())
{
paint.Color = new SKColor(255, 255, 255, 255); paint.Color = new SKColor(255, 255, 255, 255);
paint.Style = SKPaintStyle.Fill;
paint.TextSize = 30; paint.TextSize = 30;
paint.IsAntialias = true; paint.IsAntialias = true;
@ -49,4 +46,3 @@ namespace Jellyfin.Drawing.Skia
} }
} }
} }
}

View File

@ -12,7 +12,7 @@ namespace Jellyfin.Drawing.Skia
/// Initializes a new instance of the <see cref="SkiaCodecException" /> class. /// Initializes a new instance of the <see cref="SkiaCodecException" /> class.
/// </summary> /// </summary>
/// <param name="result">The non-successful codec result returned by Skia.</param> /// <param name="result">The non-successful codec result returned by Skia.</param>
public SkiaCodecException(SKCodecResult result) : base() public SkiaCodecException(SKCodecResult result)
{ {
CodecResult = result; CodecResult = result;
} }

View File

@ -29,9 +29,7 @@ namespace Jellyfin.Drawing.Skia
/// </summary> /// </summary>
/// <param name="logger">The application logger.</param> /// <param name="logger">The application logger.</param>
/// <param name="appPaths">The application paths.</param> /// <param name="appPaths">The application paths.</param>
public SkiaEncoder( public SkiaEncoder(ILogger<SkiaEncoder> logger, IApplicationPaths appPaths)
ILogger<SkiaEncoder> logger,
IApplicationPaths appPaths)
{ {
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
@ -102,19 +100,14 @@ namespace Jellyfin.Drawing.Skia
/// <returns>The converted format.</returns> /// <returns>The converted format.</returns>
public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
{ {
switch (selectedFormat) return selectedFormat switch
{ {
case ImageFormat.Bmp: ImageFormat.Bmp => SKEncodedImageFormat.Bmp,
return SKEncodedImageFormat.Bmp; ImageFormat.Jpg => SKEncodedImageFormat.Jpeg,
case ImageFormat.Jpg: ImageFormat.Gif => SKEncodedImageFormat.Gif,
return SKEncodedImageFormat.Jpeg; ImageFormat.Webp => SKEncodedImageFormat.Webp,
case ImageFormat.Gif: _ => SKEncodedImageFormat.Png
return SKEncodedImageFormat.Gif; };
case ImageFormat.Webp:
return SKEncodedImageFormat.Webp;
default:
return SKEncodedImageFormat.Png;
}
} }
private static bool IsTransparentRow(SKBitmap bmp, int row) private static bool IsTransparentRow(SKBitmap bmp, int row)
@ -146,64 +139,35 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap CropWhiteSpace(SKBitmap bitmap) private SKBitmap CropWhiteSpace(SKBitmap bitmap)
{ {
var topmost = 0; var topmost = 0;
for (int row = 0; row < bitmap.Height; ++row) while (topmost < bitmap.Height && IsTransparentRow(bitmap, topmost))
{ {
if (IsTransparentRow(bitmap, row)) topmost++;
{
topmost = row + 1;
}
else
{
break;
}
} }
int bottommost = bitmap.Height; int bottommost = bitmap.Height;
for (int row = bitmap.Height - 1; row >= 0; --row) while (bottommost >= 0 && IsTransparentRow(bitmap, bottommost - 1))
{ {
if (IsTransparentRow(bitmap, row)) bottommost--;
{
bottommost = row;
}
else
{
break;
}
} }
int leftmost = 0, rightmost = bitmap.Width; var leftmost = 0;
for (int col = 0; col < bitmap.Width; ++col) while (leftmost < bitmap.Width && IsTransparentColumn(bitmap, leftmost))
{ {
if (IsTransparentColumn(bitmap, col)) leftmost++;
{
leftmost = col + 1;
}
else
{
break;
}
} }
for (int col = bitmap.Width - 1; col >= 0; --col) var rightmost = bitmap.Width;
while (rightmost >= 0 && IsTransparentColumn(bitmap, rightmost - 1))
{ {
if (IsTransparentColumn(bitmap, col)) rightmost--;
{
rightmost = col;
}
else
{
break;
}
} }
var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost);
using (var image = SKImage.FromBitmap(bitmap)) using var image = SKImage.FromBitmap(bitmap);
using (var subset = image.Subset(newRect)) using var subset = image.Subset(newRect);
{
return SKBitmap.FromImage(subset); return SKBitmap.FromImage(subset);
} }
}
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
@ -216,15 +180,13 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path); throw new FileNotFoundException("File not found", path);
} }
using (var codec = SKCodec.Create(path, out SKCodecResult result)) using var codec = SKCodec.Create(path, out SKCodecResult result);
{
EnsureSuccess(result); EnsureSuccess(result);
var info = codec.Info; var info = codec.Info;
return new ImageDimensions(info.Width, info.Height); return new ImageDimensions(info.Width, info.Height);
} }
}
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
@ -253,12 +215,7 @@ namespace Jellyfin.Drawing.Skia
} }
} }
if (HasDiacritics(path)) return HasDiacritics(path);
{
return true;
}
return false;
} }
private string NormalizePath(string path) private string NormalizePath(string path)
@ -283,25 +240,17 @@ namespace Jellyfin.Drawing.Skia
return SKEncodedOrigin.TopLeft; return SKEncodedOrigin.TopLeft;
} }
switch (orientation.Value) return orientation.Value switch
{ {
case ImageOrientation.TopRight: ImageOrientation.TopRight => SKEncodedOrigin.TopRight,
return SKEncodedOrigin.TopRight; ImageOrientation.RightTop => SKEncodedOrigin.RightTop,
case ImageOrientation.RightTop: ImageOrientation.RightBottom => SKEncodedOrigin.RightBottom,
return SKEncodedOrigin.RightTop; ImageOrientation.LeftTop => SKEncodedOrigin.LeftTop,
case ImageOrientation.RightBottom: ImageOrientation.LeftBottom => SKEncodedOrigin.LeftBottom,
return SKEncodedOrigin.RightBottom; ImageOrientation.BottomRight => SKEncodedOrigin.BottomRight,
case ImageOrientation.LeftTop: ImageOrientation.BottomLeft => SKEncodedOrigin.BottomLeft,
return SKEncodedOrigin.LeftTop; _ => SKEncodedOrigin.TopLeft
case ImageOrientation.LeftBottom: };
return SKEncodedOrigin.LeftBottom;
case ImageOrientation.BottomRight:
return SKEncodedOrigin.BottomRight;
case ImageOrientation.BottomLeft:
return SKEncodedOrigin.BottomLeft;
default:
return SKEncodedOrigin.TopLeft;
}
} }
/// <summary> /// <summary>
@ -323,8 +272,7 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap) if (requiresTransparencyHack || forceCleanBitmap)
{ {
using (var codec = SKCodec.Create(NormalizePath(path))) using var codec = SKCodec.Create(NormalizePath(path));
{
if (codec == null) if (codec == null)
{ {
origin = GetSKEncodedOrigin(orientation); origin = GetSKEncodedOrigin(orientation);
@ -341,7 +289,6 @@ namespace Jellyfin.Drawing.Skia
return bitmap; return bitmap;
} }
}
var resultBitmap = SKBitmap.Decode(NormalizePath(path)); var resultBitmap = SKBitmap.Decode(NormalizePath(path));
@ -367,15 +314,8 @@ namespace Jellyfin.Drawing.Skia
{ {
if (cropWhitespace) if (cropWhitespace)
{ {
using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin)) using var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin);
{ return bitmap == null ? null : CropWhiteSpace(bitmap);
if (bitmap == null)
{
return null;
}
return CropWhiteSpace(bitmap);
}
} }
return Decode(path, forceAnalyzeBitmap, orientation, out origin); return Decode(path, forceAnalyzeBitmap, orientation, out origin);
@ -403,135 +343,57 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{ {
if (origin == SKEncodedOrigin.Default)
{
return bitmap;
}
var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom
|| origin == SKEncodedOrigin.RightTop;
var rotated = needsFlip
? new SKBitmap(bitmap.Height, bitmap.Width)
: new SKBitmap(bitmap.Width, bitmap.Height);
using var surface = new SKCanvas(rotated);
var midX = (float)rotated.Width / 2;
var midY = (float)rotated.Height / 2;
switch (origin) switch (origin)
{ {
case SKEncodedOrigin.TopRight: case SKEncodedOrigin.TopRight:
{ surface.Scale(-1, 1, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.BottomRight: case SKEncodedOrigin.BottomRight:
{ surface.RotateDegrees(180, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float py = (float)bitmap.Height / 2;
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.BottomLeft: case SKEncodedOrigin.BottomLeft:
{ surface.Scale(1, -1, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float py = (float)bitmap.Height / 2;
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.LeftTop: case SKEncodedOrigin.LeftTop:
{ surface.Translate(0, -rotated.Height);
// TODO: Remove dual canvases, had trouble with flipping surface.Scale(1, -1, midX, midY);
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) surface.RotateDegrees(-90);
{ break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
using (var flippedCanvas = new SKCanvas(flippedBitmap))
{
flippedCanvas.Translate(flippedBitmap.Width, 0);
flippedCanvas.Scale(-1, 1);
flippedCanvas.DrawBitmap(rotated, 0, 0);
}
return flippedBitmap;
}
}
case SKEncodedOrigin.RightTop: case SKEncodedOrigin.RightTop:
{
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0); surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90); surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0); break;
}
return rotated;
}
case SKEncodedOrigin.RightBottom: case SKEncodedOrigin.RightBottom:
{ surface.Translate(rotated.Width, 0);
// TODO: Remove dual canvases, had trouble with flipping surface.Scale(1, -1, midX, midY);
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) surface.RotateDegrees(90);
{ break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
using (var flippedCanvas = new SKCanvas(flippedBitmap))
{
flippedCanvas.Translate(flippedBitmap.Width, 0);
flippedCanvas.Scale(-1, 1);
flippedCanvas.DrawBitmap(rotated, 0, 0);
}
return flippedBitmap;
}
}
case SKEncodedOrigin.LeftBottom: case SKEncodedOrigin.LeftBottom:
{
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height); surface.Translate(0, rotated.Height);
surface.RotateDegrees(270); surface.RotateDegrees(-90);
break;
}
surface.DrawBitmap(bitmap, 0, 0); surface.DrawBitmap(bitmap, 0, 0);
}
return rotated; return rotated;
} }
default: return bitmap;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
{ {
@ -552,8 +414,7 @@ namespace Jellyfin.Drawing.Skia
var blur = options.Blur ?? 0; var blur = options.Blur ?? 0;
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) using var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation);
{
if (bitmap == null) if (bitmap == null)
{ {
throw new InvalidDataException($"Skia unable to read image {inputPath}"); throw new InvalidDataException($"Skia unable to read image {inputPath}");
@ -574,8 +435,7 @@ namespace Jellyfin.Drawing.Skia
var width = newImageSize.Width; var width = newImageSize.Width;
var height = newImageSize.Height; var height = newImageSize.Height;
using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType);
{
// scale image // scale image
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
@ -583,18 +443,15 @@ namespace Jellyfin.Drawing.Skia
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath)) using var outputStream = new SKFileWStream(outputPath);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels())) using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels());
{
pixmap.Encode(outputStream, skiaOutputFormat, quality); pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath; return outputPath;
} }
}
// create bitmap to use for canvas drawing used to draw into bitmap // create bitmap to use for canvas drawing used to draw into bitmap
using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType)) using var saveBitmap = new SKBitmap(width, height);
using (var canvas = new SKCanvas(saveBitmap)) using var canvas = new SKCanvas(saveBitmap);
{
// set background color if present // set background color if present
if (hasBackgroundColor) if (hasBackgroundColor)
{ {
@ -605,13 +462,11 @@ namespace Jellyfin.Drawing.Skia
if (blur > 0) if (blur > 0)
{ {
// create image from resized bitmap to apply blur // create image from resized bitmap to apply blur
using (var paint = new SKPaint()) using var paint = new SKPaint();
using (var filter = SKImageFilter.CreateBlur(blur, blur)) using var filter = SKImageFilter.CreateBlur(blur, blur);
{
paint.ImageFilter = filter; paint.ImageFilter = filter;
canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint);
} }
}
else else
{ {
// draw resized bitmap onto canvas // draw resized bitmap onto canvas
@ -642,9 +497,6 @@ namespace Jellyfin.Drawing.Skia
pixmap.Encode(outputStream, skiaOutputFormat, quality); pixmap.Encode(outputStream, skiaOutputFormat, quality);
} }
} }
}
}
}
return outputPath; return outputPath;
} }

View File

@ -10,7 +10,7 @@ namespace Jellyfin.Drawing.Skia
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SkiaException"/> class. /// Initializes a new instance of the <see cref="SkiaException"/> class.
/// </summary> /// </summary>
public SkiaException() : base() public SkiaException()
{ {
} }

View File

@ -69,13 +69,11 @@ namespace Jellyfin.Drawing.Skia
/// <param name="height">The desired height of the collage.</param> /// <param name="height">The desired height of the collage.</param>
public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
{ {
using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) using var bitmap = BuildSquareCollageBitmap(paths, width, height);
using (var outputStream = new SKFileWStream(outputPath)) using var outputStream = new SKFileWStream(outputPath);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels());
{
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
} }
}
/// <summary> /// <summary>
/// Create a thumb collage. /// Create a thumb collage.
@ -86,20 +84,17 @@ namespace Jellyfin.Drawing.Skia
/// <param name="height">The desired height of the collage.</param> /// <param name="height">The desired height of the collage.</param>
public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
{ {
using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) using var bitmap = BuildThumbCollageBitmap(paths, width, height);
using (var outputStream = new SKFileWStream(outputPath)) using var outputStream = new SKFileWStream(outputPath);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels());
{
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
} }
}
private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height)
{ {
var bitmap = new SKBitmap(width, height); var bitmap = new SKBitmap(width, height);
using (var canvas = new SKCanvas(bitmap)) using var canvas = new SKCanvas(bitmap);
{
canvas.Clear(SKColors.Black); canvas.Clear(SKColors.Black);
// number of images used in the thumbnail // number of images used in the thumbnail
@ -111,8 +106,7 @@ namespace Jellyfin.Drawing.Skia
int imageIndex = 0; int imageIndex = 0;
for (int i = 0; i < iCount; i++) for (int i = 0; i < iCount; i++)
{ {
using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex);
{
imageIndex = newIndex; imageIndex = newIndex;
if (currentBitmap == null) if (currentBitmap == null)
{ {
@ -121,22 +115,16 @@ namespace Jellyfin.Drawing.Skia
// resize to the same aspect as the original // resize to the same aspect as the original
int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType);
{
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
// crop image // crop image
int ix = Math.Abs((iWidth - iSlice) / 2); int ix = Math.Abs((iWidth - iSlice) / 2);
using (var image = SKImage.FromBitmap(resizeBitmap)) using var image = SKImage.FromBitmap(resizeBitmap);
using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight))) using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight));
{
// draw image onto canvas // draw image onto canvas
canvas.DrawImage(subset ?? image, iSlice * i, 0); canvas.DrawImage(subset ?? image, iSlice * i, 0);
} }
}
}
}
}
return bitmap; return bitmap;
} }
@ -176,14 +164,12 @@ namespace Jellyfin.Drawing.Skia
var cellWidth = width / 2; var cellWidth = width / 2;
var cellHeight = height / 2; var cellHeight = height / 2;
using (var canvas = new SKCanvas(bitmap)) using var canvas = new SKCanvas(bitmap);
{
for (var x = 0; x < 2; x++) for (var x = 0; x < 2; x++)
{ {
for (var y = 0; y < 2; y++) for (var y = 0; y < 2; y++)
{ {
using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex);
{
imageIndex = newIndex; imageIndex = newIndex;
if (currentBitmap == null) if (currentBitmap == null)
@ -191,8 +177,7 @@ namespace Jellyfin.Drawing.Skia
continue; continue;
} }
using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType);
{
// scale image // scale image
currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
@ -202,9 +187,6 @@ namespace Jellyfin.Drawing.Skia
canvas.DrawBitmap(resizedBitmap, xPos, yPos); canvas.DrawBitmap(resizedBitmap, xPos, yPos);
} }
} }
}
}
}
return bitmap; return bitmap;
} }

View File

@ -28,18 +28,15 @@ namespace Jellyfin.Drawing.Skia
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
var text = count.ToString(CultureInfo.InvariantCulture); var text = count.ToString(CultureInfo.InvariantCulture);
using (var paint = new SKPaint()) using var paint = new SKPaint
{ {
paint.Color = SKColor.Parse("#CC00A4DC"); Color = SKColor.Parse("#CC00A4DC"),
paint.Style = SKPaintStyle.Fill; Style = SKPaintStyle.Fill
};
canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
}
using (var paint = new SKPaint())
{
paint.Color = new SKColor(255, 255, 255, 255); paint.Color = new SKColor(255, 255, 255, 255);
paint.Style = SKPaintStyle.Fill;
paint.TextSize = 24; paint.TextSize = 24;
paint.IsAntialias = true; paint.IsAntialias = true;
@ -65,4 +62,3 @@ namespace Jellyfin.Drawing.Skia
} }
} }
} }
}

View File

@ -766,8 +766,8 @@ namespace Jellyfin.Server.Implementations.Users
{ {
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
return Regex.IsMatch(name, @"^[\w\-'._@]*$"); return Regex.IsMatch(name, @"^[\w\ \-'._@]*$");
} }
private IAuthenticationProvider GetAuthenticationProvider(User user) private IAuthenticationProvider GetAuthenticationProvider(User user)

View File

@ -457,6 +457,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
if (!IsCopyCodec(outputVideoCodec)) if (!IsCopyCodec(outputVideoCodec))
{ {
@ -529,6 +530,11 @@ namespace MediaBrowser.Controller.MediaEncoding
.Append(' ') .Append(' ')
.Append("-filter_hw_device ocl "); .Append("-filter_hw_device ocl ");
} }
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-hwaccel videotoolbox ");
} }
} }

View File

@ -174,7 +174,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
inputFiles = new[] { mediaSource.Path }; inputFiles = new[] { mediaSource.Path };
} }
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), subtitleStream, cancellationToken).ConfigureAwait(false); var protocol = mediaSource.Protocol;
if (subtitleStream.IsExternal)
{
protocol = _mediaSourceManager.GetPathProtocol(subtitleStream.Path);
}
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);

View File

@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public override string Description => "Get artist and album metadata or images from AudioDB."; public override string Description => "Get artist and album metadata or images from AudioDB.";
// TODO remove when plugin removed from server.
public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml";
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer) : base(applicationPaths, xmlSerializer)
{ {

View File

@ -23,6 +23,9 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
public const long DefaultRateLimit = 2000u; public const long DefaultRateLimit = 2000u;
// TODO remove when plugin removed from server.
public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer) : base(applicationPaths, xmlSerializer)
{ {

View File

@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public override string Description => "Get metadata for movies and other video content from OMDb."; public override string Description => "Get metadata for movies and other video content from OMDb.";
// TODO remove when plugin removed from server.
public override string ConfigurationFileName => "Jellyfin.Plugin.Omdb.xml";
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer) : base(applicationPaths, xmlSerializer)
{ {

View File

@ -17,6 +17,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public override string Description => "Get metadata for movies and other video content from TheTVDB."; public override string Description => "Get metadata for movies and other video content from TheTVDB.";
// TODO remove when plugin removed from server.
public override string ConfigurationFileName => "Jellyfin.Plugin.TheTvdb.xml";
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer) : base(applicationPaths, xmlSerializer)
{ {

View File

@ -9,17 +9,29 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("/media/small.jpg", true)] [InlineData("/media/small.jpg", true)]
[InlineData("/media/albumart.jpg", true)] [InlineData("/media/albumart.jpg", true)]
[InlineData("/media/movie.sample.mp4", true)] [InlineData("/media/movie.sample.mp4", true)]
[InlineData("/media/movie/sample.mp4", true)]
[InlineData("/media/movie/sample/movie.mp4", true)]
[InlineData("/foo/sample/bar/baz.mkv", false)]
[InlineData("/media/movies/the sample/the sample.mkv", false)]
[InlineData("/media/movies/sampler.mkv", false)]
[InlineData("/media/movies/#Recycle/test.txt", true)] [InlineData("/media/movies/#Recycle/test.txt", true)]
[InlineData("/media/movies/#recycle/", true)] [InlineData("/media/movies/#recycle/", true)]
[InlineData("/media/movies/#recycle", true)] [InlineData("/media/movies/#recycle", true)]
[InlineData("thumbs.db", true)] [InlineData("thumbs.db", true)]
[InlineData(@"C:\media\movies\movie.avi", false)] [InlineData(@"C:\media\movies\movie.avi", false)]
[InlineData("/media/.hiddendir/file.mp4", true)] [InlineData("/media/.hiddendir/file.mp4", false)]
[InlineData("/media/dir/.hiddenfile.mp4", true)] [InlineData("/media/dir/.hiddenfile.mp4", true)]
[InlineData("/media/dir/._macjunk.mp4", true)]
[InlineData("/volume1/video/Series/@eaDir", true)] [InlineData("/volume1/video/Series/@eaDir", true)]
[InlineData("/volume1/video/Series/@eaDir/file.txt", true)] [InlineData("/volume1/video/Series/@eaDir/file.txt", true)]
[InlineData("/directory/@Recycle", true)] [InlineData("/directory/@Recycle", true)]
[InlineData("/directory/@Recycle/file.mp3", true)] [InlineData("/directory/@Recycle/file.mp3", true)]
[InlineData("/media/movies/.@__thumb", true)]
[InlineData("/media/movies/.@__thumb/foo-bar-thumbnail.png", true)]
[InlineData("/media/music/Foo B.A.R./epic.flac", false)]
[InlineData("/media/music/Foo B.A.R", false)]
// This test is pending an upstream fix: https://github.com/dazinator/DotNet.Glob/issues/78
// [InlineData("/media/music/Foo B.A.R.", false)]
public void PathIgnored(string path, bool expected) public void PathIgnored(string path, bool expected)
{ {
Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path)); Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path));