Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b5d458456 | |||
| f41efb3b2c | |||
| 0155293c64 | |||
| b78efd6b1e | |||
| bfcc09db8a | |||
| a46c17e19f | |||
| b0bb22b650 | |||
| 0c039145e5 | |||
| 2a3c904a9f | |||
| 7cbdb6708b | |||
| 7058db2b04 | |||
| 8f7df590cd | |||
| 0f67a5ba2f | |||
| 19fb00b5b7 | |||
| 8683253c6d | |||
| 918a36d564 | |||
| 57ae0b5796 | |||
| 262f7dd98f | |||
| 4714b3af67 | |||
| b14edb8876 | |||
| 47c5e0c2c7 | |||
| 8709d94783 | |||
| 23b1251393 | |||
| 484aea1cdb | |||
| d1c00ba4ed | |||
| b1a5fe2f55 | |||
| d7ff6d023c | |||
| 253e95dcba | |||
| c7ce1aa4c7 | |||
| 3d87885577 | |||
| a7e2271845 | |||
| 2a02abee46 |
@@ -36,7 +36,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -98,9 +98,9 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SqliteConnection GetConnection()
|
protected SqliteConnection GetConnection(bool readOnly = false)
|
||||||
{
|
{
|
||||||
var connection = new SqliteConnection($"Filename={DbFilePath}");
|
var connection = new SqliteConnection($"Filename={DbFilePath}" + (readOnly ? ";Mode=ReadOnly" : string.Empty));
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
if (CacheSize.HasValue)
|
if (CacheSize.HasValue)
|
||||||
|
|||||||
@@ -1261,7 +1261,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
|
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
|
||||||
{
|
{
|
||||||
statement.TryBind("@guid", id);
|
statement.TryBind("@guid", id);
|
||||||
@@ -1887,7 +1887,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
var chapters = new List<ChapterInfo>();
|
var chapters = new List<ChapterInfo>();
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
|
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", item.Id);
|
statement.TryBind("@ItemId", item.Id);
|
||||||
@@ -1906,7 +1906,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"))
|
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", item.Id);
|
statement.TryBind("@ItemId", item.Id);
|
||||||
@@ -2469,7 +2469,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var commandText = commandTextBuilder.ToString();
|
var commandText = commandTextBuilder.ToString();
|
||||||
|
|
||||||
using (new QueryTimeLogger(Logger, commandText))
|
using (new QueryTimeLogger(Logger, commandText))
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText))
|
using (var statement = PrepareStatement(connection, commandText))
|
||||||
{
|
{
|
||||||
if (EnableJoinUserData(query))
|
if (EnableJoinUserData(query))
|
||||||
@@ -2537,7 +2537,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var commandText = commandTextBuilder.ToString();
|
var commandText = commandTextBuilder.ToString();
|
||||||
var items = new List<BaseItem>();
|
var items = new List<BaseItem>();
|
||||||
using (new QueryTimeLogger(Logger, commandText))
|
using (new QueryTimeLogger(Logger, commandText))
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText))
|
using (var statement = PrepareStatement(connection, commandText))
|
||||||
{
|
{
|
||||||
if (EnableJoinUserData(query))
|
if (EnableJoinUserData(query))
|
||||||
@@ -2745,7 +2745,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
var list = new List<BaseItem>();
|
var list = new List<BaseItem>();
|
||||||
var result = new QueryResult<BaseItem>();
|
var result = new QueryResult<BaseItem>();
|
||||||
using var connection = GetConnection();
|
using var connection = GetConnection(true);
|
||||||
using var transaction = connection.BeginTransaction();
|
using var transaction = connection.BeginTransaction();
|
||||||
if (!isReturningZeroItems)
|
if (!isReturningZeroItems)
|
||||||
{
|
{
|
||||||
@@ -2927,7 +2927,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var commandText = commandTextBuilder.ToString();
|
var commandText = commandTextBuilder.ToString();
|
||||||
var list = new List<Guid>();
|
var list = new List<Guid>();
|
||||||
using (new QueryTimeLogger(Logger, commandText))
|
using (new QueryTimeLogger(Logger, commandText))
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText))
|
using (var statement = PrepareStatement(connection, commandText))
|
||||||
{
|
{
|
||||||
if (EnableJoinUserData(query))
|
if (EnableJoinUserData(query))
|
||||||
@@ -4509,7 +4509,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<string>();
|
var list = new List<string>();
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
||||||
{
|
{
|
||||||
// Run this again to bind the params
|
// Run this again to bind the params
|
||||||
@@ -4547,7 +4547,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<PersonInfo>();
|
var list = new List<PersonInfo>();
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
||||||
{
|
{
|
||||||
// Run this again to bind the params
|
// Run this again to bind the params
|
||||||
@@ -4787,7 +4787,7 @@ AND Type = @InternalPersonType)");
|
|||||||
|
|
||||||
var list = new List<string>();
|
var list = new List<string>();
|
||||||
using (new QueryTimeLogger(Logger, commandText))
|
using (new QueryTimeLogger(Logger, commandText))
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, commandText))
|
using (var statement = PrepareStatement(connection, commandText))
|
||||||
{
|
{
|
||||||
foreach (var row in statement.ExecuteQuery())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
@@ -4987,8 +4987,8 @@ AND Type = @InternalPersonType)");
|
|||||||
var list = new List<(BaseItem, ItemCounts)>();
|
var list = new List<(BaseItem, ItemCounts)>();
|
||||||
var result = new QueryResult<(BaseItem, ItemCounts)>();
|
var result = new QueryResult<(BaseItem, ItemCounts)>();
|
||||||
using (new QueryTimeLogger(Logger, commandText))
|
using (new QueryTimeLogger(Logger, commandText))
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var transaction = connection.BeginTransaction(deferred: true))
|
using (var transaction = connection.BeginTransaction())
|
||||||
{
|
{
|
||||||
if (!isReturningZeroItems)
|
if (!isReturningZeroItems)
|
||||||
{
|
{
|
||||||
@@ -5335,7 +5335,7 @@ AND Type = @InternalPersonType)");
|
|||||||
|
|
||||||
cmdText += " order by StreamIndex ASC";
|
cmdText += " order by StreamIndex ASC";
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
{
|
{
|
||||||
var list = new List<MediaStream>();
|
var list = new List<MediaStream>();
|
||||||
|
|
||||||
@@ -5722,7 +5722,7 @@ AND Type = @InternalPersonType)");
|
|||||||
cmdText += " order by AttachmentIndex ASC";
|
cmdText += " order by AttachmentIndex ASC";
|
||||||
|
|
||||||
var list = new List<MediaAttachment>();
|
var list = new List<MediaAttachment>();
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = PrepareStatement(connection, cmdText))
|
using (var statement = PrepareStatement(connection, cmdText))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", query.ItemId);
|
statement.TryBind("@ItemId", query.ItemId);
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection(true))
|
||||||
{
|
{
|
||||||
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
|
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1029,7 +1029,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
|
public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
|
||||||
{
|
{
|
||||||
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
IndexNumber = seasonParserResult.SeasonNumber,
|
IndexNumber = seasonParserResult.SeasonNumber,
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
SeriesName = series.Name,
|
SeriesName = series.Name,
|
||||||
Path = seasonParserResult.IsSeasonFolder ? path : args.Parent.Path
|
Path = seasonParserResult.IsSeasonFolder ? path : null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder)
|
if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.TV
|
|||||||
}
|
}
|
||||||
|
|
||||||
string? presentationUniqueKey = null;
|
string? presentationUniqueKey = null;
|
||||||
int? limit = null;
|
int? limit = request.Limit;
|
||||||
if (!request.SeriesId.IsNullOrEmpty())
|
if (!request.SeriesId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
|
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
|
||||||
|
|||||||
@@ -180,7 +180,21 @@ public class LibraryStructureController : BaseJellyfinApiController
|
|||||||
// No need to start if scanning the library because it will handle it
|
// No need to start if scanning the library because it will handle it
|
||||||
if (refreshLibrary)
|
if (refreshLibrary)
|
||||||
{
|
{
|
||||||
await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false);
|
await _libraryManager.ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false);
|
||||||
|
var newLib = _libraryManager.GetUserRootFolder().Children.FirstOrDefault(f => f.Path.Equals(newPath, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (newLib is CollectionFolder folder)
|
||||||
|
{
|
||||||
|
foreach (var child in folder.GetPhysicalFolders())
|
||||||
|
{
|
||||||
|
await child.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
await child.ValidateChildren(new Progress<double>(), CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We don't know if this one can be validated individually, trigger a new validation
|
||||||
|
await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Backing up {Library} to {BackupPath}", DbFilename, bakPath);
|
||||||
File.Copy(dbPath, bakPath);
|
File.Copy(dbPath, bakPath);
|
||||||
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
|
_logger.LogInformation("{Library} backed up to {BackupPath}", DbFilename, bakPath);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -80,7 +81,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
{
|
{
|
||||||
IncludeItemTypes = [BaseItemKind.Audio],
|
IncludeItemTypes = [BaseItemKind.Audio],
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
Limit = 100,
|
Limit = 5000,
|
||||||
SkipDeserialization = true
|
SkipDeserialization = true
|
||||||
})
|
})
|
||||||
.Cast<Audio>()
|
.Cast<Audio>()
|
||||||
@@ -97,7 +98,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.SaveItems(results, CancellationToken.None);
|
_itemRepository.SaveItems(results, CancellationToken.None);
|
||||||
startIndex += 100;
|
startIndex += results.Count;
|
||||||
|
_logger.LogInformation("Backfilled data for {UpdatedRecords} of {TotalRecords} audio records", startIndex, records);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Common</PackageId>
|
<PackageId>Jellyfin.Common</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -149,6 +149,14 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken);
|
Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reloads the root media folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="removeRoot">Is remove the library itself allowed.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false);
|
||||||
|
|
||||||
Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false);
|
Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -28,6 +28,22 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
return _cache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem);
|
return _cache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FileSystemMetadata> GetDirectories(string path)
|
||||||
|
{
|
||||||
|
var list = new List<FileSystemMetadata>();
|
||||||
|
var items = GetFileSystemEntries(path);
|
||||||
|
for (var i = 0; i < items.Length; i++)
|
||||||
|
{
|
||||||
|
var item = items[i];
|
||||||
|
if (item.IsDirectory)
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FileSystemMetadata> GetFiles(string path)
|
public List<FileSystemMetadata> GetFiles(string path)
|
||||||
{
|
{
|
||||||
var list = new List<FileSystemMetadata>();
|
var list = new List<FileSystemMetadata>();
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
{
|
{
|
||||||
FileSystemMetadata[] GetFileSystemEntries(string path);
|
FileSystemMetadata[] GetFileSystemEntries(string path);
|
||||||
|
|
||||||
|
List<FileSystemMetadata> GetDirectories(string path);
|
||||||
|
|
||||||
List<FileSystemMetadata> GetFiles(string path);
|
List<FileSystemMetadata> GetFiles(string path);
|
||||||
|
|
||||||
FileSystemMetadata? GetFile(string path);
|
FileSystemMetadata? GetFile(string path);
|
||||||
|
|||||||
@@ -140,6 +140,14 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
|
IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
|
||||||
where T : BaseItem;
|
where T : BaseItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the metadata savers for the provided item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="libraryOptions">The library options.</param>
|
||||||
|
/// <returns>The metadata savers.</returns>
|
||||||
|
IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all metadata plugins.
|
/// Gets all metadata plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -38,19 +38,28 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||||||
}
|
}
|
||||||
|
|
||||||
var parentPathFiles = directoryService.GetFiles(parentPath);
|
var parentPathFiles = directoryService.GetFiles(parentPath);
|
||||||
|
var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path.AsSpan()).ToString();
|
||||||
|
|
||||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path.AsSpan());
|
var thumbName = string.Concat(nameWithoutExtension, "-thumb");
|
||||||
|
var images = GetImageFilesFromFolder(thumbName, parentPathFiles);
|
||||||
|
|
||||||
return GetFilesFromParentFolder(nameWithoutExtension, parentPathFiles);
|
var metadataSubPath = directoryService.GetDirectories(parentPath).Where(d => d.Name.EndsWith("metadata", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
foreach (var path in metadataSubPath)
|
||||||
|
{
|
||||||
|
var files = directoryService.GetFiles(path.FullName);
|
||||||
|
images.AddRange(GetImageFilesFromFolder(nameWithoutExtension, files));
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LocalImageInfo> GetFilesFromParentFolder(ReadOnlySpan<char> filenameWithoutExtension, List<FileSystemMetadata> parentPathFiles)
|
private List<LocalImageInfo> GetImageFilesFromFolder(ReadOnlySpan<char> filenameWithoutExtension, List<FileSystemMetadata> filePaths)
|
||||||
{
|
{
|
||||||
var thumbName = string.Concat(filenameWithoutExtension, "-thumb");
|
var thumbName = string.Concat(filenameWithoutExtension, "-thumb");
|
||||||
|
|
||||||
var list = new List<LocalImageInfo>(1);
|
var list = new List<LocalImageInfo>(1);
|
||||||
|
|
||||||
foreach (var i in parentPathFiles)
|
foreach (var i in filePaths)
|
||||||
{
|
{
|
||||||
if (i.IsDirectory)
|
if (i.IsDirectory)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
saveLocally = false;
|
saveLocally = false;
|
||||||
|
|
||||||
// If season is virtual under a physical series, save locally if using compatible convention
|
// If season is virtual under a physical series, save locally
|
||||||
if (item is Season season && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
|
if (item is Season season)
|
||||||
{
|
{
|
||||||
var series = season.Series;
|
var series = season.Series;
|
||||||
|
|
||||||
@@ -126,7 +126,11 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
|
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
|
||||||
|
|
||||||
var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
|
string[] retryPaths = [];
|
||||||
|
if (saveLocally)
|
||||||
|
{
|
||||||
|
retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
|
||||||
|
}
|
||||||
|
|
||||||
// If there are more than one output paths, the stream will need to be seekable
|
// If there are more than one output paths, the stream will need to be seekable
|
||||||
if (paths.Length > 1 && !source.CanSeek)
|
if (paths.Length > 1 && !source.CanSeek)
|
||||||
@@ -183,6 +187,13 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_fileSystem.DeleteFile(currentPath);
|
_fileSystem.DeleteFile(currentPath);
|
||||||
|
|
||||||
|
// Remove containing directory if empty
|
||||||
|
var folder = Path.GetDirectoryName(currentPath);
|
||||||
|
if (!_fileSystem.GetFiles(folder).Any())
|
||||||
|
{
|
||||||
|
Directory.Delete(folder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
@@ -374,6 +385,45 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
|
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
extension = ".jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
extension = extension.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (type == ImageType.Primary && saveLocally)
|
||||||
|
{
|
||||||
|
if (season is not null && season.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
var seriesFolder = season.SeriesPath;
|
||||||
|
|
||||||
|
var seasonMarker = season.IndexNumber.Value == 0
|
||||||
|
? "-specials"
|
||||||
|
: season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
var imageFilename = "season" + seasonMarker + "-poster" + extension;
|
||||||
|
|
||||||
|
return Path.Combine(seriesFolder, imageFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == ImageType.Backdrop && saveLocally)
|
||||||
|
{
|
||||||
|
if (season is not null && season.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
var seriesFolder = season.SeriesPath;
|
||||||
|
|
||||||
|
var seasonMarker = season.IndexNumber.Value == 0
|
||||||
|
? "-specials"
|
||||||
|
: season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
var imageFilename = "season" + seasonMarker + "-fanart" + extension;
|
||||||
|
|
||||||
|
return Path.Combine(seriesFolder, imageFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == ImageType.Thumb && saveLocally)
|
if (type == ImageType.Thumb && saveLocally)
|
||||||
{
|
{
|
||||||
if (season is not null && season.IndexNumber.HasValue)
|
if (season is not null && season.IndexNumber.HasValue)
|
||||||
@@ -447,20 +497,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
extension = ".jpg";
|
|
||||||
}
|
|
||||||
|
|
||||||
extension = extension.ToLowerInvariant();
|
|
||||||
|
|
||||||
string path = null;
|
string path = null;
|
||||||
|
|
||||||
if (saveLocally)
|
if (saveLocally)
|
||||||
{
|
{
|
||||||
if (type == ImageType.Primary && item is Episode)
|
if (type == ImageType.Primary && item is Episode)
|
||||||
{
|
{
|
||||||
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
|
path = Path.Combine(Path.GetDirectoryName(item.Path), filename + "-thumb" + extension);
|
||||||
}
|
}
|
||||||
else if (item.IsInMixedFolder)
|
else if (item.IsInMixedFolder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -371,12 +371,21 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
// nothing to do, already gone
|
// Nothing to do, already gone
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +428,8 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
var type = _singularImages[i];
|
var type = _singularImages[i];
|
||||||
var image = GetFirstLocalImageInfoByType(images, type);
|
var image = GetFirstLocalImageInfoByType(images, type);
|
||||||
|
|
||||||
if (image is not null)
|
// Only use local images if we are not replacing and saving
|
||||||
|
if (image is not null && !(item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages))
|
||||||
{
|
{
|
||||||
var currentImage = item.GetImageInfo(type, 0);
|
var currentImage = item.GetImageInfo(type, 0);
|
||||||
// if image file is stored with media, don't replace that later
|
// if image file is stored with media, don't replace that later
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
id.IsAutomated = refreshOptions.IsAutomated;
|
id.IsAutomated = refreshOptions.IsAutomated;
|
||||||
|
|
||||||
var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, cancellationToken).ConfigureAwait(false);
|
var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
|
||||||
|
var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, hasMetadataSavers, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
updateType |= result.UpdateType;
|
updateType |= result.UpdateType;
|
||||||
if (result.Failures > 0)
|
if (result.Failures > 0)
|
||||||
@@ -639,6 +640,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
MetadataRefreshOptions options,
|
MetadataRefreshOptions options,
|
||||||
ICollection<IMetadataProvider> providers,
|
ICollection<IMetadataProvider> providers,
|
||||||
ItemImageProvider imageService,
|
ItemImageProvider imageService,
|
||||||
|
bool isSavingMetadata,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var refreshResult = new RefreshResult
|
var refreshResult = new RefreshResult
|
||||||
@@ -669,69 +671,74 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
temp.Item.Id = item.Id;
|
temp.Item.Id = item.Id;
|
||||||
|
|
||||||
var foundImageTypes = new List<ImageType>();
|
var foundImageTypes = new List<ImageType>();
|
||||||
foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
|
|
||||||
|
// Do not execute local providers if we are identifying or replacing with local metadata saving enabled
|
||||||
|
if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
|
||||||
{
|
{
|
||||||
var providerName = provider.GetType().Name;
|
foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
|
||||||
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
|
|
||||||
|
|
||||||
var itemInfo = new ItemInfo(item);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
|
var providerName = provider.GetType().Name;
|
||||||
|
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
|
||||||
|
|
||||||
if (localItem.HasMetadata)
|
var itemInfo = new ItemInfo(item);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var remoteImage in localItem.RemoteImages)
|
var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (localItem.HasMetadata)
|
||||||
{
|
{
|
||||||
try
|
foreach (var remoteImage in localItem.RemoteImages)
|
||||||
{
|
{
|
||||||
if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
|
try
|
||||||
&& !options.IsReplacingImage(remoteImage.Type))
|
|
||||||
{
|
{
|
||||||
continue;
|
if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
|
||||||
|
&& !options.IsReplacingImage(remoteImage.Type))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
|
||||||
|
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||||
|
|
||||||
|
// remember imagetype that has just been downloaded
|
||||||
|
foundImageTypes.Add(remoteImage.Type);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
|
|
||||||
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
|
|
||||||
|
|
||||||
// remember imagetype that has just been downloaded
|
|
||||||
foundImageTypes.Add(remoteImage.Type);
|
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex)
|
|
||||||
|
if (foundImageTypes.Count > 0)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
|
imageService.UpdateReplaceImages(options, foundImageTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imageService.MergeImages(item, localItem.Images, options))
|
||||||
|
{
|
||||||
|
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true);
|
||||||
|
refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundImageTypes.Count > 0)
|
Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
|
||||||
{
|
|
||||||
imageService.UpdateReplaceImages(options, foundImageTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageService.MergeImages(item, localItem.Images, options))
|
|
||||||
{
|
|
||||||
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true);
|
|
||||||
refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error in {Provider}", provider.Name);
|
||||||
|
|
||||||
Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
|
// If a local provider fails, consider that a failure
|
||||||
}
|
refreshResult.ErrorMessage = ex.Message;
|
||||||
catch (OperationCanceledException)
|
}
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error in {Provider}", provider.Name);
|
|
||||||
|
|
||||||
// If a local provider fails, consider that a failure
|
|
||||||
refreshResult.ErrorMessage = ex.Message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,7 +770,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.ReplaceAllMetadata;
|
var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.ReplaceAllMetadata;
|
||||||
MergeData(temp, metadata, item.LockedFields, shouldReplace, false);
|
MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1080,7 +1087,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).Distinct().ToArray();
|
target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
MergeAlbumArtist(source, target, replaceData);
|
MergeAlbumArtist(source, target, replaceData);
|
||||||
|
|||||||
@@ -418,6 +418,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
|
return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
|
||||||
|
{
|
||||||
|
return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false));
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata)
|
private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata)
|
||||||
where T : BaseItem
|
where T : BaseItem
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ namespace MediaBrowser.Providers.TV
|
|||||||
virtualSeason,
|
virtualSeason,
|
||||||
new DeleteOptions
|
new DeleteOptions
|
||||||
{
|
{
|
||||||
DeleteFileLocation = true
|
// Internal metadata paths are removed regardless of this.
|
||||||
|
DeleteFileLocation = false
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
@@ -176,7 +177,8 @@ namespace MediaBrowser.Providers.TV
|
|||||||
episode,
|
episode,
|
||||||
new DeleteOptions
|
new DeleteOptions
|
||||||
{
|
{
|
||||||
DeleteFileLocation = true
|
// Internal metadata paths are removed regardless of this.
|
||||||
|
DeleteFileLocation = false
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -825,7 +825,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
private string GetOutputTrailerUrl(string url)
|
private string GetOutputTrailerUrl(string url)
|
||||||
{
|
{
|
||||||
// This is what xbmc expects
|
// This is what xbmc expects
|
||||||
return url.Replace(YouTubeWatchUrl, "plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase);
|
return url.Replace(YouTubeWatchUrl, "plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager)
|
private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager)
|
||||||
|
|||||||
@@ -45,27 +45,24 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
|
|
||||||
internal static IEnumerable<string> GetMovieSavePaths(ItemInfo item)
|
internal static IEnumerable<string> GetMovieSavePaths(ItemInfo item)
|
||||||
{
|
{
|
||||||
|
var path = item.ContainingFolderPath;
|
||||||
if (item.VideoType == VideoType.Dvd && !item.IsPlaceHolder)
|
if (item.VideoType == VideoType.Dvd && !item.IsPlaceHolder)
|
||||||
{
|
{
|
||||||
var path = item.ContainingFolderPath;
|
|
||||||
|
|
||||||
yield return Path.Combine(path, "VIDEO_TS", "VIDEO_TS.nfo");
|
yield return Path.Combine(path, "VIDEO_TS", "VIDEO_TS.nfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only allow movie object to read movie.nfo, not owned videos (which will be itemtype video, not movie)
|
||||||
|
if (!item.IsInMixedFolder && item.ItemType == typeof(Movie))
|
||||||
|
{
|
||||||
|
yield return Path.Combine(path, "movie.nfo");
|
||||||
|
}
|
||||||
|
|
||||||
if (!item.IsPlaceHolder && (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay))
|
if (!item.IsPlaceHolder && (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay))
|
||||||
{
|
{
|
||||||
var path = item.ContainingFolderPath;
|
|
||||||
|
|
||||||
yield return Path.Combine(path, Path.GetFileName(path) + ".nfo");
|
yield return Path.Combine(path, Path.GetFileName(path) + ".nfo");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// only allow movie object to read movie.nfo, not owned videos (which will be itemtype video, not movie)
|
|
||||||
if (!item.IsInMixedFolder && item.ItemType == typeof(Movie))
|
|
||||||
{
|
|
||||||
yield return Path.Combine(item.ContainingFolderPath, "movie.nfo");
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return Path.ChangeExtension(item.Path, ".nfo");
|
yield return Path.ChangeExtension(item.Path, ".nfo");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.9.4")]
|
[assembly: AssemblyVersion("10.9.5")]
|
||||||
[assembly: AssemblyFileVersion("10.9.4")]
|
[assembly: AssemblyFileVersion("10.9.5")]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Extensions</PackageId>
|
<PackageId>Jellyfin.Extensions</PackageId>
|
||||||
<VersionPrefix>10.9.4</VersionPrefix>
|
<VersionPrefix>10.9.5</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -575,18 +575,22 @@ namespace Jellyfin.Providers.Tests.Manager
|
|||||||
// Has to exist for querying DateModified time on file, results stored but not checked so not populating
|
// Has to exist for querying DateModified time on file, results stored but not checked so not populating
|
||||||
BaseItem.FileSystem ??= Mock.Of<IFileSystem>();
|
BaseItem.FileSystem ??= Mock.Of<IFileSystem>();
|
||||||
|
|
||||||
var item = new Video();
|
var item = new Mock<Video>
|
||||||
|
{
|
||||||
|
CallBase = true
|
||||||
|
};
|
||||||
|
item.Setup(m => m.IsSaveLocalMetadataEnabled()).Returns(false);
|
||||||
|
|
||||||
var path = validPaths ? _testDataImagePath.Format : "invalid path {0}";
|
var path = validPaths ? _testDataImagePath.Format : "invalid path {0}";
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
item.SetImagePath(type, i, new FileSystemMetadata
|
item.Object.SetImagePath(type, i, new FileSystemMetadata
|
||||||
{
|
{
|
||||||
FullName = string.Format(CultureInfo.InvariantCulture, path, i),
|
FullName = string.Format(CultureInfo.InvariantCulture, path, i),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ILocalImageProvider GetImageProvider(ImageType type, int count, bool validPaths)
|
private static ILocalImageProvider GetImageProvider(ImageType type, int count, bool validPaths)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Location
|
|||||||
var movie = new Movie() { Path = "/media/movies/Avengers Endgame", VideoType = VideoType.Dvd };
|
var movie = new Movie() { Path = "/media/movies/Avengers Endgame", VideoType = VideoType.Dvd };
|
||||||
var path1 = "/media/movies/Avengers Endgame/Avengers Endgame.nfo";
|
var path1 = "/media/movies/Avengers Endgame/Avengers Endgame.nfo";
|
||||||
var path2 = "/media/movies/Avengers Endgame/VIDEO_TS/VIDEO_TS.nfo";
|
var path2 = "/media/movies/Avengers Endgame/VIDEO_TS/VIDEO_TS.nfo";
|
||||||
|
var path3 = "/media/movies/Avengers Endgame/movie.nfo";
|
||||||
|
|
||||||
// uses ContainingFolderPath which uses Operating system specific paths
|
// uses ContainingFolderPath which uses Operating system specific paths
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
@@ -54,12 +55,14 @@ namespace Jellyfin.XbmcMetadata.Tests.Location
|
|||||||
movie.Path = movie.Path.Replace('/', '\\');
|
movie.Path = movie.Path.Replace('/', '\\');
|
||||||
path1 = path1.Replace('/', '\\');
|
path1 = path1.Replace('/', '\\');
|
||||||
path2 = path2.Replace('/', '\\');
|
path2 = path2.Replace('/', '\\');
|
||||||
|
path3 = path3.Replace('/', '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
var paths = MovieNfoSaver.GetMovieSavePaths(new ItemInfo(movie)).ToArray();
|
var paths = MovieNfoSaver.GetMovieSavePaths(new ItemInfo(movie)).ToArray();
|
||||||
Assert.Equal(2, paths.Length);
|
Assert.Equal(3, paths.Length);
|
||||||
Assert.Contains(path1, paths);
|
Assert.Contains(path1, paths);
|
||||||
Assert.Contains(path2, paths);
|
Assert.Contains(path2, paths);
|
||||||
|
Assert.Contains(path3, paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user