commit
4c6b60d69d
|
@ -198,3 +198,4 @@
|
|||
- [tikuf](https://github.com/tikuf/)
|
||||
- [Tim Hobbs](https://github.com/timhobbs)
|
||||
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
|
||||
- [olsh](https://github.com/olsh)
|
||||
|
|
|
@ -455,7 +455,7 @@ namespace Emby.Drawing
|
|||
throw new ArgumentException("Path can't be empty.", nameof(path));
|
||||
}
|
||||
|
||||
if (path.IsEmpty)
|
||||
if (filename.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("Filename can't be empty.", nameof(filename));
|
||||
}
|
||||
|
|
|
@ -50,27 +50,14 @@ namespace Emby.Naming.AudioBook
|
|||
{
|
||||
if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
|
||||
{
|
||||
result.ChapterNumber = intValue;
|
||||
result.PartNumber = intValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*var matches = _iRegexProvider.GetRegex("\\d+", RegexOptions.IgnoreCase).Matches(fileName);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
if (!result.ChapterNumber.HasValue)
|
||||
{
|
||||
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
|
||||
}
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
|
||||
}
|
||||
}*/
|
||||
result.Success = result.PartNumber.HasValue || result.ChapterNumber.HasValue;
|
||||
result.Success = result.ChapterNumber.HasValue || result.PartNumber.HasValue;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ namespace Emby.Naming.AudioBook
|
|||
{
|
||||
Path = path,
|
||||
Container = container,
|
||||
PartNumber = parsingResult.PartNumber,
|
||||
ChapterNumber = parsingResult.ChapterNumber,
|
||||
PartNumber = parsingResult.PartNumber,
|
||||
IsDirectory = isDirectory
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -119,6 +121,7 @@ namespace Emby.Server.Implementations
|
|||
private readonly IFileSystem _fileSystemManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IStartupOptions _startupOptions;
|
||||
|
||||
private IMediaEncoder _mediaEncoder;
|
||||
|
@ -255,6 +258,8 @@ namespace Emby.Server.Implementations
|
|||
IServiceCollection serviceCollection)
|
||||
{
|
||||
_xmlSerializer = new MyXmlSerializer();
|
||||
_jsonSerializer = new JsonSerializer();
|
||||
|
||||
ServiceCollection = serviceCollection;
|
||||
|
||||
_networkManager = networkManager;
|
||||
|
@ -1021,6 +1026,108 @@ 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
|
||||
{
|
||||
metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
// 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>
|
||||
|
@ -1029,7 +1136,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
|
||||
|
|
117
Emby.Server.Implementations/Localization/Core/sq.json
Normal file
117
Emby.Server.Implementations/Localization/Core/sq.json
Normal file
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"MessageApplicationUpdatedTo": "Serveri Jellyfin u përditesua në versionin {0}",
|
||||
"Inherit": "Trashgimi",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Kërkon në internet për titra që mungojnë bazuar tek konfigurimi i metadata-ve.",
|
||||
"TaskDownloadMissingSubtitles": "Shkarko titra që mungojnë",
|
||||
"TaskRefreshChannelsDescription": "Rifreskon informacionin e kanaleve të internetit.",
|
||||
"TaskRefreshChannels": "Rifresko Kanalet",
|
||||
"TaskCleanTranscodeDescription": "Fshin skedarët e transkodimit që janë më të vjetër se një ditë.",
|
||||
"TaskCleanTranscode": "Fshi dosjen e transkodimit",
|
||||
"TaskUpdatePluginsDescription": "Shkarkon dhe instalon përditësimi për plugin që janë konfiguruar të përditësohen automatikisht.",
|
||||
"TaskUpdatePlugins": "Përditëso Plugin",
|
||||
"TaskRefreshPeopleDescription": "Përditëson metadata të aktorëve dhe regjizorëve në librarinë tuaj.",
|
||||
"TaskRefreshPeople": "Rifresko aktorët",
|
||||
"TaskCleanLogsDescription": "Fshin skëdarët log që janë më të vjetër se {0} ditë.",
|
||||
"TaskCleanLogs": "Fshi dosjen Log",
|
||||
"TaskRefreshLibraryDescription": "Skanon librarinë media për skedarë të rinj dhe rifreskon metadata.",
|
||||
"TaskRefreshLibrary": "Skano librarinë media",
|
||||
"TaskRefreshChapterImagesDescription": "Krijon imazh për videot që kanë kapituj.",
|
||||
"TaskRefreshChapterImages": "Ekstrakto Imazhet e Kapitullit",
|
||||
"TaskCleanCacheDescription": "Fshi skedarët e cache-s që nuk i duhen më sistemit.",
|
||||
"TaskCleanCache": "Pastro memorjen cache",
|
||||
"TasksChannelsCategory": "Kanalet nga interneti",
|
||||
"TasksApplicationCategory": "Aplikacioni",
|
||||
"TasksLibraryCategory": "Libraria",
|
||||
"TasksMaintenanceCategory": "Mirëmbajtje",
|
||||
"VersionNumber": "Versioni {0}",
|
||||
"ValueSpecialEpisodeName": "Speciale - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} u shtua tek libraria juaj",
|
||||
"UserStoppedPlayingItemWithValues": "{0} mbaroi së shikuari {1} tek {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} po shikon {1} tek {2}",
|
||||
"UserPolicyUpdatedWithName": "Politika e përdoruesit u përditësua për {0}",
|
||||
"UserPasswordChangedWithName": "Fjalëkalimi u ndryshua për përdoruesin {0}",
|
||||
"UserOnlineFromDevice": "{0} është në linjë nga {1}",
|
||||
"UserOfflineFromDevice": "{0} u shkëput nga {1}",
|
||||
"UserLockedOutWithName": "Përdoruesi {0} u përjashtua",
|
||||
"UserDownloadingItemWithValues": "{0} po shkarkon {1}",
|
||||
"UserDeletedWithName": "Përdoruesi {0} u fshi",
|
||||
"UserCreatedWithName": "Përdoruesi {0} u krijua",
|
||||
"User": "Përdoruesi",
|
||||
"TvShows": "Seriale TV",
|
||||
"System": "Sistemi",
|
||||
"Sync": "Sinkronizo",
|
||||
"SubtitleDownloadFailureFromForItem": "Titrat deshtuan të shkarkohen nga {0} për {1}",
|
||||
"StartupEmbyServerIsLoading": "Serveri Jellyfin po ngarkohet. Ju lutemi provoni përseri pas pak.",
|
||||
"Songs": "Këngë",
|
||||
"Shows": "Seriale",
|
||||
"ServerNameNeedsToBeRestarted": "{0} duhet të ristartoj",
|
||||
"ScheduledTaskStartedWithName": "{0} filloi",
|
||||
"ScheduledTaskFailedWithName": "{0} dështoi",
|
||||
"ProviderValue": "Ofruesi: {0}",
|
||||
"PluginUpdatedWithName": "{0} u përditësua",
|
||||
"PluginUninstalledWithName": "{0} u çinstalua",
|
||||
"PluginInstalledWithName": "{0} u instalua",
|
||||
"Plugin": "Plugin",
|
||||
"Playlists": "Listat për luajtje",
|
||||
"Photos": "Fotografitë",
|
||||
"NotificationOptionVideoPlaybackStopped": "Luajtja e videos ndaloi",
|
||||
"NotificationOptionVideoPlayback": "Luajtja e videos filloi",
|
||||
"NotificationOptionUserLockedOut": "Përdoruesi u përjashtua",
|
||||
"NotificationOptionTaskFailed": "Ushtrimi i planifikuar dështoi",
|
||||
"NotificationOptionServerRestartRequired": "Kërkohet ristartim i serverit",
|
||||
"NotificationOptionPluginUpdateInstalled": "Përditësimi i plugin u instalua",
|
||||
"NotificationOptionPluginUninstalled": "Plugin u çinstalua",
|
||||
"NotificationOptionPluginInstalled": "Plugin u instalua",
|
||||
"NotificationOptionPluginError": "Plugin dështoi",
|
||||
"NotificationOptionNewLibraryContent": "Një përmbajtje e re u shtua",
|
||||
"NotificationOptionInstallationFailed": "Instalimi dështoi",
|
||||
"NotificationOptionCameraImageUploaded": "Fotoja nga kamera u ngarkua",
|
||||
"NotificationOptionAudioPlaybackStopped": "Luajtja e audios ndaloi",
|
||||
"NotificationOptionAudioPlayback": "Luajtja e audios filloi",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Përditësimi i aplikacionit u instalua",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Një perditësim i aplikacionit është gati",
|
||||
"NewVersionIsAvailable": "Një version i ri i Jellyfin është gati për tu shkarkuar.",
|
||||
"NameSeasonUnknown": "Sezon i panjohur",
|
||||
"NameSeasonNumber": "Sezoni {0}",
|
||||
"NameInstallFailed": "Instalimi i {0} dështoi",
|
||||
"MusicVideos": "Video muzikore",
|
||||
"Music": "Muzikë",
|
||||
"Movies": "Filma",
|
||||
"MixedContent": "Përmbajtje e përzier",
|
||||
"MessageServerConfigurationUpdated": "Konfigurimet e serverit u përditësuan",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Seksioni i konfigurimit të serverit {0} u përditësua",
|
||||
"MessageApplicationUpdated": "Serveri Jellyfin u përditësua",
|
||||
"Latest": "Të fundit",
|
||||
"LabelRunningTimeValue": "Kohëzgjatja: {0}",
|
||||
"LabelIpAddressValue": "Adresa IP: {0}",
|
||||
"ItemRemovedWithName": "{0} u fshi nga libraria",
|
||||
"ItemAddedWithName": "{0} u shtua tek libraria",
|
||||
"HomeVideos": "Video personale",
|
||||
"HeaderRecordingGroups": "Grupet e regjistrimit",
|
||||
"HeaderNextUp": "Në vazhdim",
|
||||
"HeaderLiveTV": "TV Live",
|
||||
"HeaderFavoriteSongs": "Kënget e preferuara",
|
||||
"HeaderFavoriteShows": "Serialet e preferuar",
|
||||
"HeaderFavoriteEpisodes": "Episodet e preferuar",
|
||||
"HeaderFavoriteArtists": "Artistët e preferuar",
|
||||
"HeaderFavoriteAlbums": "Albumet e preferuar",
|
||||
"HeaderContinueWatching": "Vazhdo të shikosh",
|
||||
"HeaderCameraUploads": "Ngarkimet nga Kamera",
|
||||
"HeaderAlbumArtists": "Artistët e albumeve",
|
||||
"Genres": "Zhanre",
|
||||
"Folders": "Dosje",
|
||||
"Favorites": "Të preferuara",
|
||||
"FailedLoginAttemptWithUserName": "Përpjekja për hyrje dështoi nga {0}",
|
||||
"DeviceOnlineWithName": "{0} u lidh",
|
||||
"DeviceOfflineWithName": "{0} u shkëput",
|
||||
"Collections": "Koleksione",
|
||||
"ChapterNameValue": "Kapituj",
|
||||
"Channels": "Kanale",
|
||||
"CameraImageUploadedFrom": "Një foto e re nga kamera u ngarkua nga {0}",
|
||||
"Books": "Libra",
|
||||
"AuthenticationSucceededWithUserName": "{0} u identifikua me sukses",
|
||||
"Artists": "Artistë",
|
||||
"Application": "Aplikacioni",
|
||||
"AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}",
|
||||
"Albums": "Albumet"
|
||||
}
|
117
Emby.Server.Implementations/Localization/Core/vi.json
Normal file
117
Emby.Server.Implementations/Localization/Core/vi.json
Normal file
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"Collections": "Bộ Sưu Tập",
|
||||
"Favorites": "Sở Thích",
|
||||
"Folders": "Thư Mục",
|
||||
"Genres": "Thể Loại",
|
||||
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
|
||||
"HeaderContinueWatching": "Tiếp Tục 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",
|
||||
"Songs": "Các Bài Hát",
|
||||
"Sync": "Đồng Bộ",
|
||||
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
|
||||
"Albums": "Bộ Sưu Tập",
|
||||
"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.",
|
||||
"TaskRefreshChannels": "Làm Mới Kênh",
|
||||
"TaskCleanTranscodeDescription": "Xóa các tệp chuyển mã cũ hơn một ngày.",
|
||||
"TaskCleanTranscode": "Làm Sạch Thư Mục Chuyển Mã",
|
||||
"TaskUpdatePluginsDescription": "Tải xuống và cài đặt các bản cập nhật cho các plugin được định cấu hình để cập nhật tự động.",
|
||||
"TaskUpdatePlugins": "Cập Nhật Plugins",
|
||||
"TaskRefreshPeopleDescription": "Cập nhật thông tin chi tiết cho diễn viên và đạo diễn trong thư viện phương tiện của bạn.",
|
||||
"TaskRefreshPeople": "Làm mới Người dùng",
|
||||
"TaskCleanLogsDescription": "Xóa tập tin nhật ký cũ hơn {0} ngày.",
|
||||
"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",
|
||||
"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",
|
||||
"TasksApplicationCategory": "Ứng Dụng",
|
||||
"TasksLibraryCategory": "Thư Viện",
|
||||
"TasksMaintenanceCategory": "Bảo Trì",
|
||||
"VersionNumber": "Phiên Bản {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} đã được thêm vào thư viện của bạn",
|
||||
"UserStoppedPlayingItemWithValues": "{0} đã phát xong {1} trên {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} đang phát {1} trên {2}",
|
||||
"UserPolicyUpdatedWithName": "Chính sách người dùng đã được cập nhật cho {0}",
|
||||
"UserPasswordChangedWithName": "Mật khẩu đã được thay đổi cho người dùng {0}",
|
||||
"UserOnlineFromDevice": "{0} trực tuyến từ {1}",
|
||||
"UserOfflineFromDevice": "{0} đã ngắt kết nối từ {1}",
|
||||
"UserLockedOutWithName": "User {0} đã bị khóa",
|
||||
"UserDownloadingItemWithValues": "{0} đang tải xuống {1}",
|
||||
"UserDeletedWithName": "Người Dùng {0} đã được xóa",
|
||||
"UserCreatedWithName": "Người Dùng {0} đã được tạo",
|
||||
"User": "Người Dùng",
|
||||
"TvShows": "Chương Trình TV",
|
||||
"System": "Hệ Thống",
|
||||
"SubtitleDownloadFailureFromForItem": "Không thể tải xuống phụ đề từ {0} cho {1}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server đang tải. Vui lòng thử lại trong thời gian ngắn.",
|
||||
"ServerNameNeedsToBeRestarted": "{0} cần được khởi động lại",
|
||||
"ScheduledTaskStartedWithName": "{0} đã bắt đầu",
|
||||
"ScheduledTaskFailedWithName": "{0} đã thất bại",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"PluginUpdatedWithName": "{0} đã cập nhật",
|
||||
"PluginUninstalledWithName": "{0} đã được gỡ bỏ",
|
||||
"PluginInstalledWithName": "{0} đã được cài đặt",
|
||||
"Plugin": "Plugin",
|
||||
"NotificationOptionVideoPlaybackStopped": "Phát lại video đã dừng",
|
||||
"NotificationOptionVideoPlayback": "Đã bắt đầu phát lại video",
|
||||
"NotificationOptionUserLockedOut": "Người dùng bị khóa",
|
||||
"NotificationOptionTaskFailed": "Lỗi tác vụ đã lên lịch",
|
||||
"NotificationOptionServerRestartRequired": "Yêu cầu khởi động lại Server",
|
||||
"NotificationOptionPluginUpdateInstalled": "Cập nhật Plugin đã được cài đặt",
|
||||
"NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin",
|
||||
"NotificationOptionPluginInstalled": "Đã cài đặt Plugin",
|
||||
"NotificationOptionPluginError": "Thất bại Plugin",
|
||||
"NotificationOptionNewLibraryContent": "Nội dung mới được thêm vào",
|
||||
"NotificationOptionInstallationFailed": "Cài đặt thất bại",
|
||||
"NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh",
|
||||
"NotificationOptionAudioPlaybackStopped": "Phát lại âm thanh đã dừng",
|
||||
"NotificationOptionAudioPlayback": "Phát lại âm thanh đã bắt đầu",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Bản cập nhật ứng dụng đã được cài đặt",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có",
|
||||
"NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.",
|
||||
"NameSeasonUnknown": "Không Rõ Mùa",
|
||||
"NameSeasonNumber": "Mùa {0}",
|
||||
"NameInstallFailed": "{0} cài đặt thất bại",
|
||||
"MusicVideos": "Video Nhạc",
|
||||
"Music": "Nhạc",
|
||||
"MixedContent": "Nội dung hỗn hợp",
|
||||
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Phần cấu hình máy chủ {0} đã được cập nhật",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server đã được cập nhật lên {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server đã được cập nhật",
|
||||
"Latest": "Gần Nhất",
|
||||
"LabelRunningTimeValue": "Thời Gian Chạy: {0}",
|
||||
"LabelIpAddressValue": "Địa Chỉ IP: {0}",
|
||||
"ItemRemovedWithName": "{0} đã xóa khỏi thư viện",
|
||||
"ItemAddedWithName": "{0} được thêm vào thư viện",
|
||||
"Inherit": "Thừa hưởng",
|
||||
"HomeVideos": "Video nhà",
|
||||
"HeaderRecordingGroups": "Nhóm Ghi Video",
|
||||
"HeaderNextUp": "Tiếp Theo",
|
||||
"HeaderFavoriteSongs": "Bài Hát Yêu Thích",
|
||||
"HeaderFavoriteShows": "Chương Trình Yêu Thích",
|
||||
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
|
||||
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
|
||||
"HeaderFavoriteAlbums": "Album Ưa Thích",
|
||||
"HeaderCameraUploads": "Máy Ảnh Tải Lên",
|
||||
"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": "Các Kênh",
|
||||
"CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}",
|
||||
"Books": "Các Quyển Sách",
|
||||
"AuthenticationSucceededWithUserName": "{0} xác thực thành công",
|
||||
"Application": "Ứng Dụng",
|
||||
"AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}"
|
||||
}
|
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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<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" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -141,6 +141,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||
Name = episode.EpisodeName,
|
||||
Overview = episode.Overview,
|
||||
CommunityRating = (float?)episode.SiteRating,
|
||||
OfficialRating = episode.ContentRating,
|
||||
}
|
||||
};
|
||||
result.ResetPeople();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
using System.Linq;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
public class AudioBookListResolverTests
|
||||
{
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
[Fact]
|
||||
public void TestStackAndExtras()
|
||||
{
|
||||
// No stacking here because there is no part/disc/etc
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Part 1.mp3",
|
||||
"Harry Potter and the Deathly Hallows/Part 2.mp3",
|
||||
"Harry Potter and the Deathly Hallows/book.nfo",
|
||||
|
||||
"Batman/Chapter 1.mp3",
|
||||
"Batman/Chapter 2.mp3",
|
||||
"Batman/Chapter 3.mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Equal(2, result[0].Files.Count);
|
||||
// Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly
|
||||
Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name);
|
||||
|
||||
Assert.Equal(3, result[1].Files.Count);
|
||||
Assert.Empty(result[1].Extras);
|
||||
Assert.Equal("Batman", result[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWithMetadata()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows.nfo"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
}));
|
||||
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWithExtra()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows trailer.mp3"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
private AudioBookListResolver GetResolver()
|
||||
{
|
||||
return new AudioBookListResolver(_namingOptions);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System.Collections.Generic;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
public class AudioBookResolverTests
|
||||
{
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
public static IEnumerable<object[]> GetResolveFileTestData()
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
|
||||
Container = "mp3",
|
||||
}
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
|
||||
Container = "ogg",
|
||||
ChapterNumber = 1
|
||||
}
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3",
|
||||
Container = "mp3",
|
||||
ChapterNumber = 2,
|
||||
PartNumber = 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetResolveFileTestData))]
|
||||
public void ResolveFile_ValidFileName_Success(AudioBookFileInfo expectedResult)
|
||||
{
|
||||
var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result.Path, expectedResult.Path);
|
||||
Assert.Equal(result.Container, expectedResult.Container);
|
||||
Assert.Equal(result.ChapterNumber, expectedResult.ChapterNumber);
|
||||
Assert.Equal(result.PartNumber, expectedResult.PartNumber);
|
||||
Assert.Equal(result.IsDirectory, expectedResult.IsDirectory);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user