Merge branch 'master' into NetworkPR2

pull/4125/head
BaronGreenback 4 years ago committed by GitHub
commit 89e67b2e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

@ -669,62 +669,57 @@ namespace Emby.Dlna.PlayTo
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) 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.VolumeDown: case GeneralCommandType.VolumeUp:
return _device.VolumeDown(cancellationToken); return _device.VolumeUp(cancellationToken);
case GeneralCommandType.VolumeUp: case GeneralCommandType.Mute:
return _device.VolumeUp(cancellationToken); return _device.Mute(cancellationToken);
case GeneralCommandType.Mute: case GeneralCommandType.Unmute:
return _device.Mute(cancellationToken); return _device.Unmute(cancellationToken);
case GeneralCommandType.Unmute: case GeneralCommandType.ToggleMute:
return _device.Unmute(cancellationToken); return _device.ToggleMute(cancellationToken);
case GeneralCommandType.ToggleMute: case GeneralCommandType.SetAudioStreamIndex:
return _device.ToggleMute(cancellationToken); if (command.Arguments.TryGetValue("Index", out string index))
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);
{
return SetAudioStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
} }
throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
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 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"); throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
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 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"); throw new ArgumentException("Unsupported volume value supplied.");
default: }
return Task.CompletedTask;
}
}
return Task.CompletedTask; throw new ArgumentException("Volume argument cannot be null");
default:
return Task.CompletedTask;
}
} }
private async Task SetAudioStreamIndex(int? newIndex) private async Task SetAudioStreamIndex(int? newIndex)

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

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

@ -4,6 +4,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -37,6 +38,7 @@ using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.QuickConnect;
using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Security;
@ -120,6 +122,7 @@ namespace Emby.Server.Implementations
private readonly IFileSystem _fileSystemManager; private readonly IFileSystem _fileSystemManager;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly IStartupOptions _startupOptions; private readonly IStartupOptions _startupOptions;
private IMediaEncoder _mediaEncoder; private IMediaEncoder _mediaEncoder;
@ -259,6 +262,8 @@ namespace Emby.Server.Implementations
IServiceCollection serviceCollection) IServiceCollection serviceCollection)
{ {
_xmlSerializer = new MyXmlSerializer(); _xmlSerializer = new MyXmlSerializer();
_jsonSerializer = new JsonSerializer();
ServiceCollection = serviceCollection; ServiceCollection = serviceCollection;
ApplicationPaths = applicationPaths; ApplicationPaths = applicationPaths;
@ -1012,6 +1017,119 @@ namespace Emby.Server.Implementations
protected abstract void RestartInternal(); 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> /// <summary>
/// Gets the composable part assemblies. /// Gets the composable part assemblies.
/// </summary> /// </summary>
@ -1020,7 +1138,7 @@ namespace Emby.Server.Implementations
{ {
if (Directory.Exists(ApplicationPaths.PluginsPath)) 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; Assembly plugAss;
try try

@ -234,7 +234,9 @@ namespace Emby.Server.Implementations.Data
{ {
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam)) 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 else
{ {

@ -32,10 +32,6 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" 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.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="Mono.Nat" Version="3.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />

@ -107,7 +107,7 @@
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.", "TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoyer le répertoire des journaux", "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.", "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.", "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extraire les images de chapitre", "TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",

@ -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"
} }

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

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

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

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

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

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

@ -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; }
}
}

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

@ -15,12 +15,14 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Common.System;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.Updates 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."); 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)) if (Directory.Exists(targetDir))
{ {
Directory.Delete(targetDir, true); try
{
Directory.Delete(targetDir, true);
}
catch
{
// Ignore any exceptions.
}
} }
stream.Position = 0; stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true); _zipClient.ExtractAllFromZip(stream, targetDir, true);
@ -423,15 +434,22 @@ namespace Emby.Server.Implementations.Updates
path = file; path = file;
} }
if (isDirectory) try
{ {
_logger.LogInformation("Deleting plugin directory {0}", path); if (isDirectory)
Directory.Delete(path, true); {
_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); // Ignore file errors.
_fileSystem.DeleteFile(path);
} }
var list = _config.Configuration.UninstalledPlugins.ToList(); var list = _config.Configuration.UninstalledPlugins.ToList();

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

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

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

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

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

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

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

@ -14,9 +14,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> <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.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" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
</ItemGroup> </ItemGroup>

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

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

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

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

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

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

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

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

@ -212,7 +212,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (match.Success) 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); var versionMap = GetFFmpegLibraryVersions(output);

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

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

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

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

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

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

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

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

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

@ -6,22 +6,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{ {
internal class TmdbImageSettings 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 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) public string GetImageUrl(string image)
{ {
return secure_base_url + image; return secure_base_url + image;
} }
} }
internal class TmdbSettingsResult
{
public TmdbImageSettings images { get; set; }
}
} }

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

@ -207,7 +207,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return results return results
.Select(i => .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)) if (!string.IsNullOrWhiteSpace(i.Release_Date))
{ {

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

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

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

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

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

@ -21,11 +21,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public abstract class TmdbEpisodeProviderBase 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 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 IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
private readonly ILogger<TmdbEpisodeProviderBase> _logger; private readonly ILogger<TmdbEpisodeProviderBase> _logger;
protected TmdbEpisodeProviderBase(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) 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; _configurationManager = configurationManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_localization = localization;
_logger = loggerFactory.CreateLogger<TmdbEpisodeProviderBase>(); _logger = loggerFactory.CreateLogger<TmdbEpisodeProviderBase>();
} }
protected ILogger Logger => _logger; 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) CancellationToken cancellationToken)
{ {
await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, 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 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), seasonNumber.ToString(CultureInfo.InvariantCulture),
episodeNumber.ToString(CultureInfo.InvariantCulture), episodeNumber.ToString(CultureInfo.InvariantCulture),
preferredLanguage); preferredLanguage);

@ -112,9 +112,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private async Task<List<Poster>> FetchImages(Season item, string tmdbId, string language, CancellationToken cancellationToken) 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)) if (!string.IsNullOrEmpty(path))
{ {

@ -28,26 +28,32 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo> 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 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 IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
private readonly ILogger<TmdbSeasonProvider> _logger; private readonly ILogger<TmdbSeasonProvider> _logger;
internal static TmdbSeasonProvider Current { get; private set; } 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; _httpClientFactory = httpClientFactory;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_localization = localization;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_logger = logger; _logger = logger;
Current = this; Current = this;
} }
public string Name => TmdbUtils.ProviderName;
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{ {
var result = new MetadataResult<Season>(); var result = new MetadataResult<Season>();
@ -116,8 +122,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return result; return result;
} }
public string Name => TmdbUtils.ProviderName;
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken) public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
{ {
return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>()); 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); 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) CancellationToken cancellationToken)
{ {
await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, 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 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), seasonNumber.ToString(CultureInfo.InvariantCulture),
preferredLanguage); preferredLanguage);

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

@ -17,8 +17,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Plugins.Tmdb.Models.Search; 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 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 IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger<TmdbSeriesProvider> _logger; private readonly ILogger<TmdbSeriesProvider> _logger;
private readonly ILocalizationManager _localization;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
internal static TmdbSeriesProvider Current { get; private set; }
public TmdbSeriesProvider( public TmdbSeriesProvider(
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
ILogger<TmdbSeriesProvider> logger, ILogger<TmdbSeriesProvider> logger,
ILocalizationManager localization,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager) ILibraryManager libraryManager)
{ {
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_logger = logger; _logger = logger;
_localization = localization;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _libraryManager = libraryManager;
Current = this; Current = this;
} }
internal static TmdbSeriesProvider Current { get; private set; }
public string Name => TmdbUtils.ProviderName; public string Name => TmdbUtils.ProviderName;
// After TheTVDB
public int Order => 1;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{ {
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); 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) public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
{ {
var result = new MetadataResult<Series>(); var result = new MetadataResult<Series>
result.QueriedById = true; {
QueriedById = true
};
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
@ -206,9 +203,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
var result = new MetadataResult<Series>(); var result = new MetadataResult<Series>
result.Item = new Series(); {
result.ResultLanguage = seriesInfo.ResultLanguage; Item = new Series(),
ResultLanguage = seriesInfo.ResultLanguage
};
var settings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); 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 path = GetDataFilePath(tmdbId, language);
var fileInfo = _fileSystem.GetFileSystemInfo(path); var fileInfo = new FileInfo(path);
if (fileInfo.Exists) if (fileInfo.Exists)
{ {
// If it's recent or automatic updates are enabled, don't re-download // 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; return Task.CompletedTask;
} }
@ -549,9 +547,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return null; return null;
} }
// After TheTVDB
public int Order => 1;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

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

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

@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Subtitles
private ISubtitleProvider GetProvider(string id) 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 /> /// <inheritdoc />

@ -48,18 +48,25 @@ namespace MediaBrowser.Providers.TV
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken) public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
{ {
var tvdbId = series.GetProviderId(MetadataProvider.Tvdb); var tvdbIdString = series.GetProviderId(MetadataProvider.Tvdb);
if (string.IsNullOrEmpty(tvdbId)) if (string.IsNullOrEmpty(tvdbIdString))
{ {
return false; 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 var episodeLookup = episodes
.Select(i => .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 seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1); var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
return (seasonNumber, episodeNumber, firstAired); return (seasonNumber, episodeNumber, firstAired);

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

@ -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. 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 #### Running From The Command Line

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

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # 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 \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -84,6 +84,10 @@ EOF
%{_libdir}/jellyfin/*.so %{_libdir}/jellyfin/*.so
%{_libdir}/jellyfin/*.a %{_libdir}/jellyfin/*.a
%{_libdir}/jellyfin/createdump %{_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 # Needs 755 else only root can run it since binary build by dotnet is 722
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin %attr(755,root,root) %{_libdir}/jellyfin/jellyfin
%{_libdir}/jellyfin/SOS_README.md %{_libdir}/jellyfin/SOS_README.md

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

@ -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));
}
}
}

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

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

Loading…
Cancel
Save