aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs3
-rw-r--r--MediaBrowser.Controller/Drawing/ImageStream.cs42
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs12
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs35
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs4
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs9
-rw-r--r--MediaBrowser.Controller/Library/ILibraryMonitor.cs9
-rw-r--r--MediaBrowser.Controller/LiveTv/IGuideManager.cs26
-rw-r--r--MediaBrowser.Controller/LiveTv/IListingsManager.cs79
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs67
-rw-r--r--MediaBrowser.Controller/LiveTv/IRecordingsManager.cs55
-rw-r--r--MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs17
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricManager.cs100
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricParser.cs4
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricProvider.cs34
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricDownloadFailureEventArgs.cs26
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricFile.cs28
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricLine.cs28
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricMetadata.cs52
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricResponse.cs20
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs589
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs20
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs7
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs23
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs11
-rw-r--r--MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs9
-rw-r--r--MediaBrowser.Controller/Plugins/IServerEntryPoint.cs20
28 files changed, 922 insertions, 413 deletions
diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs
index 94418683b..f186523b9 100644
--- a/MediaBrowser.Controller/Channels/Channel.cs
+++ b/MediaBrowser.Controller/Channels/Channel.cs
@@ -9,7 +9,6 @@ using System.Text.Json.Serialization;
using System.Threading;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Querying;
@@ -53,7 +52,7 @@ namespace MediaBrowser.Controller.Channels
query.ChannelIds = new Guid[] { Id };
// Don't blow up here because it could cause parent screens with other content to fail
- return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).GetAwaiter().GetResult();
+ return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult();
}
catch
{
diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs
deleted file mode 100644
index f4c305799..000000000
--- a/MediaBrowser.Controller/Drawing/ImageStream.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-#pragma warning disable CA1711, CS1591
-
-using System;
-using System.IO;
-using MediaBrowser.Model.Drawing;
-
-namespace MediaBrowser.Controller.Drawing
-{
- public class ImageStream : IDisposable
- {
- public ImageStream(Stream stream)
- {
- Stream = stream;
- }
-
- /// <summary>
- /// Gets the stream.
- /// </summary>
- /// <value>The stream.</value>
- public Stream Stream { get; }
-
- /// <summary>
- /// Gets or sets the format.
- /// </summary>
- /// <value>The format.</value>
- public ImageFormat Format { get; set; }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- Stream?.Dispose();
- }
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 243d2f04f..709d4b70c 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
@@ -27,6 +28,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
Artists = Array.Empty<string>();
AlbumArtists = Array.Empty<string>();
+ LyricFiles = Array.Empty<string>();
}
/// <inheritdoc />
@@ -65,6 +67,16 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public override MediaType MediaType => MediaType.Audio;
+ /// <summary>
+ /// Gets or sets a value indicating whether this audio has lyrics.
+ /// </summary>
+ public bool? HasLyrics { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of lyric paths.
+ /// </summary>
+ public IReadOnlyList<string> LyricFiles { get; set; }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 74eb089de..1f13c833b 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -13,7 +13,6 @@ using System.Threading.Tasks.Dataflow;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
-using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
@@ -429,16 +428,22 @@ namespace MediaBrowser.Controller.Entities
if (recursive)
{
- var innerProgress = new ActionableProgress<double>();
-
var folder = this;
- innerProgress.RegisterAction(innerPercent =>
+ var innerProgress = new Progress<double>(innerPercent =>
{
var percent = ProgressHelpers.GetProgress(ProgressHelpers.UpdatedChildItems, ProgressHelpers.ScannedSubfolders, innerPercent);
progress.Report(percent);
- ProviderManager.OnRefreshProgress(folder, percent);
+ // TODO: this is sometimes being called after the refresh has completed.
+ try
+ {
+ ProviderManager.OnRefreshProgress(folder, percent);
+ }
+ catch (InvalidOperationException e)
+ {
+ Logger.LogError(e, "Error refreshing folder");
+ }
});
if (validChildrenNeedGeneration)
@@ -461,10 +466,8 @@ namespace MediaBrowser.Controller.Entities
var container = this as IMetadataContainer;
- var innerProgress = new ActionableProgress<double>();
-
var folder = this;
- innerProgress.RegisterAction(innerPercent =>
+ var innerProgress = new Progress<double>(innerPercent =>
{
var percent = ProgressHelpers.GetProgress(ProgressHelpers.ScannedSubfolders, ProgressHelpers.RefreshedMetadata, innerPercent);
@@ -472,7 +475,15 @@ namespace MediaBrowser.Controller.Entities
if (recursive)
{
- ProviderManager.OnRefreshProgress(folder, percent);
+ // TODO: this is sometimes being called after the refresh has completed.
+ try
+ {
+ ProviderManager.OnRefreshProgress(folder, percent);
+ }
+ catch (InvalidOperationException e)
+ {
+ Logger.LogError(e, "Error refreshing folder");
+ }
}
});
@@ -572,9 +583,7 @@ namespace MediaBrowser.Controller.Entities
var actionBlock = new ActionBlock<int>(
async i =>
{
- var innerProgress = new ActionableProgress<double>();
-
- innerProgress.RegisterAction(innerPercent =>
+ var innerProgress = new Progress<double>(innerPercent =>
{
// round the percent and only update progress if it changed to prevent excessive UpdateProgress calls
var innerPercentRounded = Math.Round(innerPercent);
@@ -922,7 +931,7 @@ namespace MediaBrowser.Controller.Entities
query.ChannelIds = new[] { ChannelId };
// Don't blow up here because it could cause parent screens with other content to fail
- return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).GetAwaiter().GetResult();
+ return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult();
}
catch
{
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 5adadec39..04f47b729 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -171,7 +171,7 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
- public static ILiveTvManager LiveTvManager { get; set; }
+ public static IRecordingsManager RecordingsManager { get; set; }
[JsonIgnore]
public override SourceType SourceType
@@ -334,7 +334,7 @@ namespace MediaBrowser.Controller.Entities
protected override bool IsActiveRecording()
{
- return LiveTvManager.GetActiveRecordingInfo(Path) is not null;
+ return RecordingsManager.GetActiveRecordingInfo(Path) is not null;
}
public override bool CanDelete()
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 9ec22324f..e44c09783 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -169,6 +169,15 @@ namespace MediaBrowser.Controller.Library
BaseItem GetItemById(Guid id);
/// <summary>
+ /// Gets the item by id, as T.
+ /// </summary>
+ /// <param name="id">The item id.</param>
+ /// <typeparam name="T">The type of item.</typeparam>
+ /// <returns>The item.</returns>
+ T GetItemById<T>(Guid id)
+ where T : BaseItem;
+
+ /// <summary>
/// Gets the intros.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/MediaBrowser.Controller/Library/ILibraryMonitor.cs b/MediaBrowser.Controller/Library/ILibraryMonitor.cs
index de74aa5a1..6d2f5b873 100644
--- a/MediaBrowser.Controller/Library/ILibraryMonitor.cs
+++ b/MediaBrowser.Controller/Library/ILibraryMonitor.cs
@@ -1,10 +1,9 @@
-#pragma warning disable CS1591
-
-using System;
-
namespace MediaBrowser.Controller.Library
{
- public interface ILibraryMonitor : IDisposable
+ /// <summary>
+ /// Service responsible for monitoring library filesystems for changes.
+ /// </summary>
+ public interface ILibraryMonitor
{
/// <summary>
/// Starts this instance.
diff --git a/MediaBrowser.Controller/LiveTv/IGuideManager.cs b/MediaBrowser.Controller/LiveTv/IGuideManager.cs
new file mode 100644
index 000000000..9883b9283
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/IGuideManager.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv;
+
+/// <summary>
+/// Service responsible for managing the Live TV guide.
+/// </summary>
+public interface IGuideManager
+{
+ /// <summary>
+ /// Gets the guide information.
+ /// </summary>
+ /// <returns>The <see cref="GuideInfo"/>.</returns>
+ GuideInfo GetGuideInfo();
+
+ /// <summary>
+ /// Refresh the guide.
+ /// </summary>
+ /// <param name="progress">The <see cref="IProgress{T}"/> to use.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+ /// <returns>Task representing the refresh operation.</returns>
+ Task RefreshGuide(IProgress<double> progress, CancellationToken cancellationToken);
+}
diff --git a/MediaBrowser.Controller/LiveTv/IListingsManager.cs b/MediaBrowser.Controller/LiveTv/IListingsManager.cs
new file mode 100644
index 000000000..bbf569575
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/IListingsManager.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv;
+
+/// <summary>
+/// Service responsible for managing <see cref="IListingsProvider"/>s and mapping
+/// their channels to channels provided by <see cref="ITunerHost"/>s.
+/// </summary>
+public interface IListingsManager
+{
+ /// <summary>
+ /// Saves the listing provider.
+ /// </summary>
+ /// <param name="info">The listing provider information.</param>
+ /// <param name="validateLogin">A value indicating whether to validate login.</param>
+ /// <param name="validateListings">A value indicating whether to validate listings..</param>
+ /// <returns>Task.</returns>
+ Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
+
+ /// <summary>
+ /// Deletes the listing provider.
+ /// </summary>
+ /// <param name="id">The listing provider's id.</param>
+ void DeleteListingsProvider(string? id);
+
+ /// <summary>
+ /// Gets the lineups.
+ /// </summary>
+ /// <param name="providerType">Type of the provider.</param>
+ /// <param name="providerId">The provider identifier.</param>
+ /// <param name="country">The country.</param>
+ /// <param name="location">The location.</param>
+ /// <returns>The available lineups.</returns>
+ Task<List<NameIdPair>> GetLineups(string? providerType, string? providerId, string? country, string? location);
+
+ /// <summary>
+ /// Gets the programs for a provided channel.
+ /// </summary>
+ /// <param name="channel">The channel to retrieve programs for.</param>
+ /// <param name="startDateUtc">The earliest date to retrieve programs for.</param>
+ /// <param name="endDateUtc">The latest date to retrieve programs for.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+ /// <returns>The available programs.</returns>
+ Task<IEnumerable<ProgramInfo>> GetProgramsAsync(
+ ChannelInfo channel,
+ DateTime startDateUtc,
+ DateTime endDateUtc,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Adds metadata from the <see cref="IListingsProvider"/>s to the provided channels.
+ /// </summary>
+ /// <param name="channels">The channels.</param>
+ /// <param name="enableCache">A value indicating whether to use the EPG channel cache.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+ /// <returns>A task representing the metadata population.</returns>
+ Task AddProviderMetadata(IList<ChannelInfo> channels, bool enableCache, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the channel mapping options for a provider.
+ /// </summary>
+ /// <param name="providerId">The id of the provider to use.</param>
+ /// <returns>The channel mapping options.</returns>
+ Task<ChannelMappingOptionsDto> GetChannelMappingOptions(string? providerId);
+
+ /// <summary>
+ /// Sets the channel mapping.
+ /// </summary>
+ /// <param name="providerId">The id of the provider for the mapping.</param>
+ /// <param name="tunerChannelNumber">The tuner channel number.</param>
+ /// <param name="providerChannelNumber">The provider channel number.</param>
+ /// <returns>The updated channel mapping.</returns>
+ Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
+}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 26f9fe42d..c0e46ba24 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -10,7 +10,6 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
@@ -36,8 +35,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The services.</value>
IReadOnlyList<ILiveTvService> Services { get; }
- IReadOnlyList<IListingsProvider> ListingProviders { get; }
-
/// <summary>
/// Gets the new timer defaults asynchronous.
/// </summary>
@@ -68,13 +65,6 @@ namespace MediaBrowser.Controller.LiveTv
Task CancelSeriesTimer(string id);
/// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="services">The services.</param>
- /// <param name="listingProviders">The listing providers.</param>
- void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders);
-
- /// <summary>
/// Gets the timer.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -115,16 +105,6 @@ namespace MediaBrowser.Controller.LiveTv
Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel stream.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="mediaSourceId">The media source identifier.</param>
- /// <param name="currentLiveStreams">The current live streams.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{StreamResponseInfo}.</returns>
- Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(string id, string mediaSourceId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
-
- /// <summary>
/// Gets the program.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -175,12 +155,6 @@ namespace MediaBrowser.Controller.LiveTv
Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
/// <summary>
- /// Gets the guide information.
- /// </summary>
- /// <returns>GuideInfo.</returns>
- GuideInfo GetGuideInfo();
-
- /// <summary>
/// Gets the recommended programs.
/// </summary>
/// <param name="query">The query.</param>
@@ -236,14 +210,6 @@ namespace MediaBrowser.Controller.LiveTv
QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel media sources.
- /// </summary>
- /// <param name="item">Item to search for.</param>
- /// <param name="cancellationToken">CancellationToken to use for operation.</param>
- /// <returns>Channel media sources wrapped in a task.</returns>
- Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken);
-
- /// <summary>
/// Adds the information to program dto.
/// </summary>
/// <param name="programs">The programs.</param>
@@ -253,31 +219,6 @@ namespace MediaBrowser.Controller.LiveTv
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
/// <summary>
- /// Saves the listing provider.
- /// </summary>
- /// <param name="info">The information.</param>
- /// <param name="validateLogin">if set to <c>true</c> [validate login].</param>
- /// <param name="validateListings">if set to <c>true</c> [validate listings].</param>
- /// <returns>Task.</returns>
- Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
-
- void DeleteListingsProvider(string id);
-
- Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
-
- TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels);
-
- /// <summary>
- /// Gets the lineups.
- /// </summary>
- /// <param name="providerType">Type of the provider.</param>
- /// <param name="providerId">The provider identifier.</param>
- /// <param name="country">The country.</param>
- /// <param name="location">The location.</param>
- /// <returns>Task&lt;List&lt;NameIdPair&gt;&gt;.</returns>
- Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location);
-
- /// <summary>
/// Adds the channel information.
/// </summary>
/// <param name="items">The items.</param>
@@ -285,14 +226,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="user">The user.</param>
void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user);
- Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
-
- Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
-
- string GetEmbyTvActiveRecordingPath(string id);
-
- ActiveRecordingInfo GetActiveRecordingInfo(string path);
-
void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null);
Task<BaseItem[]> GetRecordingFoldersAsync(User user);
diff --git a/MediaBrowser.Controller/LiveTv/IRecordingsManager.cs b/MediaBrowser.Controller/LiveTv/IRecordingsManager.cs
new file mode 100644
index 000000000..b918e2931
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/IRecordingsManager.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.LiveTv;
+
+/// <summary>
+/// Service responsible for managing LiveTV recordings.
+/// </summary>
+public interface IRecordingsManager
+{
+ /// <summary>
+ /// Gets the path for the provided timer id.
+ /// </summary>
+ /// <param name="id">The timer id.</param>
+ /// <returns>The recording path, or <c>null</c> if none exists.</returns>
+ string? GetActiveRecordingPath(string id);
+
+ /// <summary>
+ /// Gets the information for an active recording.
+ /// </summary>
+ /// <param name="path">The recording path.</param>
+ /// <returns>The <see cref="ActiveRecordingInfo"/>, or <c>null</c> if none exists.</returns>
+ ActiveRecordingInfo? GetActiveRecordingInfo(string path);
+
+ /// <summary>
+ /// Gets the recording folders.
+ /// </summary>
+ /// <returns>The <see cref="VirtualFolderInfo"/> for each recording folder.</returns>
+ IEnumerable<VirtualFolderInfo> GetRecordingFolders();
+
+ /// <summary>
+ /// Ensures that the recording folders all exist, and removes unused folders.
+ /// </summary>
+ /// <returns>Task.</returns>
+ Task CreateRecordingFolders();
+
+ /// <summary>
+ /// Cancels the recording with the provided timer id, if one is active.
+ /// </summary>
+ /// <param name="timerId">The timer id.</param>
+ /// <param name="timer">The timer.</param>
+ void CancelRecording(string timerId, TimerInfo? timer);
+
+ /// <summary>
+ /// Records a stream.
+ /// </summary>
+ /// <param name="recordingInfo">The recording info.</param>
+ /// <param name="channel">The channel associated with the recording timer.</param>
+ /// <param name="recordingEndDate">The time to stop recording.</param>
+ /// <returns>Task representing the recording process.</returns>
+ Task RecordStream(ActiveRecordingInfo recordingInfo, BaseItem channel, DateTime recordingEndDate);
+}
diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
deleted file mode 100644
index 1c1a4417d..000000000
--- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class TunerChannelMapping
- {
- public string Name { get; set; }
-
- public string ProviderChannelName { get; set; }
-
- public string ProviderChannelId { get; set; }
-
- public string Id { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Lyrics/ILyricManager.cs b/MediaBrowser.Controller/Lyrics/ILyricManager.cs
index bb93e1e4c..f4376a1ee 100644
--- a/MediaBrowser.Controller/Lyrics/ILyricManager.cs
+++ b/MediaBrowser.Controller/Lyrics/ILyricManager.cs
@@ -1,5 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Lyrics;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Lyrics;
@@ -9,16 +16,93 @@ namespace MediaBrowser.Controller.Lyrics;
public interface ILyricManager
{
/// <summary>
- /// Gets the lyrics.
+ /// Occurs when a lyric download fails.
/// </summary>
- /// <param name="item">The media item.</param>
- /// <returns>A task representing found lyrics the passed item.</returns>
- Task<LyricResponse?> GetLyrics(BaseItem item);
+ event EventHandler<LyricDownloadFailureEventArgs> LyricDownloadFailure;
/// <summary>
- /// Checks if requested item has a matching local lyric file.
+ /// Search for lyrics for the specified song.
/// </summary>
- /// <param name="item">The media item.</param>
- /// <returns>True if item has a matching lyric file; otherwise false.</returns>
- bool HasLyricFile(BaseItem item);
+ /// <param name="audio">The song.</param>
+ /// <param name="isAutomated">Whether the request is automated.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>The list of lyrics.</returns>
+ Task<IReadOnlyList<RemoteLyricInfoDto>> SearchLyricsAsync(
+ Audio audio,
+ bool isAutomated,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Search for lyrics.
+ /// </summary>
+ /// <param name="request">The search request.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>The list of lyrics.</returns>
+ Task<IReadOnlyList<RemoteLyricInfoDto>> SearchLyricsAsync(
+ LyricSearchRequest request,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Download the lyrics.
+ /// </summary>
+ /// <param name="audio">The audio.</param>
+ /// <param name="lyricId">The remote lyric id.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>The downloaded lyrics.</returns>
+ Task<LyricDto?> DownloadLyricsAsync(
+ Audio audio,
+ string lyricId,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Download the lyrics.
+ /// </summary>
+ /// <param name="audio">The audio.</param>
+ /// <param name="libraryOptions">The library options to use.</param>
+ /// <param name="lyricId">The remote lyric id.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>The downloaded lyrics.</returns>
+ Task<LyricDto?> DownloadLyricsAsync(
+ Audio audio,
+ LibraryOptions libraryOptions,
+ string lyricId,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Upload new lyrics.
+ /// </summary>
+ /// <param name="audio">The audio file the lyrics belong to.</param>
+ /// <param name="lyricResponse">The lyric response.</param>
+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+ Task<LyricDto?> UploadLyricAsync(Audio audio, LyricResponse lyricResponse);
+
+ /// <summary>
+ /// Get the remote lyrics.
+ /// </summary>
+ /// <param name="id">The remote lyrics id.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>The lyric response.</returns>
+ Task<LyricDto?> GetRemoteLyricsAsync(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Deletes the lyrics.
+ /// </summary>
+ /// <param name="audio">The audio file to remove lyrics from.</param>
+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+ Task DeleteLyricsAsync(Audio audio);
+
+ /// <summary>
+ /// Get the list of lyric providers.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>Lyric providers.</returns>
+ IReadOnlyList<LyricProviderInfo> GetSupportedProviders(BaseItem item);
+
+ /// <summary>
+ /// Get the existing lyric for the audio.
+ /// </summary>
+ /// <param name="audio">The audio item.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The parsed lyric model.</returns>
+ Task<LyricDto?> GetLyricsAsync(Audio audio, CancellationToken cancellationToken);
}
diff --git a/MediaBrowser.Controller/Lyrics/ILyricParser.cs b/MediaBrowser.Controller/Lyrics/ILyricParser.cs
index 65a9471a3..819950d09 100644
--- a/MediaBrowser.Controller/Lyrics/ILyricParser.cs
+++ b/MediaBrowser.Controller/Lyrics/ILyricParser.cs
@@ -1,5 +1,5 @@
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Providers.Lyric;
+using MediaBrowser.Model.Lyrics;
namespace MediaBrowser.Controller.Lyrics;
@@ -24,5 +24,5 @@ public interface ILyricParser
/// </summary>
/// <param name="lyrics">The raw lyrics content.</param>
/// <returns>The parsed lyrics or null if invalid.</returns>
- LyricResponse? ParseLyrics(LyricFile lyrics);
+ LyricDto? ParseLyrics(LyricFile lyrics);
}
diff --git a/MediaBrowser.Controller/Lyrics/ILyricProvider.cs b/MediaBrowser.Controller/Lyrics/ILyricProvider.cs
new file mode 100644
index 000000000..0831a4c4e
--- /dev/null
+++ b/MediaBrowser.Controller/Lyrics/ILyricProvider.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Lyrics;
+using MediaBrowser.Model.Providers;
+
+namespace MediaBrowser.Controller.Lyrics;
+
+/// <summary>
+/// Interface ILyricsProvider.
+/// </summary>
+public interface ILyricProvider
+{
+ /// <summary>
+ /// Gets the provider name.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Search for lyrics.
+ /// </summary>
+ /// <param name="request">The search request.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The list of remote lyrics.</returns>
+ Task<IEnumerable<RemoteLyricInfo>> SearchAsync(LyricSearchRequest request, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Get the lyrics.
+ /// </summary>
+ /// <param name="id">The remote lyric id.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The lyric response.</returns>
+ Task<LyricResponse?> GetLyricsAsync(string id, CancellationToken cancellationToken);
+}
diff --git a/MediaBrowser.Controller/Lyrics/LyricDownloadFailureEventArgs.cs b/MediaBrowser.Controller/Lyrics/LyricDownloadFailureEventArgs.cs
new file mode 100644
index 000000000..1b1f36020
--- /dev/null
+++ b/MediaBrowser.Controller/Lyrics/LyricDownloadFailureEventArgs.cs
@@ -0,0 +1,26 @@
+using System;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Lyrics
+{
+ /// <summary>
+ /// An event that occurs when subtitle downloading fails.
+ /// </summary>
+ public class LyricDownloadFailureEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the item.
+ /// </summary>
+ public required BaseItem Item { get; set; }
+
+ /// <summary>
+ /// Gets or sets the provider.
+ /// </summary>
+ public required string Provider { get; set; }
+
+ /// <summary>
+ /// Gets or sets the exception.
+ /// </summary>
+ public required Exception Exception { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Lyrics/LyricFile.cs b/MediaBrowser.Controller/Lyrics/LyricFile.cs
deleted file mode 100644
index ede89403c..000000000
--- a/MediaBrowser.Controller/Lyrics/LyricFile.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace MediaBrowser.Providers.Lyric;
-
-/// <summary>
-/// The information for a raw lyrics file before parsing.
-/// </summary>
-public class LyricFile
-{
- /// <summary>
- /// Initializes a new instance of the <see cref="LyricFile"/> class.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="content">The content, must not be empty.</param>
- public LyricFile(string name, string content)
- {
- Name = name;
- Content = content;
- }
-
- /// <summary>
- /// Gets or sets the name of the lyrics file. This must include the file extension.
- /// </summary>
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the contents of the file.
- /// </summary>
- public string Content { get; set; }
-}
diff --git a/MediaBrowser.Controller/Lyrics/LyricLine.cs b/MediaBrowser.Controller/Lyrics/LyricLine.cs
deleted file mode 100644
index c406f92fc..000000000
--- a/MediaBrowser.Controller/Lyrics/LyricLine.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace MediaBrowser.Controller.Lyrics;
-
-/// <summary>
-/// Lyric model.
-/// </summary>
-public class LyricLine
-{
- /// <summary>
- /// Initializes a new instance of the <see cref="LyricLine"/> class.
- /// </summary>
- /// <param name="text">The lyric text.</param>
- /// <param name="start">The lyric start time in ticks.</param>
- public LyricLine(string text, long? start = null)
- {
- Text = text;
- Start = start;
- }
-
- /// <summary>
- /// Gets the text of this lyric line.
- /// </summary>
- public string Text { get; }
-
- /// <summary>
- /// Gets the start time in ticks.
- /// </summary>
- public long? Start { get; }
-}
diff --git a/MediaBrowser.Controller/Lyrics/LyricMetadata.cs b/MediaBrowser.Controller/Lyrics/LyricMetadata.cs
deleted file mode 100644
index c4f033489..000000000
--- a/MediaBrowser.Controller/Lyrics/LyricMetadata.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-namespace MediaBrowser.Controller.Lyrics;
-
-/// <summary>
-/// LyricMetadata model.
-/// </summary>
-public class LyricMetadata
-{
- /// <summary>
- /// Gets or sets the song artist.
- /// </summary>
- public string? Artist { get; set; }
-
- /// <summary>
- /// Gets or sets the album this song is on.
- /// </summary>
- public string? Album { get; set; }
-
- /// <summary>
- /// Gets or sets the title of the song.
- /// </summary>
- public string? Title { get; set; }
-
- /// <summary>
- /// Gets or sets the author of the lyric data.
- /// </summary>
- public string? Author { get; set; }
-
- /// <summary>
- /// Gets or sets the length of the song in ticks.
- /// </summary>
- public long? Length { get; set; }
-
- /// <summary>
- /// Gets or sets who the LRC file was created by.
- /// </summary>
- public string? By { get; set; }
-
- /// <summary>
- /// Gets or sets the lyric offset compared to audio in ticks.
- /// </summary>
- public long? Offset { get; set; }
-
- /// <summary>
- /// Gets or sets the software used to create the LRC file.
- /// </summary>
- public string? Creator { get; set; }
-
- /// <summary>
- /// Gets or sets the version of the creator used.
- /// </summary>
- public string? Version { get; set; }
-}
diff --git a/MediaBrowser.Controller/Lyrics/LyricResponse.cs b/MediaBrowser.Controller/Lyrics/LyricResponse.cs
deleted file mode 100644
index 0d52b5ec5..000000000
--- a/MediaBrowser.Controller/Lyrics/LyricResponse.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Lyrics;
-
-/// <summary>
-/// LyricResponse model.
-/// </summary>
-public class LyricResponse
-{
- /// <summary>
- /// Gets or sets Metadata for the lyrics.
- /// </summary>
- public LyricMetadata Metadata { get; set; } = new();
-
- /// <summary>
- /// Gets or sets a collection of individual lyric lines.
- /// </summary>
- public IReadOnlyList<LyricLine> Lyrics { get; set; } = Array.Empty<LyricLine>();
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index fb4e7bd1f..29dd190ab 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -88,6 +88,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string Level { get; set; }
/// <summary>
+ /// Gets or sets the codec tag.
+ /// </summary>
+ /// <value>The codec tag.</value>
+ public string CodecTag { get; set; }
+
+ /// <summary>
/// Gets or sets the framerate.
/// </summary>
/// <value>The framerate.</value>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 400e7f40f..b6738e7cc 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private const string VaapiAlias = "va";
private const string D3d11vaAlias = "dx11";
private const string VideotoolboxAlias = "vt";
+ private const string RkmppAlias = "rk";
private const string OpenclAlias = "ocl";
private const string CudaAlias = "cu";
private const string DrmAlias = "dr";
@@ -161,6 +162,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ "vaapi", hwEncoder + "_vaapi" },
{ "videotoolbox", hwEncoder + "_videotoolbox" },
{ "v4l2m2m", hwEncoder + "_v4l2m2m" },
+ { "rkmpp", hwEncoder + "_rkmpp" },
};
if (!string.IsNullOrEmpty(hwType)
@@ -217,6 +219,14 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
}
+ private bool IsRkmppFullSupported()
+ {
+ return _mediaEncoder.SupportsHwaccel("rkmpp")
+ && _mediaEncoder.SupportsFilter("scale_rkrga")
+ && _mediaEncoder.SupportsFilter("vpp_rkrga")
+ && _mediaEncoder.SupportsFilter("overlay_rkrga");
+ }
+
private bool IsOpenclFullSupported()
{
return _mediaEncoder.SupportsHwaccel("opencl")
@@ -696,6 +706,14 @@ namespace MediaBrowser.Controller.MediaEncoding
return codec.ToLowerInvariant();
}
+ private string GetRkmppDeviceArgs(string alias)
+ {
+ alias ??= RkmppAlias;
+
+ // device selection in rk is not supported.
+ return " -init_hw_device rkmpp=" + alias;
+ }
+
private string GetVideoToolboxDeviceArgs(string alias)
{
alias ??= VideotoolboxAlias;
@@ -835,30 +853,25 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
{
- // DVBSUB and DVDSUB use the fixed canvas size 720x576
+ // DVBSUB uses the fixed canvas size 720x576
if (state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& !state.SubtitleStream.IsTextSubtitleStream
- && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase))
+ && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
{
- var inW = state.VideoStream?.Width;
- var inH = state.VideoStream?.Height;
- var reqW = state.BaseRequest.Width;
- var reqH = state.BaseRequest.Height;
- var reqMaxW = state.BaseRequest.MaxWidth;
- var reqMaxH = state.BaseRequest.MaxHeight;
-
- // setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead
- var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080);
+ var subtitleWidth = state.SubtitleStream?.Width;
+ var subtitleHeight = state.SubtitleStream?.Height;
- if (overlayW.HasValue && overlayH.HasValue)
+ if (subtitleWidth.HasValue
+ && subtitleHeight.HasValue
+ && subtitleWidth.Value > 0
+ && subtitleHeight.Value > 0)
{
return string.Format(
CultureInfo.InvariantCulture,
" -canvas_size {0}x{1}",
- overlayW.Value,
- overlayH.Value);
+ subtitleWidth.Value,
+ subtitleHeight.Value);
}
}
@@ -1061,6 +1074,33 @@ namespace MediaBrowser.Controller.MediaEncoding
// no videotoolbox hw filter.
args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
}
+ else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
+ {
+ return string.Empty;
+ }
+
+ var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ if (!isRkmppDecoder && !isRkmppEncoder)
+ {
+ return string.Empty;
+ }
+
+ args.Append(GetRkmppDeviceArgs(RkmppAlias));
+
+ var filterDevArgs = string.Empty;
+ var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
+
+ if (doOclTonemap && !isRkmppDecoder)
+ {
+ args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
+ }
+
+ args.Append(filterDevArgs);
+ }
if (!string.IsNullOrEmpty(vidDecoder))
{
@@ -1477,8 +1517,10 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
@@ -1918,20 +1960,22 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "constrained_baseline";
}
- // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case.
+ // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this case.
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
&& profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
{
profile = "baseline";
}
- // libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case.
+ // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
&& profile.Contains("high", StringComparison.OrdinalIgnoreCase))
{
profile = "high";
@@ -2015,6 +2059,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -level " + level;
}
}
+ else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -level " + level;
+ }
else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + level;
@@ -2833,6 +2882,48 @@ namespace MediaBrowser.Controller.MediaEncoding
return (outputWidth, outputHeight);
}
+ public static bool IsScaleRatioSupported(
+ int? videoWidth,
+ int? videoHeight,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight,
+ double? maxScaleRatio)
+ {
+ var (outWidth, outHeight) = GetFixedOutputSize(
+ videoWidth,
+ videoHeight,
+ requestedWidth,
+ requestedHeight,
+ requestedMaxWidth,
+ requestedMaxHeight);
+
+ if (!videoWidth.HasValue
+ || !videoHeight.HasValue
+ || !outWidth.HasValue
+ || !outHeight.HasValue
+ || !maxScaleRatio.HasValue
+ || (maxScaleRatio.Value < 1.0f))
+ {
+ return false;
+ }
+
+ var minScaleRatio = 1.0f / maxScaleRatio;
+ var scaleRatioW = (double)outWidth / (double)videoWidth;
+ var scaleRatioH = (double)outHeight / (double)videoHeight;
+
+ if (scaleRatioW < minScaleRatio
+ || scaleRatioW > maxScaleRatio
+ || scaleRatioH < minScaleRatio
+ || scaleRatioH > maxScaleRatio)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
public static string GetHwScaleFilter(
string hwScaleSuffix,
string videoFormat,
@@ -2877,7 +2968,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- public static string GetCustomSwScaleFilter(
+ public static string GetGraphicalSubPreProcessFilters(
int? videoWidth,
int? videoHeight,
int? requestedWidth,
@@ -2897,7 +2988,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Format(
CultureInfo.InvariantCulture,
- "scale=s={0}x{1}:flags=fast_bilinear",
+ @"scale=-1:{1}:fast_bilinear,scale,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}",
outWidth.Value,
outHeight.Value);
}
@@ -2913,7 +3004,7 @@ namespace MediaBrowser.Controller.MediaEncoding
int? requestedHeight,
int? requestedMaxWidth,
int? requestedMaxHeight,
- int? framerate)
+ float? framerate)
{
var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture);
@@ -2932,7 +3023,7 @@ namespace MediaBrowser.Controller.MediaEncoding
"alphasrc=s={0}x{1}:r={2}:start='{3}'",
outWidth.Value,
outHeight.Value,
- framerate ?? 10,
+ framerate ?? 25,
reqTicks > 0 ? startTime : 0);
}
@@ -3340,9 +3431,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
- // [0:s]scale=s=1280x720
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -3504,15 +3594,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- // scale=s=1280x720,format=yuva420p,hwupload
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=yuva420p");
}
else if (hasTextSubs)
{
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
// alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=yuva420p");
@@ -3527,8 +3619,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -3702,15 +3794,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- // scale=s=1280x720,format=yuva420p,hwupload
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=yuva420p");
}
else if (hasTextSubs)
{
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
// alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=yuva420p");
@@ -3727,8 +3821,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -3938,16 +4032,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- // scale,format=bgra,hwupload
- // overlay_qsv can handle overlay scaling,
- // add a dummy scale filter to pair with -canvas_size.
- subFilters.Add("scale=flags=fast_bilinear");
+ // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
else if (hasTextSubs)
{
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
// alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=bgra");
@@ -3973,8 +4069,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -4158,12 +4254,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- subFilters.Add("scale=flags=fast_bilinear");
+ // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
else if (hasTextSubs)
{
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=bgra");
@@ -4189,8 +4290,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -4425,12 +4526,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- subFilters.Add("scale=flags=fast_bilinear");
+ // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
else if (hasTextSubs)
{
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=bgra");
@@ -4454,8 +4560,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
if (isVaapiEncoder)
@@ -4599,14 +4705,16 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- // scale=s=1280x720,format=bgra,hwupload
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
else if (hasTextSubs)
{
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
subFilters.Add(alphaSrcFilter);
subFilters.Add("format=bgra");
@@ -4815,8 +4923,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
if (isVaapiEncoder)
@@ -4899,6 +5007,237 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="vidEncoder">Video encoder to use.</param>
+ /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var isLinux = OperatingSystem.IsLinux();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
+
+ if ((isSwDecoder && isSwEncoder)
+ || !isRkmppOclSupported
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
+ {
+ return GetSwVidFilterChain(state, options, vidEncoder);
+ }
+
+ // prefered rkmpp + rkrga + opencl filters pipeline
+ if (isRkmppOclSupported)
+ {
+ return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ return (null, null, null);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ var inW = state.VideoStream?.Width;
+ var inH = state.VideoStream?.Height;
+ var reqW = state.BaseRequest.Width;
+ var reqH = state.BaseRequest.Height;
+ var reqMaxW = state.BaseRequest.MaxWidth;
+ var reqMaxH = state.BaseRequest.MaxHeight;
+ var threeDFormat = state.MediaSource.Video3DFormat;
+
+ var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ var isSwDecoder = !isRkmppDecoder;
+ var isSwEncoder = !isRkmppEncoder;
+ var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+ var doOclTonemap = IsHwTonemapAvailable(state, options);
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (!string.IsNullOrEmpty(swScaleFilter))
+ {
+ swScaleFilter += ":flags=fast_bilinear";
+ }
+
+ // sw scale
+ mainFilters.Add(swScaleFilter);
+ mainFilters.Add("format=" + outFormat);
+
+ // keep video at memory except ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload=derive_device=opencl");
+ }
+ }
+ else if (isRkmppDecoder)
+ {
+ // INPUT rkmpp/drm surface(gem/dma-heap)
+
+ var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap;
+ var outFormat = doOclTonemap ? "p010" : "nv12";
+ var hwScaleFilter = GetHwScaleFilter("rkrga", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var hwScaleFilter2 = GetHwScaleFilter("rkrga", string.Empty, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ if (!hasSubs
+ || !isFullAfbcPipeline
+ || !string.IsNullOrEmpty(hwScaleFilter2))
+ {
+ // try enabling AFBC to save DDR bandwidth
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
+ {
+ hwScaleFilter += ":afbc=1";
+ }
+
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+ }
+
+ if (doOclTonemap && isRkmppDecoder)
+ {
+ // map from rkmpp/drm to opencl via drm-opencl interop.
+ mainFilters.Add("hwmap=derive_device=opencl:mode=read");
+ }
+
+ // ocl tonemap
+ if (doOclTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ // enable tradeoffs for performance
+ if (!string.IsNullOrEmpty(tonemapFilter))
+ {
+ tonemapFilter += ":tradeoff=1";
+ }
+
+ mainFilters.Add(tonemapFilter);
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
+ if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // OUTPUT nv12 surface(memory)
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=nv12");
+ }
+
+ // OUTPUT nv12 surface(memory)
+ if (isSwDecoder && isRkmppEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
+
+ if (isDrmInDrmOut)
+ {
+ if (doOclTonemap)
+ {
+ // OUTPUT drm(nv12) surface(gem/dma-heap)
+ // reverse-mapping via drm-opencl interop.
+ mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1");
+ mainFilters.Add("format=drm_prime");
+ }
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isDrmInDrmOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
+ subFilters.Add("format=bgra");
+ }
+ else if (hasTextSubs)
+ {
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
+ // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
+ }
+
+ subFilters.Add("hwupload=derive_device=rkmpp");
+
+ // try enabling AFBC to save DDR bandwidth
+ overlayFilters.Add("overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12:afbc=1");
+ }
+ }
+ else if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
+ }
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ /// <summary>
/// Gets the parameter of video processing filters.
/// </summary>
/// <param name="state">Encoding state.</param>
@@ -4944,6 +5283,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
(mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
}
+ else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec);
+ }
else
{
(mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
@@ -5075,18 +5418,21 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
}
if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
{
return 10;
}
if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
{
return 12;
@@ -5139,7 +5485,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
{
- return null;
+ // One exception is that RKMPP decoder can handle H.264 High 10.
+ if (!(string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)))
+ {
+ return null;
+ }
}
if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
@@ -5166,6 +5517,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth);
}
+
+ if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetRkmppVidDecoder(state, options, videoStream, bitDepth);
+ }
}
var whichCodec = videoStream.Codec;
@@ -5231,6 +5587,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
return isCodecAvailable ? (" -c:v " + decoderName) : null;
}
@@ -5253,6 +5614,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
+ var isRkmppSupported = isLinux && IsRkmppFullSupported();
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
var ffmpegVersion = _mediaEncoder.EncoderVersion;
@@ -5355,6 +5717,14 @@ namespace MediaBrowser.Controller.MediaEncoding
return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty);
}
+ // Rockchip rkmpp
+ if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)
+ && isRkmppSupported
+ && isCodecAvailable)
+ {
+ return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime" : string.Empty);
+ }
+
return null;
}
@@ -5661,6 +6031,102 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ var isLinux = OperatingSystem.IsLinux();
+
+ if (!isLinux
+ || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var inW = state.VideoStream?.Width;
+ var inH = state.VideoStream?.Height;
+ var reqW = state.BaseRequest.Width;
+ var reqH = state.BaseRequest.Height;
+ var reqMaxW = state.BaseRequest.MaxWidth;
+ var reqMaxH = state.BaseRequest.MaxHeight;
+
+ // rkrga RGA2e supports range from 1/16 to 16
+ if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
+ {
+ return null;
+ }
+
+ var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
+ var hwSurface = isRkmppOclSupported
+ && _mediaEncoder.SupportsFilter("alphasrc");
+
+ // rkrga RGA3 supports range from 1/8 to 8
+ var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
+
+ // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
+ var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
+
+ // nv15 and nv20 are bit-stream only formats
+ if (is10bitSwFormatsRkmpp && !hwSurface)
+ {
+ return null;
+ }
+
+ if (is8bitSwFormatsRkmpp)
+ {
+ if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
+ }
+
+ if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
+ }
+
+ if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
+ }
+
+ if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
+ }
+ }
+
+ if (is8_10bitSwFormatsRkmpp)
+ {
+ if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
+ return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
+ }
+
+ if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
+ return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
+ }
+
+ if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
+ return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
+ }
+
+ if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
+ }
+ }
+
+ return null;
+ }
+
/// <summary>
/// Gets the number of threads.
/// </summary>
@@ -6075,13 +6541,14 @@ namespace MediaBrowser.Controller.MediaEncoding
return " -codec:s:0 " + codec + " -disposition:s:0 default";
}
- public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset)
+ public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string defaultPreset)
{
// Get the output codec name
var videoCodec = GetVideoEncoder(state, encodingOptions);
var format = string.Empty;
var keyFrame = string.Empty;
+ var outputPath = state.OutputFilePath;
if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
&& state.BaseRequest.Context == EncodingContext.Streaming)
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 17813559a..f2a0b906d 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -619,6 +619,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty<string>();
}
+ public string[] GetRequestedCodecTags(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.CodecTag))
+ {
+ return BaseRequest.CodecTag.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var codectag = BaseRequest.GetOption(codec, "codectag");
+
+ if (!string.IsNullOrEmpty(codectag))
+ {
+ return codectag.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ return Array.Empty<string>();
+ }
+
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))
diff --git a/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs
index c19a12ae7..09bc01f74 100644
--- a/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs
@@ -96,9 +96,10 @@ public interface ITranscodeManager
public void OnTranscodeEndRequest(TranscodingJob job);
/// <summary>
- /// Gets the transcoding lock.
+ /// Transcoding lock.
/// </summary>
/// <param name="outputPath">The output path of the transcoded file.</param>
- /// <returns>A <see cref="SemaphoreSlim"/>.</returns>
- public SemaphoreSlim GetTranscodingLock(string outputPath);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>An <see cref="IDisposable"/>.</returns>
+ ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken);
}
diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
deleted file mode 100644
index 044ba6d33..000000000
--- a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- public class ImageEncodingOptions
- {
- public string InputPath { get; set; }
-
- public int? Width { get; set; }
-
- public int? Height { get; set; }
-
- public int? MaxWidth { get; set; }
-
- public int? MaxHeight { get; set; }
-
- public int? Quality { get; set; }
-
- public string Format { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
deleted file mode 100644
index 841e7b287..000000000
--- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- /// <summary>
- /// Class MediaEncoderHelpers.
- /// </summary>
- public static class MediaEncoderHelpers
- {
- }
-}
diff --git a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs
deleted file mode 100644
index 2b831103a..000000000
--- a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace MediaBrowser.Controller.Plugins
-{
- /// <summary>
- /// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
- /// </summary>
- public interface IRunBeforeStartup
- {
- }
-}
diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
deleted file mode 100644
index 6024661e1..000000000
--- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Plugins
-{
- /// <summary>
- /// Represents an entry point for a module in the application. This interface is scanned for automatically and
- /// provides a hook to initialize the module at application start.
- /// The entry point can additionally be flagged as a pre-startup task by implementing the
- /// <see cref="IRunBeforeStartup"/> interface.
- /// </summary>
- public interface IServerEntryPoint : IDisposable
- {
- /// <summary>
- /// Run the initialization for this module. This method is invoked at application start.
- /// </summary>
- /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
- Task RunAsync();
- }
-}