using System; using System.Collections.Generic; using System.IO; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using Moq; using Xunit; using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration; namespace Jellyfin.Controller.Tests.MediaEncoding; public class EncodingHelperTests { [Fact] public void GetMapArgs_NoSubtitle_ExcludesAllSubs() { var state = BuildState(subtitle: null, deliveryMethod: null); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map -0:s", args, StringComparison.Ordinal); Assert.DoesNotContain("-map 1:", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_InternalSrt_MapsFromPrimaryInput() { var sub = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "srt" }; var state = BuildState(sub, SubtitleDeliveryMethod.Embed); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 0:2", args, StringComparison.Ordinal); Assert.DoesNotContain("-map 1:", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_InternalSubAtHigherIndex_MapsCorrectIndex() { var sub0 = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "srt" }; var sub1 = new MediaStream { Index = 3, Type = MediaStreamType.Subtitle, Codec = "ass" }; var state = BuildState(sub1, SubtitleDeliveryMethod.Embed, additionalStreams: [sub0, sub1]); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 0:3", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_ExternalSrt_MapsFirstStreamFromInput1() { var sub = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "srt", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.en.srt" }; var state = BuildState(sub, SubtitleDeliveryMethod.Embed); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 1:0", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_SecondExternalSrt_StillMaps1Colon0() { // Two separate .srt files — selecting the second one still maps 1:0 // because Jellyfin feeds only the selected file as ffmpeg input 1. var ext1 = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "srt", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.en.srt" }; var ext2 = new MediaStream { Index = 3, Type = MediaStreamType.Subtitle, Codec = "srt", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.fr.srt" }; var state = BuildState(ext2, SubtitleDeliveryMethod.Embed, additionalStreams: [ext1, ext2]); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 1:0", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_MksFirstTrack_MapsInFileIndex0() { var mks0 = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "subrip", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.mks" }; var mks1 = new MediaStream { Index = 3, Type = MediaStreamType.Subtitle, Codec = "ass", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.mks" }; var state = BuildState(mks0, SubtitleDeliveryMethod.Embed, additionalStreams: [mks0, mks1]); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 1:0", args, StringComparison.Ordinal); } [Fact] public void GetMapArgs_MksSecondTrack_MapsInFileIndex1() { var mks0 = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "subrip", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.mks" }; var mks1 = new MediaStream { Index = 3, Type = MediaStreamType.Subtitle, Codec = "ass", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.mks" }; var mks2 = new MediaStream { Index = 4, Type = MediaStreamType.Subtitle, Codec = "subrip", IsExternal = true, SupportsExternalStream = true, Path = "/media/movie.mks" }; var state = BuildState(mks1, SubtitleDeliveryMethod.Embed, additionalStreams: [mks0, mks1, mks2]); var args = CreateHelper().GetMapArgs(state); Assert.Contains("-map 1:1", args, StringComparison.Ordinal); } [Theory] [InlineData(SubtitleDeliveryMethod.Embed, true, "movie.idx")] [InlineData(SubtitleDeliveryMethod.Encode, true, "movie.idx")] [InlineData(SubtitleDeliveryMethod.Embed, false, "movie.sub")] [InlineData(SubtitleDeliveryMethod.Encode, false, "movie.sub")] public void GetInputArgument_VobSub_UsesCorrectPath( SubtitleDeliveryMethod deliveryMethod, bool createIdxFile, string expectedFilename) { var tempDir = Directory.CreateTempSubdirectory("jellyfin-test-"); try { var subFile = Path.Combine(tempDir.FullName, "movie.sub"); File.WriteAllText(subFile, "dummy"); if (createIdxFile) { File.WriteAllText(Path.Combine(tempDir.FullName, "movie.idx"), "dummy"); } var sub = new MediaStream { Index = 2, Type = MediaStreamType.Subtitle, Codec = "dvdsub", IsExternal = true, SupportsExternalStream = true, Path = subFile }; var state = BuildState(sub, deliveryMethod); var inputArgs = CreateHelper().GetInputArgument(state, new EncodingOptions(), null); Assert.Contains(expectedFilename, inputArgs, StringComparison.Ordinal); } finally { tempDir.Delete(true); } } private static EncodingJobInfo BuildState( MediaStream? subtitle, SubtitleDeliveryMethod? deliveryMethod, MediaStream[]? additionalStreams = null) { var video = new MediaStream { Index = 0, Type = MediaStreamType.Video, Codec = "h264" }; var audio = new MediaStream { Index = 1, Type = MediaStreamType.Audio, Codec = "aac" }; var streams = new List { video, audio }; if (additionalStreams is not null) { streams.AddRange(additionalStreams); } else if (subtitle is not null) { streams.Add(subtitle); } return new EncodingJobInfo(TranscodingJobType.Progressive) { MediaSource = new MediaSourceInfo { Container = "mkv", MediaStreams = streams, }, VideoStream = video, AudioStream = audio, SubtitleStream = subtitle, SubtitleDeliveryMethod = deliveryMethod ?? SubtitleDeliveryMethod.Drop, BaseRequest = new VideoRequestDto(), IsVideoRequest = true, IsInputVideo = true, }; } private static EncodingHelper CreateHelper() { var appPaths = Mock.Of(); var mediaEncoder = new Mock(); var subtitleEncoder = new Mock(); var config = new Mock(); var configurationManager = new Mock(); var pathManager = new Mock(); return new EncodingHelper( appPaths, mediaEncoder.Object, subtitleEncoder.Object, config.Object, configurationManager.Object, pathManager.Object); } }