mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-08-06 22:27:07 +02:00
Merge branch 'jellyfin:master' into feature/max-active-video-streams
This commit is contained in:
commit
03ad8cf7f4
6
.github/workflows/ci-codeql-analysis.yml
vendored
6
.github/workflows/ci-codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
|||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
|
uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
|
uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
|
uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
||||||
|
2
.github/workflows/ci-tests.yml
vendored
2
.github/workflows/ci-tests.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
--verbosity minimal
|
--verbosity minimal
|
||||||
|
|
||||||
- name: Merge code coverage results
|
- name: Merge code coverage results
|
||||||
uses: danielpalme/ReportGenerator-GitHub-Action@82ef2bc4c83f42bbeec82a5cd9a8e3f6156b64b9 # v5.4.9
|
uses: danielpalme/ReportGenerator-GitHub-Action@c1dd332d00304c5aa5d506aab698a5224a8fa24e # 5.4.11
|
||||||
with:
|
with:
|
||||||
reports: "**/coverage.cobertura.xml"
|
reports: "**/coverage.cobertura.xml"
|
||||||
targetdir: "merged/"
|
targetdir: "merged/"
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
- [ikomhoog](https://github.com/ikomhoog)
|
- [ikomhoog](https://github.com/ikomhoog)
|
||||||
- [iwalton3](https://github.com/iwalton3)
|
- [iwalton3](https://github.com/iwalton3)
|
||||||
- [jftuga](https://github.com/jftuga)
|
- [jftuga](https://github.com/jftuga)
|
||||||
|
- [jkhsjdhjs](https://github.com/jkhsjdhjs)
|
||||||
- [jmshrv](https://github.com/jmshrv)
|
- [jmshrv](https://github.com/jmshrv)
|
||||||
- [joern-h](https://github.com/joern-h)
|
- [joern-h](https://github.com/joern-h)
|
||||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
- [joshuaboniface](https://github.com/joshuaboniface)
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
|
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
|
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
|
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
|
||||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
"ValueSpecialEpisodeName": "Extra – {0}",
|
||||||
"VersionNumber": "Version {0}",
|
"VersionNumber": "Version {0}",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
|
"TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
|
||||||
"TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen",
|
"TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen",
|
||||||
|
@ -135,6 +135,6 @@
|
|||||||
"TaskDownloadMissingLyricsDescription": "Last ned sangtekster",
|
"TaskDownloadMissingLyricsDescription": "Last ned sangtekster",
|
||||||
"TaskExtractMediaSegments": "Skann mediasegment",
|
"TaskExtractMediaSegments": "Skann mediasegment",
|
||||||
"TaskMoveTrickplayImages": "Migrer bildeplassering for Trickplay",
|
"TaskMoveTrickplayImages": "Migrer bildeplassering for Trickplay",
|
||||||
"TaskMoveTrickplayImagesDescription": "Flytter eksisterende Trickplay-filer i henhold til bibliotekseinstillingene.",
|
"TaskMoveTrickplayImagesDescription": "Flytter eksisterende Trickplay-filer i henhold til biblioteksinstillingene.",
|
||||||
"TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment."
|
"TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment."
|
||||||
}
|
}
|
||||||
|
@ -119,5 +119,7 @@
|
|||||||
"Forced": "https://betpro-dealers.com/",
|
"Forced": "https://betpro-dealers.com/",
|
||||||
"Default": "Standard",
|
"Default": "Standard",
|
||||||
"External": "Ekstern",
|
"External": "Ekstern",
|
||||||
"HearingImpaired": "Nedsett høyrsel"
|
"HearingImpaired": "Nedsett høyrsel",
|
||||||
|
"TaskRefreshTrickplayImages": "Generer Trickplay-bilete",
|
||||||
|
"TaskAudioNormalization": "Normalisering av lyd"
|
||||||
}
|
}
|
||||||
|
@ -136,5 +136,6 @@
|
|||||||
"TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
|
"TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
||||||
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
|
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
|
||||||
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置"
|
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
|
||||||
|
"CleanupUserDataTask": "用戶資料清理工作"
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
|
|
||||||
private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
|
private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
|
||||||
private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1);
|
private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1);
|
||||||
|
private readonly Version _minFFmpegHlsSegmentOptions = new Version(5, 0);
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
@ -1606,6 +1607,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
var segmentFormat = string.Empty;
|
var segmentFormat = string.Empty;
|
||||||
var segmentContainer = outputExtension.TrimStart('.');
|
var segmentContainer = outputExtension.TrimStart('.');
|
||||||
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer);
|
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer);
|
||||||
|
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0";
|
||||||
|
|
||||||
if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -1621,6 +1623,11 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
|
false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var useLegacySegmentOption = _mediaEncoder.EncoderVersion < _minFFmpegHlsSegmentOptions;
|
||||||
|
|
||||||
|
// fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
|
||||||
|
hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont";
|
||||||
|
|
||||||
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1642,8 +1649,6 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
Path.GetFileNameWithoutExtension(outputPath));
|
Path.GetFileNameWithoutExtension(outputPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0";
|
|
||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"",
|
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"",
|
||||||
|
@ -90,6 +90,9 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
operation.JellyfinDbContext.AncestorIds.ExecuteDelete();
|
operation.JellyfinDbContext.AncestorIds.ExecuteDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// notify the other migration to just silently abort because the fix has been applied here already.
|
||||||
|
ReseedFolderFlag.RerunGuardFlag = true;
|
||||||
|
|
||||||
var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
|
var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
@ -105,7 +108,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId,
|
Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId,
|
||||||
DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId,
|
DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId,
|
||||||
PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate,
|
PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate,
|
||||||
ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType, SortName, CleanName, UnratedType FROM TypedBaseItems
|
ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType, SortName, CleanName, UnratedType, IsFolder FROM TypedBaseItems
|
||||||
""";
|
""";
|
||||||
using (new TrackedMigrationStep("Loading TypedBaseItems", _logger))
|
using (new TrackedMigrationStep("Loading TypedBaseItems", _logger))
|
||||||
{
|
{
|
||||||
@ -1167,6 +1170,11 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
entity.UnratedType = unratedType;
|
entity.UnratedType = unratedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetBoolean(index++, out var isFolder))
|
||||||
|
{
|
||||||
|
entity.IsFolder = isFolder;
|
||||||
|
}
|
||||||
|
|
||||||
var baseItem = BaseItemRepository.DeserializeBaseItem(entity, _logger, null, false);
|
var baseItem = BaseItemRepository.DeserializeBaseItem(entity, _logger, null, false);
|
||||||
var dataKeys = baseItem.GetUserDataKeys();
|
var dataKeys = baseItem.GetUserDataKeys();
|
||||||
userDataKeys.AddRange(dataKeys);
|
userDataKeys.AddRange(dataKeys);
|
||||||
|
74
Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs
Normal file
74
Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#pragma warning disable RS0030 // Do not use banned APIs
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Emby.Server.Implementations.Data;
|
||||||
|
using Jellyfin.Database.Implementations;
|
||||||
|
using Jellyfin.Server.ServerSetupApp;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Migrations.Routines;
|
||||||
|
|
||||||
|
[JellyfinMigration("2025-07-30T21:50:00", nameof(ReseedFolderFlag))]
|
||||||
|
[JellyfinMigrationBackup(JellyfinDb = true)]
|
||||||
|
internal class ReseedFolderFlag : IAsyncMigrationRoutine
|
||||||
|
{
|
||||||
|
private const string DbFilename = "library.db.old";
|
||||||
|
|
||||||
|
private readonly IStartupLogger _logger;
|
||||||
|
private readonly IServerApplicationPaths _paths;
|
||||||
|
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||||
|
|
||||||
|
public ReseedFolderFlag(
|
||||||
|
IStartupLogger<MigrateLibraryDb> startupLogger,
|
||||||
|
IDbContextFactory<JellyfinDbContext> provider,
|
||||||
|
IServerApplicationPaths paths)
|
||||||
|
{
|
||||||
|
_logger = startupLogger;
|
||||||
|
_provider = provider;
|
||||||
|
_paths = paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool RerunGuardFlag { get; set; } = false;
|
||||||
|
|
||||||
|
public async Task PerformAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (RerunGuardFlag)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Migration is skipped because it does not apply.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Migrating the IsFolder flag from library.db.old may take a while, do not stop Jellyfin.");
|
||||||
|
|
||||||
|
var dataPath = _paths.DataPath;
|
||||||
|
var libraryDbPath = Path.Combine(dataPath, DbFilename);
|
||||||
|
if (!File.Exists(libraryDbPath))
|
||||||
|
{
|
||||||
|
_logger.LogError("Cannot migrate IsFolder flag from {LibraryDb} as it does not exist. This migration expects the MigrateLibraryDb to run first.", libraryDbPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbContext = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await using (dbContext.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly");
|
||||||
|
var queryResult = connection.Query(
|
||||||
|
"""
|
||||||
|
SELECT guid FROM TypedBaseItems
|
||||||
|
WHERE IsFolder = true
|
||||||
|
""")
|
||||||
|
.Select(entity => entity.GetGuid(0))
|
||||||
|
.ToList();
|
||||||
|
_logger.LogInformation("Migrating the IsFolder flag for {Count} items.", queryResult.Count);
|
||||||
|
foreach (var id in queryResult)
|
||||||
|
{
|
||||||
|
await dbContext.BaseItems.Where(e => e.Id == id).ExecuteUpdateAsync(e => e.SetProperty(f => f.IsFolder, true), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -827,7 +827,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<string> ExtractVideoImagesOnIntervalAccelerated(
|
public async Task<string> ExtractVideoImagesOnIntervalAccelerated(
|
||||||
string inputFile,
|
string inputFile,
|
||||||
string container,
|
string container,
|
||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
@ -918,18 +918,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
inputArg = "-hwaccel_flags +low_priority " + inputArg;
|
inputArg = "-hwaccel_flags +low_priority " + inputArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableKeyFrameOnlyExtraction)
|
|
||||||
{
|
|
||||||
inputArg = "-skip_frame nokey " + inputArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim();
|
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim();
|
||||||
if (string.IsNullOrWhiteSpace(filterParam))
|
if (string.IsNullOrWhiteSpace(filterParam))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters.");
|
throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, vidEncoder, threads, qualityScale, priority, cancellationToken);
|
try
|
||||||
|
{
|
||||||
|
return await ExtractVideoImagesOnIntervalInternal(
|
||||||
|
(enableKeyFrameOnlyExtraction ? "-skip_frame nokey " : string.Empty) + inputArg,
|
||||||
|
filterParam,
|
||||||
|
vidEncoder,
|
||||||
|
threads,
|
||||||
|
qualityScale,
|
||||||
|
priority,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (FfmpegException ex)
|
||||||
|
{
|
||||||
|
if (!enableKeyFrameOnlyExtraction)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning(ex, "I-frame trickplay extraction failed, will attempt standard way. Input: {InputFile}", inputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, vidEncoder, threads, qualityScale, priority, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ExtractVideoImagesOnIntervalInternal(
|
private async Task<string> ExtractVideoImagesOnIntervalInternal(
|
||||||
|
Loading…
Reference in New Issue
Block a user