From e9ae2d6a1e8eba531d9597a886cf3c202ae6bcd2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 14 Apr 2024 23:19:38 +0200 Subject: Write JSON directly to logfile Can't be worse than serializing to a string, concatenating and converting to utf8, right? --- MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs') diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index a07a0f41b..ea5dbf7f7 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -492,12 +492,11 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; + await JsonSerializer.SerializeAsync(logStream, state.MediaSource, cancellationToken: cancellationTokenSource.Token).ConfigureAwait(false); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes( - JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine - + Environment.NewLine - + commandLineLogMessage + + process.StartInfo.FileName + " " + process.StartInfo.Arguments + Environment.NewLine + Environment.NewLine); -- cgit v1.2.3 From c666f9d0501bbe0235315d2c2e668ec6f86eb345 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 15 Jul 2024 14:44:14 +0200 Subject: Use real temp dir instead of cache dir for temp files (#12226) --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 2 +- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- .../Images/BaseDynamicImageProvider.cs | 1 + .../ScheduledTasks/Tasks/AudioNormalizationTask.cs | 12 +++++++----- .../Trickplay/TrickplayManager.cs | 2 +- Jellyfin.Server/Helpers/StartupHelpers.cs | 1 + MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs | 11 +---------- src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 39524be1d..dc845b2d7 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -104,6 +104,6 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the temp directory within the cache folder. /// /// The temp directory. - public string TempDirectory => Path.Combine(CachePath, "temp"); + public string TempDirectory => Path.Join(Path.GetTempPath(), "jellyfin"); } } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 250bec9ea..28bb29df8 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -466,7 +466,7 @@ namespace Emby.Server.Implementations.IO File.Copy(file1, temp1, true); File.Copy(file2, file1, true); - File.Copy(temp1, file2, true); + File.Move(temp1, file2, true); } /// diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 0a3d740cc..82db7c46b 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -122,6 +122,7 @@ namespace Emby.Server.Implementations.Images } await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); + File.Delete(outputPath); return ItemUpdateType.ImageUpdate; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs index df0fdcab8..301c04915 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs @@ -29,7 +29,7 @@ public partial class AudioNormalizationTask : IScheduledTask private readonly IItemRepository _itemRepository; private readonly ILibraryManager _libraryManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IConfigurationManager _configurationManager; + private readonly IApplicationPaths _applicationPaths; private readonly ILocalizationManager _localization; private readonly ILogger _logger; @@ -39,21 +39,21 @@ public partial class AudioNormalizationTask : IScheduledTask /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public AudioNormalizationTask( IItemRepository itemRepository, ILibraryManager libraryManager, IMediaEncoder mediaEncoder, - IConfigurationManager configurationManager, + IApplicationPaths applicationPaths, ILocalizationManager localizationManager, ILogger logger) { _itemRepository = itemRepository; _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; - _configurationManager = configurationManager; + _applicationPaths = applicationPaths; _localization = localizationManager; _logger = logger; } @@ -107,7 +107,9 @@ public partial class AudioNormalizationTask : IScheduledTask } _logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id); - var tempFile = Path.Join(_configurationManager.GetTranscodePath(), a.Id + ".concat"); + var tempDir = _applicationPaths.TempDirectory; + Directory.CreateDirectory(tempDir); + var tempFile = Path.Join(tempDir, a.Id + ".concat"); var inputLines = albumTracks.Select(x => string.Format(CultureInfo.InvariantCulture, "file '{0}'", x.Path.Replace("'", @"'\''", StringComparison.Ordinal))); await File.WriteAllLinesAsync(tempFile, inputLines, cancellationToken).ConfigureAwait(false); try diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index efdfc745f..c14be032e 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -230,7 +230,7 @@ public class TrickplayManager : ITrickplayManager throw new ArgumentException("Can't create trickplay from 0 images."); } - var workDir = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")); + var workDir = Path.Combine(_appPaths.TempDirectory, "trickplay_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(workDir); var trickplayInfo = new TrickplayInfo diff --git a/Jellyfin.Server/Helpers/StartupHelpers.cs b/Jellyfin.Server/Helpers/StartupHelpers.cs index 5311a30e4..5518d6ba8 100644 --- a/Jellyfin.Server/Helpers/StartupHelpers.cs +++ b/Jellyfin.Server/Helpers/StartupHelpers.cs @@ -60,6 +60,7 @@ public static class StartupHelpers logger.LogInformation("Log directory path: {LogDirectoryPath}", appPaths.LogDirectoryPath); logger.LogInformation("Config directory path: {ConfigurationDirectoryPath}", appPaths.ConfigurationDirectoryPath); logger.LogInformation("Cache path: {CachePath}", appPaths.CachePath); + logger.LogInformation("Temp directory path: {TempDirPath}", appPaths.TempDirectory); logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 0bb7100de..b175dc403 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1203,10 +1203,14 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = Path.Join(_configurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat"); - _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); + var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.MediaSource.Id + ".concat"); + if (!File.Exists(concatFilePath)) + { + _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath); + } + arg.Append(" -f concat -safe 0 -i \"") - .Append(tmpConcatPath) + .Append(concatFilePath) .Append("\" "); } else diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e02f2116a..a7b881221 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -1203,6 +1203,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Generate concat configuration entries for each file and write to file + Directory.CreateDirectory(Path.GetDirectoryName(concatFilePath)); using StreamWriter sw = new StreamWriter(concatFilePath); foreach (var path in files) { diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index ea5dbf7f7..0b09e57b5 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -235,15 +235,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (delete(job.Path!)) { await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); - if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) - { - var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat"); - if (File.Exists(concatFilePath)) - { - _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); - File.Delete(concatFilePath); - } - } } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) @@ -419,7 +410,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var concatPath = Path.Join(_serverConfigurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat"); + var concatPath = Path.Join(_appPaths.CachePath, "concat", state.MediaSource.Id + ".concat"); await _attachmentExtractor.ExtractAllAttachments(concatPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); } else diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 75963226a..ede93aaa5 100644 --- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -219,7 +219,7 @@ public class SkiaEncoder : IImageEncoder return path; } - var tempPath = Path.Combine(_appPaths.TempDirectory, string.Concat(Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); + var tempPath = Path.Join(_appPaths.TempDirectory, string.Concat("skia_", Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid."); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); -- cgit v1.2.3 From e4101128e011c089bad46a524856438154cfbd3d Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 22 Apr 2024 06:19:17 +0800 Subject: feat: add audio remux to UniversalAudioController Signed-off-by: gnattu --- .../Controllers/UniversalAudioController.cs | 16 ++++-- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 8 +++ .../Transcoding/TranscodeManager.cs | 5 ++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 63 +++++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs') diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 1d4adae06..3115c4750 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -137,6 +138,8 @@ public class UniversalAudioController : BaseJellyfinApiController // set device specific data foreach (var sourceInfo in info.MediaSources) { + sourceInfo.TranscodingContainer = transcodingContainer; + sourceInfo.TranscodingSubProtocol = transcodingProtocol ?? sourceInfo.TranscodingSubProtocol; _mediaInfoHelper.SetDeviceSpecificData( item, sourceInfo, @@ -171,27 +174,30 @@ public class UniversalAudioController : BaseJellyfinApiController return Redirect(mediaSource.Path); } + // This one is currently very misleading as the SupportsDirectStream is always false + // The definition of DirectStream also seems changed during development + // It used to mean HTTP direct streaming, but now HLS is used even for DirectStream var isStatic = mediaSource.SupportsDirectStream; - if (!isStatic && mediaSource.TranscodingSubProtocol == MediaStreamProtocol.hls) + if (mediaSource.TranscodingSubProtocol == MediaStreamProtocol.hls) { // hls segment container can only be mpegts or fmp4 per ffmpeg documentation // ffmpeg option -> file extension // mpegts -> ts // fmp4 -> mp4 - // TODO: remove this when we switch back to the segment muxer var supportedHlsContainers = new[] { "ts", "mp4" }; + // fallback to mpegts if device reports some weird value unsupported by hls + var segmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts"; var dynamicHlsRequestDto = new HlsAudioRequestDto { Id = itemId, Container = ".m3u8", Static = isStatic, PlaySessionId = info.PlaySessionId, - // fallback to mpegts if device reports some weird value unsupported by hls - SegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts", + SegmentContainer = segmentContainer, MediaSourceId = mediaSourceId, DeviceId = deviceId, - AudioCodec = audioCodec, + AudioCodec = mediaSource.TranscodeReasons == TranscodeReason.ContainerNotSupported ? "copy" : audioCodec, EnableAutoStreamCopy = true, AllowAudioStreamCopy = true, AllowVideoStreamCopy = true, diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index f8d89119a..8df4caea5 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -151,6 +151,14 @@ public class DynamicHlsHelper var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString(); + // from universal audio service, need to override the AudioCodec when the actual request differs from original query + if (!string.Equals(state.OutputAudioCodec, _httpContextAccessor.HttpContext.Request.Query["AudioCodec"].ToString(), StringComparison.OrdinalIgnoreCase)) + { + var newQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.QueryString.ToString()); + newQuery["AudioCodec"] = state.OutputAudioCodec; + queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery); + } + // from universal audio service if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer) && !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index ea5dbf7f7..e45e8507a 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -479,6 +479,11 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable : "FFmpeg.DirectStream-"; } + if (state.VideoRequest is null && EncodingHelper.IsCopyCodec(state.OutputAudioCodec)) + { + logFilePrefix = "FFmpeg.Remux-"; + } + var logFilePath = Path.Combine( _serverConfigurationManager.ApplicationPaths.LogDirectoryPath, $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 55d1c3d51..f9fe7d351 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.Model.Dlna var inputAudioSampleRate = audioStream?.SampleRate; var inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethod.HasValue) + if (directPlayMethod is PlayMethod.DirectPlay) { var profile = options.Profile; var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); @@ -124,6 +124,41 @@ namespace MediaBrowser.Model.Dlna } } + if (directPlayMethod is PlayMethod.DirectStream) + { + var remuxContainer = item.TranscodingContainer ?? "ts"; + bool codeIsSupported; + if (item.TranscodingSubProtocol == MediaStreamProtocol.hls) + { + // Enforce HLS audio codec restrictions + if (string.Equals(remuxContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + codeIsSupported = _supportedHlsAudioCodecsMp4.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + else + { + codeIsSupported = _supportedHlsAudioCodecsTs.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + } + else + { + // Let's assume the client has given a correct container for http + codeIsSupported = true; + } + + if (codeIsSupported) + { + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = remuxContainer; + playlistItem.TranscodeReasons = transcodeReasons; + playlistItem.SubProtocol = item.TranscodingSubProtocol; + return playlistItem; + } + + transcodeReasons |= TranscodeReason.AudioCodecNotSupported; + playlistItem.TranscodeReasons = transcodeReasons; + } + TranscodingProfile? transcodingProfile = null; foreach (var tcProfile in options.Profile.TranscodingProfiles) { @@ -387,6 +422,14 @@ namespace MediaBrowser.Model.Dlna item.Path ?? "Unknown path", audioStream.Codec ?? "Unknown codec"); + var directStreamProfile = options.Profile.DirectPlayProfiles + .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectStreamSupported(x, item, audioStream)); + + if (directStreamProfile is not null) + { + return (directStreamProfile, PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported); + } + return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } @@ -2129,5 +2172,23 @@ namespace MediaBrowser.Model.Dlna return true; } + + private static bool IsAudioDirectStreamSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + // Check container type, this should NOT be supported + if (!profile.SupportsContainer(item.Container)) + { + // Check audio codec, we cannot use the SupportsAudioCodec here + // Because that one assumes empty container supports all codec, which is just useless + string? audioCodec = audioStream?.Codec; + if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) || + string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } } } -- cgit v1.2.3