aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs2
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs54
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs10
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs26
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs34
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/pl.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json3
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs77
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ca.json15
-rw-r--r--Emby.Server.Implementations/Serialization/MyXmlSerializer.cs12
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs17
15 files changed, 227 insertions, 36 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 94e2468719..321c7da1c4 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1067,6 +1067,8 @@ namespace Emby.Server.Implementations.Dto
dto.OriginalTitle = item.OriginalTitle;
}
+ dto.OriginalLanguage = item.OriginalLanguage;
+
if (options.ContainsField(ItemFields.ParentId))
{
dto.ParentId = item.DisplayParentId;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c667fb0600..fdb4c7328b 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -23,6 +23,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
@@ -423,7 +424,7 @@ namespace Emby.Server.Implementations.Library
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage);
}
- private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
+ private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection, string originalLanguage)
{
if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
{
@@ -437,7 +438,42 @@ namespace Emby.Server.Implementations.Library
}
}
- var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);
+ if (string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase))
+ {
+ originalLanguage = !string.IsNullOrWhiteSpace(originalLanguage)
+ ? originalLanguage.Split(',').FirstOrDefault()
+ : null;
+
+ if (user.PlayDefaultAudioTrack)
+ {
+ source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
+ source.MediaStreams,
+ NormalizeLanguage(originalLanguage),
+ user.PlayDefaultAudioTrack);
+ return;
+ }
+
+ var originalIndex = source.MediaStreams.FindIndex(i => i.Type == MediaStreamType.Audio && i.IsOriginal);
+
+ if (!string.IsNullOrWhiteSpace(originalLanguage) && originalIndex != -1)
+ {
+ var mediaLanguageOriginal = source.MediaStreams[originalIndex].Language;
+ if (NormalizeLanguage(mediaLanguageOriginal).Contains(NormalizeLanguage(originalLanguage).FirstOrDefault()))
+ {
+ source.DefaultAudioStreamIndex = originalIndex;
+ return;
+ }
+ }
+ else if (originalIndex != -1)
+ {
+ source.DefaultAudioStreamIndex = originalIndex;
+ return;
+ }
+ }
+
+ var preferredAudio = string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(originalLanguage)
+ ? NormalizeLanguage(originalLanguage)
+ : NormalizeLanguage(user.AudioLanguagePreference);
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
if (user.PlayDefaultAudioTrack)
@@ -462,7 +498,19 @@ namespace Emby.Server.Implementations.Library
var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections;
- SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
+ var originalLanguage = item?.OriginalLanguage ?? item switch
+ {
+ Episode episode => episode.Series.OriginalLanguage,
+ Video video => video.GetOwner() switch
+ {
+ Episode ownerEpisode => ownerEpisode.OriginalLanguage ?? ownerEpisode.Series.OriginalLanguage,
+ BaseItem owner => owner.OriginalLanguage,
+ null => null
+ },
+ _ => null
+ };
+
+ SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection, originalLanguage);
SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
}
else if (mediaType == MediaType.Audio)
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index fc63251ad0..cfa3e7c31d 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -70,6 +70,16 @@ namespace Emby.Server.Implementations.Library
return match ? imdbId.ToString() : null;
}
+ // Allow tmdb as an alias for tmdbid
+ if (attribute.Equals("tmdbid", StringComparison.OrdinalIgnoreCase))
+ {
+ var tmdbValue = str.GetAttributeValue("tmdb");
+ if (tmdbValue is not null)
+ {
+ return tmdbValue;
+ }
+ }
+
return null;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 5fd23c9f50..85bf20cc2a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -1,8 +1,10 @@
#nullable disable
using System;
+using System.IO;
using System.Linq;
using Emby.Naming.Common;
+using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@@ -81,10 +83,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
episode.ParentIndexNumber = 1;
}
+ SetProviderIdFromPath(episode, args.Path);
+
return episode;
}
return null;
}
+
+ /// <summary>
+ /// Sets provider ids from the episode file name.
+ /// </summary>
+ /// <param name="item">The episode.</param>
+ /// <param name="path">The episode file path.</param>
+ private static void SetProviderIdFromPath(Episode item, string path)
+ {
+ var justName = Path.GetFileNameWithoutExtension(path.AsSpan());
+
+ var imdbId = justName.GetAttributeValue("imdbid");
+ item.TrySetProviderId(MetadataProvider.Imdb, imdbId);
+
+ var tvdbId = justName.GetAttributeValue("tvdbid");
+ item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId);
+
+ var tvmazeId = justName.GetAttributeValue("tvmazeid");
+ item.TrySetProviderId(MetadataProvider.TvMaze, tvmazeId);
+
+ var tmdbId = justName.GetAttributeValue("tmdbid");
+ item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId);
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 6cb63a28a2..6e9a38fd34 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,10 +1,15 @@
#nullable disable
+using System;
using System.Globalization;
+using System.IO;
+using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.TV;
+using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging;
@@ -77,6 +82,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
+
+ var hasAnyVideo = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
+ .Any(file => _namingOptions.VideoFileExtensions.Contains(Path.GetExtension(file)));
+
+ if (!hasAnyVideo)
+ {
+ return null;
+ }
}
if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name))
@@ -91,10 +104,31 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
args.LibraryOptions.PreferredMetadataLanguage);
}
+ SetProviderIdFromPath(season, path);
+
return season;
}
return null;
}
+
+ /// <summary>
+ /// Sets provider ids from the season folder name.
+ /// </summary>
+ /// <param name="item">The season.</param>
+ /// <param name="path">The season folder path.</param>
+ private static void SetProviderIdFromPath(Season item, string path)
+ {
+ var justName = Path.GetFileName(path.AsSpan());
+
+ var tvdbId = justName.GetAttributeValue("tvdbid");
+ item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId);
+
+ var tvmazeId = justName.GetAttributeValue("tvmazeid");
+ item.TrySetProviderId(MetadataProvider.TvMaze, tvmazeId);
+
+ var tmdbId = justName.GetAttributeValue("tmdbid");
+ item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId);
+ }
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index b80737d3b9..e48939b4d7 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -135,5 +135,6 @@
"TaskMoveTrickplayImages": "نقل موقع صور معاينات التنقل",
"TaskMoveTrickplayImagesDescription": "ينقل ملفات معاينات التنقل الحالية وفقاً لإعدادات المكتبة.",
"CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم",
- "CleanupUserDataTaskDescription": "ينظف جميع بيانات المستخدم (مثل حالة المشاهدة وحالة المفضلة وغيرها) للمحتوى الذي لم يعد موجوداً لمدة 90 يوماً على الأقل."
+ "CleanupUserDataTaskDescription": "ينظف جميع بيانات المستخدم (مثل حالة المشاهدة وحالة المفضلة وغيرها) للمحتوى الذي لم يعد موجوداً لمدة 90 يوماً على الأقل.",
+ "Original": "فريد"
}
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 8d43839110..3fc1895842 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -135,5 +135,6 @@
"TaskMoveTrickplayImages": "Přesunout úložiště obrázků Trickplay",
"TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny.",
"CleanupUserDataTaskDescription": "Odstraní všechna uživatelská data (stav zhlédnutí, oblíbené atd.) z médií, které již neexistují více než 90 dní.",
- "CleanupUserDataTask": "Pročistit uživatelská data"
+ "CleanupUserDataTask": "Pročistit uživatelská data",
+ "Original": "Originál"
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 45b1cbb6a0..9b5049c8c7 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -64,6 +64,7 @@
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+ "Original": "Original",
"Photos": "Photos",
"Playlists": "Playlists",
"Plugin": "Plugin",
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index bf1cbdacd1..de4c277ce7 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -135,5 +135,6 @@
"CleanupUserDataTaskDescription": "Wist alle gebruikersgegevens (kijkstatus, favorieten, etc.) van media die al minstens 90 dagen niet meer aanwezig zijn.",
"CleanupUserDataTask": "Opruimtaak gebruikersdata",
"Albums": "Albums",
- "Genres": "Genres"
+ "Genres": "Genres",
+ "Original": "Oorspronkelijk"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json
index a741fc14c0..e5af2c7801 100644
--- a/Emby.Server.Implementations/Localization/Core/pl.json
+++ b/Emby.Server.Implementations/Localization/Core/pl.json
@@ -135,5 +135,6 @@
"TaskExtractMediaSegmentsDescription": "Wyodrębnia lub pobiera segmenty mediów z wtyczek obsługujących MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Przenosi istniejące pliki Trickplay zgodnie z ustawieniami biblioteki.",
"CleanupUserDataTaskDescription": "Usuwa wszystkie dane użytkownika (stan oglądanych, status ulubionych itp.) z mediów, które nie są dostępne od co najmniej 90 dni.",
- "CleanupUserDataTask": "Zadanie czyszczenia danych użytkownika"
+ "CleanupUserDataTask": "Zadanie czyszczenia danych użytkownika",
+ "Original": "Oryginalny"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index a47ed248e9..015f59af25 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -135,5 +135,6 @@
"TaskMoveTrickplayImages": "Migrera platsen för Trickplay-bilder",
"TaskMoveTrickplayImagesDescription": "Flyttar befintliga trickplay-filer enligt bibliotekets inställningar.",
"CleanupUserDataTaskDescription": "Tar bort all användardata (såsom vad du sett, favoriter med mera) för media som inte funnits på enheten på minst 90 dagar.",
- "CleanupUserDataTask": "Uppgift för rensning av användardata"
+ "CleanupUserDataTask": "Uppgift för rensning av användardata",
+ "Original": "Original"
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 6fca5bc1ba..d8797e612b 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -320,6 +320,14 @@ namespace Emby.Server.Implementations.Localization
{
return value;
}
+
+ if (ratingsDictionary is not null && rating.Length > countryCode.Length
+ && rating.StartsWith(countryCode, StringComparison.OrdinalIgnoreCase)
+ && (rating[countryCode.Length] == '-' || rating[countryCode.Length] == ':')
+ && ratingsDictionary.TryGetValue(rating[(countryCode.Length + 1)..].Trim(), out var normalizedValue))
+ {
+ return normalizedValue;
+ }
}
else
{
@@ -345,33 +353,68 @@ namespace Emby.Server.Implementations.Localization
}
}
- // Try splitting by : to handle "Germany: FSK-18"
- if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
+ // Try splitting by country prefix separator to handle "US:PG-13", "Germany: FSK-18", "DE-FSK-18"
+ if (TryGetRatingScoreBySeparator(rating, ':', out var result)
+ || TryGetRatingScoreBySeparator(rating, '-', out result))
{
- var ratingLevelRightPart = rating.AsSpan().RightPart(':');
- if (ratingLevelRightPart.Length != 0)
- {
- return GetRatingScore(ratingLevelRightPart.ToString());
- }
+ return result;
}
- // Handle prefix country code to handle "DE-18"
- if (rating.Contains('-', StringComparison.OrdinalIgnoreCase))
+ return null;
+ }
+
+ private bool TryGetRatingScoreBySeparator(string rating, char separator, out ParentalRatingScore? result)
+ {
+ result = null;
+
+ if (rating.IndexOf(separator, StringComparison.Ordinal) < 0)
{
- var ratingSpan = rating.AsSpan();
+ return false;
+ }
- // Extract culture from country prefix
- var culture = FindLanguageInfo(ratingSpan.LeftPart('-').ToString());
+ var ratingSpan = rating.AsSpan();
+ var countryPart = ratingSpan.LeftPart(separator).Trim().ToString();
+ var ratingPart = ratingSpan.RightPart(separator).Trim().ToString();
+ if (ratingPart.Length == 0)
+ {
+ return false;
+ }
- var ratingLevelRightPart = ratingSpan.RightPart('-');
- if (ratingLevelRightPart.Length != 0)
+ string? resolvedCountryCode = null;
+
+ if (_allParentalRatings.ContainsKey(countryPart))
+ {
+ resolvedCountryCode = countryPart;
+ }
+ else
+ {
+ var culture = FindLanguageInfo(countryPart);
+ if (culture is not null)
{
- // Check rating system of culture
- return GetRatingScore(ratingLevelRightPart.ToString(), culture?.TwoLetterISOLanguageName);
+ resolvedCountryCode = culture.TwoLetterISOLanguageName;
}
}
- return null;
+ if (resolvedCountryCode is not null
+ && _allParentalRatings.TryGetValue(resolvedCountryCode, out var countryRatings))
+ {
+ if (countryRatings.TryGetValue(ratingPart, out result))
+ {
+ return true;
+ }
+
+ _logger.LogWarning(
+ "Rating '{Rating}' not found in the '{CountryCode}' rating system, treating as unrated",
+ rating,
+ resolvedCountryCode);
+
+ return true;
+ }
+
+ // Country not identified or no rating data available, try recursive lookup
+ result = GetRatingScore(ratingPart, resolvedCountryCode);
+
+ return true;
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Localization/Ratings/ca.json b/Emby.Server.Implementations/Localization/Ratings/ca.json
index fa43a8f2b7..76550b64c3 100644
--- a/Emby.Server.Implementations/Localization/Ratings/ca.json
+++ b/Emby.Server.Implementations/Localization/Ratings/ca.json
@@ -3,7 +3,7 @@
"supportsSubScores": true,
"ratings": [
{
- "ratingStrings": ["E", "G", "TV-Y", "TV-G"],
+ "ratingStrings": ["C", "E", "G", "TV-Y", "TV-G"],
"ratingScore": {
"score": 0,
"subScore": 0
@@ -24,13 +24,20 @@
}
},
{
- "ratingStrings": ["PG", "TV-PG"],
+ "ratingStrings": ["C8"],
"ratingScore": {
- "score": 9,
+ "score": 8,
"subScore": 0
}
},
{
+ "ratingStrings": ["PG", "TV-PG"],
+ "ratingScore": {
+ "score": 8,
+ "subScore": 1
+ }
+ },
+ {
"ratingStrings": ["14A"],
"ratingScore": {
"score": 14,
@@ -38,7 +45,7 @@
}
},
{
- "ratingStrings": ["TV-14"],
+ "ratingStrings": ["14+", "TV-14"],
"ratingScore": {
"score": 14,
"subScore": 1
diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
index aa5fbbdf73..5c9a94cd36 100644
--- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
+++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
@@ -85,9 +85,17 @@ namespace Emby.Server.Implementations.Serialization
/// <returns>System.Object.</returns>
public object? DeserializeFromFile(Type type, string file)
{
- using (var stream = File.OpenRead(file))
+ try
{
- return DeserializeFromStream(type, stream);
+ using (var stream = File.OpenRead(file))
+ {
+ return DeserializeFromStream(type, stream);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.Data.Add("Filename", file);
+ throw;
}
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 5652d0c9b5..1782b53e10 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1021,15 +1021,22 @@ namespace Emby.Server.Implementations.Session
ArgumentNullException.ThrowIfNull(info);
+ var session = GetSession(info.SessionId);
+
+ session.StopAutomaticProgress();
+
if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0)
{
+ // Ensure live stream is cleaned up before throwing, to prevent tuner
+ // resource leaks when stalled clients report a negative PositionTicks.
+ if (!string.IsNullOrEmpty(info.LiveStreamId))
+ {
+ await CloseLiveStreamIfNeededAsync(info.LiveStreamId, session.Id).ConfigureAwait(false);
+ }
+
throw new ArgumentOutOfRangeException(nameof(info), "The PlaybackStopInfo's PositionTicks was negative.");
}
- var session = GetSession(info.SessionId);
-
- session.StopAutomaticProgress();
-
var libraryItem = info.ItemId.IsEmpty()
? null
: GetNowPlayingItem(session, info.ItemId);
@@ -2049,7 +2056,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- var adminUserIds = _userManager.Users
+ var adminUserIds = _userManager.GetUsers()
.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
.Select(i => i.Id)
.ToList();