aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs16
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs7
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs15
-rw-r--r--MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs35
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs15
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs8
-rw-r--r--MediaBrowser.Controller/Playlists/IPlaylistManager.cs3
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs7
-rw-r--r--MediaBrowser.Controller/Sorting/SortExtensions.cs4
15 files changed, 86 insertions, 43 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index d9d2d0e3a..7586b99e7 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -2053,6 +2053,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>
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)
{