mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-23 15:30:56 -04:00
Merge branch 'master' into generated-code-cleanup
This commit is contained in:
commit
0549d59a5f
@ -13,7 +13,7 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
max_line_length = null
|
max_line_length = off
|
||||||
|
|
||||||
# YAML indentation
|
# YAML indentation
|
||||||
[*.{yml,yaml}]
|
[*.{yml,yaml}]
|
||||||
@ -22,6 +22,7 @@ indent_size = 2
|
|||||||
# XML indentation
|
# XML indentation
|
||||||
[*.{csproj,xml}]
|
[*.{csproj,xml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# .NET Coding Conventions #
|
# .NET Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
|
|||||||
dotnet_style_null_propagation = true:suggestion
|
dotnet_style_null_propagation = true:suggestion
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||||
dotnet_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
dotnet_style_prefer_auto_properties = true:silent
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Naming Conventions #
|
# Naming Conventions #
|
||||||
###############################
|
###############################
|
||||||
@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
|
|||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||||
|
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||||
@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
|
|||||||
csharp_prefer_simple_default_expression = true:suggestion
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# C# Formatting Rules #
|
# C# Formatting Rules #
|
||||||
###############################
|
###############################
|
||||||
@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
|||||||
# Wrapping preferences
|
# Wrapping preferences
|
||||||
csharp_preserve_single_line_statements = true
|
csharp_preserve_single_line_statements = true
|
||||||
csharp_preserve_single_line_blocks = true
|
csharp_preserve_single_line_blocks = true
|
||||||
###############################
|
|
||||||
# VB Coding Conventions #
|
|
||||||
###############################
|
|
||||||
[*.vb]
|
|
||||||
# Modifier preferences
|
|
||||||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
public class Chapter
|
public class Chapter
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
@ -89,14 +89,14 @@ namespace Emby.Naming.Video
|
|||||||
if (parseName)
|
if (parseName)
|
||||||
{
|
{
|
||||||
var cleanDateTimeResult = CleanDateTime(name);
|
var cleanDateTimeResult = CleanDateTime(name);
|
||||||
|
name = cleanDateTimeResult.Name;
|
||||||
|
year = cleanDateTimeResult.Year;
|
||||||
|
|
||||||
if (extraResult.ExtraType == null
|
if (extraResult.ExtraType == null
|
||||||
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
&& TryCleanString(name, out ReadOnlySpan<char> newName))
|
||||||
{
|
{
|
||||||
name = newName.ToString();
|
name = newName.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
year = cleanDateTimeResult.Year;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VideoFileInfo
|
return new VideoFileInfo
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using Emby.Server.Implementations.Updates;
|
using Emby.Server.Implementations.Updates;
|
||||||
using MediaBrowser.Providers.Music;
|
|
||||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
|
@ -375,5 +375,15 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <remarks>
|
||||||
|
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
|
||||||
|
/// <see cref="BaseSqliteRepository.WriteConnection"/> are managed by <see cref="SqliteItemRepository"/>.
|
||||||
|
/// See <see cref="Initialize(IUserManager, SemaphoreSlim, SQLiteDatabaseConnection)"/>.
|
||||||
|
/// </remarks>
|
||||||
|
protected override void Dispose(bool dispose)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -608,6 +608,31 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
IAuthenticationProvider authenticationProvider = GetAuthenticationProvider(user);
|
||||||
|
bool hasConfiguredPassword = authenticationProvider.HasPassword(user);
|
||||||
|
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(authenticationProvider.GetEasyPasswordHash(user));
|
||||||
|
|
||||||
|
bool hasPassword = user.Configuration.EnableLocalPassword &&
|
||||||
|
!string.IsNullOrEmpty(remoteEndPoint) &&
|
||||||
|
_networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword;
|
||||||
|
|
||||||
|
PublicUserDto dto = new PublicUserDto
|
||||||
|
{
|
||||||
|
Name = user.Name,
|
||||||
|
HasPassword = hasPassword,
|
||||||
|
HasConfiguredPassword = hasConfiguredPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
public UserDto GetOfflineUserDto(User user)
|
public UserDto GetOfflineUserDto(User user)
|
||||||
{
|
{
|
||||||
var dto = GetUserDto(user);
|
var dto = GetUserDto(user);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"Folders": "Fouers",
|
"Folders": "Fouers",
|
||||||
"Favorites": "Gunstelinge",
|
"Favorites": "Gunstelinge",
|
||||||
"HeaderFavoriteShows": "Gunsteling Vertonings",
|
"HeaderFavoriteShows": "Gunsteling Vertonings",
|
||||||
"ValueSpecialEpisodeName": "Spesiaal - {0}",
|
"ValueSpecialEpisodeName": "Spesiale - {0}",
|
||||||
"HeaderAlbumArtists": "Album Kunstenaars",
|
"HeaderAlbumArtists": "Album Kunstenaars",
|
||||||
"Books": "Boeke",
|
"Books": "Boeke",
|
||||||
"HeaderNextUp": "Volgende",
|
"HeaderNextUp": "Volgende",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"Collections": "Colecciones",
|
"Collections": "Colecciones",
|
||||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||||
"DeviceOnlineWithName": "{0} está conectado",
|
"DeviceOnlineWithName": "{0} está conectado",
|
||||||
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}",
|
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}",
|
||||||
"Favorites": "Favoritos",
|
"Favorites": "Favoritos",
|
||||||
"Folders": "Carpetas",
|
"Folders": "Carpetas",
|
||||||
"Genres": "Géneros",
|
"Genres": "Géneros",
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
"ScheduledTaskFailedWithName": "{0} falló",
|
"ScheduledTaskFailedWithName": "{0} falló",
|
||||||
"ScheduledTaskStartedWithName": "{0} iniciada",
|
"ScheduledTaskStartedWithName": "{0} iniciada",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
|
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
|
||||||
"Shows": "Series",
|
"Shows": "Mostrar",
|
||||||
"Songs": "Canciones",
|
"Songs": "Canciones",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
|
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
|
||||||
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
|
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"HeaderLiveTV": "Suorat lähetykset",
|
"HeaderLiveTV": "Live-TV",
|
||||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||||
"NameSeasonNumber": "Kausi {0}",
|
"NameSeasonNumber": "Kausi {0}",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}",
|
||||||
"MessageApplicationUpdated": "Jellyfin palvelin on päivitetty",
|
"MessageApplicationUpdated": "Jellyfin palvelin on päivitetty",
|
||||||
"Latest": "Viimeisin",
|
"Latest": "Uusimmat",
|
||||||
"LabelRunningTimeValue": "Toiston kesto: {0}",
|
"LabelRunningTimeValue": "Toiston kesto: {0}",
|
||||||
"LabelIpAddressValue": "IP-osoite: {0}",
|
"LabelIpAddressValue": "IP-osoite: {0}",
|
||||||
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
|
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
|
"CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
|
||||||
"Books": "Kirjat",
|
"Books": "Kirjat",
|
||||||
"AuthenticationSucceededWithUserName": "{0} todennus onnistui",
|
"AuthenticationSucceededWithUserName": "{0} todennus onnistui",
|
||||||
"Artists": "Esiintyjät",
|
"Artists": "Artistit",
|
||||||
"Application": "Sovellus",
|
"Application": "Sovellus",
|
||||||
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
|
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
|
||||||
"Albums": "Albumit",
|
"Albums": "Albumit",
|
||||||
@ -67,21 +67,21 @@
|
|||||||
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||||
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||||
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||||
"TvShows": "TV-Ohjelmat",
|
"TvShows": "TV-sarjat",
|
||||||
"Sync": "Synkronoi",
|
"Sync": "Synkronoi",
|
||||||
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
"SubtitleDownloadFailureFromForItem": "Tekstitysten lataus ({0} -> {1}) epäonnistui //this string would have to be generated for each provider and movie because of finnish cases, sorry",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.",
|
"StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.",
|
||||||
"Songs": "Kappaleet",
|
"Songs": "Kappaleet",
|
||||||
"Shows": "Ohjelmat",
|
"Shows": "Sarjat",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
"ServerNameNeedsToBeRestarted": "{0} täytyy käynnistää uudelleen",
|
||||||
"ProviderValue": "Tarjoaja: {0}",
|
"ProviderValue": "Tarjoaja: {0}",
|
||||||
"Plugin": "Liitännäinen",
|
"Plugin": "Liitännäinen",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||||
"NotificationOptionVideoPlayback": "Videon toisto aloitettu",
|
"NotificationOptionVideoPlayback": "Videota toistetaan",
|
||||||
"NotificationOptionUserLockedOut": "Käyttäjä lukittu",
|
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
||||||
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
"NotificationOptionServerRestartRequired": "Palvelin pitää käynnistää uudelleen",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
|
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
||||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||||
@ -90,8 +90,8 @@
|
|||||||
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
||||||
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
|
"NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
|
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla",
|
||||||
"TasksMaintenanceCategory": "Ylläpito",
|
"TasksMaintenanceCategory": "Ylläpito",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
||||||
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
||||||
|
@ -94,5 +94,23 @@
|
|||||||
"ValueSpecialEpisodeName": "Spécial - {0}",
|
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||||
"VersionNumber": "Version {0}",
|
"VersionNumber": "Version {0}",
|
||||||
"TasksLibraryCategory": "Bibliothèque",
|
"TasksLibraryCategory": "Bibliothèque",
|
||||||
"TasksMaintenanceCategory": "Entretien"
|
"TasksMaintenanceCategory": "Entretien",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Recherche l'internet pour des sous-titres manquants à base de métadonnées configurées.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Télécharger des sous-titres manquants",
|
||||||
|
"TaskRefreshChannelsDescription": "Rafraîchit des informations des chaines d'internet.",
|
||||||
|
"TaskRefreshChannels": "Rafraîchir des chaines",
|
||||||
|
"TaskCleanTranscodeDescription": "Retirer des fichiers de transcodage de plus qu'un jour.",
|
||||||
|
"TaskCleanTranscode": "Nettoyer le directoire de transcodage",
|
||||||
|
"TaskUpdatePluginsDescription": "Télécharger et installer des mises à jours des plugins qui sont configurés m.à.j. automisés.",
|
||||||
|
"TaskUpdatePlugins": "Mise à jour des plugins",
|
||||||
|
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
|
||||||
|
"TaskRefreshPeople": "Rafraîchir les acteurs",
|
||||||
|
"TaskCleanLogsDescription": "Retire les données qui ont plus que {0} jours.",
|
||||||
|
"TaskCleanLogs": "Nettoyer les données de directoire",
|
||||||
|
"TaskRefreshLibraryDescription": "Analyse votre bibliothèque média pour des nouveaux fichiers et rafraîchit les métadonnées.",
|
||||||
|
"TaskRefreshChapterImages": "Extraire des images du chapitre",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Créer des vignettes pour des vidéos qui ont des chapitres",
|
||||||
|
"TaskRefreshLibrary": "Analyser la bibliothèque de média",
|
||||||
|
"TaskCleanCache": "Nettoyer le cache de directoire",
|
||||||
|
"TasksApplicationCategory": "Application"
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"Albums": "Albom",
|
"Albums": "Alben",
|
||||||
"AppDeviceValues": "App: {0}, Grät: {1}",
|
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||||
"Application": "Aawändig",
|
"Application": "Anwendung",
|
||||||
"Artists": "Könstler",
|
"Artists": "Künstler",
|
||||||
"AuthenticationSucceededWithUserName": "{0} het sech aagmäudet",
|
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
||||||
"Books": "Büecher",
|
"Books": "Bücher",
|
||||||
"CameraImageUploadedFrom": "Es nöis Foti esch ufeglade worde vo {0}",
|
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
||||||
"Channels": "Kanäu",
|
"Channels": "Kanäle",
|
||||||
"ChapterNameValue": "Kapitu {0}",
|
"ChapterNameValue": "Kapitel {0}",
|
||||||
"Collections": "Sammlige",
|
"Collections": "Sammlungen",
|
||||||
"DeviceOfflineWithName": "{0} esch offline gange",
|
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||||
"DeviceOnlineWithName": "{0} esch online cho",
|
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||||
"FailedLoginAttemptWithUserName": "Fäugschlagne Aamäudeversuech vo {0}",
|
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||||
"Favorites": "Favorite",
|
"Favorites": "Favoriten",
|
||||||
"Folders": "Ordner",
|
"Folders": "Ordner",
|
||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Albom-Könstler",
|
"HeaderAlbumArtists": "Album-Künstler",
|
||||||
"HeaderCameraUploads": "Kamera-Uploads",
|
"HeaderCameraUploads": "Kamera-Uploads",
|
||||||
"HeaderContinueWatching": "Wiiterluege",
|
"HeaderContinueWatching": "weiter schauen",
|
||||||
"HeaderFavoriteAlbums": "Lieblingsalbe",
|
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||||
"HeaderFavoriteArtists": "Lieblings-Interprete",
|
"HeaderFavoriteArtists": "Lieblings-Künstler",
|
||||||
"HeaderFavoriteEpisodes": "Lieblingsepisode",
|
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||||
"HeaderFavoriteShows": "Lieblingsserie",
|
"HeaderFavoriteShows": "Lieblingsserien",
|
||||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||||
"HeaderLiveTV": "Live-Färnseh",
|
"HeaderLiveTV": "Live-Fernseh",
|
||||||
"HeaderNextUp": "Als nächts",
|
"HeaderNextUp": "Als Nächstes",
|
||||||
"HeaderRecordingGroups": "Ufnahmegruppe",
|
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
||||||
"HomeVideos": "Heimfilmli",
|
"HomeVideos": "Heimvideos",
|
||||||
"Inherit": "Hinzuefüege",
|
"Inherit": "Vererben",
|
||||||
"ItemAddedWithName": "{0} esch de Bibliothek dezuegfüegt worde",
|
"ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt",
|
||||||
"ItemRemovedWithName": "{0} esch vo de Bibliothek entfärnt worde",
|
"ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt",
|
||||||
"LabelIpAddressValue": "IP-Adrässe: {0}",
|
"LabelIpAddressValue": "IP-Adresse: {0}",
|
||||||
"LabelRunningTimeValue": "Loufziit: {0}",
|
"LabelRunningTimeValue": "Laufzeit: {0}",
|
||||||
"Latest": "Nöischti",
|
"Latest": "Neueste",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde",
|
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server esch of Version {0} aktualisiert worde",
|
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
|
||||||
"MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde",
|
"MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde",
|
||||||
"MixedContent": "Gmeschti Inhäut",
|
"MixedContent": "Gmeschti Inhäut",
|
||||||
"Movies": "Film",
|
"Movies": "Film",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Audiowedergab gstartet",
|
"NotificationOptionAudioPlayback": "Audiowedergab gstartet",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt",
|
"NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt",
|
||||||
"NotificationOptionCameraImageUploaded": "Foti ueglade",
|
"NotificationOptionCameraImageUploaded": "Foti ueglade",
|
||||||
"NotificationOptionInstallationFailed": "Installationsfäuer",
|
"NotificationOptionInstallationFailed": "Installationsfehler",
|
||||||
"NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt",
|
"NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt",
|
||||||
"NotificationOptionPluginError": "Plugin-Fäuer",
|
"NotificationOptionPluginError": "Plugin-Fäuer",
|
||||||
"NotificationOptionPluginInstalled": "Plugin installiert",
|
"NotificationOptionPluginInstalled": "Plugin installiert",
|
||||||
@ -92,5 +92,16 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt",
|
"UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde",
|
"ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde",
|
||||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
"ValueSpecialEpisodeName": "Extra - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskCleanLogs": "Lösche Log Pfad",
|
||||||
|
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
||||||
|
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.",
|
||||||
|
"TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder",
|
||||||
|
"TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.",
|
||||||
|
"TaskCleanCache": "Leere Cache Pfad",
|
||||||
|
"TasksChannelsCategory": "Internet Kanäle",
|
||||||
|
"TasksApplicationCategory": "Applikation",
|
||||||
|
"TasksLibraryCategory": "Bibliothek",
|
||||||
|
"TasksMaintenanceCategory": "Verwaltung"
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
"NotificationOptionVideoPlayback": "Video playback started",
|
"NotificationOptionVideoPlayback": "Video playback started",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||||
"Photos": "תמונות",
|
"Photos": "תמונות",
|
||||||
"Playlists": "רשימות ניגון",
|
"Playlists": "רשימות הפעלה",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Plugin",
|
||||||
"PluginInstalledWithName": "{0} was installed",
|
"PluginInstalledWithName": "{0} was installed",
|
||||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
"Inherit": "Naslijedi",
|
"Inherit": "Naslijedi",
|
||||||
"ItemAddedWithName": "{0} je dodano u biblioteku",
|
"ItemAddedWithName": "{0} je dodano u biblioteku",
|
||||||
"ItemRemovedWithName": "{0} je uklonjen iz biblioteke",
|
"ItemRemovedWithName": "{0} je uklonjen iz biblioteke",
|
||||||
"LabelIpAddressValue": "Ip adresa: {0}",
|
"LabelIpAddressValue": "IP adresa: {0}",
|
||||||
"LabelRunningTimeValue": "Vrijeme rada: {0}",
|
"LabelRunningTimeValue": "Vrijeme rada: {0}",
|
||||||
"Latest": "Najnovije",
|
"Latest": "Najnovije",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server je ažuriran",
|
"MessageApplicationUpdated": "Jellyfin Server je ažuriran",
|
||||||
@ -92,5 +92,13 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}",
|
"UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||||
"ValueSpecialEpisodeName": "Specijal - {0}",
|
"ValueSpecialEpisodeName": "Specijal - {0}",
|
||||||
"VersionNumber": "Verzija {0}"
|
"VersionNumber": "Verzija {0}",
|
||||||
|
"TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.",
|
||||||
|
"TaskRefreshLibrary": "Skeniraj medijsku knjižnicu",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Stvara sličice za videozapise koji imaju poglavlja.",
|
||||||
|
"TaskRefreshChapterImages": "Raspakiraj slike poglavlja",
|
||||||
|
"TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.",
|
||||||
|
"TaskCleanCache": "Očisti priručnu memoriju",
|
||||||
|
"TasksApplicationCategory": "Aplikacija",
|
||||||
|
"TasksMaintenanceCategory": "Održavanje"
|
||||||
}
|
}
|
||||||
|
@ -91,5 +91,12 @@
|
|||||||
"Songs": "Песни",
|
"Songs": "Песни",
|
||||||
"Shows": "Серии",
|
"Shows": "Серии",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} треба да се рестартира",
|
"ServerNameNeedsToBeRestarted": "{0} треба да се рестартира",
|
||||||
"ScheduledTaskStartedWithName": "{0} започна"
|
"ScheduledTaskStartedWithName": "{0} започна",
|
||||||
|
"TaskRefreshChapterImages": "Извези Слики од Поглавје",
|
||||||
|
"TaskCleanCacheDescription": "Ги брише кешираните фајлови што не се повеќе потребни од системот.",
|
||||||
|
"TaskCleanCache": "Исчисти Го Кешот",
|
||||||
|
"TasksChannelsCategory": "Интернет Канали",
|
||||||
|
"TasksApplicationCategory": "Апликација",
|
||||||
|
"TasksLibraryCategory": "Библиотека",
|
||||||
|
"TasksMaintenanceCategory": "Одржување"
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Artists": "Artiesten",
|
"Artists": "Artiesten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||||
"Books": "Boeken",
|
"Books": "Boeken",
|
||||||
"CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}",
|
"CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd via {0}",
|
||||||
"Channels": "Kanalen",
|
"Channels": "Kanalen",
|
||||||
"ChapterNameValue": "Hoofdstuk {0}",
|
"ChapterNameValue": "Hoofdstuk {0}",
|
||||||
"Collections": "Verzamelingen",
|
"Collections": "Verzamelingen",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "Live TV",
|
||||||
"HeaderNextUp": "Volgende",
|
"HeaderNextUp": "Volgende",
|
||||||
"HeaderRecordingGroups": "Opnamegroepen",
|
"HeaderRecordingGroups": "Opnamegroepen",
|
||||||
"HomeVideos": "Start video's",
|
"HomeVideos": "Home video's",
|
||||||
"Inherit": "Overerven",
|
"Inherit": "Overerven",
|
||||||
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
||||||
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Muziek gestart",
|
"NotificationOptionAudioPlayback": "Muziek gestart",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
||||||
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
||||||
"NotificationOptionInstallationFailed": "Installatie mislukking",
|
"NotificationOptionInstallationFailed": "Installatie mislukt",
|
||||||
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
||||||
"NotificationOptionPluginError": "Plug-in fout",
|
"NotificationOptionPluginError": "Plug-in fout",
|
||||||
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
|
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
|
||||||
|
@ -92,5 +92,26 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}",
|
"UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici",
|
"ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici",
|
||||||
"ValueSpecialEpisodeName": "Poseben - {0}",
|
"ValueSpecialEpisodeName": "Poseben - {0}",
|
||||||
"VersionNumber": "Različica {0}"
|
"VersionNumber": "Različica {0}",
|
||||||
|
"TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise",
|
||||||
|
"TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.",
|
||||||
|
"TaskRefreshChannels": "Osveži kanale",
|
||||||
|
"TaskCleanTranscodeDescription": "Izbriše več kot dan stare datoteke prekodiranja.",
|
||||||
|
"TaskCleanTranscode": "Počisti mapo prekodiranja",
|
||||||
|
"TaskUpdatePluginsDescription": "Prenese in namesti posodobitve za dodatke, ki imajo omogočene samodejne posodobitve.",
|
||||||
|
"TaskUpdatePlugins": "Posodobi dodatke",
|
||||||
|
"TaskRefreshPeopleDescription": "Osveži metapodatke za igralce in režiserje v vaši knjižnici.",
|
||||||
|
"TaskRefreshPeople": "Osveži osebe",
|
||||||
|
"TaskCleanLogsDescription": "Izbriše dnevniške datoteke starejše od {0} dni.",
|
||||||
|
"TaskCleanLogs": "Počisti mapo dnevnika",
|
||||||
|
"TaskRefreshLibraryDescription": "Preišče vašo knjižnico za nove datoteke in osveži metapodatke.",
|
||||||
|
"TaskRefreshLibrary": "Preišči knjižnico predstavnosti",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Ustvari sličice za poglavja videoposnetkov.",
|
||||||
|
"TaskRefreshChapterImages": "Izvleči slike poglavij",
|
||||||
|
"TaskCleanCacheDescription": "Izbriše predpomnjene datoteke, ki niso več potrebne.",
|
||||||
|
"TaskCleanCache": "Počisti mapo predpomnilnika",
|
||||||
|
"TasksChannelsCategory": "Spletni kanali",
|
||||||
|
"TasksApplicationCategory": "Aplikacija",
|
||||||
|
"TasksLibraryCategory": "Knjižnica",
|
||||||
|
"TasksMaintenanceCategory": "Vzdrževanje"
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"Channels": "Kanaler",
|
"Channels": "Kanaler",
|
||||||
"ChapterNameValue": "Kapitel {0}",
|
"ChapterNameValue": "Kapitel {0}",
|
||||||
"Collections": "Samlingar",
|
"Collections": "Samlingar",
|
||||||
"DeviceOfflineWithName": "{0} har tappat anslutningen",
|
"DeviceOfflineWithName": "{0} har kopplat från",
|
||||||
"DeviceOnlineWithName": "{0} är ansluten",
|
"DeviceOnlineWithName": "{0} är ansluten",
|
||||||
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
|
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
|
||||||
"Favorites": "Favoriter",
|
"Favorites": "Favoriter",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
|
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades",
|
"NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades",
|
||||||
"NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp",
|
"NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp",
|
||||||
"NotificationOptionInstallationFailed": "Fel vid installation",
|
"NotificationOptionInstallationFailed": "Installationen misslyckades",
|
||||||
"NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till",
|
"NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till",
|
||||||
"NotificationOptionPluginError": "Fel uppstod med tillägget",
|
"NotificationOptionPluginError": "Fel uppstod med tillägget",
|
||||||
"NotificationOptionPluginInstalled": "Tillägg har installerats",
|
"NotificationOptionPluginInstalled": "Tillägg har installerats",
|
||||||
@ -113,5 +113,6 @@
|
|||||||
"TasksChannelsCategory": "Internetkanaler",
|
"TasksChannelsCategory": "Internetkanaler",
|
||||||
"TasksApplicationCategory": "Applikation",
|
"TasksApplicationCategory": "Applikation",
|
||||||
"TasksLibraryCategory": "Bibliotek",
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
"TasksMaintenanceCategory": "Underhåll"
|
"TasksMaintenanceCategory": "Underhåll",
|
||||||
|
"TaskRefreshPeople": "Uppdatera Personer"
|
||||||
}
|
}
|
||||||
|
36
Emby.Server.Implementations/Localization/Core/uk.json
Normal file
36
Emby.Server.Implementations/Localization/Core/uk.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"MusicVideos": "Музичні відео",
|
||||||
|
"Music": "Музика",
|
||||||
|
"Movies": "Фільми",
|
||||||
|
"MessageApplicationUpdatedTo": "Jellyfin Server був оновлений до версії {0}",
|
||||||
|
"MessageApplicationUpdated": "Jellyfin Server був оновлений",
|
||||||
|
"Latest": "Останні",
|
||||||
|
"LabelIpAddressValue": "IP-адреси: {0}",
|
||||||
|
"ItemRemovedWithName": "{0} видалено з бібліотеки",
|
||||||
|
"ItemAddedWithName": "{0} додано до бібліотеки",
|
||||||
|
"HeaderNextUp": "Наступний",
|
||||||
|
"HeaderLiveTV": "Ефірне ТБ",
|
||||||
|
"HeaderFavoriteSongs": "Улюблені пісні",
|
||||||
|
"HeaderFavoriteShows": "Улюблені шоу",
|
||||||
|
"HeaderFavoriteEpisodes": "Улюблені серії",
|
||||||
|
"HeaderFavoriteArtists": "Улюблені виконавці",
|
||||||
|
"HeaderFavoriteAlbums": "Улюблені альбоми",
|
||||||
|
"HeaderContinueWatching": "Продовжити перегляд",
|
||||||
|
"HeaderCameraUploads": "Завантажено з камери",
|
||||||
|
"HeaderAlbumArtists": "Виконавці альбомів",
|
||||||
|
"Genres": "Жанри",
|
||||||
|
"Folders": "Директорії",
|
||||||
|
"Favorites": "Улюблені",
|
||||||
|
"DeviceOnlineWithName": "{0} під'єднано",
|
||||||
|
"DeviceOfflineWithName": "{0} від'єднано",
|
||||||
|
"Collections": "Колекції",
|
||||||
|
"ChapterNameValue": "Глава {0}",
|
||||||
|
"Channels": "Канали",
|
||||||
|
"CameraImageUploadedFrom": "Нова фотографія завантажена з {0}",
|
||||||
|
"Books": "Книги",
|
||||||
|
"AuthenticationSucceededWithUserName": "{0} успішно авторизовані",
|
||||||
|
"Artists": "Виконавці",
|
||||||
|
"Application": "Додаток",
|
||||||
|
"AppDeviceValues": "Додаток: {0}, Пристрій: {1}",
|
||||||
|
"Albums": "Альбоми"
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Albums": "專輯",
|
"Albums": "專輯",
|
||||||
"AppDeviceValues": "軟體: {0}, 設備: {1}",
|
"AppDeviceValues": "軟件: {0}, 設備: {1}",
|
||||||
"Application": "應用程式",
|
"Application": "應用程式",
|
||||||
"Artists": "藝人",
|
"Artists": "藝人",
|
||||||
"AuthenticationSucceededWithUserName": "{0} 授權成功",
|
"AuthenticationSucceededWithUserName": "{0} 授權成功",
|
||||||
@ -92,5 +92,8 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
|
"UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫",
|
"ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫",
|
||||||
"ValueSpecialEpisodeName": "特典 - {0}",
|
"ValueSpecialEpisodeName": "特典 - {0}",
|
||||||
"VersionNumber": "版本{0}"
|
"VersionNumber": "版本{0}",
|
||||||
|
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
|
||||||
|
"TaskUpdatePlugins": "更新插件",
|
||||||
|
"TasksApplicationCategory": "應用程式"
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,9 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||||||
if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip))
|
if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip))
|
||||||
{
|
{
|
||||||
ip = Request.HttpContext.Connection.RemoteIpAddress;
|
ip = Request.HttpContext.Connection.RemoteIpAddress;
|
||||||
|
|
||||||
|
// Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests)
|
||||||
|
ip ??= IPAddress.Loopback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +93,10 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||||||
|
|
||||||
public IQueryCollection QueryString => Request.Query;
|
public IQueryCollection QueryString => Request.Query;
|
||||||
|
|
||||||
public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress);
|
public bool IsLocal =>
|
||||||
|
(Request.HttpContext.Connection.LocalIpAddress == null
|
||||||
|
&& Request.HttpContext.Connection.RemoteIpAddress == null)
|
||||||
|
|| Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress);
|
||||||
|
|
||||||
public string HttpMethod => Request.Method;
|
public string HttpMethod => Request.Method;
|
||||||
|
|
||||||
|
@ -161,23 +161,7 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
||||||
|
|
||||||
// Make sure we have all the code pages we can get
|
PerformStaticInitialization();
|
||||||
// Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
|
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
||||||
|
|
||||||
// Increase the max http request limit
|
|
||||||
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
|
|
||||||
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
|
||||||
|
|
||||||
// Disable the "Expect: 100-Continue" header by default
|
|
||||||
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
|
||||||
ServicePointManager.Expect100Continue = false;
|
|
||||||
|
|
||||||
Batteries_V2.Init();
|
|
||||||
if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Failed to enable shared cache for SQLite");
|
|
||||||
}
|
|
||||||
|
|
||||||
var appHost = new CoreAppHost(
|
var appHost = new CoreAppHost(
|
||||||
appPaths,
|
appPaths,
|
||||||
@ -205,7 +189,7 @@ namespace Jellyfin.Server
|
|||||||
ServiceCollection serviceCollection = new ServiceCollection();
|
ServiceCollection serviceCollection = new ServiceCollection();
|
||||||
appHost.Init(serviceCollection);
|
appHost.Init(serviceCollection);
|
||||||
|
|
||||||
var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
||||||
|
|
||||||
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
|
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
|
||||||
appHost.ServiceProvider = webHost.Services;
|
appHost.ServiceProvider = webHost.Services;
|
||||||
@ -250,14 +234,49 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IWebHostBuilder CreateWebHostBuilder(
|
/// <summary>
|
||||||
|
/// Call static initialization methods for the application.
|
||||||
|
/// </summary>
|
||||||
|
public static void PerformStaticInitialization()
|
||||||
|
{
|
||||||
|
// Make sure we have all the code pages we can get
|
||||||
|
// Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
// Increase the max http request limit
|
||||||
|
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
|
||||||
|
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
||||||
|
|
||||||
|
// Disable the "Expect: 100-Continue" header by default
|
||||||
|
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||||
|
ServicePointManager.Expect100Continue = false;
|
||||||
|
|
||||||
|
Batteries_V2.Init();
|
||||||
|
if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Failed to enable shared cache for SQLite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure the web host builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to configure.</param>
|
||||||
|
/// <param name="appHost">The application host.</param>
|
||||||
|
/// <param name="serviceCollection">The application service collection.</param>
|
||||||
|
/// <param name="commandLineOpts">The command line options passed to the application.</param>
|
||||||
|
/// <param name="startupConfig">The application configuration.</param>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <returns>The configured web host builder.</returns>
|
||||||
|
public static IWebHostBuilder ConfigureWebHostBuilder(
|
||||||
|
this IWebHostBuilder builder,
|
||||||
ApplicationHost appHost,
|
ApplicationHost appHost,
|
||||||
IServiceCollection serviceCollection,
|
IServiceCollection serviceCollection,
|
||||||
StartupOptions commandLineOpts,
|
StartupOptions commandLineOpts,
|
||||||
IConfiguration startupConfig,
|
IConfiguration startupConfig,
|
||||||
IApplicationPaths appPaths)
|
IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
return new WebHostBuilder()
|
return builder
|
||||||
.UseKestrel((builderContext, options) =>
|
.UseKestrel((builderContext, options) =>
|
||||||
{
|
{
|
||||||
var addresses = appHost.ServerConfigurationManager
|
var addresses = appHost.ServerConfigurationManager
|
||||||
@ -278,7 +297,6 @@ namespace Jellyfin.Server
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Kestrel listening on {IpAddress}", address);
|
_logger.LogInformation("Kestrel listening on {IpAddress}", address);
|
||||||
options.Listen(address, appHost.HttpPort);
|
options.Listen(address, appHost.HttpPort);
|
||||||
|
|
||||||
if (appHost.EnableHttps && appHost.Certificate != null)
|
if (appHost.EnableHttps && appHost.Certificate != null)
|
||||||
{
|
{
|
||||||
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
||||||
@ -289,11 +307,18 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
else if (builderContext.HostingEnvironment.IsDevelopment())
|
else if (builderContext.HostingEnvironment.IsDevelopment())
|
||||||
{
|
{
|
||||||
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
try
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps();
|
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
||||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
{
|
||||||
});
|
listenOptions.UseHttps();
|
||||||
|
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,11 +337,18 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
else if (builderContext.HostingEnvironment.IsDevelopment())
|
else if (builderContext.HostingEnvironment.IsDevelopment())
|
||||||
{
|
{
|
||||||
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
|
try
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps();
|
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
|
||||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
{
|
||||||
});
|
listenOptions.UseHttps();
|
||||||
|
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -496,7 +528,9 @@ namespace Jellyfin.Server
|
|||||||
/// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist
|
/// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist
|
||||||
/// already.
|
/// already.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task InitLoggingConfigFile(IApplicationPaths appPaths)
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <returns>A task representing the creation of the configuration file, or a completed task if the file already exists.</returns>
|
||||||
|
public static async Task InitLoggingConfigFile(IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
// Do nothing if the config file already exists
|
// Do nothing if the config file already exists
|
||||||
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault);
|
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault);
|
||||||
@ -516,7 +550,13 @@ namespace Jellyfin.Server
|
|||||||
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
|
/// <summary>
|
||||||
|
/// Create the application configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandLineOpts">The command line options passed to the program.</param>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <returns>The application configuration.</returns>
|
||||||
|
public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
return new ConfigurationBuilder()
|
return new ConfigurationBuilder()
|
||||||
.ConfigureAppConfiguration(commandLineOpts, appPaths)
|
.ConfigureAppConfiguration(commandLineOpts, appPaths)
|
||||||
|
@ -265,17 +265,20 @@ namespace MediaBrowser.Api.Images
|
|||||||
{
|
{
|
||||||
Url = url,
|
Url = url,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
var ext = result.ContentType.Split('/').Last();
|
var ext = result.ContentType.Split('/')[^1];
|
||||||
|
|
||||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||||
using (var stream = result.Content)
|
var stream = result.Content;
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
await using (filestream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||||
|
@ -299,22 +299,26 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
|
var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var ext = result.ContentType.Split('/').Last();
|
var ext = result.ContentType.Split('/')[^1];
|
||||||
|
|
||||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||||
using (var stream = result.Content)
|
var stream = result.Content;
|
||||||
|
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
using var fileStream = new FileStream(
|
var fileStream = new FileStream(
|
||||||
fullCachePath,
|
fullCachePath,
|
||||||
FileMode.Create,
|
FileMode.Create,
|
||||||
FileAccess.Write,
|
FileAccess.Write,
|
||||||
FileShare.Read,
|
FileShare.Read,
|
||||||
IODefaults.FileStreamBufferSize,
|
IODefaults.FileStreamBufferSize,
|
||||||
true);
|
true);
|
||||||
|
await using (fileStream.ConfigureAwait(false))
|
||||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
{
|
||||||
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||||
|
@ -209,24 +209,28 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||||
using var fileStream = GetPlaylistFileStream(playlist);
|
var fileStream = GetPlaylistFileStream(playlist);
|
||||||
using var reader = new StreamReader(fileStream);
|
await using (fileStream.ConfigureAwait(false))
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
{
|
||||||
var line = reader.ReadLine();
|
using var reader = new StreamReader(fileStream);
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
while (!reader.EndOfStream)
|
||||||
{
|
{
|
||||||
count++;
|
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||||
if (count >= segmentCount)
|
|
||||||
|
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
count++;
|
||||||
return;
|
if (count >= segmentCount)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
|
@ -720,22 +720,203 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
//return state.VideoRequest.VideoBitRate.HasValue;
|
//return state.VideoRequest.VideoBitRate.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the H.26X level of the output video stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
/// <returns>H.26X level of the output video stream.</returns>
|
||||||
|
private int? GetOutputVideoCodecLevel(StreamState state)
|
||||||
|
{
|
||||||
|
string levelString;
|
||||||
|
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& state.VideoStream.Level.HasValue)
|
||||||
|
{
|
||||||
|
levelString = state.VideoStream?.Level.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
|
||||||
|
{
|
||||||
|
return parsedLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a formatted string of the output audio codec, for use in the CODECS field.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
|
||||||
|
/// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
/// <returns>Formatted audio codec string.</returns>
|
||||||
|
private string GetPlaylistAudioCodecs(StreamState state)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string profile = state.GetRequestedProfiles("aac").FirstOrDefault();
|
||||||
|
|
||||||
|
return HlsCodecStringFactory.GetAACString(profile);
|
||||||
|
}
|
||||||
|
else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return HlsCodecStringFactory.GetMP3String();
|
||||||
|
}
|
||||||
|
else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return HlsCodecStringFactory.GetAC3String();
|
||||||
|
}
|
||||||
|
else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return HlsCodecStringFactory.GetEAC3String();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a formatted string of the output video codec, for use in the CODECS field.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
|
||||||
|
/// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
/// <returns>Formatted video codec string.</returns>
|
||||||
|
private string GetPlaylistVideoCodecs(StreamState state, string codec, int level)
|
||||||
|
{
|
||||||
|
if (level == 0)
|
||||||
|
{
|
||||||
|
// This is 0 when there's no requested H.26X level in the device profile
|
||||||
|
// and the source is not encoded in H.26X
|
||||||
|
Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
|
||||||
|
|
||||||
|
return HlsCodecStringFactory.GetH264String(profile, level);
|
||||||
|
}
|
||||||
|
else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
|
||||||
|
|
||||||
|
return HlsCodecStringFactory.GetH265String(profile, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a CODECS field containing formatted strings of
|
||||||
|
/// the active streams output video and audio codecs.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
|
||||||
|
/// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
|
||||||
|
/// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
|
||||||
|
/// <param name="builder">StringBuilder to append the field to.</param>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
|
||||||
|
{
|
||||||
|
// Video
|
||||||
|
string videoCodecs = string.Empty;
|
||||||
|
int? videoCodecLevel = GetOutputVideoCodecLevel(state);
|
||||||
|
if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue)
|
||||||
|
{
|
||||||
|
videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
string audioCodecs = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
|
||||||
|
{
|
||||||
|
audioCodecs = GetPlaylistAudioCodecs(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder codecs = new StringBuilder();
|
||||||
|
|
||||||
|
codecs.Append(videoCodecs)
|
||||||
|
.Append(',')
|
||||||
|
.Append(audioCodecs);
|
||||||
|
|
||||||
|
if (codecs.Length > 1)
|
||||||
|
{
|
||||||
|
builder.Append(",CODECS=\"")
|
||||||
|
.Append(codecs)
|
||||||
|
.Append('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a FRAME-RATE field containing the framerate of the output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
|
||||||
|
/// <param name="builder">StringBuilder to append the field to.</param>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state)
|
||||||
|
{
|
||||||
|
double? framerate = null;
|
||||||
|
if (state.TargetFramerate.HasValue)
|
||||||
|
{
|
||||||
|
framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3);
|
||||||
|
}
|
||||||
|
else if (state.VideoStream.RealFrameRate.HasValue)
|
||||||
|
{
|
||||||
|
framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framerate.HasValue)
|
||||||
|
{
|
||||||
|
builder.Append(",FRAME-RATE=\"")
|
||||||
|
.Append(framerate.Value)
|
||||||
|
.Append('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a RESOLUTION field containing the resolution of the output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
|
||||||
|
/// <param name="builder">StringBuilder to append the field to.</param>
|
||||||
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
|
private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state)
|
||||||
|
{
|
||||||
|
if (state.OutputWidth.HasValue && state.OutputHeight.HasValue)
|
||||||
|
{
|
||||||
|
builder.Append(",RESOLUTION=\"")
|
||||||
|
.Append(state.OutputWidth.GetValueOrDefault())
|
||||||
|
.Append('x')
|
||||||
|
.Append(state.OutputHeight.GetValueOrDefault())
|
||||||
|
.Append('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
|
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
|
||||||
{
|
{
|
||||||
var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture);
|
builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
|
||||||
|
.Append(bitrate.ToString(CultureInfo.InvariantCulture))
|
||||||
|
.Append(",AVERAGE-BANDWIDTH=")
|
||||||
|
.Append(bitrate.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
// tvos wants resolution, codecs, framerate
|
AppendPlaylistCodecsField(builder, state);
|
||||||
//if (state.TargetFramerate.HasValue)
|
|
||||||
//{
|
AppendPlaylistResolutionField(builder, state);
|
||||||
// header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture));
|
|
||||||
//}
|
AppendPlaylistFramerateField(builder, state);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||||
{
|
{
|
||||||
header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
|
builder.Append(",SUBTITLES=\"")
|
||||||
|
.Append(subtitleGroup)
|
||||||
|
.Append('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine(header);
|
builder.Append(Environment.NewLine);
|
||||||
builder.AppendLine(url);
|
builder.AppendLine(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
126
MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
Normal file
126
MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Playback
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get various codec strings for use in HLS playlists.
|
||||||
|
/// </summary>
|
||||||
|
static class HlsCodecStringFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a MP3 codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>MP3 codec string.</returns>
|
||||||
|
public static string GetMP3String()
|
||||||
|
{
|
||||||
|
return "mp4a.40.34";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an AAC codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">AAC profile.</param>
|
||||||
|
/// <returns>AAC codec string.</returns>
|
||||||
|
public static string GetAACString(string profile)
|
||||||
|
{
|
||||||
|
StringBuilder result = new StringBuilder("mp4a", 9);
|
||||||
|
|
||||||
|
if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Append(".40.5");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default to LC if profile is invalid
|
||||||
|
result.Append(".40.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a H.264 codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">H.264 profile.</param>
|
||||||
|
/// <param name="level">H.264 level.</param>
|
||||||
|
/// <returns>H.264 string.</returns>
|
||||||
|
public static string GetH264String(string profile, int level)
|
||||||
|
{
|
||||||
|
StringBuilder result = new StringBuilder("avc1", 11);
|
||||||
|
|
||||||
|
if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Append(".6400");
|
||||||
|
}
|
||||||
|
else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Append(".4D40");
|
||||||
|
}
|
||||||
|
else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Append(".42E0");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default to constrained baseline if profile is invalid
|
||||||
|
result.Append(".4240");
|
||||||
|
}
|
||||||
|
|
||||||
|
string levelHex = level.ToString("X2");
|
||||||
|
result.Append(levelHex);
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a H.265 codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">H.265 profile.</param>
|
||||||
|
/// <param name="level">H.265 level.</param>
|
||||||
|
/// <returns>H.265 string.</returns>
|
||||||
|
public static string GetH265String(string profile, int level)
|
||||||
|
{
|
||||||
|
// The h265 syntax is a bit of a mystery at the time this comment was written.
|
||||||
|
// This is what I've found through various sources:
|
||||||
|
// FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
|
||||||
|
StringBuilder result = new StringBuilder("hev1", 16);
|
||||||
|
|
||||||
|
if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Append(".2.6");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default to main if profile is invalid
|
||||||
|
result.Append(".1.6");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Append(".L")
|
||||||
|
.Append(level * 3)
|
||||||
|
.Append(".B0");
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an AC-3 codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>AC-3 codec string.</returns>
|
||||||
|
public static string GetAC3String()
|
||||||
|
{
|
||||||
|
return "mp4a.a5";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an E-AC-3 codec string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>E-AC-3 codec string.</returns>
|
||||||
|
public static string GetEAC3String()
|
||||||
|
{
|
||||||
|
return "mp4a.a6";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")]
|
[Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")]
|
||||||
public class GetPublicUsers : IReturn<UserDto[]>
|
public class GetPublicUsers : IReturn<PublicUserDto[]>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,22 +266,38 @@ namespace MediaBrowser.Api
|
|||||||
_authContext = authContext;
|
_authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the public available Users information
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetPublicUsers request)
|
public object Get(GetPublicUsers request)
|
||||||
{
|
{
|
||||||
// If the startup wizard hasn't been completed then just return all users
|
var result = _userManager
|
||||||
if (!ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
|
.Users
|
||||||
|
.Where(item => !item.Policy.IsDisabled);
|
||||||
|
|
||||||
|
if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
|
||||||
{
|
{
|
||||||
return Get(new GetUsers
|
var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId;
|
||||||
|
result = result.Where(item => !item.Policy.IsHidden);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||||
{
|
{
|
||||||
IsDisabled = false
|
result = result.Where(i => _deviceManager.CanAccessDevice(i, deviceId));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (!_networkManager.IsInLocalNetwork(Request.RemoteIp))
|
||||||
|
{
|
||||||
|
result = result.Where(i => i.Policy.EnableRemoteAccess);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Get(new GetUsers
|
return ToOptimizedResult(result
|
||||||
{
|
.OrderBy(u => u.Name)
|
||||||
IsHidden = false,
|
.Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp))
|
||||||
IsDisabled = false
|
.ToArray()
|
||||||
}, true, true);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -143,6 +143,14 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <returns>UserDto.</returns>
|
/// <returns>UserDto.</returns>
|
||||||
UserDto GetUserDto(User user, string remoteEndPoint = null);
|
UserDto GetUserDto(User user, string remoteEndPoint = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user public dto.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Ther user.</param>\
|
||||||
|
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||||
|
/// <returns>A public UserDto, aka a UserDto stripped of personal data.</returns>
|
||||||
|
PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authenticates the user.
|
/// Authenticates the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
foreach (var val in codec)
|
foreach (var val in codec)
|
||||||
{
|
{
|
||||||
if (ListHelper.ContainsIgnoreCase(codecs, val))
|
if (codecs.Contains(val, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
@ -167,9 +167,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
switch (condition.Condition)
|
switch (condition.Condition)
|
||||||
{
|
{
|
||||||
case ProfileConditionType.EqualsAny:
|
case ProfileConditionType.EqualsAny:
|
||||||
{
|
return expected.Split('|').Contains(currentValue, StringComparer.OrdinalIgnoreCase);
|
||||||
return ListHelper.ContainsIgnoreCase(expected.Split('|'), currentValue);
|
|
||||||
}
|
|
||||||
case ProfileConditionType.Equals:
|
case ProfileConditionType.Equals:
|
||||||
return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
|
||||||
case ProfileConditionType.NotEquals:
|
case ProfileConditionType.NotEquals:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
@ -45,7 +45,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
public static bool ContainsContainer(string profileContainers, string inputContainer)
|
public static bool ContainsContainer(string profileContainers, string inputContainer)
|
||||||
{
|
{
|
||||||
var isNegativeList = false;
|
var isNegativeList = false;
|
||||||
if (profileContainers != null && profileContainers.StartsWith("-"))
|
if (profileContainers != null && profileContainers.StartsWith("-", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
isNegativeList = true;
|
isNegativeList = true;
|
||||||
profileContainers = profileContainers.Substring(1);
|
profileContainers = profileContainers.Substring(1);
|
||||||
@ -72,7 +72,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
foreach (var container in allInputContainers)
|
foreach (var container in allInputContainers)
|
||||||
{
|
{
|
||||||
if (ListHelper.ContainsIgnoreCase(profileContainers, container))
|
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
foreach (var container in allInputContainers)
|
foreach (var container in allInputContainers)
|
||||||
{
|
{
|
||||||
if (ListHelper.ContainsIgnoreCase(profileContainers, container))
|
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
@ -93,14 +93,14 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
public DeviceProfile()
|
public DeviceProfile()
|
||||||
{
|
{
|
||||||
DirectPlayProfiles = new DirectPlayProfile[] { };
|
DirectPlayProfiles = Array.Empty<DirectPlayProfile>();
|
||||||
TranscodingProfiles = new TranscodingProfile[] { };
|
TranscodingProfiles = Array.Empty<TranscodingProfile>();
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
CodecProfiles = new CodecProfile[] { };
|
CodecProfiles = Array.Empty<CodecProfile>();
|
||||||
ContainerProfiles = new ContainerProfile[] { };
|
ContainerProfiles = Array.Empty<ContainerProfile>();
|
||||||
SubtitleProfiles = Array.Empty<SubtitleProfile>();
|
SubtitleProfiles = Array.Empty<SubtitleProfile>();
|
||||||
|
|
||||||
XmlRootAttributes = new XmlAttribute[] { };
|
XmlRootAttributes = Array.Empty<XmlAttribute>();
|
||||||
|
|
||||||
SupportedMediaTypes = "Audio,Photo,Video";
|
SupportedMediaTypes = "Audio,Photo,Video";
|
||||||
MaxStreamingBitrate = 8000000;
|
MaxStreamingBitrate = 8000000;
|
||||||
@ -129,13 +129,14 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ListHelper.ContainsIgnoreCase(i.GetAudioCodecs(), audioCodec ?? string.Empty))
|
if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ListHelper.ContainsIgnoreCase(i.GetAudioCodecs(), audioCodec ?? string.Empty))
|
if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -185,7 +186,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
var audioCodecs = i.GetAudioCodecs();
|
var audioCodecs = i.GetAudioCodecs();
|
||||||
if (audioCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec ?? string.Empty))
|
if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -288,13 +289,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
var audioCodecs = i.GetAudioCodecs();
|
var audioCodecs = i.GetAudioCodecs();
|
||||||
if (audioCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec ?? string.Empty))
|
if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoCodecs = i.GetVideoCodecs();
|
var videoCodecs = i.GetVideoCodecs();
|
||||||
if (videoCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec ?? string.Empty))
|
if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
@ -40,7 +41,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
var languages = GetLanguages();
|
var languages = GetLanguages();
|
||||||
return languages.Length == 0 || ListHelper.ContainsIgnoreCase(languages, subLanguage);
|
return languages.Length == 0 || languages.Contains(subLanguage, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
MediaBrowser.Model/Dto/PublicUserDto.cs
Normal file
48
MediaBrowser.Model/Dto/PublicUserDto.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Dto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class PublicUserDto. Its goal is to show only public information about a user
|
||||||
|
/// </summary>
|
||||||
|
public class PublicUserDto : IItemDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the primary image tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The primary image tag.</value>
|
||||||
|
public string PrimaryImageTag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this instance has password.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance has password; otherwise, <c>false</c>.</value>
|
||||||
|
public bool HasPassword { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this instance has configured password.
|
||||||
|
/// Note that in this case this method should not be here, but it is necessary when changing password at the
|
||||||
|
/// first login.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance has configured password; otherwise, <c>false</c>.</value>
|
||||||
|
public bool HasConfiguredPassword { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the primary image aspect ratio.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The primary image aspect ratio.</value>
|
||||||
|
public double? PrimaryImageAspectRatio { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name ?? base.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Extensions
|
|
||||||
{
|
|
||||||
// TODO: @bond remove
|
|
||||||
public static class ListHelper
|
|
||||||
{
|
|
||||||
public static bool ContainsIgnoreCase(string[] list, string value)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in list)
|
|
||||||
{
|
|
||||||
if (string.Equals(item, value, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,115 +17,132 @@ namespace MediaBrowser.Model.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly HashSet<string> _videoFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> _videoFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
".mkv",
|
".3gp",
|
||||||
".m2t",
|
".asf",
|
||||||
".m2ts",
|
|
||||||
".img",
|
|
||||||
".iso",
|
|
||||||
".mk3d",
|
|
||||||
".ts",
|
|
||||||
".rmvb",
|
|
||||||
".mov",
|
|
||||||
".avi",
|
".avi",
|
||||||
".mpg",
|
|
||||||
".mpeg",
|
|
||||||
".wmv",
|
|
||||||
".mp4",
|
|
||||||
".divx",
|
".divx",
|
||||||
".dvr-ms",
|
".dvr-ms",
|
||||||
".wtv",
|
".f4v",
|
||||||
|
".flv",
|
||||||
|
".img",
|
||||||
|
".iso",
|
||||||
|
".m2t",
|
||||||
|
".m2ts",
|
||||||
|
".m2v",
|
||||||
|
".m4v",
|
||||||
|
".mk3d",
|
||||||
|
".mkv",
|
||||||
|
".mov",
|
||||||
|
".mp4",
|
||||||
|
".mpg",
|
||||||
|
".mpeg",
|
||||||
|
".mts",
|
||||||
|
".ogg",
|
||||||
".ogm",
|
".ogm",
|
||||||
".ogv",
|
".ogv",
|
||||||
".asf",
|
".rec",
|
||||||
".m4v",
|
".ts",
|
||||||
".flv",
|
".rmvb",
|
||||||
".f4v",
|
|
||||||
".3gp",
|
|
||||||
".webm",
|
".webm",
|
||||||
".mts",
|
".wmv",
|
||||||
".m2v",
|
".wtv",
|
||||||
".rec"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// http://en.wikipedia.org/wiki/Internet_media_type
|
// http://en.wikipedia.org/wiki/Internet_media_type
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||||
|
// http://www.iana.org/assignments/media-types/media-types.xhtml
|
||||||
// Add more as needed
|
// Add more as needed
|
||||||
private static readonly Dictionary<string, string> _mimeTypeLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly Dictionary<string, string> _mimeTypeLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
// Type application
|
// Type application
|
||||||
|
{ ".7z", "application/x-7z-compressed" },
|
||||||
|
{ ".azw", "application/vnd.amazon.ebook" },
|
||||||
|
{ ".azw3", "application/vnd.amazon.ebook" },
|
||||||
{ ".cbz", "application/x-cbz" },
|
{ ".cbz", "application/x-cbz" },
|
||||||
{ ".cbr", "application/epub+zip" },
|
{ ".cbr", "application/epub+zip" },
|
||||||
{ ".eot", "application/vnd.ms-fontobject" },
|
{ ".eot", "application/vnd.ms-fontobject" },
|
||||||
{ ".epub", "application/epub+zip" },
|
{ ".epub", "application/epub+zip" },
|
||||||
{ ".js", "application/x-javascript" },
|
{ ".js", "application/x-javascript" },
|
||||||
{ ".json", "application/json" },
|
{ ".json", "application/json" },
|
||||||
{ ".map", "application/x-javascript" },
|
|
||||||
{ ".pdf", "application/pdf" },
|
|
||||||
{ ".ttml", "application/ttml+xml" },
|
|
||||||
{ ".m3u8", "application/x-mpegURL" },
|
{ ".m3u8", "application/x-mpegURL" },
|
||||||
|
{ ".map", "application/x-javascript" },
|
||||||
{ ".mobi", "application/x-mobipocket-ebook" },
|
{ ".mobi", "application/x-mobipocket-ebook" },
|
||||||
{ ".xml", "application/xml" },
|
{ ".pdf", "application/pdf" },
|
||||||
|
{ ".rar", "application/vnd.rar" },
|
||||||
|
{ ".srt", "application/x-subrip" },
|
||||||
|
{ ".ttml", "application/ttml+xml" },
|
||||||
{ ".wasm", "application/wasm" },
|
{ ".wasm", "application/wasm" },
|
||||||
|
{ ".xml", "application/xml" },
|
||||||
|
{ ".zip", "application/zip" },
|
||||||
|
|
||||||
// Type image
|
// Type image
|
||||||
|
{ ".bmp", "image/bmp" },
|
||||||
|
{ ".gif", "image/gif" },
|
||||||
|
{ ".ico", "image/vnd.microsoft.icon" },
|
||||||
{ ".jpg", "image/jpeg" },
|
{ ".jpg", "image/jpeg" },
|
||||||
{ ".jpeg", "image/jpeg" },
|
{ ".jpeg", "image/jpeg" },
|
||||||
{ ".tbn", "image/jpeg" },
|
|
||||||
{ ".png", "image/png" },
|
{ ".png", "image/png" },
|
||||||
{ ".gif", "image/gif" },
|
|
||||||
{ ".tiff", "image/tiff" },
|
|
||||||
{ ".webp", "image/webp" },
|
|
||||||
{ ".ico", "image/vnd.microsoft.icon" },
|
|
||||||
{ ".svg", "image/svg+xml" },
|
{ ".svg", "image/svg+xml" },
|
||||||
{ ".svgz", "image/svg+xml" },
|
{ ".svgz", "image/svg+xml" },
|
||||||
|
{ ".tbn", "image/jpeg" },
|
||||||
|
{ ".tif", "image/tiff" },
|
||||||
|
{ ".tiff", "image/tiff" },
|
||||||
|
{ ".webp", "image/webp" },
|
||||||
|
|
||||||
// Type font
|
// Type font
|
||||||
{ ".ttf" , "font/ttf" },
|
{ ".ttf" , "font/ttf" },
|
||||||
{ ".woff" , "font/woff" },
|
{ ".woff" , "font/woff" },
|
||||||
|
{ ".woff2" , "font/woff2" },
|
||||||
|
|
||||||
// Type text
|
// Type text
|
||||||
{ ".ass", "text/x-ssa" },
|
{ ".ass", "text/x-ssa" },
|
||||||
{ ".ssa", "text/x-ssa" },
|
{ ".ssa", "text/x-ssa" },
|
||||||
{ ".css", "text/css" },
|
{ ".css", "text/css" },
|
||||||
{ ".csv", "text/csv" },
|
{ ".csv", "text/csv" },
|
||||||
|
{ ".rtf", "text/rtf" },
|
||||||
{ ".txt", "text/plain" },
|
{ ".txt", "text/plain" },
|
||||||
{ ".vtt", "text/vtt" },
|
{ ".vtt", "text/vtt" },
|
||||||
|
|
||||||
// Type video
|
// Type video
|
||||||
{ ".mpg", "video/mpeg" },
|
|
||||||
{ ".ogv", "video/ogg" },
|
|
||||||
{ ".mov", "video/quicktime" },
|
|
||||||
{ ".webm", "video/webm" },
|
|
||||||
{ ".mkv", "video/x-matroska" },
|
|
||||||
{ ".wmv", "video/x-ms-wmv" },
|
|
||||||
{ ".flv", "video/x-flv" },
|
|
||||||
{ ".avi", "video/x-msvideo" },
|
|
||||||
{ ".asf", "video/x-ms-asf" },
|
|
||||||
{ ".m4v", "video/x-m4v" },
|
|
||||||
{ ".m4s", "video/mp4" },
|
|
||||||
{ ".3gp", "video/3gpp" },
|
{ ".3gp", "video/3gpp" },
|
||||||
{ ".3g2", "video/3gpp2" },
|
{ ".3g2", "video/3gpp2" },
|
||||||
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
|
{ ".asf", "video/x-ms-asf" },
|
||||||
{ ".ts", "video/mp2t" },
|
{ ".avi", "video/x-msvideo" },
|
||||||
|
{ ".flv", "video/x-flv" },
|
||||||
|
{ ".mp4", "video/mp4" },
|
||||||
|
{ ".m4s", "video/mp4" },
|
||||||
|
{ ".m4v", "video/x-m4v" },
|
||||||
{ ".mpegts", "video/mp2t" },
|
{ ".mpegts", "video/mp2t" },
|
||||||
|
{ ".mpg", "video/mpeg" },
|
||||||
|
{ ".mkv", "video/x-matroska" },
|
||||||
|
{ ".mov", "video/quicktime" },
|
||||||
|
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
|
||||||
|
{ ".ogv", "video/ogg" },
|
||||||
|
{ ".ts", "video/mp2t" },
|
||||||
|
{ ".webm", "video/webm" },
|
||||||
|
{ ".wmv", "video/x-ms-wmv" },
|
||||||
|
|
||||||
// Type audio
|
// Type audio
|
||||||
{ ".mp3", "audio/mpeg" },
|
|
||||||
{ ".m4a", "audio/mp4" },
|
|
||||||
{ ".aac", "audio/mp4" },
|
{ ".aac", "audio/mp4" },
|
||||||
{ ".webma", "audio/webm" },
|
|
||||||
{ ".wav", "audio/wav" },
|
|
||||||
{ ".wma", "audio/x-ms-wma" },
|
|
||||||
{ ".ogg", "audio/ogg" },
|
|
||||||
{ ".oga", "audio/ogg" },
|
|
||||||
{ ".opus", "audio/ogg" },
|
|
||||||
{ ".ac3", "audio/ac3" },
|
{ ".ac3", "audio/ac3" },
|
||||||
|
{ ".ape", "audio/x-ape" },
|
||||||
{ ".dsf", "audio/dsf" },
|
{ ".dsf", "audio/dsf" },
|
||||||
{ ".m4b", "audio/m4b" },
|
|
||||||
{ ".xsp", "audio/xsp" },
|
|
||||||
{ ".dsp", "audio/dsp" },
|
{ ".dsp", "audio/dsp" },
|
||||||
{ ".flac", "audio/flac" },
|
{ ".flac", "audio/flac" },
|
||||||
{ ".ape", "audio/x-ape" },
|
{ ".m4a", "audio/mp4" },
|
||||||
|
{ ".m4b", "audio/m4b" },
|
||||||
|
{ ".mid", "audio/midi" },
|
||||||
|
{ ".midi", "audio/midi" },
|
||||||
|
{ ".mp3", "audio/mpeg" },
|
||||||
|
{ ".oga", "audio/ogg" },
|
||||||
|
{ ".ogg", "audio/ogg" },
|
||||||
|
{ ".opus", "audio/ogg" },
|
||||||
|
{ ".vorbis", "audio/vorbis" },
|
||||||
|
{ ".wav", "audio/wav" },
|
||||||
|
{ ".webma", "audio/webm" },
|
||||||
|
{ ".wma", "audio/x-ms-wma" },
|
||||||
{ ".wv", "audio/x-wavpack" },
|
{ ".wv", "audio/x-wavpack" },
|
||||||
|
{ ".xsp", "audio/xsp" },
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
|
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Model.Extensions;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Notifications
|
namespace MediaBrowser.Model.Notifications
|
||||||
@ -81,8 +81,12 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
{
|
{
|
||||||
foreach (NotificationOption i in Options)
|
foreach (NotificationOption i in Options)
|
||||||
{
|
{
|
||||||
if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) return i;
|
if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,7 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
NotificationOption opt = GetOptions(notificationType);
|
NotificationOption opt = GetOptions(notificationType);
|
||||||
|
|
||||||
return opt == null ||
|
return opt == null ||
|
||||||
!ListHelper.ContainsIgnoreCase(opt.DisabledServices, service);
|
!opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEnabledToMonitorUser(string type, Guid userId)
|
public bool IsEnabledToMonitorUser(string type, Guid userId)
|
||||||
@ -106,7 +110,7 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
NotificationOption opt = GetOptions(type);
|
NotificationOption opt = GetOptions(type);
|
||||||
|
|
||||||
return opt != null && opt.Enabled &&
|
return opt != null && opt.Enabled &&
|
||||||
!ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId.ToString(""));
|
!opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy)
|
public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy)
|
||||||
@ -125,7 +129,7 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId);
|
return opt.SendToUsers.Contains(userId, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26730.3
|
VisualStudioVersion = 15.0.26730.3
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}"
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
|
||||||
@ -36,35 +38,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
|
jellyfin.ruleset = jellyfin.ruleset
|
||||||
SharedVersion.cs = SharedVersion.cs
|
SharedVersion.cs = SharedVersion.cs
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
tests\jellyfin-tests.ruleset = tests\jellyfin-tests.ruleset
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}"
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api.Tests", "tests\MediaBrowser.Api.Tests\MediaBrowser.Api.Tests.csproj", "{7C93C84F-105C-48E5-A878-406FA0A5B296}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -116,10 +121,6 @@ Global
|
|||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@ -188,6 +189,10 @@ Global
|
|||||||
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.Build.0 = Release|Any CPU
|
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -220,5 +225,6 @@ Global
|
|||||||
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
|
{7C93C84F-105C-48E5-A878-406FA0A5B296} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
using Emby.Naming.Common;
|
|
||||||
using Emby.Naming.Video;
|
|
||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
|
||||||
{
|
|
||||||
public abstract class BaseVideoTest
|
|
||||||
{
|
|
||||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
|
||||||
|
|
||||||
protected VideoResolver GetParser()
|
|
||||||
=> new VideoResolver(_namingOptions);
|
|
||||||
}
|
|
||||||
}
|
|
@ -46,6 +46,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
[InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
|
[InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
|
||||||
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
|
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
|
||||||
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
||||||
|
[InlineData("3 days to kill (2005).mkv", "3 days to kill", 2005)]
|
||||||
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
||||||
{
|
{
|
||||||
input = Path.GetFileName(input);
|
input = Path.GetFileName(input);
|
||||||
|
@ -5,7 +5,7 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class ExtraTests : BaseVideoTest
|
public class ExtraTests
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _videoOptions = new NamingOptions();
|
private readonly NamingOptions _videoOptions = new NamingOptions();
|
||||||
|
|
||||||
|
@ -4,26 +4,26 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class Format3DTests : BaseVideoTest
|
public class Format3DTests
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestKodiFormat3D()
|
public void TestKodiFormat3D()
|
||||||
{
|
{
|
||||||
var options = new NamingOptions();
|
Test("Super movie.3d.mp4", false, null);
|
||||||
|
Test("Super movie.3d.hsbs.mp4", true, "hsbs");
|
||||||
Test("Super movie.3d.mp4", false, null, options);
|
Test("Super movie.3d.sbs.mp4", true, "sbs");
|
||||||
Test("Super movie.3d.hsbs.mp4", true, "hsbs", options);
|
Test("Super movie.3d.htab.mp4", true, "htab");
|
||||||
Test("Super movie.3d.sbs.mp4", true, "sbs", options);
|
Test("Super movie.3d.tab.mp4", true, "tab");
|
||||||
Test("Super movie.3d.htab.mp4", true, "htab", options);
|
Test("Super movie 3d hsbs.mp4", true, "hsbs");
|
||||||
Test("Super movie.3d.tab.mp4", true, "tab", options);
|
|
||||||
Test("Super movie 3d hsbs.mp4", true, "hsbs", options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Test3DName()
|
public void Test3DName()
|
||||||
{
|
{
|
||||||
var result =
|
var result =
|
||||||
GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv");
|
new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv");
|
||||||
|
|
||||||
Assert.Equal("hsbs", result.Format3D);
|
Assert.Equal("hsbs", result.Format3D);
|
||||||
Assert.Equal("Oblivion", result.Name);
|
Assert.Equal("Oblivion", result.Name);
|
||||||
@ -34,32 +34,31 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
{
|
{
|
||||||
// These were introduced for Media Browser 3
|
// These were introduced for Media Browser 3
|
||||||
// Kodi conventions are preferred but these still need to be supported
|
// Kodi conventions are preferred but these still need to be supported
|
||||||
var options = new NamingOptions();
|
|
||||||
|
|
||||||
Test("Super movie.3d.mp4", false, null, options);
|
Test("Super movie.3d.mp4", false, null);
|
||||||
Test("Super movie.3d.hsbs.mp4", true, "hsbs", options);
|
Test("Super movie.3d.hsbs.mp4", true, "hsbs");
|
||||||
Test("Super movie.3d.sbs.mp4", true, "sbs", options);
|
Test("Super movie.3d.sbs.mp4", true, "sbs");
|
||||||
Test("Super movie.3d.htab.mp4", true, "htab", options);
|
Test("Super movie.3d.htab.mp4", true, "htab");
|
||||||
Test("Super movie.3d.tab.mp4", true, "tab", options);
|
Test("Super movie.3d.tab.mp4", true, "tab");
|
||||||
|
|
||||||
Test("Super movie.hsbs.mp4", true, "hsbs", options);
|
Test("Super movie.hsbs.mp4", true, "hsbs");
|
||||||
Test("Super movie.sbs.mp4", true, "sbs", options);
|
Test("Super movie.sbs.mp4", true, "sbs");
|
||||||
Test("Super movie.htab.mp4", true, "htab", options);
|
Test("Super movie.htab.mp4", true, "htab");
|
||||||
Test("Super movie.tab.mp4", true, "tab", options);
|
Test("Super movie.tab.mp4", true, "tab");
|
||||||
Test("Super movie.sbs3d.mp4", true, "sbs3d", options);
|
Test("Super movie.sbs3d.mp4", true, "sbs3d");
|
||||||
Test("Super movie.3d.mvc.mp4", true, "mvc", options);
|
Test("Super movie.3d.mvc.mp4", true, "mvc");
|
||||||
|
|
||||||
Test("Super movie [3d].mp4", false, null, options);
|
Test("Super movie [3d].mp4", false, null);
|
||||||
Test("Super movie [hsbs].mp4", true, "hsbs", options);
|
Test("Super movie [hsbs].mp4", true, "hsbs");
|
||||||
Test("Super movie [fsbs].mp4", true, "fsbs", options);
|
Test("Super movie [fsbs].mp4", true, "fsbs");
|
||||||
Test("Super movie [ftab].mp4", true, "ftab", options);
|
Test("Super movie [ftab].mp4", true, "ftab");
|
||||||
Test("Super movie [htab].mp4", true, "htab", options);
|
Test("Super movie [htab].mp4", true, "htab");
|
||||||
Test("Super movie [sbs3d].mp4", true, "sbs3d", options);
|
Test("Super movie [sbs3d].mp4", true, "sbs3d");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Test(string input, bool is3D, string format3D, NamingOptions options)
|
private void Test(string input, bool is3D, string? format3D)
|
||||||
{
|
{
|
||||||
var parser = new Format3DParser(options);
|
var parser = new Format3DParser(_namingOptions);
|
||||||
|
|
||||||
var result = parser.Parse(input);
|
var result = parser.Parse(input);
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
{
|
{
|
||||||
public class MultiVersionTests
|
public class MultiVersionTests
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
// [Fact]
|
// [Fact]
|
||||||
public void TestMultiEdition1()
|
public void TestMultiEdition1()
|
||||||
@ -430,8 +432,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
|
|
||||||
private VideoListResolver GetResolver()
|
private VideoListResolver GetResolver()
|
||||||
{
|
{
|
||||||
var options = new NamingOptions();
|
return new VideoListResolver(_namingOptions);
|
||||||
return new VideoListResolver(options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class StackTests : BaseVideoTest
|
public class StackTests
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestSimpleStack()
|
public void TestSimpleStack()
|
||||||
{
|
{
|
||||||
@ -446,7 +448,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
|
|
||||||
private StackResolver GetResolver()
|
private StackResolver GetResolver()
|
||||||
{
|
{
|
||||||
return new StackResolver(new NamingOptions());
|
return new StackResolver(_namingOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class StubTests : BaseVideoTest
|
public class StubTests
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestStubs()
|
public void TestStubs()
|
||||||
{
|
{
|
||||||
@ -27,16 +29,14 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
public void TestStubName()
|
public void TestStubName()
|
||||||
{
|
{
|
||||||
var result =
|
var result =
|
||||||
GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc");
|
new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc");
|
||||||
|
|
||||||
Assert.Equal("Oblivion", result.Name);
|
Assert.Equal("Oblivion", result.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Test(string path, bool isStub, string stubType)
|
private void Test(string path, bool isStub, string stubType)
|
||||||
{
|
{
|
||||||
var options = new NamingOptions();
|
var isStubResult = StubResolver.TryResolveFile(path, _namingOptions, out var stubTypeResult);
|
||||||
|
|
||||||
var isStubResult = StubResolver.TryResolveFile(path, options, out var stubTypeResult);
|
|
||||||
|
|
||||||
Assert.Equal(isStub, isStubResult);
|
Assert.Equal(isStub, isStubResult);
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
{
|
{
|
||||||
public class VideoListResolverTests
|
public class VideoListResolverTests
|
||||||
{
|
{
|
||||||
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
// FIXME
|
// FIXME
|
||||||
// [Fact]
|
// [Fact]
|
||||||
public void TestStackAndExtras()
|
public void TestStackAndExtras()
|
||||||
@ -450,8 +451,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
|
|
||||||
private VideoListResolver GetResolver()
|
private VideoListResolver GetResolver()
|
||||||
{
|
{
|
||||||
var options = new NamingOptions();
|
return new VideoListResolver(_namingOptions);
|
||||||
return new VideoListResolver(options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,275 +1,200 @@
|
|||||||
using MediaBrowser.Model.Entities;
|
using System.Collections.Generic;
|
||||||
|
using Emby.Naming.Common;
|
||||||
|
using Emby.Naming.Video;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Jellyfin.Naming.Tests.Video
|
namespace Jellyfin.Naming.Tests.Video
|
||||||
{
|
{
|
||||||
public class VideoResolverTests : BaseVideoTest
|
public class VideoResolverTests
|
||||||
{
|
{
|
||||||
// FIXME
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
// [Fact]
|
|
||||||
public void TestSimpleFile()
|
public static IEnumerable<object[]> GetResolveFileTestData()
|
||||||
{
|
{
|
||||||
var parser = GetParser();
|
yield return new object[]
|
||||||
|
{
|
||||||
var result =
|
new VideoFileInfo()
|
||||||
parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).mkv");
|
{
|
||||||
|
Path = @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv",
|
||||||
Assert.Equal(2006, result.Year);
|
Container = "mkv",
|
||||||
Assert.False(result.IsStub);
|
Name = "7 Psychos"
|
||||||
Assert.False(result.Is3D);
|
}
|
||||||
Assert.Equal("Brave", result.Name);
|
};
|
||||||
Assert.Null(result.ExtraType);
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "3 days to kill",
|
||||||
|
Year = 2005
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/American Psycho/American.Psycho.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "American.Psycho",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "brave",
|
||||||
|
Year = 2006,
|
||||||
|
Is3D = true,
|
||||||
|
Format3D = "sbs",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006,
|
||||||
|
Is3D = true,
|
||||||
|
Format3D = "sbs",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc",
|
||||||
|
Container = "disc",
|
||||||
|
Name = "brave",
|
||||||
|
Year = 2006,
|
||||||
|
IsStub = true,
|
||||||
|
StubType = "bluray",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc",
|
||||||
|
Container = "disc",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006,
|
||||||
|
IsStub = true,
|
||||||
|
StubType = "bluray",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc",
|
||||||
|
Container = "disc",
|
||||||
|
Name = "Brave",
|
||||||
|
Year = 2006,
|
||||||
|
IsStub = true,
|
||||||
|
StubType = "bluray",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006).bluray.disc",
|
||||||
|
Container = "disc",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006,
|
||||||
|
IsStub = true,
|
||||||
|
StubType = "bluray",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006,
|
||||||
|
ExtraType = ExtraType.Trailer,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "Brave",
|
||||||
|
Year = 2006,
|
||||||
|
ExtraType = ExtraType.Trailer,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/300 (2007)/300 (2006).mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "300",
|
||||||
|
Year = 2006
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "Bad Boys",
|
||||||
|
Year = 1995,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new VideoFileInfo()
|
||||||
|
{
|
||||||
|
Path = @"/server/Movies/Brave (2007)/Brave (2006).mkv",
|
||||||
|
Container = "mkv",
|
||||||
|
Name = "Brave",
|
||||||
|
Year = 2006,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
[Theory]
|
||||||
public void TestSimpleFile2()
|
[MemberData(nameof(GetResolveFileTestData))]
|
||||||
|
public void ResolveFile_ValidFileName_Success(VideoFileInfo expectedResult)
|
||||||
{
|
{
|
||||||
var parser = GetParser();
|
var result = new VideoResolver(_namingOptions).ResolveFile(expectedResult.Path);
|
||||||
|
|
||||||
var result =
|
Assert.NotNull(result);
|
||||||
parser.ResolveFile(@"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv");
|
Assert.Equal(result.Path, expectedResult.Path);
|
||||||
|
Assert.Equal(result.Container, expectedResult.Container);
|
||||||
Assert.Equal(1995, result.Year);
|
Assert.Equal(result.Name, expectedResult.Name);
|
||||||
Assert.False(result.IsStub);
|
Assert.Equal(result.Year, expectedResult.Year);
|
||||||
Assert.False(result.Is3D);
|
Assert.Equal(result.ExtraType, expectedResult.ExtraType);
|
||||||
Assert.Equal("Bad Boys", result.Name);
|
Assert.Equal(result.Format3D, expectedResult.Format3D);
|
||||||
Assert.Null(result.ExtraType);
|
Assert.Equal(result.Is3D, expectedResult.Is3D);
|
||||||
}
|
Assert.Equal(result.IsStub, expectedResult.IsStub);
|
||||||
|
Assert.Equal(result.StubType, expectedResult.StubType);
|
||||||
// FIXME
|
Assert.Equal(result.IsDirectory, expectedResult.IsDirectory);
|
||||||
// [Fact]
|
Assert.Equal(result.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension);
|
||||||
public void TestSimpleFileWithNumericName()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("300", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestExtra()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal(ExtraType.Trailer, result.ExtraType);
|
|
||||||
Assert.Equal("Brave (2006)-trailer", result.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestExtraWithNumericName()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("300 (2006)-trailer", result.Name);
|
|
||||||
Assert.Equal(ExtraType.Trailer, result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestStubFileWithNumericName()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).bluray.disc");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.True(result.IsStub);
|
|
||||||
Assert.Equal("bluray", result.StubType);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("300", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestStubFile()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).bluray.disc");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.True(result.IsStub);
|
|
||||||
Assert.Equal("bluray", result.StubType);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("Brave", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestExtraStubWithNumericNameNotSupported()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.True(result.IsStub);
|
|
||||||
Assert.Equal("bluray", result.StubType);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("300", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestExtraStubNotSupported()
|
|
||||||
{
|
|
||||||
// Using a stub for an extra is currently not supported
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.True(result.IsStub);
|
|
||||||
Assert.Equal("bluray", result.StubType);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("brave", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void Test3DFileWithNumericName()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.True(result.Is3D);
|
|
||||||
Assert.Equal("sbs", result.Format3D);
|
|
||||||
Assert.Equal("300", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestBad3DFileWithNumericName()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("300", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
Assert.Null(result.Format3D);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void Test3DFile()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2006, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.True(result.Is3D);
|
|
||||||
Assert.Equal("sbs", result.Format3D);
|
|
||||||
Assert.Equal("brave", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestNameWithoutDate()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/American Psycho/American.Psycho.mkv");
|
|
||||||
|
|
||||||
Assert.Null(result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Null(result.Format3D);
|
|
||||||
Assert.Equal("American.Psycho", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestCleanDateAndStringsSequence()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
// In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/3.Days.to.Kill/3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2014, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Null(result.Format3D);
|
|
||||||
Assert.Equal("3.Days.to.Kill", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// [Fact]
|
|
||||||
public void TestCleanDateAndStringsSequence1()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
// In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv");
|
|
||||||
|
|
||||||
Assert.Equal(2005, result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Null(result.Format3D);
|
|
||||||
Assert.Equal("3 days to kill", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestFolderNameWithExtension()
|
|
||||||
{
|
|
||||||
var parser = GetParser();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
parser.ResolveFile(@"/server/Movies/7 Psychos.mkv/7 Psychos.mkv");
|
|
||||||
|
|
||||||
Assert.Null(result.Year);
|
|
||||||
Assert.False(result.IsStub);
|
|
||||||
Assert.False(result.Is3D);
|
|
||||||
Assert.Equal("7 Psychos", result.Name);
|
|
||||||
Assert.Null(result.ExtraType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs
Normal file
49
tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Branding;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Tests
|
||||||
|
{
|
||||||
|
public sealed class BrandingServiceTests : IClassFixture<JellyfinApplicationFactory>
|
||||||
|
{
|
||||||
|
private readonly JellyfinApplicationFactory _factory;
|
||||||
|
|
||||||
|
public BrandingServiceTests(JellyfinApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConfiguration_ReturnsCorrectResponse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("/Branding/Configuration");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
var responseBody = await response.Content.ReadAsStreamAsync();
|
||||||
|
_ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/Branding/Css")]
|
||||||
|
[InlineData("/Branding/Css.css")]
|
||||||
|
public async Task GetCss_ReturnsCorrectResponse(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Assert.Equal("text/css", response.Content.Headers.ContentType.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs
Normal file
120
tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO;
|
||||||
|
using Emby.Server.Implementations;
|
||||||
|
using Emby.Server.Implementations.IO;
|
||||||
|
using Emby.Server.Implementations.Networking;
|
||||||
|
using Jellyfin.Drawing.Skia;
|
||||||
|
using Jellyfin.Server;
|
||||||
|
using MediaBrowser.Common;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
|
||||||
|
/// </summary>
|
||||||
|
public class JellyfinApplicationFactory : WebApplicationFactory<Startup>
|
||||||
|
{
|
||||||
|
private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
|
||||||
|
private static readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JellyfinApplicationFactory"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JellyfinApplicationFactory()
|
||||||
|
{
|
||||||
|
// Perform static initialization that only needs to happen once per test-run
|
||||||
|
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
|
||||||
|
Program.PerformStaticInitialization();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override IWebHostBuilder CreateWebHostBuilder()
|
||||||
|
{
|
||||||
|
return new WebHostBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
// Specify the startup command line options
|
||||||
|
var commandLineOpts = new StartupOptions
|
||||||
|
{
|
||||||
|
NoWebClient = true,
|
||||||
|
NoAutoRunWebApp = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a temporary directory for the application paths
|
||||||
|
var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
|
||||||
|
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web"));
|
||||||
|
var appPaths = new ServerApplicationPaths(
|
||||||
|
webHostPathRoot,
|
||||||
|
Path.Combine(webHostPathRoot, "logs"),
|
||||||
|
Path.Combine(webHostPathRoot, "config"),
|
||||||
|
Path.Combine(webHostPathRoot, "cache"),
|
||||||
|
Path.Combine(webHostPathRoot, "jellyfin-web"));
|
||||||
|
|
||||||
|
// Create the logging config file
|
||||||
|
// TODO: We shouldn't need to do this since we are only logging to console
|
||||||
|
Program.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
// Create a copy of the application configuration to use for startup
|
||||||
|
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
|
||||||
|
|
||||||
|
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
|
||||||
|
_disposableComponents.Add(loggerFactory);
|
||||||
|
|
||||||
|
// Create the app host and initialize it
|
||||||
|
var appHost = new CoreAppHost(
|
||||||
|
appPaths,
|
||||||
|
loggerFactory,
|
||||||
|
commandLineOpts,
|
||||||
|
new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||||
|
new NetworkManager(loggerFactory.CreateLogger<NetworkManager>()));
|
||||||
|
_disposableComponents.Add(appHost);
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
appHost.Init(serviceCollection);
|
||||||
|
|
||||||
|
// Configure the web host builder
|
||||||
|
Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override TestServer CreateServer(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
// Create the test server using the base implementation
|
||||||
|
var testServer = base.CreateServer(builder);
|
||||||
|
|
||||||
|
// Finish initializing the app host
|
||||||
|
var appHost = (CoreAppHost)testServer.Services.GetRequiredService<IApplicationHost>();
|
||||||
|
appHost.ServiceProvider = testServer.Services;
|
||||||
|
appHost.InitializeServices().GetAwaiter().GetResult();
|
||||||
|
appHost.RunStartupTasksAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
return testServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
foreach (var disposable in _disposableComponents)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposableComponents.Clear();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
Normal file
33
tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.3" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" />
|
||||||
|
<ProjectReference Include="..\..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Code Analyzers-->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
22
tests/jellyfin-tests.ruleset
Normal file
22
tests/jellyfin-tests.ruleset
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RuleSet Name="Rules for MediaBrowser.Api.Tests" Description="Code analysis rules for MediaBrowser.Api.Tests.csproj" ToolsVersion="14.0">
|
||||||
|
|
||||||
|
<!-- Include the solution default RuleSet. The rules in this file will override the defaults. -->
|
||||||
|
<Include Path="../jellyfin.ruleset" Action="Default" />
|
||||||
|
|
||||||
|
<!-- StyleCop Analyzer Rules -->
|
||||||
|
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||||
|
<!-- SA0001: XML comment analysis is disabled due to project configuration -->
|
||||||
|
<Rule Id="SA0001" Action="None" />
|
||||||
|
</Rules>
|
||||||
|
|
||||||
|
<!-- FxCop Analyzer Rules -->
|
||||||
|
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
|
||||||
|
<!-- CA1707: Identifiers should not contain underscores -->
|
||||||
|
<Rule Id="CA1707" Action="None" />
|
||||||
|
<!-- CA2007: Consider calling ConfigureAwait on the awaited task -->
|
||||||
|
<Rule Id="CA2007" Action="None" />
|
||||||
|
<!-- CA2234: Pass system uri objects instead of strings -->
|
||||||
|
<Rule Id="CA2234" Action="Info" />
|
||||||
|
</Rules>
|
||||||
|
</RuleSet>
|
Loading…
x
Reference in New Issue
Block a user