Merge branch 'master' into NetworkPR2

This commit is contained in:
BaronGreenback 2020-09-24 15:15:32 +01:00 committed by GitHub
commit 89e67b2e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 590 additions and 348 deletions

View File

@ -135,6 +135,7 @@
- [YouKnowBlom](https://github.com/YouKnowBlom)
- [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta)
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
# Emby Contributors

View File

@ -126,14 +126,14 @@ namespace Emby.Dlna
var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used.");
builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelDescription:{0}", profile.ModelDescription ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelName:{0}", profile.ModelName ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelNumber:{0}", profile.ModelNumber ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelUrl:{0}", profile.ModelUrl ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "SerialNumber:{0}", profile.SerialNumber ?? string.Empty).AppendLine();
builder.Append("FriendlyName:").AppendLine(profile.FriendlyName);
builder.Append("Manufacturer:").AppendLine(profile.Manufacturer);
builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl);
builder.Append("ModelDescription:").AppendLine(profile.ModelDescription);
builder.Append("ModelName:").AppendLine(profile.ModelName);
builder.Append("ModelNumber:").AppendLine(profile.ModelNumber);
builder.Append("ModelUrl:").AppendLine(profile.ModelUrl);
builder.Append("SerialNumber:").AppendLine(profile.SerialNumber);
_logger.LogInformation(builder.ToString());
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 278 B

View File

@ -669,62 +669,57 @@ namespace Emby.Dlna.PlayTo
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
switch (command.Name)
{
switch (commandType)
{
case GeneralCommandType.VolumeDown:
return _device.VolumeDown(cancellationToken);
case GeneralCommandType.VolumeUp:
return _device.VolumeUp(cancellationToken);
case GeneralCommandType.Mute:
return _device.Mute(cancellationToken);
case GeneralCommandType.Unmute:
return _device.Unmute(cancellationToken);
case GeneralCommandType.ToggleMute:
return _device.ToggleMute(cancellationToken);
case GeneralCommandType.SetAudioStreamIndex:
if (command.Arguments.TryGetValue("Index", out string index))
case GeneralCommandType.VolumeDown:
return _device.VolumeDown(cancellationToken);
case GeneralCommandType.VolumeUp:
return _device.VolumeUp(cancellationToken);
case GeneralCommandType.Mute:
return _device.Mute(cancellationToken);
case GeneralCommandType.Unmute:
return _device.Unmute(cancellationToken);
case GeneralCommandType.ToggleMute:
return _device.ToggleMute(cancellationToken);
case GeneralCommandType.SetAudioStreamIndex:
if (command.Arguments.TryGetValue("Index", out string index))
{
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
return SetAudioStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
return SetAudioStreamIndex(val);
}
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
case GeneralCommandType.SetSubtitleStreamIndex:
if (command.Arguments.TryGetValue("Index", out index))
{
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
}
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
case GeneralCommandType.SetSubtitleStreamIndex:
if (command.Arguments.TryGetValue("Index", out index))
{
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
case GeneralCommandType.SetVolume:
if (command.Arguments.TryGetValue("Volume", out string vol))
{
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
{
return _device.SetVolume(volume, cancellationToken);
}
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
}
throw new ArgumentException("Unsupported volume value supplied.");
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
case GeneralCommandType.SetVolume:
if (command.Arguments.TryGetValue("Volume", out string vol))
{
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
{
return _device.SetVolume(volume, cancellationToken);
}
throw new ArgumentException("Volume argument cannot be null");
default:
return Task.CompletedTask;
}
throw new ArgumentException("Unsupported volume value supplied.");
}
throw new ArgumentException("Volume argument cannot be null");
default:
return Task.CompletedTask;
}
return Task.CompletedTask;
}
private async Task SetAudioStreamIndex(int? newIndex)

View File

@ -1,6 +1,6 @@
#nullable enable
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
@ -19,12 +19,7 @@ namespace Emby.Naming.AudioBook
public AudioBookFilePathParserResult Parse(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
var result = new AudioBookFilePathParserResult();
AudioBookFilePathParserResult result = default;
var fileName = Path.GetFileNameWithoutExtension(path);
foreach (var expression in _options.AudioBookPartsExpressions)
{

View File

@ -1,8 +1,9 @@
#nullable enable
#pragma warning disable CS1591
namespace Emby.Naming.AudioBook
{
public class AudioBookFilePathParserResult
public struct AudioBookFilePathParserResult
{
public int? PartNumber { get; set; }

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@ -37,6 +38,7 @@ using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
using Emby.Server.Implementations.QuickConnect;
using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security;
@ -120,6 +122,7 @@ namespace Emby.Server.Implementations
private readonly IFileSystem _fileSystemManager;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly IStartupOptions _startupOptions;
private IMediaEncoder _mediaEncoder;
@ -259,6 +262,8 @@ namespace Emby.Server.Implementations
IServiceCollection serviceCollection)
{
_xmlSerializer = new MyXmlSerializer();
_jsonSerializer = new JsonSerializer();
ServiceCollection = serviceCollection;
ApplicationPaths = applicationPaths;
@ -1012,6 +1017,119 @@ namespace Emby.Server.Implementations
protected abstract void RestartInternal();
/// <summary>
/// Comparison function used in <see cref="GetPlugins" />.
/// </summary>
/// <param name="a">Item to compare.</param>
/// <param name="b">Item to compare with.</param>
/// <returns>Boolean result of the operation.</returns>
private static int VersionCompare(
(Version PluginVersion, string Name, string Path) a,
(Version PluginVersion, string Name, string Path) b)
{
int compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture);
if (compare == 0)
{
return a.PluginVersion.CompareTo(b.PluginVersion);
}
return compare;
}
/// <summary>
/// Returns a list of plugins to install.
/// </summary>
/// <param name="path">Path to check.</param>
/// <param name="cleanup">True if an attempt should be made to delete old plugs.</param>
/// <returns>Enumerable list of dlls to load.</returns>
private IEnumerable<string> GetPlugins(string path, bool cleanup = true)
{
var dllList = new List<string>();
var versions = new List<(Version PluginVersion, string Name, string Path)>();
var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly);
string metafile;
foreach (var dir in directories)
{
try
{
metafile = Path.Combine(dir, "meta.json");
if (File.Exists(metafile))
{
var manifest = _jsonSerializer.DeserializeFromFile<PluginManifest>(metafile);
if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
{
targetAbi = new Version(0, 0, 0, 1);
}
if (!Version.TryParse(manifest.Version, out var version))
{
version = new Version(0, 0, 0, 1);
}
if (ApplicationVersion >= targetAbi)
{
// Only load Plugins if the plugin is built for this version or below.
versions.Add((version, manifest.Name, dir));
}
}
else
{
// No metafile, so lets see if the folder is versioned.
metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1];
int versionIndex = dir.LastIndexOf('_');
if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version ver))
{
// Versioned folder.
versions.Add((ver, metafile, dir));
}
else
{
// Un-versioned folder - Add it under the path name and version 0.0.0.1.
versions.Add((new Version(0, 0, 0, 1), metafile, dir));
}
}
}
catch
{
continue;
}
}
string lastName = string.Empty;
versions.Sort(VersionCompare);
// Traverse backwards through the list.
// The first item will be the latest version.
for (int x = versions.Count - 1; x >= 0; x--)
{
if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase))
{
dllList.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories));
lastName = versions[x].Name;
continue;
}
if (!string.IsNullOrEmpty(lastName) && cleanup)
{
// Attempt a cleanup of old folders.
try
{
Logger.LogDebug("Deleting {Path}", versions[x].Path);
Directory.Delete(versions[x].Path, true);
}
catch (Exception e)
{
Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path);
}
}
}
return dllList;
}
/// <summary>
/// Gets the composable part assemblies.
/// </summary>
@ -1020,7 +1138,7 @@ namespace Emby.Server.Implementations
{
if (Directory.Exists(ApplicationPaths.PluginsPath))
{
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories))
foreach (var file in GetPlugins(ApplicationPaths.PluginsPath))
{
Assembly plugAss;
try

View File

@ -234,7 +234,9 @@ namespace Emby.Server.Implementations.Data
{
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
{
bindParam.Bind(value.ToByteArray());
Span<byte> byteValue = stackalloc byte[16];
value.TryWriteBytes(byteValue);
bindParam.Bind(byteValue);
}
else
{

View File

@ -32,10 +32,6 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.7" />
<PackageReference Include="Mono.Nat" Version="3.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />

View File

@ -107,7 +107,7 @@
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
"TaskRefreshLibrary": "Scanner toute les Bibliothèques",
"TaskRefreshLibrary": "Scanner toutes les Bibliothèques",
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",

View File

@ -1,3 +1,11 @@
{
"Albums": "Álbumes"
"Albums": "Álbumes",
"Collections": "Colecións",
"ChapterNameValue": "Capítulos {0}",
"Channels": "Canles",
"CameraImageUploadedFrom": "Cargouse unha nova imaxe da cámara desde {0}",
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autenticouse correctamente",
"Artists": "Artistas",
"Application": "Aplicativo"
}

View File

@ -84,8 +84,8 @@
"UserDeletedWithName": "사용자 {0} 삭제됨",
"UserDownloadingItemWithValues": "{0}이(가) {1}을 다운로드 중입니다",
"UserLockedOutWithName": "유저 {0} 은(는) 잠금처리 되었습니다",
"UserOfflineFromDevice": "{1}로부터 {0}의 연결이 끊겼습니다",
"UserOnlineFromDevice": "{0}은 {1}에서 온라인 상태입니다",
"UserOfflineFromDevice": "{1}에서 {0}의 연결이 끊킴",
"UserOnlineFromDevice": "{0}이 {1}으로 접속",
"UserPasswordChangedWithName": "사용자 {0}의 비밀번호가 변경되었습니다",
"UserPolicyUpdatedWithName": "{0}의 사용자 정책이 업데이트되었습니다",
"UserStartedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생 중",

View File

@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Lydavspilling startet",
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet",
"NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp",
"NotificationOptionInstallationFailed": "Installasjonsfeil",
"NotificationOptionInstallationFailed": "Installasjonen feilet",
"NotificationOptionNewLibraryContent": "Nytt innhold lagt til",
"NotificationOptionPluginError": "Pluginfeil",
"NotificationOptionPluginInstalled": "Plugin installert",

View File

@ -26,7 +26,7 @@
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
"Collections": "தொகுப்புகள்",
"CameraImageUploadedFrom": "{0} இலிருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது",
"CameraImageUploadedFrom": "{0} இல் இருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது",
"AppDeviceValues": "செயலி: {0}, சாதனம்: {1}",
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
"TaskRefreshChannels": "சேனல்களை புதுப்பி",

View File

@ -1,20 +1,20 @@
{
"Collections": "Bộ Sưu Tập",
"Favorites": "Sở Thích",
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
"HeaderContinueWatching": "Tiếp Tục Xem",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
"Photos": "Ảnh",
"Playlists": "Danh Sách Chơi",
"Shows": "Các Chương Trình",
"Playlists": "Danh sách phát",
"Shows": "Chương Trình TV",
"Songs": "Các Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
"Albums": "Bộ Sưu Tập",
"Artists": "Nghệ Sĩ",
"Albums": "Albums",
"Artists": "Các Nghệ Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình thông tin chi tiết.",
"TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
@ -29,8 +29,8 @@
"TaskCleanLogs": "Làm sạch nhật ký",
"TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm các tệp mới và làm mới thông tin chi tiết.",
"TaskRefreshLibrary": "Quét Thư viện Phương tiện",
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho các video có chương.",
"TaskRefreshChapterImages": "Trích xuất hình ảnh chương",
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.",
"TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh",
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
"TaskCleanCache": "Làm Sạch Thư Mục Cache",
"TasksChannelsCategory": "Kênh Internet",
@ -107,8 +107,8 @@
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Chương {0}",
"Channels": "Kênh",
"ChapterNameValue": "Phân Cảnh {0}",
"Channels": "Các Kênh",
"CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}",
"Books": "Sách",
"AuthenticationSucceededWithUserName": "{0} xác thực thành công",

View File

@ -96,7 +96,7 @@
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道",
"TaskUpdatePlugins": "更新外掛",
"TaskRefreshPeople": "重新整理人員",
"TaskRefreshPeople": "刷新用戶",
"TaskCleanLogsDescription": "刪除超過 {0} 天的舊紀錄檔。",
"TaskCleanLogs": "清空紀錄資料夾",
"TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新描述資料。",

View File

@ -413,6 +413,7 @@ namespace Emby.Server.Implementations.Localization
yield return new LocalizationOption("Swedish", "sv");
yield return new LocalizationOption("Swiss German", "gsw");
yield return new LocalizationOption("Turkish", "tr");
yield return new LocalizationOption("Tiếng Việt", "vi");
}
}
}

View File

@ -0,0 +1,60 @@
using System;
namespace Emby.Server.Implementations.Plugins
{
/// <summary>
/// Defines a Plugin manifest file.
/// </summary>
public class PluginManifest
{
/// <summary>
/// Gets or sets the category of the plugin.
/// </summary>
public string Category { get; set; }
/// <summary>
/// Gets or sets the changelog information.
/// </summary>
public string Changelog { get; set; }
/// <summary>
/// Gets or sets the description of the plugin.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the Global Unique Identifier for the plugin.
/// </summary>
public Guid Guid { get; set; }
/// <summary>
/// Gets or sets the Name of the plugin.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets an overview of the plugin.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// Gets or sets the owner of the plugin.
/// </summary>
public string Owner { get; set; }
/// <summary>
/// Gets or sets the compatibility version for the plugin.
/// </summary>
public string TargetAbi { get; set; }
/// <summary>
/// Gets or sets the timestamp of the plugin.
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// Gets or sets the Version number of the plugin.
/// </summary>
public string Version { get; set; }
}
}

View File

@ -1037,7 +1037,7 @@ namespace Emby.Server.Implementations.Session
var generalCommand = new GeneralCommand
{
Name = GeneralCommandType.DisplayMessage.ToString()
Name = GeneralCommandType.DisplayMessage
};
generalCommand.Arguments["Header"] = command.Header;
@ -1268,7 +1268,7 @@ namespace Emby.Server.Implementations.Session
{
var generalCommand = new GeneralCommand
{
Name = GeneralCommandType.DisplayContent.ToString(),
Name = GeneralCommandType.DisplayContent,
Arguments =
{
["ItemId"] = command.ItemId,

View File

@ -15,12 +15,14 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Common.System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.Updates
{
@ -377,11 +379,20 @@ namespace Emby.Server.Implementations.Updates
throw new InvalidDataException("The checksum of the received data doesn't match.");
}
// Version folder as they cannot be overwritten in Windows.
targetDir += "_" + package.Version;
if (Directory.Exists(targetDir))
{
Directory.Delete(targetDir, true);
try
{
Directory.Delete(targetDir, true);
}
catch
{
// Ignore any exceptions.
}
}
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
@ -423,15 +434,22 @@ namespace Emby.Server.Implementations.Updates
path = file;
}
if (isDirectory)
try
{
_logger.LogInformation("Deleting plugin directory {0}", path);
Directory.Delete(path, true);
if (isDirectory)
{
_logger.LogInformation("Deleting plugin directory {0}", path);
Directory.Delete(path, true);
}
else
{
_logger.LogInformation("Deleting plugin file {0}", path);
_fileSystem.DeleteFile(path);
}
}
else
catch
{
_logger.LogInformation("Deleting plugin file {0}", path);
_fileSystem.DeleteFile(path);
// Ignore file errors.
}
var list = _config.Configuration.UninstalledPlugins.ToList();

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
@ -113,7 +113,6 @@ namespace Jellyfin.Api.Controllers
/// Gets a video hls playlist stream.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
@ -170,7 +169,6 @@ namespace Jellyfin.Api.Controllers
[ProducesPlaylistFile]
public async Task<ActionResult> GetMasterHlsVideoPlaylist(
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -223,7 +221,6 @@ namespace Jellyfin.Api.Controllers
var streamingRequest = new HlsVideoRequestDto
{
Id = itemId,
Container = container,
Static = @static ?? true,
Params = @params,
Tag = tag,
@ -281,7 +278,6 @@ namespace Jellyfin.Api.Controllers
/// Gets an audio hls playlist stream.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
@ -338,7 +334,6 @@ namespace Jellyfin.Api.Controllers
[ProducesPlaylistFile]
public async Task<ActionResult> GetMasterHlsAudioPlaylist(
[FromRoute, Required] Guid itemId,
[FromQuery, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -391,7 +386,6 @@ namespace Jellyfin.Api.Controllers
var streamingRequest = new HlsAudioRequestDto
{
Id = itemId,
Container = container,
Static = @static ?? true,
Params = @params,
Tag = tag,
@ -449,7 +443,6 @@ namespace Jellyfin.Api.Controllers
/// Gets a video stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
@ -504,7 +497,6 @@ namespace Jellyfin.Api.Controllers
[ProducesPlaylistFile]
public async Task<ActionResult> GetVariantHlsVideoPlaylist(
[FromRoute, Required] Guid itemId,
[FromQuery, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -557,7 +549,6 @@ namespace Jellyfin.Api.Controllers
var streamingRequest = new VideoRequestDto
{
Id = itemId,
Container = container,
Static = @static ?? true,
Params = @params,
Tag = tag,
@ -615,7 +606,6 @@ namespace Jellyfin.Api.Controllers
/// Gets an audio stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
@ -670,7 +660,6 @@ namespace Jellyfin.Api.Controllers
[ProducesPlaylistFile]
public async Task<ActionResult> GetVariantHlsAudioPlaylist(
[FromRoute, Required] Guid itemId,
[FromQuery, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -723,7 +712,6 @@ namespace Jellyfin.Api.Controllers
var streamingRequest = new StreamingRequestDto
{
Id = itemId,
Container = container,
Static = @static ?? true,
Params = @params,
Tag = tag,
@ -841,7 +829,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
[FromRoute, Required] string container,
[FromRoute] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -1011,7 +999,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
[FromRoute, Required] string container,
[FromRoute] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@ -1144,30 +1132,30 @@ namespace Jellyfin.Api.Controllers
var builder = new StringBuilder();
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(CultureInfo.InvariantCulture));
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
builder.AppendLine("#EXTM3U")
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
.AppendLine("#EXT-X-VERSION:3")
.Append("#EXT-X-TARGETDURATION:")
.Append(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength))
.AppendLine()
.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
var queryString = Request.QueryString;
var index = 0;
var segmentExtension = GetSegmentFileExtension(streamingRequest.SegmentContainer);
var queryString = Request.QueryString;
foreach (var length in segmentLengths)
{
builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc");
builder.AppendLine(
string.Format(
CultureInfo.InvariantCulture,
"hls1/{0}/{1}{2}{3}",
name,
index.ToString(CultureInfo.InvariantCulture),
segmentExtension,
queryString));
index++;
builder.Append("#EXTINF:")
.Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
.AppendLine(", nodesc")
.Append("hls1/")
.Append(name)
.Append('/')
.Append(index++)
.Append(segmentExtension)
.Append(queryString)
.AppendLine();
}
builder.AppendLine("#EXT-X-ENDLIST");
@ -1465,7 +1453,7 @@ namespace Jellyfin.Api.Controllers
var args = "-codec:v:0 " + codec;
// if (state.EnableMpegtsM2TsMode)
// if (state.EnableMpegtsM2TsMode)
// {
// args += " -mpegts_m2ts_mode 1";
// }

View File

@ -1017,9 +1017,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool validateListings = false,
[FromQuery] bool validateLogin = false)
{
using var sha = SHA1.Create();
if (!string.IsNullOrEmpty(pw))
{
using var sha = SHA1.Create();
listingsProviderInfo.Password = Hex.Encode(sha.ComputeHash(Encoding.UTF8.GetBytes(pw)));
}

View File

@ -1,5 +1,3 @@
#pragma warning disable CA1801
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@ -150,25 +148,25 @@ namespace Jellyfin.Api.Controllers
/// Instructs a session to play an item.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="command">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
/// <param name="startPositionTicks">The starting position of the first item.</param>
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
[HttpPost("Sessions/{sessionId}/Playing")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Play(
[FromRoute, Required] string sessionId,
[FromRoute, Required] PlayCommand command,
[FromQuery] Guid[] itemIds,
[FromQuery, Required] PlayCommand playCommand,
[FromQuery, Required] string itemIds,
[FromQuery] long? startPositionTicks)
{
var playRequest = new PlayRequest
{
ItemIds = itemIds,
ItemIds = RequestHelpers.GetGuids(itemIds),
StartPositionTicks = startPositionTicks,
PlayCommand = command
PlayCommand = playCommand
};
_sessionManager.SendPlayCommand(
@ -184,20 +182,29 @@ namespace Jellyfin.Api.Controllers
/// Issues a playstate command to a client.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
/// <param name="command">The <see cref="PlaystateCommand"/>.</param>
/// <param name="seekPositionTicks">The optional position ticks.</param>
/// <param name="controllingUserId">The optional controlling user id.</param>
/// <response code="204">Playstate command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing")]
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendPlaystateCommand(
[FromRoute, Required] string sessionId,
[FromBody] PlaystateRequest playstateRequest)
[FromRoute, Required] PlaystateCommand command,
[FromQuery] long? seekPositionTicks,
[FromQuery] string? controllingUserId)
{
_sessionManager.SendPlaystateCommand(
RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,
sessionId,
playstateRequest,
new PlaystateRequest()
{
Command = command,
ControllingUserId = controllingUserId,
SeekPositionTicks = seekPositionTicks,
},
CancellationToken.None);
return NoContent();
@ -215,18 +222,12 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendSystemCommand(
[FromRoute, Required] string sessionId,
[FromRoute, Required] string command)
[FromRoute, Required] GeneralCommandType command)
{
var name = command;
if (Enum.TryParse(name, true, out GeneralCommandType commandType))
{
name = commandType.ToString();
}
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
var generalCommand = new GeneralCommand
{
Name = name,
Name = command,
ControllingUserId = currentSession.UserId
};
@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendGeneralCommand(
[FromRoute, Required] string sessionId,
[FromRoute, Required] string command)
[FromRoute, Required] GeneralCommandType command)
{
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@ -434,9 +435,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportViewing(
[FromQuery] string? sessionId,
[FromQuery] string? itemId)
[FromQuery, Required] string? itemId)
{
string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
string session = sessionId ?? RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
_sessionManager.ReportNowViewingItem(session, itemId);
return NoContent();

View File

@ -281,7 +281,8 @@ namespace Jellyfin.Api.Controllers
var builder = new StringBuilder();
builder.AppendLine("#EXTM3U")
.Append("#EXT-X-TARGETDURATION:")
.AppendLine(segmentLength.ToString(CultureInfo.InvariantCulture))
.Append(segmentLength)
.AppendLine()
.AppendLine("#EXT-X-VERSION:3")
.AppendLine("#EXT-X-MEDIA-SEQUENCE:0")
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
@ -296,8 +297,9 @@ namespace Jellyfin.Api.Controllers
var lengthTicks = Math.Min(remaining, segmentLengthTicks);
builder.Append("#EXTINF:")
.Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture))
.AppendLine(",");
.Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds)
.Append(',')
.AppendLine();
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);

View File

@ -326,9 +326,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetVideoStream_2")]
[HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetVideoStreamWithExt")]
[HttpGet("{itemId}/stream")]
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStream_2")]
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStreamWithExt")]
[HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesVideoFile]

View File

@ -169,7 +169,7 @@ namespace Jellyfin.Api.Helpers
string? containerInternal = Path.GetExtension(state.RequestedUrl);
if (string.IsNullOrEmpty(streamingRequest.Container))
if (!string.IsNullOrEmpty(streamingRequest.Container))
{
containerInternal = streamingRequest.Container;
}

View File

@ -504,6 +504,11 @@ namespace Jellyfin.Api.Helpers
}
}
if (string.IsNullOrEmpty(_mediaEncoder.EncoderPath))
{
throw new ArgumentException("FFMPEG path not set.");
}
var process = new Process
{
StartInfo = new ProcessStartInfo

View File

@ -14,9 +14,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.7" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
</ItemGroup>

View File

@ -41,8 +41,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.8" />
</ItemGroup>
<ItemGroup>

View File

@ -20,8 +20,8 @@
<ItemGroup>
<PackageReference Include="BlurHashSharp" Version="1.1.0" />
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.1" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.2" />
</ItemGroup>
<ItemGroup>

View File

@ -24,11 +24,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -41,10 +41,10 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.8" />
<PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />

View File

@ -18,9 +18,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="NetworkCollection" Version="1.0.1" />
</ItemGroup>

View File

@ -1,3 +1,4 @@
#nullable enable
#pragma warning disable CS1591
using System;
@ -15,11 +16,6 @@ namespace MediaBrowser.Controller.Extensions
{
public static string RemoveDiacritics(this string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var chars = Normalize(text, NormalizationForm.FormD)
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark);

View File

@ -1,3 +1,4 @@
#nullable enable
#pragma warning disable CS1591
using System;
@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Library
{
public static class NameExtensions
{
private static string RemoveDiacritics(string name)
private static string RemoveDiacritics(string? name)
{
if (name == null)
{

View File

@ -14,8 +14,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>

View File

@ -212,7 +212,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (match.Success)
{
return new Version(match.Groups[1].Value);
if (Version.TryParse(match.Groups[1].Value, out var result))
{
return result;
}
}
var versionMap = GetFFmpegLibraryVersions(output);

View File

@ -15,6 +15,7 @@ namespace MediaBrowser.Model.Dlna
new ResolutionConfiguration(720, 950000),
new ResolutionConfiguration(1280, 2500000),
new ResolutionConfiguration(1920, 4000000),
new ResolutionConfiguration(2560, 8000000),
new ResolutionConfiguration(3840, 35000000)
};

View File

@ -34,7 +34,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.8" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="5.0.0-preview.8.20407.11" />
</ItemGroup>

View File

@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Session
{
public class GeneralCommand
{
public string Name { get; set; }
public GeneralCommandType Name { get; set; }
public Guid ControllingUserId { get; set; }

View File

@ -297,7 +297,7 @@ namespace MediaBrowser.Providers.Manager
}
/// <summary>
/// Befores the save.
/// Before the save.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
@ -355,13 +355,12 @@ namespace MediaBrowser.Providers.Manager
protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
{
var folder = item as Folder;
if (folder != null)
if (item is Folder folder)
{
return folder.GetRecursiveChildren();
}
return new List<BaseItem>();
return Array.Empty<BaseItem>();
}
protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
@ -814,7 +813,7 @@ namespace MediaBrowser.Providers.Manager
try
{
refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@ -882,16 +881,6 @@ namespace MediaBrowser.Providers.Manager
return refreshResult;
}
private string NormalizeLanguage(string language)
{
if (string.IsNullOrWhiteSpace(language))
{
return "en";
}
return language;
}
private void MergeNewData(TItemType source, TIdType lookupInfo)
{
// Copy new provider id's that may have been obtained

View File

@ -16,9 +16,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
<PackageReference Include="TvDbSharper" Version="3.2.1" />

View File

@ -6,6 +6,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Videos
{
public List<Video> Results { get; set; }
public IReadOnlyList<Video> Results { get; set; }
}
}

View File

@ -6,6 +6,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class Trailers
{
public List<Youtube> Youtube { get; set; }
public IReadOnlyList<Youtube> Youtube { get; set; }
}
}

View File

@ -7,6 +7,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
{
public class PersonImages
{
public List<Profile> Profiles { get; set; }
public IReadOnlyList<Profile> Profiles { get; set; }
}
}

View File

@ -38,6 +38,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public static string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc />
public int Order => 0;
public bool Supports(BaseItem item)
{
return item is Movie || item is MusicVideo || item is Trailer;
@ -201,8 +204,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return null;
}
public int Order => 0;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

View File

@ -6,22 +6,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
internal class TmdbImageSettings
{
public List<string> backdrop_sizes { get; set; }
public IReadOnlyList<string> backdrop_sizes { get; set; }
public string secure_base_url { get; set; }
public List<string> poster_sizes { get; set; }
public IReadOnlyList<string> poster_sizes { get; set; }
public List<string> profile_sizes { get; set; }
public IReadOnlyList<string> profile_sizes { get; set; }
public string GetImageUrl(string image)
{
return secure_base_url + image;
}
}
internal class TmdbSettingsResult
{
public TmdbImageSettings images { get; set; }
}
}

View File

@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
private const string TmdbConfigUrl = TmdbUtils.BaseTmdbApiUrl + "3/configuration?api_key={0}";
private const string GetMovieInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers";
internal static TmdbMovieProvider Current { get; private set; }
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
@ -44,7 +44,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
private readonly ILibraryManager _libraryManager;
private readonly IApplicationHost _appHost;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
/// <summary>
/// The _TMDB settings task.
/// </summary>
private TmdbSettingsResult _tmdbSettings;
public TmdbMovieProvider(
IJsonSerializer jsonSerializer,
@ -65,6 +68,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
Current = this;
}
internal static TmdbMovieProvider Current { get; private set; }
/// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
/// <inheritdoc />
public int Order => 1;
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{
return GetMovieSearchResults(searchInfo, cancellationToken);
@ -131,13 +142,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return movieDb.GetMetadata(id, cancellationToken);
}
public string Name => TmdbUtils.ProviderName;
/// <summary>
/// The _TMDB settings task.
/// </summary>
private TmdbSettingsResult _tmdbSettings;
/// <summary>
/// Gets the TMDB settings.
/// </summary>
@ -272,7 +276,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
languages.Add("en");
}
return string.Join(",", languages);
return string.Join(',', languages);
}
public static string NormalizeLanguage(string language)
@ -381,15 +385,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <summary>
/// Gets the movie db response.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message, CancellationToken cancellationToken = default)
{
message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message, cancellationToken);
}
/// <inheritdoc />
public int Order => 1;
/// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{

View File

@ -207,7 +207,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return results
.Select(i =>
{
var remoteResult = new RemoteSearchResult {SearchProviderName = TmdbMovieProvider.Current.Name, Name = i.Title ?? i.Name ?? i.Original_Title, ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path};
var remoteResult = new RemoteSearchResult
{
SearchProviderName = TmdbMovieProvider.Current.Name,
Name = i.Title ?? i.Name ?? i.Original_Title,
ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
};
if (!string.IsNullOrWhiteSpace(i.Release_Date))
{

View File

@ -0,0 +1,9 @@
#pragma warning disable CS1591
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
internal class TmdbSettingsResult
{
public TmdbImageSettings images { get; set; }
}
}

View File

@ -14,6 +14,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Music
{
public class TmdbMusicVideoProvider : IRemoteMetadataProvider<MusicVideo, MusicVideoInfo>
{
public string Name => TmdbMovieProvider.Current.Name;
public Task<MetadataResult<MusicVideo>> GetMetadata(MusicVideoInfo info, CancellationToken cancellationToken)
{
return TmdbMovieProvider.Current.GetItemMetadata<MusicVideo>(info, cancellationToken);
@ -24,8 +26,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Music
return Task.FromResult((IEnumerable<RemoteSearchResult>)new List<RemoteSearchResult>());
}
public string Name => TmdbMovieProvider.Current.Name;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();

View File

@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
{
const string DataFileName = "info.json";
private const string DataFileName = "info.json";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
@ -39,20 +39,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<TmdbPersonProvider> _logger;
public TmdbPersonProvider(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
ILogger<TmdbPersonProvider> logger)
IHttpClientFactory httpClientFactory)
{
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_logger = logger;
Current = this;
}
@ -75,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
var images = (info.Images ?? new PersonImages()).Profiles ?? new List<Profile>();
IReadOnlyList<Profile> images = info.Images?.Profiles ?? Array.Empty<Profile>();
var result = new RemoteSearchResult
{
@ -95,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
if (searchInfo.IsAutomated)
{
// Don't hammer moviedb searching by name
return new List<RemoteSearchResult>();
return Array.Empty<RemoteSearchResult>();
}
var url = string.Format(

View File

@ -28,7 +28,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
: base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
{ }
{
}
public string Name => TmdbUtils.ProviderName;
// After TheTvDb
public int Order => 1;
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
@ -43,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var episode = (Controller.Entities.TV.Episode)item;
var series = episode.Series;
var seriesId = series != null ? series.GetProviderId(MetadataProvider.Tmdb) : null;
var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
var list = new List<RemoteImageInfo>();
@ -62,8 +68,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var language = item.GetPreferredMetadataLanguage();
var response = await GetEpisodeInfo(seriesId, seasonNumber.Value, episodeNumber.Value,
language, cancellationToken).ConfigureAwait(false);
var response = await GetEpisodeInfo(
seriesId,
seasonNumber.Value,
episodeNumber.Value,
language,
cancellationToken).ConfigureAwait(false);
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
@ -120,14 +130,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return GetResponse(url, cancellationToken);
}
public string Name => TmdbUtils.ProviderName;
public bool Supports(BaseItem item)
{
return item is Controller.Entities.TV.Episode;
}
// After TheTvDb
public int Order => 1;
}
}

View File

@ -29,7 +29,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
: base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
{ }
{
}
// After TheTvDb
public int Order => 1;
public string Name => TmdbUtils.ProviderName;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
@ -41,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return list;
}
var metadataResult = await GetMetadata(searchInfo, cancellationToken);
var metadataResult = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false);
if (metadataResult.HasMetadata)
{
@ -205,10 +211,5 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
return GetResponse(url, cancellationToken);
}
// After TheTvDb
public int Order => 1;
public string Name => TmdbUtils.ProviderName;
}
}

View File

@ -21,11 +21,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public abstract class TmdbEpisodeProviderBase
{
private const string EpisodeUrlPattern = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
private readonly ILogger<TmdbEpisodeProviderBase> _logger;
protected TmdbEpisodeProviderBase(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
@ -34,13 +34,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_localization = localization;
_logger = loggerFactory.CreateLogger<TmdbEpisodeProviderBase>();
}
protected ILogger Logger => _logger;
protected async Task<EpisodeResult> GetEpisodeInfo(string seriesTmdbId, int season, int episodeNumber, string preferredMetadataLanguage,
protected async Task<EpisodeResult> GetEpisodeInfo(
string seriesTmdbId,
int season,
int episodeNumber,
string preferredMetadataLanguage,
CancellationToken cancellationToken)
{
await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, cancellationToken)
@ -93,7 +96,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-episode-{1}-{2}.json",
var filename = string.Format(
CultureInfo.InvariantCulture,
"season-{0}-episode-{1}-{2}.json",
seasonNumber.ToString(CultureInfo.InvariantCulture),
episodeNumber.ToString(CultureInfo.InvariantCulture),
preferredLanguage);

View File

@ -112,9 +112,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private async Task<List<Poster>> FetchImages(Season item, string tmdbId, string language, CancellationToken cancellationToken)
{
await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, item.IndexNumber.GetValueOrDefault(), language, cancellationToken).ConfigureAwait(false);
var seasonNumber = item.IndexNumber.GetValueOrDefault();
await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, seasonNumber, language, cancellationToken).ConfigureAwait(false);
var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
var path = TmdbSeasonProvider.Current.GetDataFilePath(tmdbId, seasonNumber, language);
if (!string.IsNullOrEmpty(path))
{

View File

@ -28,26 +28,32 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
{
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
private readonly ILogger<TmdbSeasonProvider> _logger;
internal static TmdbSeasonProvider Current { get; private set; }
public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger<TmdbSeasonProvider> logger)
public TmdbSeasonProvider(
IHttpClientFactory httpClientFactory,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
IJsonSerializer jsonSerializer,
ILogger<TmdbSeasonProvider> logger)
{
_httpClientFactory = httpClientFactory;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
_jsonSerializer = jsonSerializer;
_logger = logger;
Current = this;
}
public string Name => TmdbUtils.ProviderName;
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Season>();
@ -116,8 +122,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return result;
}
public string Name => TmdbUtils.ProviderName;
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
{
return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
@ -128,7 +132,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
private async Task<SeasonResult> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,
private async Task<SeasonResult> GetSeasonInfo(
string seriesTmdbId,
int season,
string preferredMetadataLanguage,
CancellationToken cancellationToken)
{
await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken)
@ -181,7 +188,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-{1}.json",
var filename = string.Format(
CultureInfo.InvariantCulture,
"season-{0}-{1}.json",
seasonNumber.ToString(CultureInfo.InvariantCulture),
preferredLanguage);

View File

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
@ -12,7 +13,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@ -25,19 +25,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
{
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
}
public string Name => ProviderName;
public static string ProviderName => TmdbUtils.ProviderName;
// After tvdb and fanart
public int Order => 2;
public bool Supports(BaseItem item)
{
return item is Series;
@ -56,7 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
var list = new List<RemoteImageInfo>();
var results = await FetchImages(item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false);
var results = await FetchImages(item, null, cancellationToken).ConfigureAwait(false);
if (results == null)
{
@ -148,10 +149,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
/// </summary>
/// <param name="item">The item.</param>
/// <param name="language">The language.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MovieImages}.</returns>
private async Task<Images> FetchImages(BaseItem item, string language, IJsonSerializer jsonSerializer,
private async Task<Images> FetchImages(
BaseItem item,
string language,
CancellationToken cancellationToken)
{
var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
@ -165,22 +167,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
if (!string.IsNullOrEmpty(path))
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
var fileInfo = _fileSystem.GetFileInfo(path);
if (fileInfo.Exists)
{
return jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
}
return _jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
}
return null;
}
// After tvdb and fanart
public int Order => 2;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

View File

@ -17,8 +17,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Plugins.Tmdb.Models.Search;
@ -33,38 +31,35 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings";
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger<TmdbSeriesProvider> _logger;
private readonly ILocalizationManager _localization;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
internal static TmdbSeriesProvider Current { get; private set; }
public TmdbSeriesProvider(
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILogger<TmdbSeriesProvider> logger,
ILocalizationManager localization,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager)
{
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
_localization = localization;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
Current = this;
}
internal static TmdbSeriesProvider Current { get; private set; }
public string Name => TmdbUtils.ProviderName;
// After TheTVDB
public int Order => 1;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
@ -129,8 +124,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Series>();
result.QueriedById = true;
var result = new MetadataResult<Series>
{
QueriedById = true
};
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
@ -206,9 +203,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
var result = new MetadataResult<Series>();
result.Item = new Series();
result.ResultLanguage = seriesInfo.ResultLanguage;
var result = new MetadataResult<Series>
{
Item = new Series(),
ResultLanguage = seriesInfo.ResultLanguage
};
var settings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
@ -474,12 +473,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var path = GetDataFilePath(tmdbId, language);
var fileInfo = _fileSystem.GetFileSystemInfo(path);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
// If it's recent or automatic updates are enabled, don't re-download
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
if ((DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalDays <= 2)
{
return Task.CompletedTask;
}
@ -549,9 +547,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return null;
}
// After TheTVDB
public int Order => 1;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

View File

@ -21,6 +21,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
_httpClientFactory = httpClientFactory;
}
public string Name => TmdbMovieProvider.Current.Name;
public int Order => 0;
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken)
{
return TmdbMovieProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
@ -31,10 +35,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
return TmdbMovieProvider.Current.GetItemMetadata<Trailer>(info, cancellationToken);
}
public string Name => TmdbMovieProvider.Current.Name;
public int Order => 0;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

View File

@ -33,6 +33,8 @@ namespace MediaBrowser.Providers.Studios
public string Name => "Emby Designs";
public int Order => 0;
public bool Supports(BaseItem item)
{
return item is Studio;
@ -119,8 +121,6 @@ namespace MediaBrowser.Providers.Studios
return EnsureList(url, file, _fileSystem, cancellationToken);
}
public int Order => 0;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
@ -161,12 +161,12 @@ namespace MediaBrowser.Providers.Studios
private string GetComparableName(string name)
{
return name.Replace(" ", string.Empty)
.Replace(".", string.Empty)
.Replace("&", string.Empty)
.Replace("!", string.Empty)
.Replace(",", string.Empty)
.Replace("/", string.Empty);
return name.Replace(" ", string.Empty, StringComparison.Ordinal)
.Replace(".", string.Empty, StringComparison.Ordinal)
.Replace("&", string.Empty, StringComparison.Ordinal)
.Replace("!", string.Empty, StringComparison.Ordinal)
.Replace(",", string.Empty, StringComparison.Ordinal)
.Replace("/", string.Empty, StringComparison.Ordinal);
}
public IEnumerable<string> GetAvailableImages(string file)

View File

@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Subtitles
private ISubtitleProvider GetProvider(string id)
{
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name), StringComparison.Ordinal));
}
/// <inheritdoc />

View File

@ -48,18 +48,25 @@ namespace MediaBrowser.Providers.TV
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
{
var tvdbId = series.GetProviderId(MetadataProvider.Tvdb);
if (string.IsNullOrEmpty(tvdbId))
var tvdbIdString = series.GetProviderId(MetadataProvider.Tvdb);
if (string.IsNullOrEmpty(tvdbIdString))
{
return false;
}
var episodes = await _tvdbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
var episodes = await _tvdbClientManager.GetAllEpisodesAsync(
int.Parse(tvdbIdString, CultureInfo.InvariantCulture),
series.GetPreferredMetadataLanguage(),
cancellationToken).ConfigureAwait(false);
var episodeLookup = episodes
.Select(i =>
{
DateTime.TryParse(i.FirstAired, out var firstAired);
if (!DateTime.TryParse(i.FirstAired, out var firstAired))
{
firstAired = default;
}
var seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
return (seasonNumber, episodeNumber, firstAired);

View File

@ -27,6 +27,9 @@ namespace MediaBrowser.Providers.TV
{
}
/// <inheritdoc />
protected override bool EnableUpdatingPremiereDateFromChildren => true;
/// <inheritdoc />
protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
@ -67,9 +70,6 @@ namespace MediaBrowser.Providers.TV
return updateType;
}
/// <inheritdoc />
protected override bool EnableUpdatingPremiereDateFromChildren => true;
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Season item)
=> item.GetEpisodes();

View File

@ -124,7 +124,7 @@ To run the project with Visual Studio Code you will first need to open the repos
Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
After the required extensions are installed, you can can run the server by pressing `F5`.
After the required extensions are installed, you can run the server by pressing `F5`.
#### Running From The Command Line

View File

@ -43,13 +43,13 @@ namespace Rssdp.Infrastructure
{
var builder = new StringBuilder();
const string argFormat = "{0}: {1}\r\n";
const string ArgFormat = "{0}: {1}\r\n";
builder.AppendFormat("{0}\r\n", header);
foreach (var pair in values)
{
builder.AppendFormat(argFormat, pair.Key, pair.Value);
builder.AppendFormat(ArgFormat, pair.Key, pair.Value);
}
builder.Append("\r\n");

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@ -84,6 +84,10 @@ EOF
%{_libdir}/jellyfin/*.so
%{_libdir}/jellyfin/*.a
%{_libdir}/jellyfin/createdump
%{_libdir}/jellyfin/*.xml
%{_libdir}/jellyfin/wwwroot/api-docs/*
%{_libdir}/jellyfin/wwwroot/api-docs/redoc/*
%{_libdir}/jellyfin/wwwroot/api-docs/swagger/*
# Needs 755 else only root can run it since binary build by dotnet is 722
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
%{_libdir}/jellyfin/SOS_README.md

View File

@ -16,8 +16,8 @@
<PackageReference Include="AutoFixture" Version="4.13.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.13.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

View File

@ -0,0 +1,30 @@
using Emby.Naming.AudioBook;
using Xunit;
namespace Jellyfin.Naming.Tests.AudioBook
{
public class AudioBookFileInfoTests
{
[Fact]
public void CompareTo_Same_Success()
{
var info = new AudioBookFileInfo();
Assert.Equal(0, info.CompareTo(info));
}
[Fact]
public void CompareTo_Null_Success()
{
var info = new AudioBookFileInfo();
Assert.Equal(1, info.CompareTo(null));
}
[Fact]
public void CompareTo_Empty_Success()
{
var info1 = new AudioBookFileInfo();
var info2 = new AudioBookFileInfo();
Assert.Equal(0, info1.CompareTo(info2));
}
}
}

View File

@ -44,14 +44,14 @@ namespace Jellyfin.Naming.Tests.Video
}
[Theory]
[InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
[InlineData(ExtraType.DeletedScene, "deleted scenes" )]
[InlineData(ExtraType.Interview, "interviews" )]
[InlineData(ExtraType.Scene, "scenes" )]
[InlineData(ExtraType.Sample, "samples" )]
[InlineData(ExtraType.Clip, "shorts" )]
[InlineData(ExtraType.Clip, "featurettes" )]
[InlineData(ExtraType.Unknown, "extras" )]
[InlineData(ExtraType.BehindTheScenes, "behind the scenes")]
[InlineData(ExtraType.DeletedScene, "deleted scenes")]
[InlineData(ExtraType.Interview, "interviews")]
[InlineData(ExtraType.Scene, "scenes")]
[InlineData(ExtraType.Sample, "samples")]
[InlineData(ExtraType.Clip, "shorts")]
[InlineData(ExtraType.Clip, "featurettes")]
[InlineData(ExtraType.Unknown, "extras")]
public void TestDirectories(ExtraType type, string dirName)
{
Test(dirName + "/300.mp4", type, _videoOptions);

View File

@ -10,6 +10,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son", "imdbid", null)]
[InlineData("Superman: Red Son", "something", null)]
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
{
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));