mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-08-06 06:07:05 +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'
|
||||
|
||||
- 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
|
||||
|
2
.github/workflows/ci-tests.yml
vendored
2
.github/workflows/ci-tests.yml
vendored
@ -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/"
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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."
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -136,5 +136,6 @@
|
||||
"TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
|
||||
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
||||
"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 _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}\"",
|
||||
|
@ -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);
|
||||
|
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 />
|
||||
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(
|
||||
|
Loading…
Reference in New Issue
Block a user