Merge branch 'master' into NetworkPR2
This commit is contained in:
commit
89e67b2e7f
|
@ -135,6 +135,7 @@
|
|||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||
- [Pusta](https://github.com/pusta)
|
||||
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
|
@ -126,14 +126,14 @@ namespace Emby.Dlna
|
|||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelDescription:{0}", profile.ModelDescription ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelName:{0}", profile.ModelName ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelNumber:{0}", profile.ModelNumber ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelUrl:{0}", profile.ModelUrl ?? string.Empty).AppendLine();
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, "SerialNumber:{0}", profile.SerialNumber ?? string.Empty).AppendLine();
|
||||
builder.Append("FriendlyName:").AppendLine(profile.FriendlyName);
|
||||
builder.Append("Manufacturer:").AppendLine(profile.Manufacturer);
|
||||
builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl);
|
||||
builder.Append("ModelDescription:").AppendLine(profile.ModelDescription);
|
||||
builder.Append("ModelName:").AppendLine(profile.ModelName);
|
||||
builder.Append("ModelNumber:").AppendLine(profile.ModelNumber);
|
||||
builder.Append("ModelUrl:").AppendLine(profile.ModelUrl);
|
||||
builder.Append("SerialNumber:").AppendLine(profile.SerialNumber);
|
||||
|
||||
_logger.LogInformation(builder.ToString());
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 278 B |
|
@ -669,62 +669,57 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
|
||||
switch (command.Name)
|
||||
{
|
||||
switch (commandType)
|
||||
{
|
||||
case GeneralCommandType.VolumeDown:
|
||||
return _device.VolumeDown(cancellationToken);
|
||||
case GeneralCommandType.VolumeUp:
|
||||
return _device.VolumeUp(cancellationToken);
|
||||
case GeneralCommandType.Mute:
|
||||
return _device.Mute(cancellationToken);
|
||||
case GeneralCommandType.Unmute:
|
||||
return _device.Unmute(cancellationToken);
|
||||
case GeneralCommandType.ToggleMute:
|
||||
return _device.ToggleMute(cancellationToken);
|
||||
case GeneralCommandType.SetAudioStreamIndex:
|
||||
if (command.Arguments.TryGetValue("Index", out string index))
|
||||
case GeneralCommandType.VolumeDown:
|
||||
return _device.VolumeDown(cancellationToken);
|
||||
case GeneralCommandType.VolumeUp:
|
||||
return _device.VolumeUp(cancellationToken);
|
||||
case GeneralCommandType.Mute:
|
||||
return _device.Mute(cancellationToken);
|
||||
case GeneralCommandType.Unmute:
|
||||
return _device.Unmute(cancellationToken);
|
||||
case GeneralCommandType.ToggleMute:
|
||||
return _device.ToggleMute(cancellationToken);
|
||||
case GeneralCommandType.SetAudioStreamIndex:
|
||||
if (command.Arguments.TryGetValue("Index", out string index))
|
||||
{
|
||||
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
return SetAudioStreamIndex(val);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
|
||||
return SetAudioStreamIndex(val);
|
||||
}
|
||||
|
||||
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
||||
case GeneralCommandType.SetSubtitleStreamIndex:
|
||||
if (command.Arguments.TryGetValue("Index", out index))
|
||||
{
|
||||
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
return SetSubtitleStreamIndex(val);
|
||||
}
|
||||
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
|
||||
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
||||
case GeneralCommandType.SetSubtitleStreamIndex:
|
||||
if (command.Arguments.TryGetValue("Index", out index))
|
||||
{
|
||||
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
return SetSubtitleStreamIndex(val);
|
||||
}
|
||||
|
||||
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
||||
case GeneralCommandType.SetVolume:
|
||||
if (command.Arguments.TryGetValue("Volume", out string vol))
|
||||
{
|
||||
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
|
||||
{
|
||||
return _device.SetVolume(volume, cancellationToken);
|
||||
}
|
||||
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported volume value supplied.");
|
||||
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
||||
case GeneralCommandType.SetVolume:
|
||||
if (command.Arguments.TryGetValue("Volume", out string vol))
|
||||
{
|
||||
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
|
||||
{
|
||||
return _device.SetVolume(volume, cancellationToken);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Volume argument cannot be null");
|
||||
default:
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
throw new ArgumentException("Unsupported volume value supplied.");
|
||||
}
|
||||
|
||||
throw new ArgumentException("Volume argument cannot be null");
|
||||
default:
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task SetAudioStreamIndex(int? newIndex)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -19,12 +19,7 @@ namespace Emby.Naming.AudioBook
|
|||
|
||||
public AudioBookFilePathParserResult Parse(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
var result = new AudioBookFilePathParserResult();
|
||||
AudioBookFilePathParserResult result = default;
|
||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||
foreach (var expression in _options.AudioBookPartsExpressions)
|
||||
{
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Naming.AudioBook
|
||||
{
|
||||
public class AudioBookFilePathParserResult
|
||||
public struct AudioBookFilePathParserResult
|
||||
{
|
||||
public int? PartNumber { get; set; }
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
@ -37,6 +38,7 @@ using Emby.Server.Implementations.LiveTv;
|
|||
using Emby.Server.Implementations.Localization;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.Plugins;
|
||||
using Emby.Server.Implementations.QuickConnect;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Emby.Server.Implementations.Security;
|
||||
|
@ -120,6 +122,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
private readonly IFileSystem _fileSystemManager;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IStartupOptions _startupOptions;
|
||||
|
||||
private IMediaEncoder _mediaEncoder;
|
||||
|
@ -259,6 +262,8 @@ namespace Emby.Server.Implementations
|
|||
IServiceCollection serviceCollection)
|
||||
{
|
||||
_xmlSerializer = new MyXmlSerializer();
|
||||
_jsonSerializer = new JsonSerializer();
|
||||
|
||||
ServiceCollection = serviceCollection;
|
||||
|
||||
ApplicationPaths = applicationPaths;
|
||||
|
@ -1012,6 +1017,119 @@ namespace Emby.Server.Implementations
|
|||
|
||||
protected abstract void RestartInternal();
|
||||
|
||||
/// <summary>
|
||||
/// Comparison function used in <see cref="GetPlugins" />.
|
||||
/// </summary>
|
||||
/// <param name="a">Item to compare.</param>
|
||||
/// <param name="b">Item to compare with.</param>
|
||||
/// <returns>Boolean result of the operation.</returns>
|
||||
private static int VersionCompare(
|
||||
(Version PluginVersion, string Name, string Path) a,
|
||||
(Version PluginVersion, string Name, string Path) b)
|
||||
{
|
||||
int compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture);
|
||||
|
||||
if (compare == 0)
|
||||
{
|
||||
return a.PluginVersion.CompareTo(b.PluginVersion);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of plugins to install.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to check.</param>
|
||||
/// <param name="cleanup">True if an attempt should be made to delete old plugs.</param>
|
||||
/// <returns>Enumerable list of dlls to load.</returns>
|
||||
private IEnumerable<string> GetPlugins(string path, bool cleanup = true)
|
||||
{
|
||||
var dllList = new List<string>();
|
||||
var versions = new List<(Version PluginVersion, string Name, string Path)>();
|
||||
var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly);
|
||||
string metafile;
|
||||
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
try
|
||||
{
|
||||
metafile = Path.Combine(dir, "meta.json");
|
||||
if (File.Exists(metafile))
|
||||
{
|
||||
var manifest = _jsonSerializer.DeserializeFromFile<PluginManifest>(metafile);
|
||||
|
||||
if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
|
||||
{
|
||||
targetAbi = new Version(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
if (!Version.TryParse(manifest.Version, out var version))
|
||||
{
|
||||
version = new Version(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
if (ApplicationVersion >= targetAbi)
|
||||
{
|
||||
// Only load Plugins if the plugin is built for this version or below.
|
||||
versions.Add((version, manifest.Name, dir));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No metafile, so lets see if the folder is versioned.
|
||||
metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
|
||||
int versionIndex = dir.LastIndexOf('_');
|
||||
if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version ver))
|
||||
{
|
||||
// Versioned folder.
|
||||
versions.Add((ver, metafile, dir));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Un-versioned folder - Add it under the path name and version 0.0.0.1.
|
||||
versions.Add((new Version(0, 0, 0, 1), metafile, dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string lastName = string.Empty;
|
||||
versions.Sort(VersionCompare);
|
||||
// Traverse backwards through the list.
|
||||
// The first item will be the latest version.
|
||||
for (int x = versions.Count - 1; x >= 0; x--)
|
||||
{
|
||||
if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
dllList.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories));
|
||||
lastName = versions[x].Name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastName) && cleanup)
|
||||
{
|
||||
// Attempt a cleanup of old folders.
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Deleting {Path}", versions[x].Path);
|
||||
Directory.Delete(versions[x].Path, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dllList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the composable part assemblies.
|
||||
/// </summary>
|
||||
|
@ -1020,7 +1138,7 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
if (Directory.Exists(ApplicationPaths.PluginsPath))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories))
|
||||
foreach (var file in GetPlugins(ApplicationPaths.PluginsPath))
|
||||
{
|
||||
Assembly plugAss;
|
||||
try
|
||||
|
|
|
@ -234,7 +234,9 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||
{
|
||||
bindParam.Bind(value.ToByteArray());
|
||||
Span<byte> byteValue = stackalloc byte[16];
|
||||
value.TryWriteBytes(byteValue);
|
||||
bindParam.Bind(byteValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -32,10 +32,6 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.0" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
||||
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
|
||||
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
||||
"TaskRefreshLibrary": "Scanner toute les Bibliothèques",
|
||||
"TaskRefreshLibrary": "Scanner toutes les Bibliothèques",
|
||||
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
||||
"TaskRefreshChapterImages": "Extraire les images de chapitre",
|
||||
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
||||
|
|
|
@ -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} 삭제됨",
|
||||
"UserDownloadingItemWithValues": "{0}이(가) {1}을 다운로드 중입니다",
|
||||
"UserLockedOutWithName": "유저 {0} 은(는) 잠금처리 되었습니다",
|
||||
"UserOfflineFromDevice": "{1}로부터 {0}의 연결이 끊겼습니다",
|
||||
"UserOnlineFromDevice": "{0}은 {1}에서 온라인 상태입니다",
|
||||
"UserOfflineFromDevice": "{1}에서 {0}의 연결이 끊킴",
|
||||
"UserOnlineFromDevice": "{0}이 {1}으로 접속",
|
||||
"UserPasswordChangedWithName": "사용자 {0}의 비밀번호가 변경되었습니다",
|
||||
"UserPolicyUpdatedWithName": "{0}의 사용자 정책이 업데이트되었습니다",
|
||||
"UserStartedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생 중",
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"NotificationOptionAudioPlayback": "Lydavspilling startet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp",
|
||||
"NotificationOptionInstallationFailed": "Installasjonsfeil",
|
||||
"NotificationOptionInstallationFailed": "Installasjonen feilet",
|
||||
"NotificationOptionNewLibraryContent": "Nytt innhold lagt til",
|
||||
"NotificationOptionPluginError": "Pluginfeil",
|
||||
"NotificationOptionPluginInstalled": "Plugin installert",
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
|
||||
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
|
||||
"Collections": "தொகுப்புகள்",
|
||||
"CameraImageUploadedFrom": "{0} இலிருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது",
|
||||
"CameraImageUploadedFrom": "{0} இல் இருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது",
|
||||
"AppDeviceValues": "செயலி: {0}, சாதனம்: {1}",
|
||||
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
|
||||
"TaskRefreshChannels": "சேனல்களை புதுப்பி",
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
{
|
||||
"Collections": "Bộ Sưu Tập",
|
||||
"Favorites": "Sở Thích",
|
||||
"Favorites": "Yêu Thích",
|
||||
"Folders": "Thư Mục",
|
||||
"Genres": "Thể Loại",
|
||||
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
|
||||
"HeaderContinueWatching": "Tiếp Tục Xem",
|
||||
"HeaderContinueWatching": "Xem Tiếp",
|
||||
"HeaderLiveTV": "TV Trực Tiếp",
|
||||
"Movies": "Phim",
|
||||
"Photos": "Ảnh",
|
||||
"Playlists": "Danh Sách Chơi",
|
||||
"Shows": "Các Chương Trình",
|
||||
"Playlists": "Danh sách phát",
|
||||
"Shows": "Chương Trình TV",
|
||||
"Songs": "Các Bài Hát",
|
||||
"Sync": "Đồng Bộ",
|
||||
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
|
||||
"Albums": "Bộ Sưu Tập",
|
||||
"Artists": "Nghệ Sĩ",
|
||||
"Albums": "Albums",
|
||||
"Artists": "Các Nghệ Sĩ",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình thông tin chi tiết.",
|
||||
"TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
|
||||
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
|
||||
|
@ -29,8 +29,8 @@
|
|||
"TaskCleanLogs": "Làm sạch nhật ký",
|
||||
"TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm các tệp mới và làm mới thông tin chi tiết.",
|
||||
"TaskRefreshLibrary": "Quét Thư viện Phương tiện",
|
||||
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho các video có chương.",
|
||||
"TaskRefreshChapterImages": "Trích xuất hình ảnh chương",
|
||||
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.",
|
||||
"TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh",
|
||||
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
|
||||
"TaskCleanCache": "Làm Sạch Thư Mục Cache",
|
||||
"TasksChannelsCategory": "Kênh Internet",
|
||||
|
@ -107,8 +107,8 @@
|
|||
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
|
||||
"DeviceOnlineWithName": "{0} đã kết nối",
|
||||
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
|
||||
"ChapterNameValue": "Chương {0}",
|
||||
"Channels": "Kênh",
|
||||
"ChapterNameValue": "Phân Cảnh {0}",
|
||||
"Channels": "Các Kênh",
|
||||
"CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}",
|
||||
"Books": "Sách",
|
||||
"AuthenticationSucceededWithUserName": "{0} xác thực thành công",
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
|
||||
"TaskRefreshChannels": "重新整理頻道",
|
||||
"TaskUpdatePlugins": "更新外掛",
|
||||
"TaskRefreshPeople": "重新整理人員",
|
||||
"TaskRefreshPeople": "刷新用戶",
|
||||
"TaskCleanLogsDescription": "刪除超過 {0} 天的舊紀錄檔。",
|
||||
"TaskCleanLogs": "清空紀錄資料夾",
|
||||
"TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新描述資料。",
|
||||
|
|
|
@ -413,6 +413,7 @@ namespace Emby.Server.Implementations.Localization
|
|||
yield return new LocalizationOption("Swedish", "sv");
|
||||
yield return new LocalizationOption("Swiss German", "gsw");
|
||||
yield return new LocalizationOption("Turkish", "tr");
|
||||
yield return new LocalizationOption("Tiếng Việt", "vi");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
60
Emby.Server.Implementations/Plugins/PluginManifest.cs
Normal file
60
Emby.Server.Implementations/Plugins/PluginManifest.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
|
||||
namespace Emby.Server.Implementations.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a Plugin manifest file.
|
||||
/// </summary>
|
||||
public class PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the category of the plugin.
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the changelog information.
|
||||
/// </summary>
|
||||
public string Changelog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of the plugin.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Global Unique Identifier for the plugin.
|
||||
/// </summary>
|
||||
public Guid Guid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Name of the plugin.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an overview of the plugin.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner of the plugin.
|
||||
/// </summary>
|
||||
public string Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compatibility version for the plugin.
|
||||
/// </summary>
|
||||
public string TargetAbi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of the plugin.
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Version number of the plugin.
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
|
@ -1037,7 +1037,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
var generalCommand = new GeneralCommand
|
||||
{
|
||||
Name = GeneralCommandType.DisplayMessage.ToString()
|
||||
Name = GeneralCommandType.DisplayMessage
|
||||
};
|
||||
|
||||
generalCommand.Arguments["Header"] = command.Header;
|
||||
|
@ -1268,7 +1268,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
var generalCommand = new GeneralCommand
|
||||
{
|
||||
Name = GeneralCommandType.DisplayContent.ToString(),
|
||||
Name = GeneralCommandType.DisplayContent,
|
||||
Arguments =
|
||||
{
|
||||
["ItemId"] = command.ItemId,
|
||||
|
|
|
@ -15,12 +15,14 @@ using MediaBrowser.Common.Configuration;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Common.System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
|
@ -377,11 +379,20 @@ namespace Emby.Server.Implementations.Updates
|
|||
throw new InvalidDataException("The checksum of the received data doesn't match.");
|
||||
}
|
||||
|
||||
// Version folder as they cannot be overwritten in Windows.
|
||||
targetDir += "_" + package.Version;
|
||||
|
||||
if (Directory.Exists(targetDir))
|
||||
{
|
||||
Directory.Delete(targetDir, true);
|
||||
try
|
||||
{
|
||||
Directory.Delete(targetDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore any exceptions.
|
||||
}
|
||||
}
|
||||
|
||||
stream.Position = 0;
|
||||
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
||||
|
||||
|
@ -423,15 +434,22 @@ namespace Emby.Server.Implementations.Updates
|
|||
path = file;
|
||||
}
|
||||
|
||||
if (isDirectory)
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin directory {0}", path);
|
||||
Directory.Delete(path, true);
|
||||
if (isDirectory)
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin directory {0}", path);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin file {0}", path);
|
||||
_fileSystem.DeleteFile(path);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin file {0}", path);
|
||||
_fileSystem.DeleteFile(path);
|
||||
// Ignore file errors.
|
||||
}
|
||||
|
||||
var list = _config.Configuration.UninstalledPlugins.ToList();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -113,7 +113,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Gets a video hls playlist stream.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
|
||||
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
|
||||
/// <param name="params">The streaming parameters.</param>
|
||||
/// <param name="tag">The tag.</param>
|
||||
|
@ -170,7 +169,6 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetMasterHlsVideoPlaylist(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -223,7 +221,6 @@ namespace Jellyfin.Api.Controllers
|
|||
var streamingRequest = new HlsVideoRequestDto
|
||||
{
|
||||
Id = itemId,
|
||||
Container = container,
|
||||
Static = @static ?? true,
|
||||
Params = @params,
|
||||
Tag = tag,
|
||||
|
@ -281,7 +278,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Gets an audio hls playlist stream.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
|
||||
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
|
||||
/// <param name="params">The streaming parameters.</param>
|
||||
/// <param name="tag">The tag.</param>
|
||||
|
@ -338,7 +334,6 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetMasterHlsAudioPlaylist(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery, Required] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -391,7 +386,6 @@ namespace Jellyfin.Api.Controllers
|
|||
var streamingRequest = new HlsAudioRequestDto
|
||||
{
|
||||
Id = itemId,
|
||||
Container = container,
|
||||
Static = @static ?? true,
|
||||
Params = @params,
|
||||
Tag = tag,
|
||||
|
@ -449,7 +443,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Gets a video stream using HTTP live streaming.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
|
||||
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
|
||||
/// <param name="params">The streaming parameters.</param>
|
||||
/// <param name="tag">The tag.</param>
|
||||
|
@ -504,7 +497,6 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetVariantHlsVideoPlaylist(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery, Required] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -557,7 +549,6 @@ namespace Jellyfin.Api.Controllers
|
|||
var streamingRequest = new VideoRequestDto
|
||||
{
|
||||
Id = itemId,
|
||||
Container = container,
|
||||
Static = @static ?? true,
|
||||
Params = @params,
|
||||
Tag = tag,
|
||||
|
@ -615,7 +606,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Gets an audio stream using HTTP live streaming.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
|
||||
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
|
||||
/// <param name="params">The streaming parameters.</param>
|
||||
/// <param name="tag">The tag.</param>
|
||||
|
@ -670,7 +660,6 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetVariantHlsAudioPlaylist(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery, Required] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -723,7 +712,6 @@ namespace Jellyfin.Api.Controllers
|
|||
var streamingRequest = new StreamingRequestDto
|
||||
{
|
||||
Id = itemId,
|
||||
Container = container,
|
||||
Static = @static ?? true,
|
||||
Params = @params,
|
||||
Tag = tag,
|
||||
|
@ -841,7 +829,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -1011,7 +999,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
|
@ -1144,30 +1132,30 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("#EXTM3U");
|
||||
builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
builder.AppendLine("#EXT-X-VERSION:3");
|
||||
builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture));
|
||||
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
||||
builder.AppendLine("#EXTM3U")
|
||||
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
|
||||
.AppendLine("#EXT-X-VERSION:3")
|
||||
.Append("#EXT-X-TARGETDURATION:")
|
||||
.Append(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength))
|
||||
.AppendLine()
|
||||
.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
||||
|
||||
var queryString = Request.QueryString;
|
||||
var index = 0;
|
||||
|
||||
var segmentExtension = GetSegmentFileExtension(streamingRequest.SegmentContainer);
|
||||
var queryString = Request.QueryString;
|
||||
|
||||
foreach (var length in segmentLengths)
|
||||
{
|
||||
builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc");
|
||||
builder.AppendLine(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"hls1/{0}/{1}{2}{3}",
|
||||
name,
|
||||
index.ToString(CultureInfo.InvariantCulture),
|
||||
segmentExtension,
|
||||
queryString));
|
||||
|
||||
index++;
|
||||
builder.Append("#EXTINF:")
|
||||
.Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
|
||||
.AppendLine(", nodesc")
|
||||
.Append("hls1/")
|
||||
.Append(name)
|
||||
.Append('/')
|
||||
.Append(index++)
|
||||
.Append(segmentExtension)
|
||||
.Append(queryString)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine("#EXT-X-ENDLIST");
|
||||
|
@ -1465,7 +1453,7 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var args = "-codec:v:0 " + codec;
|
||||
|
||||
// if (state.EnableMpegtsM2TsMode)
|
||||
// if (state.EnableMpegtsM2TsMode)
|
||||
// {
|
||||
// args += " -mpegts_m2ts_mode 1";
|
||||
// }
|
||||
|
|
|
@ -1017,9 +1017,9 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool validateListings = false,
|
||||
[FromQuery] bool validateLogin = false)
|
||||
{
|
||||
using var sha = SHA1.Create();
|
||||
if (!string.IsNullOrEmpty(pw))
|
||||
{
|
||||
using var sha = SHA1.Create();
|
||||
listingsProviderInfo.Password = Hex.Encode(sha.ComputeHash(Encoding.UTF8.GetBytes(pw)));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#pragma warning disable CA1801
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
@ -150,25 +148,25 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Instructs a session to play an item.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session id.</param>
|
||||
/// <param name="command">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
||||
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
||||
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
|
||||
/// <param name="startPositionTicks">The starting position of the first item.</param>
|
||||
/// <response code="204">Instruction sent to session.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
|
||||
[HttpPost("Sessions/{sessionId}/Playing")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult Play(
|
||||
[FromRoute, Required] string sessionId,
|
||||
[FromRoute, Required] PlayCommand command,
|
||||
[FromQuery] Guid[] itemIds,
|
||||
[FromQuery, Required] PlayCommand playCommand,
|
||||
[FromQuery, Required] string itemIds,
|
||||
[FromQuery] long? startPositionTicks)
|
||||
{
|
||||
var playRequest = new PlayRequest
|
||||
{
|
||||
ItemIds = itemIds,
|
||||
ItemIds = RequestHelpers.GetGuids(itemIds),
|
||||
StartPositionTicks = startPositionTicks,
|
||||
PlayCommand = command
|
||||
PlayCommand = playCommand
|
||||
};
|
||||
|
||||
_sessionManager.SendPlayCommand(
|
||||
|
@ -184,20 +182,29 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Issues a playstate command to a client.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session id.</param>
|
||||
/// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
|
||||
/// <param name="command">The <see cref="PlaystateCommand"/>.</param>
|
||||
/// <param name="seekPositionTicks">The optional position ticks.</param>
|
||||
/// <param name="controllingUserId">The optional controlling user id.</param>
|
||||
/// <response code="204">Playstate command sent to session.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("Sessions/{sessionId}/Playing")]
|
||||
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult SendPlaystateCommand(
|
||||
[FromRoute, Required] string sessionId,
|
||||
[FromBody] PlaystateRequest playstateRequest)
|
||||
[FromRoute, Required] PlaystateCommand command,
|
||||
[FromQuery] long? seekPositionTicks,
|
||||
[FromQuery] string? controllingUserId)
|
||||
{
|
||||
_sessionManager.SendPlaystateCommand(
|
||||
RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,
|
||||
sessionId,
|
||||
playstateRequest,
|
||||
new PlaystateRequest()
|
||||
{
|
||||
Command = command,
|
||||
ControllingUserId = controllingUserId,
|
||||
SeekPositionTicks = seekPositionTicks,
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
return NoContent();
|
||||
|
@ -215,18 +222,12 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult SendSystemCommand(
|
||||
[FromRoute, Required] string sessionId,
|
||||
[FromRoute, Required] string command)
|
||||
[FromRoute, Required] GeneralCommandType command)
|
||||
{
|
||||
var name = command;
|
||||
if (Enum.TryParse(name, true, out GeneralCommandType commandType))
|
||||
{
|
||||
name = commandType.ToString();
|
||||
}
|
||||
|
||||
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
|
||||
var generalCommand = new GeneralCommand
|
||||
{
|
||||
Name = name,
|
||||
Name = command,
|
||||
ControllingUserId = currentSession.UserId
|
||||
};
|
||||
|
||||
|
@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult SendGeneralCommand(
|
||||
[FromRoute, Required] string sessionId,
|
||||
[FromRoute, Required] string command)
|
||||
[FromRoute, Required] GeneralCommandType command)
|
||||
{
|
||||
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
|
||||
|
||||
|
@ -434,9 +435,9 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult ReportViewing(
|
||||
[FromQuery] string? sessionId,
|
||||
[FromQuery] string? itemId)
|
||||
[FromQuery, Required] string? itemId)
|
||||
{
|
||||
string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
|
||||
string session = sessionId ?? RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
|
||||
|
||||
_sessionManager.ReportNowViewingItem(session, itemId);
|
||||
return NoContent();
|
||||
|
|
|
@ -281,7 +281,8 @@ namespace Jellyfin.Api.Controllers
|
|||
var builder = new StringBuilder();
|
||||
builder.AppendLine("#EXTM3U")
|
||||
.Append("#EXT-X-TARGETDURATION:")
|
||||
.AppendLine(segmentLength.ToString(CultureInfo.InvariantCulture))
|
||||
.Append(segmentLength)
|
||||
.AppendLine()
|
||||
.AppendLine("#EXT-X-VERSION:3")
|
||||
.AppendLine("#EXT-X-MEDIA-SEQUENCE:0")
|
||||
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
|
@ -296,8 +297,9 @@ namespace Jellyfin.Api.Controllers
|
|||
var lengthTicks = Math.Min(remaining, segmentLengthTicks);
|
||||
|
||||
builder.Append("#EXTINF:")
|
||||
.Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture))
|
||||
.AppendLine(",");
|
||||
.Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds)
|
||||
.Append(',')
|
||||
.AppendLine();
|
||||
|
||||
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
|
||||
|
||||
|
|
|
@ -326,9 +326,9 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <param name="streamOptions">Optional. The streaming options.</param>
|
||||
/// <response code="200">Video stream returned.</response>
|
||||
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
|
||||
[HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetVideoStream_2")]
|
||||
[HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetVideoStreamWithExt")]
|
||||
[HttpGet("{itemId}/stream")]
|
||||
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStream_2")]
|
||||
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStreamWithExt")]
|
||||
[HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesVideoFile]
|
||||
|
|
|
@ -169,7 +169,7 @@ namespace Jellyfin.Api.Helpers
|
|||
|
||||
string? containerInternal = Path.GetExtension(state.RequestedUrl);
|
||||
|
||||
if (string.IsNullOrEmpty(streamingRequest.Container))
|
||||
if (!string.IsNullOrEmpty(streamingRequest.Container))
|
||||
{
|
||||
containerInternal = streamingRequest.Container;
|
||||
}
|
||||
|
|
|
@ -504,6 +504,11 @@ namespace Jellyfin.Api.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_mediaEncoder.EncoderPath))
|
||||
{
|
||||
throw new ArgumentException("FFMPEG path not set.");
|
||||
}
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="BlurHashSharp" Version="1.1.0" />
|
||||
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.1" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -41,10 +41,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.8" />
|
||||
<PackageReference Include="prometheus-net" Version="3.6.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||
<PackageReference Include="NetworkCollection" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
@ -15,11 +16,6 @@ namespace MediaBrowser.Controller.Extensions
|
|||
{
|
||||
public static string RemoveDiacritics(this string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
var chars = Normalize(text, NormalizationForm.FormD)
|
||||
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Library
|
|||
{
|
||||
public static class NameExtensions
|
||||
{
|
||||
private static string RemoveDiacritics(string name)
|
||||
private static string RemoveDiacritics(string? name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -212,7 +212,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (match.Success)
|
||||
{
|
||||
return new Version(match.Groups[1].Value);
|
||||
if (Version.TryParse(match.Groups[1].Value, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
var versionMap = GetFFmpegLibraryVersions(output);
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
new ResolutionConfiguration(720, 950000),
|
||||
new ResolutionConfiguration(1280, 2500000),
|
||||
new ResolutionConfiguration(1920, 4000000),
|
||||
new ResolutionConfiguration(2560, 8000000),
|
||||
new ResolutionConfiguration(3840, 35000000)
|
||||
};
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.0-preview.8.20407.11" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Session
|
|||
{
|
||||
public class GeneralCommand
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public GeneralCommandType Name { get; set; }
|
||||
|
||||
public Guid ControllingUserId { get; set; }
|
||||
|
||||
|
|
|
@ -297,7 +297,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Befores the save.
|
||||
/// Before the save.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
|
||||
|
@ -355,13 +355,12 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
|
||||
{
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
return folder.GetRecursiveChildren();
|
||||
}
|
||||
|
||||
return new List<BaseItem>();
|
||||
return Array.Empty<BaseItem>();
|
||||
}
|
||||
|
||||
protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
||||
|
@ -814,7 +813,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
try
|
||||
{
|
||||
refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
|
||||
refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
@ -882,16 +881,6 @@ namespace MediaBrowser.Providers.Manager
|
|||
return refreshResult;
|
||||
}
|
||||
|
||||
private string NormalizeLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(language))
|
||||
{
|
||||
return "en";
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
private void MergeNewData(TItemType source, TIdType lookupInfo)
|
||||
{
|
||||
// Copy new provider id's that may have been obtained
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
|
||||
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
|
||||
<PackageReference Include="TvDbSharper" Version="3.2.1" />
|
||||
|
|
|
@ -6,6 +6,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|||
{
|
||||
public class Videos
|
||||
{
|
||||
public List<Video> Results { get; set; }
|
||||
public IReadOnlyList<Video> Results { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|||
{
|
||||
public class Trailers
|
||||
{
|
||||
public List<Youtube> Youtube { get; set; }
|
||||
public IReadOnlyList<Youtube> Youtube { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
|
|||
{
|
||||
public class PersonImages
|
||||
{
|
||||
public List<Profile> Profiles { get; set; }
|
||||
public IReadOnlyList<Profile> Profiles { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
|
||||
public static string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Movie || item is MusicVideo || item is Trailer;
|
||||
|
@ -201,8 +204,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -6,22 +6,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
{
|
||||
internal class TmdbImageSettings
|
||||
{
|
||||
public List<string> backdrop_sizes { get; set; }
|
||||
public IReadOnlyList<string> backdrop_sizes { get; set; }
|
||||
|
||||
public string secure_base_url { get; set; }
|
||||
|
||||
public List<string> poster_sizes { get; set; }
|
||||
public IReadOnlyList<string> poster_sizes { get; set; }
|
||||
|
||||
public List<string> profile_sizes { get; set; }
|
||||
public IReadOnlyList<string> profile_sizes { get; set; }
|
||||
|
||||
public string GetImageUrl(string image)
|
||||
{
|
||||
return secure_base_url + image;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TmdbSettingsResult
|
||||
{
|
||||
public TmdbImageSettings images { get; set; }
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
private const string TmdbConfigUrl = TmdbUtils.BaseTmdbApiUrl + "3/configuration?api_key={0}";
|
||||
private const string GetMovieInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers";
|
||||
|
||||
internal static TmdbMovieProvider Current { get; private set; }
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
@ -44,7 +44,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
/// <summary>
|
||||
/// The _TMDB settings task.
|
||||
/// </summary>
|
||||
private TmdbSettingsResult _tmdbSettings;
|
||||
|
||||
public TmdbMovieProvider(
|
||||
IJsonSerializer jsonSerializer,
|
||||
|
@ -65,6 +68,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
Current = this;
|
||||
}
|
||||
|
||||
internal static TmdbMovieProvider Current { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 1;
|
||||
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetMovieSearchResults(searchInfo, cancellationToken);
|
||||
|
@ -131,13 +142,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
return movieDb.GetMetadata(id, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
/// <summary>
|
||||
/// The _TMDB settings task.
|
||||
/// </summary>
|
||||
private TmdbSettingsResult _tmdbSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TMDB settings.
|
||||
/// </summary>
|
||||
|
@ -272,7 +276,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
languages.Add("en");
|
||||
}
|
||||
|
||||
return string.Join(",", languages);
|
||||
return string.Join(',', languages);
|
||||
}
|
||||
|
||||
public static string NormalizeLanguage(string language)
|
||||
|
@ -381,15 +385,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
/// <summary>
|
||||
/// Gets the movie db response.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
|
|
|
@ -207,7 +207,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||
return results
|
||||
.Select(i =>
|
||||
{
|
||||
var remoteResult = new RemoteSearchResult {SearchProviderName = TmdbMovieProvider.Current.Name, Name = i.Title ?? i.Name ?? i.Original_Title, ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path};
|
||||
var remoteResult = new RemoteSearchResult
|
||||
{
|
||||
SearchProviderName = TmdbMovieProvider.Current.Name,
|
||||
Name = i.Title ?? i.Name ?? i.Original_Title,
|
||||
ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(i.Release_Date))
|
||||
{
|
||||
|
|
|
@ -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 string Name => TmdbMovieProvider.Current.Name;
|
||||
|
||||
public Task<MetadataResult<MusicVideo>> GetMetadata(MusicVideoInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
return TmdbMovieProvider.Current.GetItemMetadata<MusicVideo>(info, cancellationToken);
|
||||
|
@ -24,8 +26,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Music
|
|||
return Task.FromResult((IEnumerable<RemoteSearchResult>)new List<RemoteSearchResult>());
|
||||
}
|
||||
|
||||
public string Name => TmdbMovieProvider.Current.Name;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||
{
|
||||
public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
|
||||
{
|
||||
const string DataFileName = "info.json";
|
||||
private const string DataFileName = "info.json";
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
|
@ -39,20 +39,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TmdbPersonProvider> _logger;
|
||||
|
||||
public TmdbPersonProvider(
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<TmdbPersonProvider> logger)
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
|
@ -75,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||
var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
|
||||
|
||||
var images = (info.Images ?? new PersonImages()).Profiles ?? new List<Profile>();
|
||||
IReadOnlyList<Profile> images = info.Images?.Profiles ?? Array.Empty<Profile>();
|
||||
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
|
@ -95,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||
if (searchInfo.IsAutomated)
|
||||
{
|
||||
// Don't hammer moviedb searching by name
|
||||
return new List<RemoteSearchResult>();
|
||||
return Array.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var url = string.Format(
|
||||
|
|
|
@ -28,7 +28,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
|
||||
: base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
|
@ -43,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
var episode = (Controller.Entities.TV.Episode)item;
|
||||
var series = episode.Series;
|
||||
|
||||
var seriesId = series != null ? series.GetProviderId(MetadataProvider.Tmdb) : null;
|
||||
var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
|
@ -62,8 +68,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var response = await GetEpisodeInfo(seriesId, seasonNumber.Value, episodeNumber.Value,
|
||||
language, cancellationToken).ConfigureAwait(false);
|
||||
var response = await GetEpisodeInfo(
|
||||
seriesId,
|
||||
seasonNumber.Value,
|
||||
episodeNumber.Value,
|
||||
language,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@ -120,14 +130,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return GetResponse(url, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Controller.Entities.TV.Episode;
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
|
||||
: base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -41,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return list;
|
||||
}
|
||||
|
||||
var metadataResult = await GetMetadata(searchInfo, cancellationToken);
|
||||
var metadataResult = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (metadataResult.HasMetadata)
|
||||
{
|
||||
|
@ -205,10 +211,5 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
return GetResponse(url, cancellationToken);
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
public abstract class TmdbEpisodeProviderBase
|
||||
{
|
||||
private const string EpisodeUrlPattern = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILogger<TmdbEpisodeProviderBase> _logger;
|
||||
|
||||
protected TmdbEpisodeProviderBase(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
|
||||
|
@ -34,13 +34,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
_configurationManager = configurationManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_localization = localization;
|
||||
_logger = loggerFactory.CreateLogger<TmdbEpisodeProviderBase>();
|
||||
}
|
||||
|
||||
protected ILogger Logger => _logger;
|
||||
|
||||
protected async Task<EpisodeResult> GetEpisodeInfo(string seriesTmdbId, int season, int episodeNumber, string preferredMetadataLanguage,
|
||||
protected async Task<EpisodeResult> GetEpisodeInfo(
|
||||
string seriesTmdbId,
|
||||
int season,
|
||||
int episodeNumber,
|
||||
string preferredMetadataLanguage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, cancellationToken)
|
||||
|
@ -93,7 +96,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-episode-{1}-{2}.json",
|
||||
var filename = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"season-{0}-episode-{1}-{2}.json",
|
||||
seasonNumber.ToString(CultureInfo.InvariantCulture),
|
||||
episodeNumber.ToString(CultureInfo.InvariantCulture),
|
||||
preferredLanguage);
|
||||
|
|
|
@ -112,9 +112,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
private async Task<List<Poster>> FetchImages(Season item, string tmdbId, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, item.IndexNumber.GetValueOrDefault(), language, cancellationToken).ConfigureAwait(false);
|
||||
var seasonNumber = item.IndexNumber.GetValueOrDefault();
|
||||
await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, seasonNumber, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
var path = TmdbSeasonProvider.Current.GetDataFilePath(tmdbId, seasonNumber, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
|
|
|
@ -28,26 +28,32 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
|
||||
{
|
||||
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILogger<TmdbSeasonProvider> _logger;
|
||||
|
||||
internal static TmdbSeasonProvider Current { get; private set; }
|
||||
|
||||
public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger<TmdbSeasonProvider> logger)
|
||||
public TmdbSeasonProvider(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILogger<TmdbSeasonProvider> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_localization = localization;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Season>();
|
||||
|
@ -116,8 +122,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return result;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
|
||||
|
@ -128,7 +132,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<SeasonResult> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,
|
||||
private async Task<SeasonResult> GetSeasonInfo(
|
||||
string seriesTmdbId,
|
||||
int season,
|
||||
string preferredMetadataLanguage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken)
|
||||
|
@ -181,7 +188,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-{1}.json",
|
||||
var filename = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"season-{0}-{1}.json",
|
||||
seasonNumber.ToString(CultureInfo.InvariantCulture),
|
||||
preferredLanguage);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
@ -12,7 +13,6 @@ using MediaBrowser.Controller.Entities.TV;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
||||
|
@ -25,19 +25,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
|
||||
public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string Name => ProviderName;
|
||||
|
||||
public static string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
// After tvdb and fanart
|
||||
public int Order => 2;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Series;
|
||||
|
@ -56,7 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
var results = await FetchImages(item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false);
|
||||
var results = await FetchImages(item, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
|
@ -148,10 +149,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{MovieImages}.</returns>
|
||||
private async Task<Images> FetchImages(BaseItem item, string language, IJsonSerializer jsonSerializer,
|
||||
private async Task<Images> FetchImages(
|
||||
BaseItem item,
|
||||
string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
@ -165,22 +167,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
{
|
||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
return jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
|
||||
}
|
||||
return _jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// After tvdb and fanart
|
||||
public int Order => 2;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -17,8 +17,6 @@ using MediaBrowser.Controller.Entities.TV;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.Search;
|
||||
|
@ -33,38 +31,35 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings";
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly ILogger<TmdbSeriesProvider> _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
internal static TmdbSeriesProvider Current { get; private set; }
|
||||
|
||||
public TmdbSeriesProvider(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager,
|
||||
ILogger<TmdbSeriesProvider> logger,
|
||||
ILocalizationManager localization,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_logger = logger;
|
||||
_localization = localization;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
internal static TmdbSeriesProvider Current { get; private set; }
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
// After TheTVDB
|
||||
public int Order => 1;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
@ -129,8 +124,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Series>();
|
||||
result.QueriedById = true;
|
||||
var result = new MetadataResult<Series>
|
||||
{
|
||||
QueriedById = true
|
||||
};
|
||||
|
||||
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
|
@ -206,9 +203,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var result = new MetadataResult<Series>();
|
||||
result.Item = new Series();
|
||||
result.ResultLanguage = seriesInfo.ResultLanguage;
|
||||
var result = new MetadataResult<Series>
|
||||
{
|
||||
Item = new Series(),
|
||||
ResultLanguage = seriesInfo.ResultLanguage
|
||||
};
|
||||
|
||||
var settings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@ -474,12 +473,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
|
||||
var path = GetDataFilePath(tmdbId, language);
|
||||
|
||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
||||
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
// If it's recent or automatic updates are enabled, don't re-download
|
||||
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
|
||||
if ((DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalDays <= 2)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@ -549,9 +547,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return null;
|
||||
}
|
||||
|
||||
// After TheTVDB
|
||||
public int Order => 1;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -21,6 +21,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
|
|||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public string Name => TmdbMovieProvider.Current.Name;
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return TmdbMovieProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
|
||||
|
@ -31,10 +35,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
|
|||
return TmdbMovieProvider.Current.GetItemMetadata<Trailer>(info, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => TmdbMovieProvider.Current.Name;
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -33,6 +33,8 @@ namespace MediaBrowser.Providers.Studios
|
|||
|
||||
public string Name => "Emby Designs";
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Studio;
|
||||
|
@ -119,8 +121,6 @@ namespace MediaBrowser.Providers.Studios
|
|||
return EnsureList(url, file, _fileSystem, cancellationToken);
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
|
@ -161,12 +161,12 @@ namespace MediaBrowser.Providers.Studios
|
|||
|
||||
private string GetComparableName(string name)
|
||||
{
|
||||
return name.Replace(" ", string.Empty)
|
||||
.Replace(".", string.Empty)
|
||||
.Replace("&", string.Empty)
|
||||
.Replace("!", string.Empty)
|
||||
.Replace(",", string.Empty)
|
||||
.Replace("/", string.Empty);
|
||||
return name.Replace(" ", string.Empty, StringComparison.Ordinal)
|
||||
.Replace(".", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("&", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("!", string.Empty, StringComparison.Ordinal)
|
||||
.Replace(",", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("/", string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAvailableImages(string file)
|
||||
|
|
|
@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Subtitles
|
|||
|
||||
private ISubtitleProvider GetProvider(string id)
|
||||
{
|
||||
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
|
||||
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -48,18 +48,25 @@ namespace MediaBrowser.Providers.TV
|
|||
|
||||
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
|
||||
{
|
||||
var tvdbId = series.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (string.IsNullOrEmpty(tvdbId))
|
||||
var tvdbIdString = series.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (string.IsNullOrEmpty(tvdbIdString))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var episodes = await _tvdbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
|
||||
var episodes = await _tvdbClientManager.GetAllEpisodesAsync(
|
||||
int.Parse(tvdbIdString, CultureInfo.InvariantCulture),
|
||||
series.GetPreferredMetadataLanguage(),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var episodeLookup = episodes
|
||||
.Select(i =>
|
||||
{
|
||||
DateTime.TryParse(i.FirstAired, out var firstAired);
|
||||
if (!DateTime.TryParse(i.FirstAired, out var firstAired))
|
||||
{
|
||||
firstAired = default;
|
||||
}
|
||||
|
||||
var seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
|
||||
var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
|
||||
return (seasonNumber, episodeNumber, firstAired);
|
||||
|
|
|
@ -27,6 +27,9 @@ namespace MediaBrowser.Providers.TV
|
|||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool EnableUpdatingPremiereDateFromChildren => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
||||
{
|
||||
|
@ -67,9 +70,6 @@ namespace MediaBrowser.Providers.TV
|
|||
return updateType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool EnableUpdatingPremiereDateFromChildren => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Season item)
|
||||
=> item.GetEpisodes();
|
||||
|
|
|
@ -124,7 +124,7 @@ To run the project with Visual Studio Code you will first need to open the repos
|
|||
|
||||
Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
|
||||
|
||||
After the required extensions are installed, you can can run the server by pressing `F5`.
|
||||
After the required extensions are installed, you can run the server by pressing `F5`.
|
||||
|
||||
#### Running From The Command Line
|
||||
|
||||
|
|
|
@ -43,13 +43,13 @@ namespace Rssdp.Infrastructure
|
|||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
const string argFormat = "{0}: {1}\r\n";
|
||||
const string ArgFormat = "{0}: {1}\r\n";
|
||||
|
||||
builder.AppendFormat("{0}\r\n", header);
|
||||
|
||||
foreach (var pair in values)
|
||||
{
|
||||
builder.AppendFormat(argFormat, pair.Key, pair.Value);
|
||||
builder.AppendFormat(ArgFormat, pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
builder.Append("\r\n");
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -15,7 +15,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -16,7 +16,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -15,7 +15,7 @@ RUN apt-get update \
|
|||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
|
|
@ -84,6 +84,10 @@ EOF
|
|||
%{_libdir}/jellyfin/*.so
|
||||
%{_libdir}/jellyfin/*.a
|
||||
%{_libdir}/jellyfin/createdump
|
||||
%{_libdir}/jellyfin/*.xml
|
||||
%{_libdir}/jellyfin/wwwroot/api-docs/*
|
||||
%{_libdir}/jellyfin/wwwroot/api-docs/redoc/*
|
||||
%{_libdir}/jellyfin/wwwroot/api-docs/swagger/*
|
||||
# Needs 755 else only root can run it since binary build by dotnet is 722
|
||||
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
|
||||
%{_libdir}/jellyfin/SOS_README.md
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
|
|
|
@ -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]
|
||||
[InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
|
||||
[InlineData(ExtraType.DeletedScene, "deleted scenes" )]
|
||||
[InlineData(ExtraType.Interview, "interviews" )]
|
||||
[InlineData(ExtraType.Scene, "scenes" )]
|
||||
[InlineData(ExtraType.Sample, "samples" )]
|
||||
[InlineData(ExtraType.Clip, "shorts" )]
|
||||
[InlineData(ExtraType.Clip, "featurettes" )]
|
||||
[InlineData(ExtraType.Unknown, "extras" )]
|
||||
[InlineData(ExtraType.BehindTheScenes, "behind the scenes")]
|
||||
[InlineData(ExtraType.DeletedScene, "deleted scenes")]
|
||||
[InlineData(ExtraType.Interview, "interviews")]
|
||||
[InlineData(ExtraType.Scene, "scenes")]
|
||||
[InlineData(ExtraType.Sample, "samples")]
|
||||
[InlineData(ExtraType.Clip, "shorts")]
|
||||
[InlineData(ExtraType.Clip, "featurettes")]
|
||||
[InlineData(ExtraType.Unknown, "extras")]
|
||||
public void TestDirectories(ExtraType type, string dirName)
|
||||
{
|
||||
Test(dirName + "/300.mp4", type, _videoOptions);
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
|||
[InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
|
||||
[InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
|
||||
[InlineData("Superman: Red Son", "imdbid", null)]
|
||||
[InlineData("Superman: Red Son", "something", null)]
|
||||
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
|
||||
{
|
||||
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
|
||||
|
|
Loading…
Reference in New Issue
Block a user