diff options
Diffstat (limited to 'MediaBrowser.Controller')
15 files changed, 86 insertions, 55 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d9d2d0e3a..cb38b6111 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -22,7 +22,6 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -2053,6 +2052,9 @@ namespace MediaBrowser.Controller.Entities public virtual async Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken) => await LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken).ConfigureAwait(false); + public async Task ReattachUserDataAsync(CancellationToken cancellationToken) => + await LibraryManager.ReattachUserDataAsync(this, cancellationToken).ConfigureAwait(false); + /// <summary> /// Validates that images within the item are still on the filesystem. /// </summary> @@ -2126,17 +2128,6 @@ namespace MediaBrowser.Controller.Entities }; } - // Music albums usually don't have dedicated backdrops, so return one from the artist instead - if (GetType() == typeof(MusicAlbum) && imageType == ImageType.Backdrop) - { - var artist = FindParent<MusicArtist>(); - - if (artist is not null) - { - return artist.GetImages(imageType).ElementAtOrDefault(imageIndex); - } - } - return GetImages(imageType) .ElementAtOrDefault(imageIndex); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d2a3290c4..2ecb6cbdf 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -452,6 +452,7 @@ namespace MediaBrowser.Controller.Entities // That's all the new and changed ones - now see if any have been removed and need cleanup var itemsRemoved = currentChildren.Values.Except(validChildren).ToList(); var shouldRemove = !IsRoot || allowRemoveRoot; + var actuallyRemoved = new List<BaseItem>(); // If it's an AggregateFolder, don't remove if (shouldRemove && itemsRemoved.Count > 0) { @@ -467,6 +468,7 @@ namespace MediaBrowser.Controller.Entities { Logger.LogDebug("Removed item: {Path}", item.Path); + actuallyRemoved.Add(item); item.SetParent(null); LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false); } @@ -477,6 +479,20 @@ namespace MediaBrowser.Controller.Entities { LibraryManager.CreateItems(newItems, this, cancellationToken); } + + // After removing items, reattach any detached user data to remaining children + // that share the same user data keys (eg. same episode replaced with a new file). + if (actuallyRemoved.Count > 0) + { + var removedKeys = actuallyRemoved.SelectMany(i => i.GetUserDataKeys()).ToHashSet(); + foreach (var child in validChildren) + { + if (child.GetUserDataKeys().Any(removedKeys.Contains)) + { + await child.ReattachUserDataAsync(cancellationToken).ConfigureAwait(false); + } + } + } } else { diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index b972ebaa6..4360253b0 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -201,12 +201,17 @@ namespace MediaBrowser.Controller.Entities.TV public List<BaseItem> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes, DtoOptions options, bool shouldIncludeMissingEpisodes) { + if (series is null) + { + return []; + } + return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes); } public List<BaseItem> GetEpisodes() { - return Series.GetSeasonEpisodes(this, null, null, new DtoOptions(true), true); + return GetEpisodes(Series, null, null, new DtoOptions(true), true); } public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 6396631f9..6a26ecaeb 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -451,7 +451,7 @@ namespace MediaBrowser.Controller.Entities.TV if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual) { - return true; + return episodeItem.Season is null or { LocationType: LocationType.Virtual }; } var season = episodeItem.Season; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index fcc5ed672..df1c98f3f 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -282,6 +282,14 @@ namespace MediaBrowser.Controller.Library Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); /// <summary> + /// Reattaches the user data to the item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A task that represents the asynchronous reattachment operation.</returns> + Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken); + + /// <summary> /// Retrieves the item. /// </summary> /// <param name="id">The id.</param> @@ -652,5 +660,12 @@ namespace MediaBrowser.Controller.Library /// This exists so plugins can trigger a library scan. /// </remarks> void QueueLibraryScan(); + + /// <summary> + /// Add mblink file for a media path. + /// </summary> + /// <param name="virtualFolderPath">The path to the virtualfolder.</param> + /// <param name="pathInfo">The new virtualfolder.</param> + public void CreateShortcut(string virtualFolderPath, MediaPathInfo pathInfo); } } diff --git a/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs b/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs index 2811a081a..6da398129 100644 --- a/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs +++ b/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs @@ -188,7 +188,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr await item.Worker(item.Data).ConfigureAwait(true); } - catch (System.Exception ex) + catch (Exception ex) { _logger.LogError(ex, "Error while performing a library operation"); } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index b5d14e94b..0025080cc 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -19,9 +19,7 @@ <ItemGroup> <PackageReference Include="BitFaster.Caching" /> - <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" /> - <PackageReference Include="System.Threading.Tasks.Dataflow" /> </ItemGroup> <ItemGroup> @@ -36,7 +34,7 @@ </ItemGroup> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <PublishRepositoryUrl>true</PublishRepositoryUrl> diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs index 20f51ddb7..10f2f04af 100644 --- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs @@ -43,8 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding public bool AllowAudioStreamCopy { get; set; } - public bool BreakOnNonKeyFrames { get; set; } - /// <summary> /// Gets or sets the audio sample rate. /// </summary> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91d88dc08..11eee1a37 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2914,8 +2914,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (time > 0) { - // For direct streaming/remuxing, we seek at the exact position of the keyframe - // However, ffmpeg will seek to previous keyframe when the exact time is the input + // For direct streaming/remuxing, HLS segments start at keyframes. + // However, ffmpeg will seek to previous keyframe when the exact frame time is the input // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos. // This will help subtitle syncing. var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec); @@ -2932,17 +2932,16 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest) { - var outputVideoCodec = GetVideoEncoder(state, options); - var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.'); - - // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients, - // but it's still required for fMP4 container otherwise the audio can't be synced to the video. - if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase) - && state.TranscodingType != TranscodingJobType.Progressive - && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) - && (state.BaseRequest.StartTimeTicks ?? 0) > 0) + // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest + // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to + // avoid A/V sync issues which cause playback issues on some devices. + // When remuxing video, the segment start times correspond to key frames in the source stream, so this + // option shouldn't change the seeked point that much. + // Important: make sure not to use it with wtv because it breaks seeking + if (state.TranscodingType is TranscodingJobType.Hls + && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase) + && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec)) + && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)) { seekParam += " -noaccurate_seek"; } @@ -7084,7 +7083,7 @@ namespace MediaBrowser.Controller.MediaEncoding } #nullable disable - public void TryStreamCopy(EncodingJobInfo state) + public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options) { if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream)) { @@ -7101,8 +7100,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls + && state.VideoStream is not null + && !IsCopyCodec(state.OutputVideoCodec) + && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio; + if (state.AudioStream is not null - && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) + && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs) + && !preventHlsAudioCopy) { state.OutputAudioCodec = "copy"; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 43680f5c0..7d0384ef2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -515,21 +515,6 @@ namespace MediaBrowser.Controller.MediaEncoding public int HlsListSize => 0; - public bool EnableBreakOnNonKeyFrames(string videoCodec) - { - if (TranscodingType != TranscodingJobType.Progressive) - { - if (IsSegmentedLiveStream) - { - return false; - } - - return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec); - } - - return false; - } - private int? GetMediaStreamCount(MediaStreamType type, int limit) { var count = MediaSource.GetStreamCount(type); diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index 3d288b9f8..2702e3bc0 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -27,10 +27,9 @@ namespace MediaBrowser.Controller.MediaEncoding using (target) using (reader) { - while (!reader.EndOfStream && reader.BaseStream.CanRead) + string line = await reader.ReadLineAsync().ConfigureAwait(false); + while (line is not null && reader.BaseStream.CanRead) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - ParseLogLine(line, state); var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); @@ -50,6 +49,7 @@ namespace MediaBrowser.Controller.MediaEncoding } await target.FlushAsync().ConfigureAwait(false); + line = await reader.ReadLineAsync().ConfigureAwait(false); } } } diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 00c492742..bf80b7d0a 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -36,6 +36,14 @@ public interface IItemRepository Task SaveImagesAsync(BaseItem item, CancellationToken cancellationToken = default); /// <summary> + /// Reattaches the user data to the item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A task that represents the asynchronous reattachment operation.</returns> + Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken); + + /// <summary> /// Retrieves the item. /// </summary> /// <param name="id">The id.</param> diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 497c4a511..92aa92396 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -61,9 +61,10 @@ namespace MediaBrowser.Controller.Playlists /// </summary> /// <param name="playlistId">The playlist identifier.</param> /// <param name="itemIds">The item ids.</param> + /// <param name="position">Optional. 0-based index where to place the items or at the end if null.</param> /// <param name="userId">The user identifier.</param> /// <returns>Task.</returns> - Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId); + Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, int? position, Guid userId); /// <summary> /// Removes from playlist. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 2b3afa117..c11c65c33 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -350,5 +350,12 @@ namespace MediaBrowser.Controller.Session /// <param name="sessionIdOrPlaySessionId">The session id or playsession id.</param> /// <returns>Task.</returns> Task CloseLiveStreamIfNeededAsync(string liveStreamId, string sessionIdOrPlaySessionId); + + /// <summary> + /// Gets the dto for session info. + /// </summary> + /// <param name="sessionInfo">The session info.</param> + /// <returns><see cref="SessionInfoDto"/> of the session.</returns> + SessionInfoDto ToSessionInfoDto(SessionInfo sessionInfo); } } diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index f9c0d39dd..ec8878dcb 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -1,7 +1,9 @@ #pragma warning disable CS1591 using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Jellyfin.Extensions; @@ -9,7 +11,7 @@ namespace MediaBrowser.Controller.Sorting { public static class SortExtensions { - private static readonly AlphanumericComparator _comparer = new AlphanumericComparator(); + private static readonly StringComparer _comparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering); public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName) { |
