Merge branch 'jellyfin:master' into feature/max-active-video-streams

This commit is contained in:
Mahmoud Almontasser 2025-08-03 15:50:13 +02:00 committed by GitHub
commit 03ad8cf7f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 125 additions and 18 deletions

View File

@ -27,11 +27,11 @@ jobs:
dotnet-version: '9.0.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5

View File

@ -35,7 +35,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@82ef2bc4c83f42bbeec82a5cd9a8e3f6156b64b9 # v5.4.9
uses: danielpalme/ReportGenerator-GitHub-Action@c1dd332d00304c5aa5d506aab698a5224a8fa24e # 5.4.11
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"

View File

@ -61,6 +61,7 @@
- [ikomhoog](https://github.com/ikomhoog)
- [iwalton3](https://github.com/iwalton3)
- [jftuga](https://github.com/jftuga)
- [jkhsjdhjs](https://github.com/jkhsjdhjs)
- [jmshrv](https://github.com/jmshrv)
- [joern-h](https://github.com/joern-h)
- [joshuaboniface](https://github.com/joshuaboniface)

View File

@ -90,7 +90,7 @@
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Extra - {0}",
"ValueSpecialEpisodeName": "Extra {0}",
"VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
"TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen",

View File

@ -135,6 +135,6 @@
"TaskDownloadMissingLyricsDescription": "Last ned sangtekster",
"TaskExtractMediaSegments": "Skann mediasegment",
"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."
}

View File

@ -119,5 +119,7 @@
"Forced": "https://betpro-dealers.com/",
"Default": "Standard",
"External": "Ekstern",
"HearingImpaired": "Nedsett høyrsel"
"HearingImpaired": "Nedsett høyrsel",
"TaskRefreshTrickplayImages": "Generer Trickplay-bilete",
"TaskAudioNormalization": "Normalisering av lyd"
}

View File

@ -136,5 +136,6 @@
"TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置"
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
"CleanupUserDataTask": "用戶資料清理工作"
}

View File

@ -46,6 +46,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1);
private readonly Version _minFFmpegHlsSegmentOptions = new Version(5, 0);
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
@ -1606,6 +1607,7 @@ public class DynamicHlsController : BaseJellyfinApiController
var segmentFormat = string.Empty;
var segmentContainer = outputExtension.TrimStart('.');
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))
{
@ -1621,6 +1623,11 @@ public class DynamicHlsController : BaseJellyfinApiController
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;
}
else
@ -1642,8 +1649,6 @@ public class DynamicHlsController : BaseJellyfinApiController
Path.GetFileNameWithoutExtension(outputPath));
}
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0";
return string.Format(
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}\"",

View File

@ -90,6 +90,9 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
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>();
connection.Open();
@ -105,7 +108,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId,
DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId,
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))
{
@ -1167,6 +1170,11 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
entity.UnratedType = unratedType;
}
if (reader.TryGetBoolean(index++, out var isFolder))
{
entity.IsFolder = isFolder;
}
var baseItem = BaseItemRepository.DeserializeBaseItem(entity, _logger, null, false);
var dataKeys = baseItem.GetUserDataKeys();
userDataKeys.AddRange(dataKeys);

View 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);
}
}
}
}

View File

@ -827,7 +827,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <inheritdoc />
public Task<string> ExtractVideoImagesOnIntervalAccelerated(
public async Task<string> ExtractVideoImagesOnIntervalAccelerated(
string inputFile,
string container,
MediaSourceInfo mediaSource,
@ -918,18 +918,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
inputArg = "-hwaccel_flags +low_priority " + inputArg;
}
if (enableKeyFrameOnlyExtraction)
{
inputArg = "-skip_frame nokey " + inputArg;
}
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim();
if (string.IsNullOrWhiteSpace(filterParam))
{
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(