Compare commits

..

67 Commits

Author SHA1 Message Date
Jellyfin Release Bot
3c79d7a3f3 Bump version to 10.9.8 2024-07-21 01:11:35 -04:00
Bond-009
4afa6db108 Properly escape paths in concat file for BDMV (#12296) 2024-07-17 12:33:11 -04:00
Bond-009
f7a90b6383 Merge pull request #12278 from Bond-009/localizeaudio
Fix localization of audio title
2024-07-16 14:45:13 +02:00
Bond_009
b8e2d8e11a Fix tests 2024-07-15 14:40:12 +02:00
Niels van Velzen
c1f7ccbca4 Fix season handling ("Season Unknown" / unneccesary empty seasons) (#12240) 2024-07-15 08:27:19 -04:00
Bond_009
5bab02fa54 Fix localization of audio title
Maybe passing ILocalizationManager into the ctor of MediaStream is a better solution for master?
2024-07-15 14:27:12 +02:00
Bond-009
f2fa0b9025 Merge pull request #12246 from jellyfin/renovate/dotnet-monorepo
Update dotnet monorepo

(cherry picked from commit 5ef76a5e31)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:47:58 -04:00
Bond-009
9c0edd2905 Merge pull request #12259 from jellyfin/renovate/serilog.settings.configuration-8.x
Update dependency Serilog.Settings.Configuration to v8.0.2

(cherry picked from commit c050abf3e8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:47:23 -04:00
Bond-009
62deebc04c Merge pull request #12248 from jellyfin/renovate/dotnet-monorepo
Update dotnet monorepo to v8.0.7

(cherry picked from commit f62af90ae3)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:44:46 -04:00
Jellyfin Release Bot
478d8b07bf Bump version to 10.9.7 2024-06-24 20:19:28 -04:00
Bond-009
c9b6ebd94f Merge pull request #11911 from Bond-009/infoaudionorm
Log album name and id in normalization task
2024-06-24 22:51:57 +02:00
Bond-009
30fc089dd5 Merge pull request #12166 from Bond-009/4kbdmv
Fix HDR detection for 4K Blu-Ray BDMVs
2024-06-24 22:51:41 +02:00
Bond-009
25f02658f0 Merge pull request #12126 from gnattu/add-extracted-lyrics
Try to add extracted lyrics during scanning
2024-06-24 22:51:28 +02:00
Bond-009
2266a00337 Merge pull request #12055 from Shadowghost/fix-season-backdrops
Fix season backdrops
2024-06-24 22:41:57 +02:00
Shadowghost
afeff31dca Merge remote-tracking branch 'upstream/release-10.9.z' into fix-season-backdrops 2024-06-24 22:34:43 +02:00
Bond-009
476dc01f4d Merge pull request #12025 from Shadowghost/remove-empty-image-folders-recursive
Fix empty image folder removal for legacy locations
2024-06-24 22:14:09 +02:00
Bond_009
b81b674ae1 Log album name and id in normalization task
Filename of the concat file is now the same as the album id.
Temp file gets deleted even if LUFS calculation failed
2024-06-24 22:01:49 +02:00
Bond_009
15eb7a25b9 Fix HDR detection for 4K Blu-Ray BDMVs 2024-06-24 11:43:01 +02:00
Shadowghost
aadd57bc48 Fix check 2024-06-24 09:16:51 +02:00
Bond-009
cbbe5db813 Merge pull request #12053 from Shadowghost/fix-local-playlist-scanning
Rewrite PlaylistItemsProvider as ILocalMetadataProvider
2024-06-23 17:56:23 +02:00
Bond-009
4601097d3e Merge pull request #12050 from Shadowghost/fix-seasons
Fix season handling
2024-06-23 17:48:48 +02:00
gnattu
10cd9a7f79 Only add first stream
Signed-off-by: gnattu <gnattuoc@me.com>
2024-06-22 18:26:59 +08:00
Tim Eisele
6cf98d4930 Only cleanup children on specific exceptions (#12134) 2024-06-21 09:08:05 -06:00
Tim Eisele
34a65980e3 Remove incomplete mediatype restriction from playlists (#12024) 2024-06-21 09:07:38 -06:00
Nyanmisaka
6010bc01c3 Fix MicroDVD being recognized as DVDSUB subtitles (#12149) 2024-06-21 09:07:25 -06:00
Shadowghost
a00f9e1a10 Cleanup seasons after creating real ones 2024-06-20 22:03:01 +02:00
Bond-009
85078d8f10 Merge pull request #12123 from Shadowghost/fix-cleanup-task
Fix Cleanup Task metadata saving
2024-06-20 11:36:48 +02:00
Bond-009
1606b6c0f6 Merge pull request #12043 from jellyfin/pr-parental-au-1
Fix the Australian PG rating
2024-06-20 11:20:19 +02:00
Bond-009
f097aad01e Merge pull request #12094 from Shadowghost/fix-dual-socket-address-handling
Map IPv6 mapped IPv4 addresses back to IPv4 before running checks
2024-06-20 11:20:06 +02:00
Tim Eisele
bf53f1ae38 Do not override <year> if <releasedate> is set (#12120) 2024-06-18 08:16:21 -06:00
gnattu
31237f778a Try to add extracted lyrics during scanning
The extraction process does not add the extracted lyrics to the audio media streams. Try to add it when tryExtractEmbeddedLyrics is true.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-06-18 10:08:11 +08:00
Shadowghost
55d245a77b Fix saving item metadata 2024-06-17 23:13:53 +02:00
Odd Stråbø
9f35f56eaf Fix the Australian PG rating
As per https://www.classification.gov.au/classification-ratings/what-are-ratings

Fixes #11650
Well, sort of. I don't think it is possible to differentiate between them, as we'd be comparing the integer values, not the position in the list?
2024-06-15 19:55:14 +02:00
Bond-009
f2a5ccf102 Merge pull request #12065 from Rivenlalala/fix-bdmv-file-extension-case-issue
Make m2ts extension case-insensitive
2024-06-15 17:52:05 +02:00
Bond-009
2b78980747 Merge pull request #12017 from gnattu/overwrite-livetv-codec
Overwrite supported codecs for livetv
2024-06-15 17:38:10 +02:00
Bond-009
a89678074e Merge pull request #12026 from Bond-009/hisubs
Check hearing impared flags with equality instead of contains
2024-06-15 17:37:49 +02:00
Bond-009
d813f83b4a Merge pull request #12039 from Shadowghost/fix-local-episode-thumb
Fix local episode image thumb recognition
2024-06-15 17:37:30 +02:00
Bond-009
37b7e953f7 Merge pull request #12031 from jellyfin/fix-video-embedded-image
Fix video embedded image detection
2024-06-15 17:36:19 +02:00
Bond-009
08b64c5502 Merge pull request #12028 from Shadowghost/fix-replace
Fix replace all and respect metadata settings
2024-06-15 17:36:05 +02:00
Bond-009
23a660e917 Merge pull request #12073 from Shadowghost/fix-mb
Fix Music Brainz release group query
2024-06-15 17:34:44 +02:00
Bond-009
78eb9b2f78 Merge pull request #12046 from gnattu/fix-wrong-mpegts-detection
Fix mpeg-ts detection
2024-06-15 17:34:31 +02:00
Bond-009
d90f504ca7 Merge pull request #12037 from Shadowghost/fix-user-delete
Do not fail user deletion if we have no playlist folder
2024-06-15 17:34:21 +02:00
Shadowghost
56104d3042 Map IPv6 mapped IPv4 addresses back to IPv4 before running checks 2024-06-14 10:22:10 +02:00
Rivenlalala
fcec1fcc4d Make m2ts extension case-insensitive 2024-06-12 04:16:50 +08:00
Shadowghost
1a14902da8 Apply review suggestion 2024-06-11 18:48:38 +02:00
Shadowghost
34bdf8bf78 Do not cleanup old backdrops 2024-06-11 10:29:06 +02:00
Shadowghost
7ff3f6af6c Fix MB release group query 2024-06-10 21:43:42 +02:00
Shadowghost
bd8b0c4c03 Remove all existing backdrops when replacing all images 2024-06-09 23:17:57 +02:00
Shadowghost
0c560a313a Fix local season backdrop saving 2024-06-09 21:35:12 +02:00
Shadowghost
8882bb495c Rewrite PlaylistItemsProvider as ILocalMetadataProvider 2024-06-09 20:53:33 +02:00
Shadowghost
e4078f984a Fix season handling 2024-06-09 18:47:21 +02:00
gnattu
8e5a2f565c Fix mpeg-ts detection
When the container name is `mpeg`, it means it is MPEG-PS, while the TS container should have the explicit name `mpeg-ts`.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-06-09 21:23:13 +08:00
Shadowghost
8b442a7749 Check for existence before trying to delete directory 2024-06-09 08:24:58 +02:00
Shadowghost
b63f7a2bc0 Only remove image from item if file system delete was successful 2024-06-09 00:46:46 +02:00
Shadowghost
f9e7d5229e Limit removal scope 2024-06-09 00:46:19 +02:00
Shadowghost
b24d05bff7 Apply review suggestion 2024-06-08 22:35:50 +02:00
Shadowghost
fd009fc71b Simplify metadata subdir check 2024-06-08 21:57:21 +02:00
Shadowghost
302eea1cb7 Fix local episode image thumb recognition 2024-06-08 21:51:08 +02:00
Shadowghost
b116a2742e Do not fail user deletion if we have no playlist folder 2024-06-08 16:46:12 +02:00
nyanmisaka
99a04e23d9 Fix video embedded image detection
Fixes debbfaa. Embedded images also exist in video.

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2024-06-08 16:55:27 +08:00
Shadowghost
19a89d5a60 Remove folder after removing empty subfolders 2024-06-08 00:12:36 +02:00
Shadowghost
feb20c131a Use helper 2024-06-08 00:08:11 +02:00
Shadowghost
ec82023265 Respect different metadata settings on refresh 2024-06-07 23:19:30 +02:00
Shadowghost
e4f3f0b3b6 Remove all data when replacing all 2024-06-07 23:19:04 +02:00
Shadowghost
28274d4c75 Remove empty image folders recursively 2024-06-07 22:12:48 +02:00
Bond_009
b6595e4efc Check hearing impared flags with equality instead of contains
Fixes #12019
2024-06-07 22:12:35 +02:00
gnattu
4046ef1c13 Overwrite supported codecs for livetv
Only changeing streamingRequest is not enough. The internal logic will do codec shifting based on supported codecs, need to overwrite all of them.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-06-07 17:58:35 +08:00
42 changed files with 471 additions and 384 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.4",
"version": "8.0.7",
"commands": [
"dotnet-ef"
]

View File

@@ -25,25 +25,25 @@
<PackageVersion Include="libse" Version="4.0.5" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
@@ -61,7 +61,7 @@
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
@@ -79,8 +79,8 @@
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />

View File

@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -107,7 +107,7 @@ namespace Emby.Naming.ExternalFiles
pathInfo.Language = culture.ThreeLetterISOLanguageName;
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);
}
else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase)))
else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Equals(s, StringComparison.OrdinalIgnoreCase)))
{
pathInfo.IsHearingImpaired = true;
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);

View File

@@ -5694,13 +5694,17 @@ AND Type = @InternalPersonType)");
item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result;
if (item.Type == MediaStreamType.Subtitle)
if (item.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedDefault = _localization.GetLocalizedString("Default");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedExternal = _localization.GetLocalizedString("External");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
if (item.Type is MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
}
}
return item;

View File

@@ -389,7 +389,7 @@ namespace Emby.Server.Implementations.IO
var info = new FileInfo(path);
if (info.Exists &&
((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) != isHidden)
(info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden != isHidden)
{
if (isHidden)
{
@@ -417,8 +417,8 @@ namespace Emby.Server.Implementations.IO
return;
}
if (((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) == readOnly
&& ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) == isHidden)
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly == readOnly
&& (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden == isHidden)
{
return;
}

View File

@@ -1884,7 +1884,7 @@ namespace Emby.Server.Implementations.Library
try
{
var index = item.GetImageIndex(img);
image = await ConvertImageToLocal(item, img, index, removeOnFailure: true).ConfigureAwait(false);
image = await ConvertImageToLocal(item, img, index, true).ConfigureAwait(false);
}
catch (ArgumentException)
{

View File

@@ -1,11 +1,11 @@
Exempt,0
G,0
7+,7
PG,15
M,15
MA,15
MA15+,15
MA 15+,15
PG,16
16+,16
R,18
R18+,18
1 Exempt 0
2 G 0
3 7+ 7
4 PG 15
5 M 15
6 MA 15
7 MA15+ 15
8 MA 15+ 15
PG 16
9 16+ 16
10 R 18
11 R18+ 18

View File

@@ -170,8 +170,13 @@ namespace Emby.Server.Implementations.Playlists
private List<Playlist> GetUserPlaylists(Guid userId)
{
var user = _userManager.GetUserById(userId);
var playlistsFolder = GetPlaylistsFolder(userId);
if (playlistsFolder is null)
{
return [];
}
return GetPlaylistsFolder(userId).GetChildren(user, true).OfType<Playlist>().ToList();
return playlistsFolder.GetChildren(user, true).OfType<Playlist>().ToList();
}
private static string GetTargetPath(string path)
@@ -184,11 +189,11 @@ namespace Emby.Server.Implementations.Playlists
return path;
}
private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, MediaType playlistMediaType, User user, DtoOptions options)
private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, User user, DtoOptions options)
{
var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null);
return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
return Playlist.GetPlaylistItems(items, user, options);
}
public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
@@ -208,7 +213,7 @@ namespace Emby.Server.Implementations.Playlists
?? throw new ArgumentException("No Playlist exists with Id " + playlistId);
// Retrieve all the items to be added to the playlist
var newItems = GetPlaylistItems(newItemIds, playlist.MediaType, user, options)
var newItems = GetPlaylistItems(newItemIds, user, options)
.Where(i => i.SupportsAddingToPlaylist);
// Filter out duplicate items, if necessary

View File

@@ -106,13 +106,20 @@ public partial class AudioNormalizationTask : IScheduledTask
continue;
}
var tempFile = Path.Join(_configurationManager.GetTranscodePath(), Guid.NewGuid() + ".concat");
_logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id);
var tempFile = Path.Join(_configurationManager.GetTranscodePath(), a.Id + ".concat");
var inputLines = albumTracks.Select(x => string.Format(CultureInfo.InvariantCulture, "file '{0}'", x.Path.Replace("'", @"'\''", StringComparison.Ordinal)));
await File.WriteAllLinesAsync(tempFile, inputLines, cancellationToken).ConfigureAwait(false);
a.LUFS = await CalculateLUFSAsync(
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
cancellationToken).ConfigureAwait(false);
File.Delete(tempFile);
try
{
a.LUFS = await CalculateLUFSAsync(
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
cancellationToken).ConfigureAwait(false);
}
finally
{
File.Delete(tempFile);
}
}
_itemRepository.SaveItems(albums, cancellationToken);

View File

@@ -127,15 +127,8 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
{
_logger.LogDebug("Updating {FolderName}", folder.Name);
folder.LinkedChildren = folder.LinkedChildren.Except(itemsToRemove).ToArray();
_providerManager.SaveMetadataAsync(folder, ItemUpdateType.MetadataEdit);
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken);
_providerManager.QueueRefresh(
folder.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
},
RefreshPriority.High);
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
@@ -133,53 +134,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
cancellationToken.ThrowIfCancellationRequested();
DeleteFile(file.FullName);
FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
}
DeleteEmptyFolders(directory);
FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
progress.Report(100);
}
private void DeleteEmptyFolders(string parent)
{
foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
{
DeleteEmptyFolders(directory);
if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
{
try
{
Directory.Delete(directory, false);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", directory);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", directory);
}
}
}
}
private void DeleteFile(string path)
{
try
{
_fileSystem.DeleteFile(path);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Error deleting file {Path}", path);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting file {Path}", path);
}
}
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
@@ -113,53 +113,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
cancellationToken.ThrowIfCancellationRequested();
DeleteFile(file.FullName);
FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
}
DeleteEmptyFolders(directory);
FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
progress.Report(100);
}
private void DeleteEmptyFolders(string parent)
{
foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
{
DeleteEmptyFolders(directory);
if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
{
try
{
Directory.Delete(directory, false);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", directory);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", directory);
}
}
}
}
private void DeleteFile(string path)
{
try
{
_fileSystem.DeleteFile(path);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Error deleting file {Path}", path);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting file {Path}", path);
}
}
}
}

View File

@@ -80,7 +80,8 @@ public class ItemRefreshController : BaseJellyfinApiController
|| imageRefreshMode == MetadataRefreshMode.FullRefresh
|| replaceAllImages
|| replaceAllMetadata,
IsAutomated = false
IsAutomated = false,
RemoveOldMetadata = replaceAllMetadata
};
_providerManager.QueueRefresh(item.Id, refreshOptions, RefreshPriority.High);

View File

@@ -154,6 +154,11 @@ public static class StreamingHelpers
// Some channels from HDHomerun will experience A/V sync issues
streamingRequest.SegmentContainer = "ts";
streamingRequest.VideoCodec = "h264";
streamingRequest.AudioCodec = "aac";
state.SupportedVideoCodecs = ["h264"];
state.Request.VideoCodec = "h264";
state.SupportedAudioCodecs = ["aac"];
state.Request.AudioCodec = "aac";
}
var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false);

View File

@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1949,14 +1949,15 @@ namespace MediaBrowser.Controller.Entities
return;
}
// Remove it from the item
RemoveImage(info);
// Remove from file system
if (info.IsLocalFile)
{
FileSystem.DeleteFile(info.Path);
}
// Remove from item
RemoveImage(info);
await UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
}

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
@@ -370,9 +371,18 @@ namespace MediaBrowser.Controller.Entities
{
nonCachedChildren = GetNonCachedChildren(directoryService);
}
catch (IOException ex)
{
Logger.LogError(ex, "Error retrieving children from file system");
}
catch (SecurityException ex)
{
Logger.LogError(ex, "Error retrieving children from file system");
}
catch (Exception ex)
{
Logger.LogError(ex, "Error retrieving children folder");
Logger.LogError(ex, "Error retrieving children");
return;
}
progress.Report(ProgressHelpers.RetrievedChildren);

View File

@@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.IO;
/// <summary>
/// Helper methods for file system management.
/// </summary>
public static class FileSystemHelper
{
/// <summary>
/// Deletes the file.
/// </summary>
/// <param name="fileSystem">The fileSystem.</param>
/// <param name="path">The path.</param>
/// <param name="logger">The logger.</param>
public static void DeleteFile(IFileSystem fileSystem, string path, ILogger logger)
{
try
{
fileSystem.DeleteFile(path);
}
catch (UnauthorizedAccessException ex)
{
logger.LogError(ex, "Error deleting file {Path}", path);
}
catch (IOException ex)
{
logger.LogError(ex, "Error deleting file {Path}", path);
}
}
/// <summary>
/// Recursively delete empty folders.
/// </summary>
/// <param name="fileSystem">The fileSystem.</param>
/// <param name="path">The path.</param>
/// <param name="logger">The logger.</param>
public static void DeleteEmptyFolders(IFileSystem fileSystem, string path, ILogger logger)
{
foreach (var directory in fileSystem.GetDirectoryPaths(path))
{
DeleteEmptyFolders(fileSystem, directory, logger);
if (!fileSystem.GetFileSystemEntryPaths(directory).Any())
{
try
{
Directory.Delete(directory, false);
}
catch (UnauthorizedAccessException ex)
{
logger.LogError(ex, "Error deleting directory {Path}", directory);
}
catch (IOException ex)
{
logger.LogError(ex, "Error deleting directory {Path}", directory);
}
}
}
}
}

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1208,8 +1208,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subtitlePath = state.SubtitleStream.Path;
var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
|| subtitleExtension.Equals(".sup", StringComparison.OrdinalIgnoreCase))
// dvdsub/vobsub graphical subtitles use .sub+.idx pairs
if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))

View File

@@ -166,7 +166,7 @@ namespace MediaBrowser.Controller.Playlists
return base.GetChildren(user, true, query);
}
public static IReadOnlyList<BaseItem> GetPlaylistItems(MediaType playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options)
public static IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<BaseItem> inputItems, User user, DtoOptions options)
{
if (user is not null)
{
@@ -177,14 +177,14 @@ namespace MediaBrowser.Controller.Playlists
foreach (var item in inputItems)
{
var playlistItems = GetPlaylistItems(item, user, playlistMediaType, options);
var playlistItems = GetPlaylistItems(item, user, options);
list.AddRange(playlistItems);
}
return list;
}
private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, MediaType mediaType, DtoOptions options)
private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, DtoOptions options)
{
if (item is MusicGenre musicGenre)
{
@@ -216,7 +216,7 @@ namespace MediaBrowser.Controller.Playlists
{
Recursive = true,
IsFolder = false,
MediaTypes = [mediaType],
MediaTypes = [MediaType.Audio, MediaType.Video],
EnableTotalRecordCount = false,
DtoOptions = options
};

View File

@@ -40,13 +40,12 @@ namespace MediaBrowser.LocalMetadata.Images
var parentPathFiles = directoryService.GetFiles(parentPath);
var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path.AsSpan()).ToString();
var thumbName = string.Concat(nameWithoutExtension, "-thumb");
var images = GetImageFilesFromFolder(thumbName, parentPathFiles);
var images = GetImageFilesFromFolder(nameWithoutExtension, parentPathFiles);
var metadataSubPath = directoryService.GetDirectories(parentPath).Where(d => d.Name.EndsWith("metadata", StringComparison.OrdinalIgnoreCase)).ToList();
foreach (var path in metadataSubPath)
var metadataSubDir = directoryService.GetDirectories(parentPath).FirstOrDefault(d => d.Name.Equals("metadata", StringComparison.Ordinal));
if (metadataSubDir is not null)
{
var files = directoryService.GetFiles(path.FullName);
var files = directoryService.GetFiles(metadataSubDir.FullName);
images.AddRange(GetImageFilesFromFolder(nameWithoutExtension, files));
}
@@ -55,9 +54,8 @@ namespace MediaBrowser.LocalMetadata.Images
private List<LocalImageInfo> GetImageFilesFromFolder(ReadOnlySpan<char> filenameWithoutExtension, List<FileSystemMetadata> filePaths)
{
var thumbName = string.Concat(filenameWithoutExtension, "-thumb");
var list = new List<LocalImageInfo>(1);
var thumbName = string.Concat(filenameWithoutExtension, "-thumb");
foreach (var i in filePaths)
{

View File

@@ -1155,10 +1155,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Get all files from the BDMV/STREAMING directory
// Only return playable local .m2ts files
var files = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")).ToList();
return validPlaybackFiles
.Select(f => _fileSystem.GetFileInfo(Path.Join(path, "BDMV", "STREAM", f)))
.Where(f => f.Exists)
.Select(f => f.FullName)
.Select(validFile => files.FirstOrDefault(f => Path.GetFileName(f.FullName.AsSpan()).Equals(validFile, StringComparison.OrdinalIgnoreCase))?.FullName)
.Where(f => f is not null)
.ToList();
}
@@ -1216,7 +1216,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
// Add file path stanza to concat configuration
sw.WriteLine("file '{0}'", path);
sw.WriteLine("file '{0}'", path.Replace("'", @"'\''", StringComparison.Ordinal));
// Add duration stanza to concat configuration
sw.WriteLine("duration {0}", duration);

View File

@@ -280,8 +280,8 @@ namespace MediaBrowser.MediaEncoding.Probing
splitFormat[i] = "mpeg";
}
// Handle MPEG-2 container
else if (string.Equals(splitFormat[i], "mpeg", StringComparison.OrdinalIgnoreCase))
// Handle MPEG-TS container
else if (string.Equals(splitFormat[i], "mpegts", StringComparison.OrdinalIgnoreCase))
{
splitFormat[i] = "ts";
}
@@ -624,15 +624,19 @@ namespace MediaBrowser.MediaEncoding.Probing
{
if (string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase))
{
codec = "dvbsub";
codec = "DVBSUB";
}
else if ((codec ?? string.Empty).Contains("PGS", StringComparison.OrdinalIgnoreCase))
else if (string.Equals(codec, "dvb_teletext", StringComparison.OrdinalIgnoreCase))
{
codec = "PGSSUB";
codec = "DVBTXT";
}
else if ((codec ?? string.Empty).Contains("DVD", StringComparison.OrdinalIgnoreCase))
else if (string.Equals(codec, "dvd_subtitle", StringComparison.OrdinalIgnoreCase))
{
codec = "DVDSUB";
codec = "DVDSUB"; // .sub+.idx
}
else if (string.Equals(codec, "hdmv_pgs_subtitle", StringComparison.OrdinalIgnoreCase))
{
codec = "PGSSUB"; // .sup
}
return codec;
@@ -717,6 +721,8 @@ namespace MediaBrowser.MediaEncoding.Probing
if (streamInfo.CodecType == CodecType.Audio)
{
stream.Type = MediaStreamType.Audio;
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.LocalizedExternal = _localization.GetLocalizedString("External");
stream.Channels = streamInfo.Channels;
@@ -779,11 +785,10 @@ namespace MediaBrowser.MediaEncoding.Probing
&& !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase);
if (isAudio
&& (string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase)))
|| string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.EmbeddedImage;
}

View File

@@ -656,14 +656,14 @@ namespace MediaBrowser.Model.Entities
{
string codec = format ?? string.Empty;
// sub = external .sub file
// microdvd and dvdsub/vobsub share the ".sub" file extension, but it's text-based.
return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase);
return codec.Contains("microdvd", StringComparison.OrdinalIgnoreCase)
|| (!codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase));
}
public bool SupportsSubtitleConversionTo(string toCodec)

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -14,6 +14,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@@ -188,11 +189,27 @@ namespace MediaBrowser.Providers.Manager
{
_fileSystem.DeleteFile(currentPath);
// Remove containing directory if empty
var folder = Path.GetDirectoryName(currentPath);
if (!_fileSystem.GetFiles(folder).Any())
// Remove local episode metadata directory if it exists and is empty
var directory = Path.GetDirectoryName(currentPath);
if (item is Episode && directory.Equals("metadata", StringComparison.Ordinal))
{
Directory.Delete(folder);
var parentDirectoryPath = Directory.GetParent(currentPath).FullName;
if (_fileSystem.DirectoryExists(parentDirectoryPath) && !_fileSystem.GetFiles(parentDirectoryPath).Any())
{
try
{
_logger.LogInformation("Deleting empty local metadata folder {Folder}", parentDirectoryPath);
Directory.Delete(parentDirectoryPath);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
}
}
}
}
catch (FileNotFoundException)
@@ -410,13 +427,15 @@ namespace MediaBrowser.Providers.Manager
if (type == ImageType.Backdrop && saveLocally)
{
if (season is not null && season.IndexNumber.HasValue)
if (season is not null
&& season.IndexNumber.HasValue
&& (imageIndex is null || imageIndex == 0))
{
var seriesFolder = season.SeriesPath;
var seasonMarker = season.IndexNumber.Value == 0
? "-specials"
: season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
? "-specials"
: season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
var imageFilename = "season" + seasonMarker + "-fanart" + extension;

View File

@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Manager
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, ImageRefreshOptions refreshOptions)
{
var hasChanges = false;
IDirectoryService directoryService = refreshOptions?.DirectoryService;
var directoryService = refreshOptions?.DirectoryService;
if (item is not Photo)
{
@@ -158,7 +159,7 @@ namespace MediaBrowser.Providers.Manager
}
}
// only delete existing multi-images if new ones were added
// Only delete existing multi-images if new ones were added
if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
{
PruneImages(item, oldBackdropImages);
@@ -359,10 +360,8 @@ namespace MediaBrowser.Providers.Manager
private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
{
for (var i = 0; i < images.Count; i++)
foreach (var image in images)
{
var image = images[i];
if (image.IsLocalFile)
{
try
@@ -377,19 +376,20 @@ namespace MediaBrowser.Providers.Manager
{
_logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
}
finally
{
// Always remove empty parent folder
var folder = Path.GetDirectoryName(image.Path);
if (Directory.Exists(folder) && !_fileSystem.GetFiles(folder).Any())
{
Directory.Delete(folder);
}
}
}
}
item.RemoveImages(images);
// Cleanup old metadata directory for episodes if empty
if (item is Episode)
{
var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any())
{
Directory.Delete(oldLocalMetadataDirectory);
}
}
}
/// <summary>
@@ -422,14 +422,11 @@ namespace MediaBrowser.Providers.Manager
{
var changed = item.ValidateImages();
var foundImageTypes = new List<ImageType>();
for (var i = 0; i < _singularImages.Length; i++)
{
var type = _singularImages[i];
var image = GetFirstLocalImageInfoByType(images, type);
// Only use local images if we are not replacing and saving
if (image is not null && !(item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages))
if (image is not null)
{
var currentImage = item.GetImageInfo(type, 0);
// if image file is stored with media, don't replace that later

View File

@@ -92,10 +92,6 @@ namespace MediaBrowser.Providers.Manager
}
}
var localImagesFailed = false;
var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
{
if (ImageProvider.RemoveImages(item))
@@ -104,19 +100,29 @@ namespace MediaBrowser.Providers.Manager
}
}
// Start by validating images
try
var localImagesFailed = false;
var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
// Only validate already registered images if we are replacing and saving locally
if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
{
// Always validate images and check for new locally stored ones.
if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
{
updateType |= ItemUpdateType.ImageUpdate;
}
item.ValidateImages();
}
catch (Exception ex)
else
{
localImagesFailed = true;
Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
// Run full image validation and register new local images
try
{
if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
{
updateType |= ItemUpdateType.ImageUpdate;
}
}
catch (Exception ex)
{
localImagesFailed = true;
Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
}
}
var metadataResult = new MetadataResult<TItemType>
@@ -669,6 +675,8 @@ namespace MediaBrowser.Providers.Manager
};
temp.Item.Path = item.Path;
temp.Item.Id = item.Id;
temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
var foundImageTypes = new List<ImageType>();
@@ -811,19 +819,16 @@ namespace MediaBrowser.Providers.Manager
{
var refreshResult = new RefreshResult();
var tmpDataMerged = false;
if (id is not null)
{
MergeNewData(temp.Item, id);
}
foreach (var provider in providers)
{
var providerName = provider.GetType().Name;
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
if (id is not null && !tmpDataMerged)
{
MergeNewData(temp.Item, id);
tmpDataMerged = true;
}
try
{
var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -136,6 +137,10 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.IsLocked)
{
await FetchDataFromTags(audio, mediaInfo, options, tryExtractEmbeddedLyrics).ConfigureAwait(false);
if (tryExtractEmbeddedLyrics)
{
AddExternalLyrics(audio, mediaStreams, options);
}
}
audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric);
@@ -369,7 +374,10 @@ namespace MediaBrowser.Providers.MediaInfo
var externalLyricFiles = _lyricResolver.GetExternalStreams(audio, startIndex, options.DirectoryService, false);
audio.LyricFiles = externalLyricFiles.Select(i => i.Path).Distinct().ToArray();
currentStreams.AddRange(externalLyricFiles);
if (externalLyricFiles.Count > 0)
{
currentStreams.Add(externalLyricFiles[0]);
}
}
}
}

View File

@@ -358,6 +358,10 @@ namespace MediaBrowser.Providers.MediaInfo
blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRate : blurayVideoStream.BitRate;
blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Width;
blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Height;
blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
}
}

View File

@@ -1,7 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
@@ -18,182 +16,212 @@ using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using PlaylistsNET.Content;
namespace MediaBrowser.Providers.Playlists
namespace MediaBrowser.Providers.Playlists;
/// <summary>
/// Local playlist provider.
/// </summary>
public class PlaylistItemsProvider : ILocalMetadataProvider<Playlist>,
IHasOrder,
IForcedProvider,
IHasItemChangeMonitor
{
public class PlaylistItemsProvider : ICustomMetadataProvider<Playlist>,
IHasOrder,
IForcedProvider,
IPreRefreshProvider,
IHasItemChangeMonitor
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly ILogger<PlaylistItemsProvider> _logger;
private readonly CollectionType[] _ignoredCollections = [CollectionType.livetv, CollectionType.boxsets, CollectionType.playlists];
/// <summary>
/// Initializes a new instance of the <see cref="PlaylistItemsProvider"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{PlaylistItemsProvider}"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
public PlaylistItemsProvider(ILogger<PlaylistItemsProvider> logger, ILibraryManager libraryManager, IFileSystem fileSystem)
{
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly ILogger<PlaylistItemsProvider> _logger;
private readonly CollectionType[] _ignoredCollections = [CollectionType.livetv, CollectionType.boxsets, CollectionType.playlists];
_logger = logger;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
}
public PlaylistItemsProvider(ILogger<PlaylistItemsProvider> logger, ILibraryManager libraryManager, IFileSystem fileSystem)
/// <inheritdoc />
public string Name => "Playlist Item Provider";
/// <inheritdoc />
public int Order => 100;
/// <inheritdoc />
public Task<MetadataResult<Playlist>> GetMetadata(
ItemInfo info,
IDirectoryService directoryService,
CancellationToken cancellationToken)
{
var result = new MetadataResult<Playlist>()
{
_logger = logger;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
Item = new Playlist
{
Path = info.Path
}
};
Fetch(result);
return Task.FromResult(result);
}
private void Fetch(MetadataResult<Playlist> result)
{
var item = result.Item;
var path = item.Path;
if (!Playlist.IsPlaylistFile(path))
{
return;
}
public string Name => "Playlist Reader";
// Run last
public int Order => 100;
public Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
var extension = Path.GetExtension(path);
if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
var path = item.Path;
if (!Playlist.IsPlaylistFile(path))
{
return Task.FromResult(ItemUpdateType.None);
}
var extension = Path.GetExtension(path);
if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(ItemUpdateType.None);
}
var items = GetItems(path, extension).ToArray();
return;
}
var items = GetItems(path, extension).ToArray();
if (items.Length > 0)
{
result.HasMetadata = true;
item.LinkedChildren = items;
return Task.FromResult(ItemUpdateType.MetadataImport);
}
private IEnumerable<LinkedChild> GetItems(string path, string extension)
return;
}
private IEnumerable<LinkedChild> GetItems(string path, string extension)
{
var libraryRoots = _libraryManager.GetUserRootFolder().Children
.OfType<CollectionFolder>()
.Where(f => f.CollectionType.HasValue && !_ignoredCollections.Contains(f.CollectionType.Value))
.SelectMany(f => f.PhysicalLocations)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
using (var stream = File.OpenRead(path))
{
var libraryRoots = _libraryManager.GetUserRootFolder().Children
.OfType<CollectionFolder>()
.Where(f => f.CollectionType.HasValue && !_ignoredCollections.Contains(f.CollectionType.Value))
.SelectMany(f => f.PhysicalLocations)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
using (var stream = File.OpenRead(path))
if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase))
{
return GetWplItems(stream, path, libraryRoots);
}
if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase))
{
return GetZplItems(stream, path, libraryRoots);
}
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase))
{
return GetPlsItems(stream, path, libraryRoots);
}
return GetWplItems(stream, path, libraryRoots);
}
return Enumerable.Empty<LinkedChild>();
}
private IEnumerable<LinkedChild> GetPlsItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new PlsContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetM3uItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new M3uContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetZplItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new ZplContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetWplItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new WplContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private LinkedChild GetLinkedChild(string itemPath, string playlistPath, List<string> libraryRoots)
{
if (TryGetPlaylistItemPath(itemPath, playlistPath, libraryRoots, out var parsedPath))
if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase))
{
return new LinkedChild
{
Path = parsedPath,
Type = LinkedChildType.Manual
};
return GetZplItems(stream, path, libraryRoots);
}
return null;
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase))
{
return GetPlsItems(stream, path, libraryRoots);
}
}
private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List<string> libraryPaths, out string path)
return Enumerable.Empty<LinkedChild>();
}
private IEnumerable<LinkedChild> GetPlsItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new PlsContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetM3uItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new M3uContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetZplItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new ZplContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private IEnumerable<LinkedChild> GetWplItems(Stream stream, string playlistPath, List<string> libraryRoots)
{
var content = new WplContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries
.Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
.Where(i => i is not null);
}
private LinkedChild GetLinkedChild(string itemPath, string playlistPath, List<string> libraryRoots)
{
if (TryGetPlaylistItemPath(itemPath, playlistPath, libraryRoots, out var parsedPath))
{
path = null;
string pathToCheck = _fileSystem.MakeAbsolutePath(Path.GetDirectoryName(playlistPath), itemPath);
if (!File.Exists(pathToCheck))
return new LinkedChild
{
return false;
}
Path = parsedPath,
Type = LinkedChildType.Manual
};
}
foreach (var libraryPath in libraryPaths)
{
if (pathToCheck.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase))
{
path = pathToCheck;
return true;
}
}
return null;
}
private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List<string> libraryPaths, out string path)
{
path = null;
string pathToCheck = _fileSystem.MakeAbsolutePath(Path.GetDirectoryName(playlistPath), itemPath);
if (!File.Exists(pathToCheck))
{
return false;
}
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
foreach (var libraryPath in libraryPaths)
{
var path = item.Path;
if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
if (pathToCheck.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase))
{
var file = directoryService.GetFile(path);
if (file is not null && file.LastWriteTimeUtc != item.DateModified)
{
_logger.LogDebug("Refreshing {Path} due to date modified timestamp change.", path);
return true;
}
path = pathToCheck;
return true;
}
return false;
}
return false;
}
/// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
var path = item.Path;
if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
{
var file = directoryService.GetFile(path);
if (file is not null && file.LastWriteTimeUtc != item.DateModified)
{
_logger.LogDebug("Refreshing {Path} due to date modified timestamp change.", path);
return true;
}
}
return false;
}
}

View File

@@ -250,7 +250,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
// If we have a release ID but not a release group ID, lookup the release group
if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
{
var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false);
var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).ConfigureAwait(false);
releaseGroupId = release.ReleaseGroup?.Id.ToString();
result.HasMetadata = true;
}

View File

@@ -61,8 +61,8 @@ namespace MediaBrowser.Providers.TV
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
RemoveObsoleteEpisodes(item);
RemoveObsoleteSeasons(item);
await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
RemoveObsoleteSeasons(item);
}
/// <inheritdoc />
@@ -91,7 +91,7 @@ namespace MediaBrowser.Providers.TV
private void RemoveObsoleteSeasons(Series series)
{
// TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in UpdateAndCreateSeasonsAsync.
// TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync.
var physicalSeasonNumbers = new HashSet<int>();
var virtualSeasons = new List<Season>();
foreach (var existingSeason in series.Children.OfType<Season>())
@@ -203,11 +203,16 @@ namespace MediaBrowser.Providers.TV
foreach (var seasonNumber in uniqueSeasonNumbers)
{
// Null season numbers will have a 'dummy' season created because seasons are always required.
if (!seasons.Any(i => i.IndexNumber == seasonNumber))
var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
if (existingSeason is null)
{
var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
var season = await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
series.AddChild(season);
await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
}
else if (existingSeason.IsVirtualItem)
{
existingSeason.IsVirtualItem = false;
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -220,7 +225,7 @@ namespace MediaBrowser.Providers.TV
/// <param name="seasonNumber">The season number.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The newly created season.</returns>
private async Task<Season> CreateSeasonAsync(
private async Task CreateSeasonAsync(
Series series,
string? seasonName,
int? seasonNumber,
@@ -237,14 +242,12 @@ namespace MediaBrowser.Providers.TV
typeof(Season)),
IsVirtualItem = false,
SeriesId = series.Id,
SeriesName = series.Name
SeriesName = series.Name,
SeriesPresentationUniqueKey = series.GetPresentationUniqueKey()
};
series.AddChild(season);
await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false);
return season;
}
private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber)

View File

@@ -519,7 +519,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (reader.TryReadDateTimeExact(nfoConfiguration.ReleaseDateFormat, out var releaseDate))
{
item.PremiereDate = releaseDate;
item.ProductionYear = releaseDate.Year;
// Production year can already be set by the year tag
item.ProductionYear ??= releaseDate.Year;
}
break;

View File

@@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("10.9.6")]
[assembly: AssemblyFileVersion("10.9.6")]
[assembly: AssemblyVersion("10.9.8")]
[assembly: AssemblyFileVersion("10.9.8")]

View File

@@ -15,7 +15,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.9.6</VersionPrefix>
<VersionPrefix>10.9.8</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -919,10 +919,14 @@ public class NetworkManager : INetworkManager, IDisposable
{
ArgumentNullException.ThrowIfNull(address);
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
// Map IPv6 mapped IPv4 back to IPv4 (happens if Kestrel runs in dual-socket mode)
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
|| address.Equals(IPAddress.Loopback)
|| address.Equals(IPAddress.IPv6Loopback))
|| IPAddress.IsLoopback(address))
{
return true;
}

View File

@@ -18,7 +18,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
public class ProbeResultNormalizerTests
{
private readonly JsonSerializerOptions _jsonOptions;
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null);
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), new Mock<ILocalizationManager>().Object);
public ProbeResultNormalizerTests()
{

View File

@@ -104,6 +104,7 @@ public class ExternalPathParserTests
[InlineData(".en.cc.title", "title", "eng", false, false, true)]
[InlineData(".hi.en.title", "title", "eng", false, false, true)]
[InlineData(".en.hi.title", "title", "eng", false, false, true)]
[InlineData(".Subs for Chinese Audio.eng", "Subs for Chinese Audio", "eng", false, false, false)]
public void ParseFile_ExtraTokens_ParseToValues(string tokens, string? title, string? language, bool isDefault = false, bool isForced = false, bool isHearingImpaired = false)
{
var path = "My.Video" + tokens + ".srt";