From 519709bf10e30a3666af100df4fcca206dcef498 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 3 Feb 2023 18:49:23 +0100 Subject: [PATCH 01/12] Revert "Remove DvdLib (#9068)" This reverts commit db1913b08fac0749133634efebd1ee7a7876147a. --- DvdLib/BigEndianBinaryReader.cs | 25 +++ DvdLib/DvdLib.csproj | 20 ++ DvdLib/Ifo/Cell.cs | 23 +++ DvdLib/Ifo/CellPlaybackInfo.cs | 52 +++++ DvdLib/Ifo/CellPositionInfo.cs | 19 ++ DvdLib/Ifo/Chapter.cs | 20 ++ DvdLib/Ifo/Dvd.cs | 167 +++++++++++++++ DvdLib/Ifo/DvdTime.cs | 39 ++++ DvdLib/Ifo/Program.cs | 16 ++ DvdLib/Ifo/ProgramChain.cs | 121 +++++++++++ DvdLib/Ifo/Title.cs | 70 +++++++ DvdLib/Ifo/UserOperation.cs | 37 ++++ DvdLib/Properties/AssemblyInfo.cs | 21 ++ .../ApplicationHost.cs | 4 + Jellyfin.sln | 6 + .../MediaEncoding/IMediaEncoder.cs | 8 + .../BdInfo/BdInfoDirectoryInfo.cs | 83 ++++++++ .../BdInfo/BdInfoExaminer.cs | 194 ++++++++++++++++++ .../BdInfo/BdInfoFileInfo.cs | 41 ++++ .../Encoder/MediaEncoder.cs | 79 +++++++ .../MediaBrowser.MediaEncoding.csproj | 1 + .../MediaInfo/BlurayDiscInfo.cs | 39 ++++ .../MediaInfo/IBlurayExaminer.cs | 15 ++ .../MediaBrowser.Providers.csproj | 1 + .../MediaInfo/FFProbeVideoInfo.cs | 155 +++++++++++++- .../MediaInfo/ProbeProvider.cs | 3 + 26 files changed, 1258 insertions(+), 1 deletion(-) create mode 100644 DvdLib/BigEndianBinaryReader.cs create mode 100644 DvdLib/DvdLib.csproj create mode 100644 DvdLib/Ifo/Cell.cs create mode 100644 DvdLib/Ifo/CellPlaybackInfo.cs create mode 100644 DvdLib/Ifo/CellPositionInfo.cs create mode 100644 DvdLib/Ifo/Chapter.cs create mode 100644 DvdLib/Ifo/Dvd.cs create mode 100644 DvdLib/Ifo/DvdTime.cs create mode 100644 DvdLib/Ifo/Program.cs create mode 100644 DvdLib/Ifo/ProgramChain.cs create mode 100644 DvdLib/Ifo/Title.cs create mode 100644 DvdLib/Ifo/UserOperation.cs create mode 100644 DvdLib/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs create mode 100644 MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs create mode 100644 MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs new file mode 100644 index 0000000000..b3aad85cec --- /dev/null +++ b/DvdLib/BigEndianBinaryReader.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS1591 + +using System.Buffers.Binary; +using System.IO; + +namespace DvdLib +{ + public class BigEndianBinaryReader : BinaryReader + { + public BigEndianBinaryReader(Stream input) + : base(input) + { + } + + public override ushort ReadUInt16() + { + return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2)); + } + + public override uint ReadUInt32() + { + return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4)); + } + } +} diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj new file mode 100644 index 0000000000..1053c0089f --- /dev/null +++ b/DvdLib/DvdLib.csproj @@ -0,0 +1,20 @@ + + + + + {713F42B5-878E-499D-A878-E4C652B1D5E8} + + + + + + + + net7.0 + false + true + AllDisabledByDefault + disable + + + diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs new file mode 100644 index 0000000000..ea0b50e430 --- /dev/null +++ b/DvdLib/Ifo/Cell.cs @@ -0,0 +1,23 @@ +#pragma warning disable CS1591 + +using System.IO; + +namespace DvdLib.Ifo +{ + public class Cell + { + public CellPlaybackInfo PlaybackInfo { get; private set; } + + public CellPositionInfo PositionInfo { get; private set; } + + internal void ParsePlayback(BinaryReader br) + { + PlaybackInfo = new CellPlaybackInfo(br); + } + + internal void ParsePosition(BinaryReader br) + { + PositionInfo = new CellPositionInfo(br); + } + } +} diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs new file mode 100644 index 0000000000..6e33a0ec5a --- /dev/null +++ b/DvdLib/Ifo/CellPlaybackInfo.cs @@ -0,0 +1,52 @@ +#pragma warning disable CS1591 + +using System.IO; + +namespace DvdLib.Ifo +{ + public enum BlockMode + { + NotInBlock = 0, + FirstCell = 1, + InBlock = 2, + LastCell = 3, + } + + public enum BlockType + { + Normal = 0, + Angle = 1, + } + + public enum PlaybackMode + { + Normal = 0, + StillAfterEachVOBU = 1, + } + + public class CellPlaybackInfo + { + public readonly BlockMode Mode; + public readonly BlockType Type; + public readonly bool SeamlessPlay; + public readonly bool Interleaved; + public readonly bool STCDiscontinuity; + public readonly bool SeamlessAngle; + public readonly PlaybackMode PlaybackMode; + public readonly bool Restricted; + public readonly byte StillTime; + public readonly byte CommandNumber; + public readonly DvdTime PlaybackTime; + public readonly uint FirstSector; + public readonly uint FirstILVUEndSector; + public readonly uint LastVOBUStartSector; + public readonly uint LastSector; + + internal CellPlaybackInfo(BinaryReader br) + { + br.BaseStream.Seek(0x4, SeekOrigin.Current); + PlaybackTime = new DvdTime(br.ReadBytes(4)); + br.BaseStream.Seek(0x10, SeekOrigin.Current); + } + } +} diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs new file mode 100644 index 0000000000..216aa0f77a --- /dev/null +++ b/DvdLib/Ifo/CellPositionInfo.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using System.IO; + +namespace DvdLib.Ifo +{ + public class CellPositionInfo + { + public readonly ushort VOBId; + public readonly byte CellId; + + internal CellPositionInfo(BinaryReader br) + { + VOBId = br.ReadUInt16(); + br.ReadByte(); + CellId = br.ReadByte(); + } + } +} diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs new file mode 100644 index 0000000000..e786cb5536 --- /dev/null +++ b/DvdLib/Ifo/Chapter.cs @@ -0,0 +1,20 @@ +#pragma warning disable CS1591 + +namespace DvdLib.Ifo +{ + public class Chapter + { + public ushort ProgramChainNumber { get; private set; } + + public ushort ProgramNumber { get; private set; } + + public uint ChapterNumber { get; private set; } + + public Chapter(ushort pgcNum, ushort programNum, uint chapterNum) + { + ProgramChainNumber = pgcNum; + ProgramNumber = programNum; + ChapterNumber = chapterNum; + } + } +} diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs new file mode 100644 index 0000000000..7f8ece47dc --- /dev/null +++ b/DvdLib/Ifo/Dvd.cs @@ -0,0 +1,167 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace DvdLib.Ifo +{ + public class Dvd + { + private readonly ushort _titleSetCount; + public readonly List Titles; + + private ushort _titleCount; + public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>(); + public Dvd(string path) + { + Titles = new List<Title>(); + var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories); + + var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ?? + allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase)); + + if (vmgPath == null) + { + foreach (var ifo in allFiles) + { + if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries); + if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) + { + ReadVTS(ifoNumber, ifo.FullName); + } + } + } + else + { + using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var vmgRead = new BigEndianBinaryReader(vmgFs)) + { + vmgFs.Seek(0x3E, SeekOrigin.Begin); + _titleSetCount = vmgRead.ReadUInt16(); + + // read address of TT_SRPT + vmgFs.Seek(0xC4, SeekOrigin.Begin); + uint ttSectorPtr = vmgRead.ReadUInt32(); + vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin); + ReadTT_SRPT(vmgRead); + } + } + + for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++) + { + ReadVTS(titleSetNum, allFiles); + } + } + } + + private void ReadTT_SRPT(BinaryReader read) + { + _titleCount = read.ReadUInt16(); + read.BaseStream.Seek(6, SeekOrigin.Current); + for (uint titleNum = 1; titleNum <= _titleCount; titleNum++) + { + var t = new Title(titleNum); + t.ParseTT_SRPT(read); + Titles.Add(t); + } + } + + private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles) + { + var filename = string.Format(CultureInfo.InvariantCulture, "VTS_{0:00}_0.IFO", vtsNum); + + var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ?? + allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase)); + + if (vtsPath == null) + { + throw new FileNotFoundException("Unable to find VTS IFO file"); + } + + ReadVTS(vtsNum, vtsPath.FullName); + } + + private void ReadVTS(ushort vtsNum, string vtsPath) + { + VTSPaths[vtsNum] = vtsPath; + + using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var vtsRead = new BigEndianBinaryReader(vtsFs)) + { + // Read VTS_PTT_SRPT + vtsFs.Seek(0xC8, SeekOrigin.Begin); + uint vtsPttSrptSecPtr = vtsRead.ReadUInt32(); + uint baseAddr = (vtsPttSrptSecPtr * 2048); + vtsFs.Seek(baseAddr, SeekOrigin.Begin); + + ushort numTitles = vtsRead.ReadUInt16(); + vtsRead.ReadUInt16(); + uint endaddr = vtsRead.ReadUInt32(); + uint[] offsets = new uint[numTitles]; + for (ushort titleNum = 0; titleNum < numTitles; titleNum++) + { + offsets[titleNum] = vtsRead.ReadUInt32(); + } + + for (uint titleNum = 0; titleNum < numTitles; titleNum++) + { + uint chapNum = 1; + vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin); + var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1)); + if (t == null) + { + continue; + } + + do + { + t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum)); + if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) + { + break; + } + + chapNum++; + } + while (vtsFs.Position < (baseAddr + endaddr)); + } + + // Read VTS_PGCI + vtsFs.Seek(0xCC, SeekOrigin.Begin); + uint vtsPgciSecPtr = vtsRead.ReadUInt32(); + vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin); + + long startByte = vtsFs.Position; + + ushort numPgcs = vtsRead.ReadUInt16(); + vtsFs.Seek(6, SeekOrigin.Current); + for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++) + { + byte pgcCat = vtsRead.ReadByte(); + bool entryPgc = (pgcCat & 0x80) != 0; + uint titleNum = (uint)(pgcCat & 0x7F); + + vtsFs.Seek(3, SeekOrigin.Current); + uint vtsPgcOffset = vtsRead.ReadUInt32(); + + var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum)); + if (t != null) + { + t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum); + } + } + } + } + } + } +} diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs new file mode 100644 index 0000000000..d231406106 --- /dev/null +++ b/DvdLib/Ifo/DvdTime.cs @@ -0,0 +1,39 @@ +#pragma warning disable CS1591 + +using System; + +namespace DvdLib.Ifo +{ + public class DvdTime + { + public readonly byte Hour, Minute, Second, Frames, FrameRate; + + public DvdTime(byte[] data) + { + Hour = GetBCDValue(data[0]); + Minute = GetBCDValue(data[1]); + Second = GetBCDValue(data[2]); + Frames = GetBCDValue((byte)(data[3] & 0x3F)); + + if ((data[3] & 0x80) != 0) + { + FrameRate = 30; + } + else if ((data[3] & 0x40) != 0) + { + FrameRate = 25; + } + } + + private static byte GetBCDValue(byte data) + { + return (byte)((((data & 0xF0) >> 4) * 10) + (data & 0x0F)); + } + + public static explicit operator TimeSpan(DvdTime time) + { + int ms = (int)(((1.0 / (double)time.FrameRate) * time.Frames) * 1000.0); + return new TimeSpan(0, time.Hour, time.Minute, time.Second, ms); + } + } +} diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs new file mode 100644 index 0000000000..3d94fa7dc1 --- /dev/null +++ b/DvdLib/Ifo/Program.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; + +namespace DvdLib.Ifo +{ + public class Program + { + public IReadOnlyList<Cell> Cells { get; } + + public Program(List<Cell> cells) + { + Cells = cells; + } + } +} diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs new file mode 100644 index 0000000000..83c0051b90 --- /dev/null +++ b/DvdLib/Ifo/ProgramChain.cs @@ -0,0 +1,121 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace DvdLib.Ifo +{ + public enum ProgramPlaybackMode + { + Sequential, + Random, + Shuffle + } + + public class ProgramChain + { + private byte _programCount; + public readonly List<Program> Programs; + + private byte _cellCount; + public readonly List<Cell> Cells; + + public DvdTime PlaybackTime { get; private set; } + + public UserOperation ProhibitedUserOperations { get; private set; } + + public byte[] AudioStreamControl { get; private set; } // 8*2 entries + public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries + + private ushort _nextProgramNumber; + + private ushort _prevProgramNumber; + + private ushort _goupProgramNumber; + + public ProgramPlaybackMode PlaybackMode { get; private set; } + + public uint ProgramCount { get; private set; } + + public byte StillTime { get; private set; } + + public byte[] Palette { get; private set; } // 16*4 entries + + private ushort _commandTableOffset; + + private ushort _programMapOffset; + private ushort _cellPlaybackOffset; + private ushort _cellPositionOffset; + + public readonly uint VideoTitleSetIndex; + + internal ProgramChain(uint vtsPgcNum) + { + VideoTitleSetIndex = vtsPgcNum; + Cells = new List<Cell>(); + Programs = new List<Program>(); + } + + internal void ParseHeader(BinaryReader br) + { + long startPos = br.BaseStream.Position; + + br.ReadUInt16(); + _programCount = br.ReadByte(); + _cellCount = br.ReadByte(); + PlaybackTime = new DvdTime(br.ReadBytes(4)); + ProhibitedUserOperations = (UserOperation)br.ReadUInt32(); + AudioStreamControl = br.ReadBytes(16); + SubpictureStreamControl = br.ReadBytes(128); + + _nextProgramNumber = br.ReadUInt16(); + _prevProgramNumber = br.ReadUInt16(); + _goupProgramNumber = br.ReadUInt16(); + + StillTime = br.ReadByte(); + byte pbMode = br.ReadByte(); + if (pbMode == 0) + { + PlaybackMode = ProgramPlaybackMode.Sequential; + } + else + { + PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle; + } + + ProgramCount = (uint)(pbMode & 0x7F); + + Palette = br.ReadBytes(64); + _commandTableOffset = br.ReadUInt16(); + _programMapOffset = br.ReadUInt16(); + _cellPlaybackOffset = br.ReadUInt16(); + _cellPositionOffset = br.ReadUInt16(); + + // read position info + br.BaseStream.Seek(startPos + _cellPositionOffset, SeekOrigin.Begin); + for (int cellNum = 0; cellNum < _cellCount; cellNum++) + { + var c = new Cell(); + c.ParsePosition(br); + Cells.Add(c); + } + + br.BaseStream.Seek(startPos + _cellPlaybackOffset, SeekOrigin.Begin); + for (int cellNum = 0; cellNum < _cellCount; cellNum++) + { + Cells[cellNum].ParsePlayback(br); + } + + br.BaseStream.Seek(startPos + _programMapOffset, SeekOrigin.Begin); + var cellNumbers = new List<int>(); + for (int progNum = 0; progNum < _programCount; progNum++) cellNumbers.Add(br.ReadByte() - 1); + + for (int i = 0; i < cellNumbers.Count; i++) + { + int max = (i + 1 == cellNumbers.Count) ? _cellCount : cellNumbers[i + 1]; + Programs.Add(new Program(Cells.Where((c, idx) => idx >= cellNumbers[i] && idx < max).ToList())); + } + } + } +} diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs new file mode 100644 index 0000000000..29a0b95c72 --- /dev/null +++ b/DvdLib/Ifo/Title.cs @@ -0,0 +1,70 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.IO; + +namespace DvdLib.Ifo +{ + public class Title + { + public uint TitleNumber { get; private set; } + + public uint AngleCount { get; private set; } + + public ushort ChapterCount { get; private set; } + + public byte VideoTitleSetNumber { get; private set; } + + private ushort _parentalManagementMask; + private byte _titleNumberInVTS; + private uint _vtsStartSector; // relative to start of entire disk + + public ProgramChain EntryProgramChain { get; private set; } + + public readonly List<ProgramChain> ProgramChains; + + public readonly List<Chapter> Chapters; + + public Title(uint titleNum) + { + ProgramChains = new List<ProgramChain>(); + Chapters = new List<Chapter>(); + Chapters = new List<Chapter>(); + TitleNumber = titleNum; + } + + public bool IsVTSTitle(uint vtsNum, uint vtsTitleNum) + { + return (vtsNum == VideoTitleSetNumber && vtsTitleNum == _titleNumberInVTS); + } + + internal void ParseTT_SRPT(BinaryReader br) + { + byte titleType = br.ReadByte(); + // TODO parse Title Type + + AngleCount = br.ReadByte(); + ChapterCount = br.ReadUInt16(); + _parentalManagementMask = br.ReadUInt16(); + VideoTitleSetNumber = br.ReadByte(); + _titleNumberInVTS = br.ReadByte(); + _vtsStartSector = br.ReadUInt32(); + } + + internal void AddPgc(BinaryReader br, long startByte, bool entryPgc, uint pgcNum) + { + long curPos = br.BaseStream.Position; + br.BaseStream.Seek(startByte, SeekOrigin.Begin); + + var pgc = new ProgramChain(pgcNum); + pgc.ParseHeader(br); + ProgramChains.Add(pgc); + if (entryPgc) + { + EntryProgramChain = pgc; + } + + br.BaseStream.Seek(curPos, SeekOrigin.Begin); + } + } +} diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs new file mode 100644 index 0000000000..5d111ebc06 --- /dev/null +++ b/DvdLib/Ifo/UserOperation.cs @@ -0,0 +1,37 @@ +#pragma warning disable CS1591 + +using System; + +namespace DvdLib.Ifo +{ + [Flags] + public enum UserOperation + { + None = 0, + TitleOrTimePlay = 1, + ChapterSearchOrPlay = 2, + TitlePlay = 4, + Stop = 8, + GoUp = 16, + TimeOrChapterSearch = 32, + PrevOrTopProgramSearch = 64, + NextProgramSearch = 128, + ForwardScan = 256, + BackwardScan = 512, + TitleMenuCall = 1024, + RootMenuCall = 2048, + SubpictureMenuCall = 4096, + AudioMenuCall = 8192, + AngleMenuCall = 16384, + ChapterMenuCall = 32768, + Resume = 65536, + ButtonSelectOrActive = 131072, + StillOff = 262144, + PauseOn = 524288, + AudioStreamChange = 1048576, + SubpictureStreamChange = 2097152, + AngleChange = 4194304, + KaraokeAudioPresentationModeChange = 8388608, + VideoPresentationModeChange = 16777216, + } +} diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6acd571d68 --- /dev/null +++ b/DvdLib/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DvdLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 37a9e7715d..d43d368e0b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -80,11 +80,13 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; +using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Subtitles; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; @@ -529,6 +531,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); + serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); + serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); diff --git a/Jellyfin.sln b/Jellyfin.sln index cad23fc5ee..ee772cbe40 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing", "src\Jel EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" @@ -135,6 +137,10 @@ Global {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index bc6207ac51..fe8e9063ee 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -187,5 +187,13 @@ namespace MediaBrowser.Controller.MediaEncoding /// <param name="path">The path.</param> /// <param name="pathType">The type of path.</param> void UpdateEncoderPath(string path, string pathType); + + /// <summary> + /// Gets the primary playlist of .vob files. + /// </summary> + /// <param name="path">The to the .vob files.</param> + /// <param name="titleNumber">The title number to start with.</param> + /// <returns>A playlist.</returns> + IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); } } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs new file mode 100644 index 0000000000..7e026b42e3 --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -0,0 +1,83 @@ +#pragma warning disable CS1591 + +using System; +using System.Linq; +using BDInfo.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.MediaEncoding.BdInfo +{ + public class BdInfoDirectoryInfo : IDirectoryInfo + { + private readonly IFileSystem _fileSystem; + + private readonly FileSystemMetadata _impl; + + public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) + { + _fileSystem = fileSystem; + _impl = _fileSystem.GetDirectoryInfo(path); + } + + private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) + { + _fileSystem = fileSystem; + _impl = impl; + } + + public string Name => _impl.Name; + + public string FullName => _impl.FullName; + + public IDirectoryInfo? Parent + { + get + { + var parentFolder = System.IO.Path.GetDirectoryName(_impl.FullName); + if (parentFolder is not null) + { + return new BdInfoDirectoryInfo(_fileSystem, parentFolder); + } + + return null; + } + } + + public IDirectoryInfo[] GetDirectories() + { + return Array.ConvertAll( + _fileSystem.GetDirectories(_impl.FullName).ToArray(), + x => new BdInfoDirectoryInfo(_fileSystem, x)); + } + + public IFileInfo[] GetFiles() + { + return Array.ConvertAll( + _fileSystem.GetFiles(_impl.FullName).ToArray(), + x => new BdInfoFileInfo(x)); + } + + public IFileInfo[] GetFiles(string searchPattern) + { + return Array.ConvertAll( + _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(), + x => new BdInfoFileInfo(x)); + } + + public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption) + { + return Array.ConvertAll( + _fileSystem.GetFiles( + _impl.FullName, + new[] { searchPattern }, + false, + (searchOption & System.IO.SearchOption.AllDirectories) == System.IO.SearchOption.AllDirectories).ToArray(), + x => new BdInfoFileInfo(x)); + } + + public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) + { + return new BdInfoDirectoryInfo(fs, path); + } + } +} diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs new file mode 100644 index 0000000000..3e53cbf29f --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BDInfo; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.MediaEncoding.BdInfo +{ + /// <summary> + /// Class BdInfoExaminer. + /// </summary> + public class BdInfoExaminer : IBlurayExaminer + { + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoExaminer" /> class. + /// </summary> + /// <param name="fileSystem">The filesystem.</param> + public BdInfoExaminer(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + /// <summary> + /// Gets the disc info. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + public BlurayDiscInfo GetDiscInfo(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); + + bdrom.Scan(); + + // Get the longest playlist + var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); + + var outputStream = new BlurayDiscInfo + { + MediaStreams = Array.Empty<MediaStream>() + }; + + if (playlist is null) + { + return outputStream; + } + + outputStream.Chapters = playlist.Chapters.ToArray(); + + outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; + + var mediaStreams = new List<MediaStream>(); + + foreach (var stream in playlist.SortedStreams) + { + if (stream is TSVideoStream videoStream) + { + AddVideoStream(mediaStreams, videoStream); + continue; + } + + if (stream is TSAudioStream audioStream) + { + AddAudioStream(mediaStreams, audioStream); + continue; + } + + if (stream is TSTextStream textStream) + { + AddSubtitleStream(mediaStreams, textStream); + continue; + } + + if (stream is TSGraphicsStream graphicsStream) + { + AddSubtitleStream(mediaStreams, graphicsStream); + } + } + + outputStream.MediaStreams = mediaStreams.ToArray(); + + outputStream.PlaylistName = playlist.Name; + + if (playlist.StreamClips is not null && playlist.StreamClips.Any()) + { + // Get the files in the playlist + outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray(); + } + + return outputStream; + } + + /// <summary> + /// Adds the video stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="videoStream">The video stream.</param> + private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream) + { + var mediaStream = new MediaStream + { + BitRate = Convert.ToInt32(videoStream.BitRate), + Width = videoStream.Width, + Height = videoStream.Height, + Codec = videoStream.CodecShortName, + IsInterlaced = videoStream.IsInterlaced, + Type = MediaStreamType.Video, + Index = streams.Count + }; + + if (videoStream.FrameRateDenominator > 0) + { + float frameRateEnumerator = videoStream.FrameRateEnumerator; + float frameRateDenominator = videoStream.FrameRateDenominator; + + mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; + } + + streams.Add(mediaStream); + } + + /// <summary> + /// Adds the audio stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="audioStream">The audio stream.</param> + private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream) + { + var stream = new MediaStream + { + Codec = audioStream.CodecShortName, + Language = audioStream.LanguageCode, + Channels = audioStream.ChannelCount, + SampleRate = audioStream.SampleRate, + Type = MediaStreamType.Audio, + Index = streams.Count + }; + + var bitrate = Convert.ToInt32(audioStream.BitRate); + + if (bitrate > 0) + { + stream.BitRate = bitrate; + } + + if (audioStream.LFE > 0) + { + stream.Channels = audioStream.ChannelCount + 1; + } + + streams.Add(stream); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) + { + streams.Add(new MediaStream + { + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) + { + streams.Add(new MediaStream + { + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); + } + } +} diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs new file mode 100644 index 0000000000..d55688e3df --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -0,0 +1,41 @@ +#pragma warning disable CS1591 + +using System.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.MediaEncoding.BdInfo +{ + public class BdInfoFileInfo : BDInfo.IO.IFileInfo + { + private FileSystemMetadata _impl; + + public BdInfoFileInfo(FileSystemMetadata impl) + { + _impl = impl; + } + + public string Name => _impl.Name; + + public string FullName => _impl.FullName; + + public string Extension => _impl.Extension; + + public long Length => _impl.Length; + + public bool IsDir => _impl.IsDirectory; + + public Stream OpenRead() + { + return new FileStream( + FullName, + FileMode.Open, + FileAccess.Read, + FileShare.Read); + } + + public StreamReader OpenText() + { + return new StreamReader(OpenRead()); + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index cef02d5f8f..4a795625d0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -865,6 +865,85 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new NotImplementedException(); } + /// <inheritdoc /> + public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) + { + // min size 300 mb + const long MinPlayableSize = 314572800; + + // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size + // Once we reach a file that is at least the minimum, return all subsequent ones + var allVobs = _fileSystem.GetFiles(path, true) + .Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase)) + .OrderBy(i => i.FullName) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (allVobs.Count == 0) + { + _logger.LogWarning("No vobs found in dvd structure."); + return Enumerable.Empty<string>(); + } + + if (titleNumber.HasValue) + { + var prefix = string.Format( + CultureInfo.InvariantCulture, + titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_", + titleNumber.Value); + var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (vobs.Count > 0) + { + var minSizeVobs = vobs + .SkipWhile(f => f.Length < MinPlayableSize) + .ToList(); + + return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName); + } + + _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path); + } + + var files = allVobs + .SkipWhile(f => f.Length < MinPlayableSize) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (files.Count == 0) + { + _logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + + // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file + if (files.Count > 0) + { + var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_'); + + if (parts.Length == 3) + { + var title = parts[1]; + + files = files.TakeWhile(f => + { + var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); + + return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); + }).ToList(); + + // If this resulted in not getting any vobs, just take them all + if (files.Count == 0) + { + _logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + } + } + + return files.Select(i => i.FullName); + } + public bool CanExtractSubtitles(string codec) { // TODO is there ever a case when a subtitle can't be extracted?? diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index f4438fe194..a0624fe76b 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -22,6 +22,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="BDInfo" /> <PackageReference Include="libse" /> <PackageReference Include="Microsoft.Extensions.Http" /> <PackageReference Include="System.Text.Encoding.CodePages" /> diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs new file mode 100644 index 0000000000..83f982a5c8 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -0,0 +1,39 @@ +#nullable disable +#pragma warning disable CS1591 + +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.MediaInfo +{ + /// <summary> + /// Represents the result of BDInfo output. + /// </summary> + public class BlurayDiscInfo + { + /// <summary> + /// Gets or sets the media streams. + /// </summary> + /// <value>The media streams.</value> + public MediaStream[] MediaStreams { get; set; } + + /// <summary> + /// Gets or sets the run time ticks. + /// </summary> + /// <value>The run time ticks.</value> + public long? RunTimeTicks { get; set; } + + /// <summary> + /// Gets or sets the files. + /// </summary> + /// <value>The files.</value> + public string[] Files { get; set; } + + public string PlaylistName { get; set; } + + /// <summary> + /// Gets or sets the chapters. + /// </summary> + /// <value>The chapters.</value> + public double[] Chapters { get; set; } + } +} diff --git a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs new file mode 100644 index 0000000000..5b7d1d03c3 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs @@ -0,0 +1,15 @@ +namespace MediaBrowser.Model.MediaInfo +{ + /// <summary> + /// Interface IBlurayExaminer. + /// </summary> + public interface IBlurayExaminer + { + /// <summary> + /// Gets the disc info. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + BlurayDiscInfo GetDiscInfo(string path); + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 6a40833d7f..a0f97df40d 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -8,6 +8,7 @@ <ItemGroup> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + <ProjectReference Include="..\DvdLib\DvdLib.csproj" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 0f35c6a5ea..81434b8620 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using DvdLib.Ifo; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -36,6 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILogger<FFProbeVideoInfo> _logger; private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; + private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; private readonly IEncodingManager _encodingManager; private readonly IServerConfigurationManager _config; @@ -51,6 +53,7 @@ namespace MediaBrowser.Providers.MediaInfo IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, + IBlurayExaminer blurayExaminer, ILocalizationManager localization, IEncodingManager encodingManager, IServerConfigurationManager config, @@ -64,6 +67,7 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; + _blurayExaminer = blurayExaminer; _localization = localization; _encodingManager = encodingManager; _config = config; @@ -80,16 +84,47 @@ namespace MediaBrowser.Providers.MediaInfo CancellationToken cancellationToken) where T : Video { + BlurayDiscInfo blurayDiscInfo = null; + Model.MediaInfo.MediaInfo mediaInfoResult = null; if (!item.IsShortcut || options.EnableRemoteContentProbe) { + string[] streamFileNames = null; + + if (item.VideoType == VideoType.Dvd) + { + streamFileNames = FetchFromDvdLib(item); + + if (streamFileNames.Length == 0) + { + _logger.LogError("No playable vobs found in dvd structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } + else if (item.VideoType == VideoType.BluRay) + { + var inputPath = item.Path; + + blurayDiscInfo = GetBDInfo(inputPath); + + streamFileNames = blurayDiscInfo.Files; + + if (streamFileNames.Length == 0) + { + _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } + + streamFileNames ??= Array.Empty<string>(); + mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } - await Fetch(item, cancellationToken, mediaInfoResult, options).ConfigureAwait(false); + await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false); return ItemUpdateType.MetadataImport; } @@ -129,6 +164,7 @@ namespace MediaBrowser.Providers.MediaInfo Video video, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo, + BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) { List<MediaStream> mediaStreams; @@ -182,6 +218,10 @@ namespace MediaBrowser.Providers.MediaInfo video.Container = mediaInfo.Container; chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>(); + if (blurayInfo is not null) + { + FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo); + } } else { @@ -277,6 +317,91 @@ namespace MediaBrowser.Providers.MediaInfo } } + private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo) + { + var video = (Video)item; + + // video.PlayableStreamFileNames = blurayInfo.Files.ToList(); + + // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output + if (blurayInfo.Files.Length > 1) + { + int? currentHeight = null; + int? currentWidth = null; + int? currentBitRate = null; + + var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + + // Grab the values that ffprobe recorded + if (videoStream is not null) + { + currentBitRate = videoStream.BitRate; + currentWidth = videoStream.Width; + currentHeight = videoStream.Height; + } + + // Fill video properties from the BDInfo result + mediaStreams.Clear(); + mediaStreams.AddRange(blurayInfo.MediaStreams); + + if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) + { + video.RunTimeTicks = blurayInfo.RunTimeTicks; + } + + if (blurayInfo.Chapters is not null) + { + double[] brChapter = blurayInfo.Chapters; + chapters = new ChapterInfo[brChapter.Length]; + for (int i = 0; i < brChapter.Length; i++) + { + chapters[i] = new ChapterInfo + { + StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks + }; + } + } + + videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + + // Use the ffprobe values if these are empty + if (videoStream is not null) + { + videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; + videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; + videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; + } + } + } + + private bool IsEmpty(int? num) + { + return !num.HasValue || num.Value == 0; + } + + /// <summary> + /// Gets information about the longest playlist on a bdrom. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>VideoStream.</returns> + private BlurayDiscInfo GetBDInfo(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + try + { + return _blurayExaminer.GetDiscInfo(path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting BDInfo"); + return null; + } + } + private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOptions, LibraryOptions libraryOptions) { var replaceData = refreshOptions.ReplaceAllMetadata; @@ -558,5 +683,33 @@ namespace MediaBrowser.Providers.MediaInfo return chapters; } + + private string[] FetchFromDvdLib(Video item) + { + var path = item.Path; + var dvd = new Dvd(path); + + var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault(); + + byte? titleNumber = null; + + if (primaryTitle is not null) + { + titleNumber = primaryTitle.VideoTitleSetNumber; + item.RunTimeTicks = GetRuntime(primaryTitle); + } + + return _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, titleNumber) + .Select(Path.GetFileName) + .ToArray(); + } + + private long GetRuntime(Title title) + { + return title.ProgramChains + .Select(i => (TimeSpan)i.PlaybackTime) + .Select(i => i.Ticks) + .Sum(); + } } } diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index 31fa3da1ce..2800219552 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -53,6 +53,7 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param> + /// <param name="blurayExaminer">Instance of the <see cref="IBlurayExaminer"/> interface.</param> /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> @@ -66,6 +67,7 @@ namespace MediaBrowser.Providers.MediaInfo IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, + IBlurayExaminer blurayExaminer, ILocalizationManager localization, IEncodingManager encodingManager, IServerConfigurationManager config, @@ -85,6 +87,7 @@ namespace MediaBrowser.Providers.MediaInfo mediaSourceManager, mediaEncoder, itemRepo, + blurayExaminer, localization, encodingManager, config, From ddfdec7f46dd31d437dc6210658392fc7f894138 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Fri, 3 Feb 2023 20:03:55 +0100 Subject: [PATCH 02/12] Fix BD and DVD folder probing and playback --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +- .../MediaEncoding/EncodingHelper.cs | 10 ++ .../MediaEncoding/IMediaEncoder.cs | 16 ++ .../Encoder/EncodingUtils.cs | 27 +++- .../Encoder/MediaEncoder.cs | 146 +++++++----------- .../MediaInfo/FFProbeVideoInfo.cs | 68 ++++---- 6 files changed, 147 insertions(+), 125 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 12960f87a2..29cb3d2f9f 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -525,7 +525,10 @@ public class TranscodingJobHelper : IDisposable if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); - await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + if (state.VideoType != VideoType.Dvd) + { + await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + } if (state.SubtitleStream.IsExternal && string.Equals(Path.GetExtension(state.SubtitleStream.Path), ".mks", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a844e64433..b3a1b2a991 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -535,6 +535,16 @@ namespace MediaBrowser.Controller.MediaEncoding { var mediaPath = state.MediaPath ?? string.Empty; + if (state.MediaSource.VideoType == VideoType.Dvd) + { + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource); + } + + if (state.MediaSource.VideoType == VideoType.BluRay) + { + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2TsFiles(state.MediaPath, null).ToList(), state.MediaSource); + } + return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index fe8e9063ee..c34ce5d29b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -153,6 +153,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// <returns>System.String.</returns> string GetInputArgument(string inputFile, MediaSourceInfo mediaSource); + /// <summary> + /// Gets the input argument. + /// </summary> + /// <param name="inputFiles">The input files.</param> + /// <param name="mediaSource">The mediaSource.</param> + /// <returns>System.String.</returns> + string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource); + /// <summary> /// Gets the input argument for an external subtitle file. /// </summary> @@ -195,5 +203,13 @@ namespace MediaBrowser.Controller.MediaEncoding /// <param name="titleNumber">The title number to start with.</param> /// <returns>A playlist.</returns> IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); + + /// <summary> + /// Gets the primary playlist of .m2ts files. + /// </summary> + /// <param name="path">The to the .m2ts files.</param> + /// <param name="titleNumber">The title number to start with.</param> + /// <returns>A playlist.</returns> + IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index d0ea0429b5..0f202a90e5 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -1,7 +1,9 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.Encoder @@ -15,21 +17,38 @@ namespace MediaBrowser.MediaEncoding.Encoder return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFile); } - return GetConcatInputArgument(inputFile, inputPrefix); + return GetFileInputArgument(inputFile, inputPrefix); + } + + public static string GetInputArgument(string inputPrefix, IReadOnlyList<string> inputFiles, MediaProtocol protocol) + { + if (protocol != MediaProtocol.File) + { + return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFiles[0]); + } + + return GetConcatInputArgument(inputFiles, inputPrefix); } /// <summary> /// Gets the concat input argument. /// </summary> - /// <param name="inputFile">The input file.</param> + /// <param name="inputFiles">The input files.</param> /// <param name="inputPrefix">The input prefix.</param> /// <returns>System.String.</returns> - private static string GetConcatInputArgument(string inputFile, string inputPrefix) + private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles, string inputPrefix) { // Get all streams // If there's more than one we'll need to use the concat command + if (inputFiles.Count > 1) + { + var files = string.Join("|", inputFiles.Select(NormalizePath)); + + return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files); + } + // Determine the input path for video files - return GetFileInputArgument(inputFile, inputPrefix); + return GetFileInputArgument(inputFiles[0], inputPrefix); } /// <summary> diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4a795625d0..df31ba83e5 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; + private readonly IBlurayExaminer _blurayExaminer; private readonly IConfiguration _config; private readonly IServerConfigurationManager _serverConfig; private readonly string _startupOptionFFmpegPath; @@ -95,6 +96,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ILogger<MediaEncoder> logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, + IBlurayExaminer blurayExaminer, ILocalizationManager localization, IConfiguration config, IServerConfigurationManager serverConfig) @@ -102,6 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; + _blurayExaminer = blurayExaminer; _localization = localization; _config = config; _serverConfig = serverConfig; @@ -117,16 +120,22 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public string ProbePath => _ffprobePath; + /// <inheritdoc /> public Version EncoderVersion => _ffmpegVersion; + /// <inheritdoc /> public bool IsPkeyPauseSupported => _isPkeyPauseSupported; + /// <inheritdoc /> public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; + /// <inheritdoc /> public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; + /// <inheritdoc /> public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; + /// <inheritdoc /> public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier; /// <summary> @@ -344,26 +353,31 @@ namespace MediaBrowser.MediaEncoding.Encoder _ffmpegVersion = validator.GetFFmpegVersion(); } + /// <inheritdoc /> public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsDecoder(string decoder) { return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsHwaccel(string hwaccel) { return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsFilter(string filter) { return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsFilterWithOption(FilterOptionType option) { if (_filtersWithOption.TryGetValue((int)option, out var val)) @@ -394,24 +408,16 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// <summary> - /// Gets the media info. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> + /// <inheritdoc /> public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) { var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - var inputFile = request.MediaSource.Path; - string analyzeDuration = string.Empty; string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty; if (request.MediaSource.AnalyzeDurationMs > 0) { - analyzeDuration = "-analyzeduration " + - (request.MediaSource.AnalyzeDurationMs * 1000).ToString(); + analyzeDuration = "-analyzeduration " + (request.MediaSource.AnalyzeDurationMs * 1000).ToString(); } else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration)) { @@ -419,7 +425,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } return GetMediaInfoInternal( - GetInputArgument(inputFile, request.MediaSource), + GetInputArgument(request.MediaSource.Path, request.MediaSource), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters, @@ -429,36 +435,24 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken); } - /// <summary> - /// Gets the input argument. - /// </summary> - /// <param name="inputFile">The input file.</param> - /// <param name="mediaSource">The mediaSource.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentException">Unrecognized InputType.</exception> - public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) + /// <inheritdoc /> + public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource) { - var prefix = "file"; - if (mediaSource.VideoType == VideoType.BluRay - || mediaSource.IsoType == IsoType.BluRay) - { - prefix = "bluray"; - } + return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol); + } - return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol); + /// <inheritdoc /> + public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) + { + return EncodingUtils.GetInputArgument("file", new List<string>() { inputFile }, mediaSource.Protocol); } - /// <summary> - /// Gets the input argument for an external subtitle file. - /// </summary> - /// <param name="inputFile">The input file.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentException">Unrecognized InputType.</exception> + /// <inheritdoc /> public string GetExternalSubtitleInputArgument(string inputFile) { const string Prefix = "file"; - return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File); + return EncodingUtils.GetInputArgument(Prefix, new List<string> { inputFile }, MediaProtocol.File); } /// <summary> @@ -549,6 +543,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// <inheritdoc /> public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) { var mediaSource = new MediaSourceInfo @@ -559,11 +554,13 @@ namespace MediaBrowser.MediaEncoding.Encoder return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken); } + /// <inheritdoc /> public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken); } + /// <inheritdoc /> public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken) { return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken); @@ -767,6 +764,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// <inheritdoc /> public string GetTimeParameter(long ticks) { var time = TimeSpan.FromTicks(ticks); @@ -868,80 +866,56 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { - // min size 300 mb - const long MinPlayableSize = 314572800; - - // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size - // Once we reach a file that is at least the minimum, return all subsequent ones + // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title VOBs ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) - .Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase)) + .Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase)) + .Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase)) + .Where(file => !file.Name.EndsWith("_0.VOB", StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.FullName) .ToList(); - // If we didn't find any satisfying the min length, just take them all - if (allVobs.Count == 0) - { - _logger.LogWarning("No vobs found in dvd structure."); - return Enumerable.Empty<string>(); - } - if (titleNumber.HasValue) { - var prefix = string.Format( - CultureInfo.InvariantCulture, - titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_", - titleNumber.Value); + var prefix = string.Format(CultureInfo.InvariantCulture, "VTS_{0:D2}_", titleNumber.Value); var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); if (vobs.Count > 0) { - var minSizeVobs = vobs - .SkipWhile(f => f.Length < MinPlayableSize) - .ToList(); - - return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName); + return vobs.Select(i => i.FullName); } - _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path); + _logger.LogWarning("Could not determine VOB file list for title {Title} of {Path}.", titleNumber, path); } - var files = allVobs - .SkipWhile(f => f.Length < MinPlayableSize) + // Check for multiple big titles (> 900 MB) + var titles = allVobs + .Where(vob => vob.Length >= 900 * 1024 * 1024) + .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]) + .GroupBy(x => x) + .Select(y => y.First()) .ToList(); - // If we didn't find any satisfying the min length, just take them all - if (files.Count == 0) + // Fall back to first title if no big title is found + if (titles.FirstOrDefault() == null) { - _logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs."); - files = allVobs; + titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).Split('_')[1]); } - // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file - if (files.Count > 0) - { - var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_'); - - if (parts.Length == 3) - { - var title = parts[1]; - - files = files.TakeWhile(f => - { - var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); - - return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); - }).ToList(); + // Aggregate all VOBs of the titles + return allVobs + .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1])) + .Select(i => i.FullName) + .ToList(); + } - // If this resulted in not getting any vobs, just take them all - if (files.Count == 0) - { - _logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs."); - files = allVobs; - } - } - } + public IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) + { + var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; + var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/"); - return files.Select(i => i.FullName); + return directoryFiles + .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) + .Select(f => f.FullName); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 81434b8620..393e1f22a7 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -90,36 +90,49 @@ namespace MediaBrowser.Providers.MediaInfo if (!item.IsShortcut || options.EnableRemoteContentProbe) { - string[] streamFileNames = null; - if (item.VideoType == VideoType.Dvd) { - streamFileNames = FetchFromDvdLib(item); + // Fetch metadata of first VOB + var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + mediaInfoResult = await GetMediaInfo( + new Video + { + Path = vobs.First() + }, + cancellationToken).ConfigureAwait(false); - if (streamFileNames.Length == 0) + // Remove first VOB + vobs.RemoveAt(0); + + // Add runtime from all other VOBs + foreach (var vob in vobs) { - _logger.LogError("No playable vobs found in dvd structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; + var tmpMediaInfo = await GetMediaInfo( + new Video + { + Path = vob + }, + cancellationToken).ConfigureAwait(false); + + mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks; } } - else if (item.VideoType == VideoType.BluRay) + else { - var inputPath = item.Path; - - blurayDiscInfo = GetBDInfo(inputPath); - - streamFileNames = blurayDiscInfo.Files; - - if (streamFileNames.Length == 0) + if (item.VideoType == VideoType.BluRay) { - _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } - } + blurayDiscInfo = GetBDInfo(item.Path); - streamFileNames ??= Array.Empty<string>(); + if (blurayDiscInfo.Files.Length == 0) + { + _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } - mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); + mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } cancellationToken.ThrowIfCancellationRequested(); } @@ -189,19 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo } mediaAttachments = mediaInfo.MediaAttachments; - video.TotalBitrate = mediaInfo.Bitrate; - // video.FormatName = (mediaInfo.Container ?? string.Empty) - // .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); - - // For DVDs this may not always be accurate, so don't set the runtime if the item already has one - var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks is null || video.RunTimeTicks.Value == 0; - - if (needToSetRuntime) - { - video.RunTimeTicks = mediaInfo.RunTimeTicks; - } - + video.RunTimeTicks = mediaInfo.RunTimeTicks; video.Size = mediaInfo.Size; if (video.VideoType == VideoType.VideoFile) @@ -321,8 +323,6 @@ namespace MediaBrowser.Providers.MediaInfo { var video = (Video)item; - // video.PlayableStreamFileNames = blurayInfo.Files.ToList(); - // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output if (blurayInfo.Files.Length > 1) { From edf3909157a5ef10436b7ebf5717b36a6bac9e7c Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 00:08:51 +0100 Subject: [PATCH 03/12] Use FFmpeg concat for DVD and BD folder playback --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +++ .../MediaEncoding/EncodingHelper.cs | 14 ++++++- .../MediaEncoding/IMediaEncoder.cs | 7 ++++ .../Encoder/MediaEncoder.cs | 40 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 29cb3d2f9f..cb73ad7657 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -323,6 +323,11 @@ public class TranscodingJobHelper : 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 path = Path.GetDirectoryName(job.Path) + "/" + job.MediaSource.Id + ".concat"; + File.Delete(path); + } } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b3a1b2a991..7c4892ea50 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -941,8 +941,18 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append(canvasArgs); } - arg.Append(" -i ") - .Append(GetInputPathArgument(state)); + if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) + { + var tmpConcatPath = options.TranscodingTempPath + "/" + state.MediaSource.Id + ".concat"; + _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); + arg.Append(" -f concat -safe 0 "); + arg.Append(" -i " + tmpConcatPath + " "); + } + else + { + arg.Append(" -i ") + .Append(GetInputPathArgument(state)); + } // sub2video for external graphical subtitles if (state.SubtitleStream is not null diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c34ce5d29b..716adc8f0b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -211,5 +211,12 @@ namespace MediaBrowser.Controller.MediaEncoding /// <param name="titleNumber">The title number to start with.</param> /// <returns>A playlist.</returns> IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); + + /// <summary> + /// Generates a FFmpeg concat config for the source. + /// </summary> + /// <param name="source">The <see cref="MediaSourceInfo"/>.</param> + /// <param name="concatFilePath">The path the config should be written to.</param> + void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index df31ba83e5..f4e6ea4285 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -918,6 +918,46 @@ namespace MediaBrowser.MediaEncoding.Encoder .Select(f => f.FullName); } + public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) + { + var files = new List<string>(); + var videoType = source.VideoType; + if (videoType == VideoType.Dvd) + { + files = GetPrimaryPlaylistVobFiles(source.Path, null).ToList(); + } + else if (videoType == VideoType.BluRay) + { + files = GetPrimaryPlaylistM2TsFiles(source.Path, null).ToList(); + } + + var lines = new List<string>(); + + foreach (var path in files) + { + var fileinfo = _fileSystem.GetFileInfo(path); + var mediaInfoResult = GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Video, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File, + VideoType = videoType + } + }, + CancellationToken.None).GetAwaiter().GetResult(); + + var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + + lines.Add("file " + "'" + path + "'"); + lines.Add("duration " + duration); + } + + File.WriteAllLinesAsync(concatFilePath, lines, CancellationToken.None).GetAwaiter().GetResult(); + } + public bool CanExtractSubtitles(string codec) { // TODO is there ever a case when a subtitle can't be extracted?? From d89cd188eb015cff3450e3c4f7f1e48adec52e8c Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 12:33:22 +0100 Subject: [PATCH 04/12] Fix BD ISO playback --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index f4e6ea4285..0bf89ec477 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -444,7 +444,13 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) { - return EncodingUtils.GetInputArgument("file", new List<string>() { inputFile }, mediaSource.Protocol); + var prefix = "file"; + if (mediaSource.IsoType == IsoType.BluRay) + { + prefix = "bluray"; + } + + return EncodingUtils.GetInputArgument(prefix, new List<string>() { inputFile }, mediaSource.Protocol); } /// <inheritdoc /> From 3d4b2f840a3652490e0dbe559e5aa853a45130e2 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 12:34:24 +0100 Subject: [PATCH 05/12] Fix BD and DVD folder recognition for tv episodes --- .../Library/Resolvers/BaseVideoResolver.cs | 16 ++++++++++-- .../MediaInfo/FFProbeVideoInfo.cs | 26 ++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index e8615e7db7..27062228fa 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -67,11 +67,23 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (IsDvdDirectory(child.FullName, filename, args.DirectoryService)) { - videoType = VideoType.Dvd; + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.Dvd + }; + Set3DFormat(videoTmp); + return videoTmp; } else if (IsBluRayDirectory(filename)) { - videoType = VideoType.BluRay; + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.BluRay + }; + Set3DFormat(videoTmp); + return videoTmp; } } else if (IsDvdFile(filename)) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 393e1f22a7..f2f6d8a124 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -117,19 +117,25 @@ namespace MediaBrowser.Providers.MediaInfo mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks; } } - else + else if (item.VideoType == VideoType.BluRay) { - if (item.VideoType == VideoType.BluRay) - { - blurayDiscInfo = GetBDInfo(item.Path); - - if (blurayDiscInfo.Files.Length == 0) + blurayDiscInfo = GetBDInfo(item.Path); + var m2ts = _mediaEncoder.GetPrimaryPlaylistM2TsFiles(item.Path, null).ToList(); + mediaInfoResult = await GetMediaInfo( + new Video { - _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } - } + Path = m2ts.First() + }, + cancellationToken).ConfigureAwait(false); + if (blurayDiscInfo.Files.Length == 0) + { + _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } + else + { mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } From 626bb24bdd0aaca0fafde98bed973f770575b490 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 16:35:14 +0100 Subject: [PATCH 06/12] Remove DvdLib --- DvdLib/BigEndianBinaryReader.cs | 25 --- DvdLib/DvdLib.csproj | 20 --- DvdLib/Ifo/Cell.cs | 23 --- DvdLib/Ifo/CellPlaybackInfo.cs | 52 ------ DvdLib/Ifo/CellPositionInfo.cs | 19 -- DvdLib/Ifo/Chapter.cs | 20 --- DvdLib/Ifo/Dvd.cs | 167 ------------------ DvdLib/Ifo/DvdTime.cs | 39 ---- DvdLib/Ifo/Program.cs | 16 -- DvdLib/Ifo/ProgramChain.cs | 121 ------------- DvdLib/Ifo/Title.cs | 70 -------- DvdLib/Ifo/UserOperation.cs | 37 ---- DvdLib/Properties/AssemblyInfo.cs | 21 --- Jellyfin.sln | 6 - .../MediaBrowser.Providers.csproj | 1 - .../MediaInfo/FFProbeVideoInfo.cs | 29 --- 16 files changed, 666 deletions(-) delete mode 100644 DvdLib/BigEndianBinaryReader.cs delete mode 100644 DvdLib/DvdLib.csproj delete mode 100644 DvdLib/Ifo/Cell.cs delete mode 100644 DvdLib/Ifo/CellPlaybackInfo.cs delete mode 100644 DvdLib/Ifo/CellPositionInfo.cs delete mode 100644 DvdLib/Ifo/Chapter.cs delete mode 100644 DvdLib/Ifo/Dvd.cs delete mode 100644 DvdLib/Ifo/DvdTime.cs delete mode 100644 DvdLib/Ifo/Program.cs delete mode 100644 DvdLib/Ifo/ProgramChain.cs delete mode 100644 DvdLib/Ifo/Title.cs delete mode 100644 DvdLib/Ifo/UserOperation.cs delete mode 100644 DvdLib/Properties/AssemblyInfo.cs diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs deleted file mode 100644 index b3aad85cec..0000000000 --- a/DvdLib/BigEndianBinaryReader.cs +++ /dev/null @@ -1,25 +0,0 @@ -#pragma warning disable CS1591 - -using System.Buffers.Binary; -using System.IO; - -namespace DvdLib -{ - public class BigEndianBinaryReader : BinaryReader - { - public BigEndianBinaryReader(Stream input) - : base(input) - { - } - - public override ushort ReadUInt16() - { - return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2)); - } - - public override uint ReadUInt32() - { - return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4)); - } - } -} diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj deleted file mode 100644 index 1053c0089f..0000000000 --- a/DvdLib/DvdLib.csproj +++ /dev/null @@ -1,20 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> - <PropertyGroup> - <ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid> - </PropertyGroup> - - <ItemGroup> - <Compile Include="..\SharedVersion.cs" /> - </ItemGroup> - - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <GenerateAssemblyInfo>false</GenerateAssemblyInfo> - <GenerateDocumentationFile>true</GenerateDocumentationFile> - <AnalysisMode>AllDisabledByDefault</AnalysisMode> - <Nullable>disable</Nullable> - </PropertyGroup> - -</Project> diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs deleted file mode 100644 index ea0b50e430..0000000000 --- a/DvdLib/Ifo/Cell.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -using System.IO; - -namespace DvdLib.Ifo -{ - public class Cell - { - public CellPlaybackInfo PlaybackInfo { get; private set; } - - public CellPositionInfo PositionInfo { get; private set; } - - internal void ParsePlayback(BinaryReader br) - { - PlaybackInfo = new CellPlaybackInfo(br); - } - - internal void ParsePosition(BinaryReader br) - { - PositionInfo = new CellPositionInfo(br); - } - } -} diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs deleted file mode 100644 index 6e33a0ec5a..0000000000 --- a/DvdLib/Ifo/CellPlaybackInfo.cs +++ /dev/null @@ -1,52 +0,0 @@ -#pragma warning disable CS1591 - -using System.IO; - -namespace DvdLib.Ifo -{ - public enum BlockMode - { - NotInBlock = 0, - FirstCell = 1, - InBlock = 2, - LastCell = 3, - } - - public enum BlockType - { - Normal = 0, - Angle = 1, - } - - public enum PlaybackMode - { - Normal = 0, - StillAfterEachVOBU = 1, - } - - public class CellPlaybackInfo - { - public readonly BlockMode Mode; - public readonly BlockType Type; - public readonly bool SeamlessPlay; - public readonly bool Interleaved; - public readonly bool STCDiscontinuity; - public readonly bool SeamlessAngle; - public readonly PlaybackMode PlaybackMode; - public readonly bool Restricted; - public readonly byte StillTime; - public readonly byte CommandNumber; - public readonly DvdTime PlaybackTime; - public readonly uint FirstSector; - public readonly uint FirstILVUEndSector; - public readonly uint LastVOBUStartSector; - public readonly uint LastSector; - - internal CellPlaybackInfo(BinaryReader br) - { - br.BaseStream.Seek(0x4, SeekOrigin.Current); - PlaybackTime = new DvdTime(br.ReadBytes(4)); - br.BaseStream.Seek(0x10, SeekOrigin.Current); - } - } -} diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs deleted file mode 100644 index 216aa0f77a..0000000000 --- a/DvdLib/Ifo/CellPositionInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable CS1591 - -using System.IO; - -namespace DvdLib.Ifo -{ - public class CellPositionInfo - { - public readonly ushort VOBId; - public readonly byte CellId; - - internal CellPositionInfo(BinaryReader br) - { - VOBId = br.ReadUInt16(); - br.ReadByte(); - CellId = br.ReadByte(); - } - } -} diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs deleted file mode 100644 index e786cb5536..0000000000 --- a/DvdLib/Ifo/Chapter.cs +++ /dev/null @@ -1,20 +0,0 @@ -#pragma warning disable CS1591 - -namespace DvdLib.Ifo -{ - public class Chapter - { - public ushort ProgramChainNumber { get; private set; } - - public ushort ProgramNumber { get; private set; } - - public uint ChapterNumber { get; private set; } - - public Chapter(ushort pgcNum, ushort programNum, uint chapterNum) - { - ProgramChainNumber = pgcNum; - ProgramNumber = programNum; - ChapterNumber = chapterNum; - } - } -} diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs deleted file mode 100644 index 7f8ece47dc..0000000000 --- a/DvdLib/Ifo/Dvd.cs +++ /dev/null @@ -1,167 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; - -namespace DvdLib.Ifo -{ - public class Dvd - { - private readonly ushort _titleSetCount; - public readonly List<Title> Titles; - - private ushort _titleCount; - public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>(); - public Dvd(string path) - { - Titles = new List<Title>(); - var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories); - - var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ?? - allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase)); - - if (vmgPath == null) - { - foreach (var ifo in allFiles) - { - if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries); - if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) - { - ReadVTS(ifoNumber, ifo.FullName); - } - } - } - else - { - using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - using (var vmgRead = new BigEndianBinaryReader(vmgFs)) - { - vmgFs.Seek(0x3E, SeekOrigin.Begin); - _titleSetCount = vmgRead.ReadUInt16(); - - // read address of TT_SRPT - vmgFs.Seek(0xC4, SeekOrigin.Begin); - uint ttSectorPtr = vmgRead.ReadUInt32(); - vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin); - ReadTT_SRPT(vmgRead); - } - } - - for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++) - { - ReadVTS(titleSetNum, allFiles); - } - } - } - - private void ReadTT_SRPT(BinaryReader read) - { - _titleCount = read.ReadUInt16(); - read.BaseStream.Seek(6, SeekOrigin.Current); - for (uint titleNum = 1; titleNum <= _titleCount; titleNum++) - { - var t = new Title(titleNum); - t.ParseTT_SRPT(read); - Titles.Add(t); - } - } - - private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles) - { - var filename = string.Format(CultureInfo.InvariantCulture, "VTS_{0:00}_0.IFO", vtsNum); - - var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ?? - allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase)); - - if (vtsPath == null) - { - throw new FileNotFoundException("Unable to find VTS IFO file"); - } - - ReadVTS(vtsNum, vtsPath.FullName); - } - - private void ReadVTS(ushort vtsNum, string vtsPath) - { - VTSPaths[vtsNum] = vtsPath; - - using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - using (var vtsRead = new BigEndianBinaryReader(vtsFs)) - { - // Read VTS_PTT_SRPT - vtsFs.Seek(0xC8, SeekOrigin.Begin); - uint vtsPttSrptSecPtr = vtsRead.ReadUInt32(); - uint baseAddr = (vtsPttSrptSecPtr * 2048); - vtsFs.Seek(baseAddr, SeekOrigin.Begin); - - ushort numTitles = vtsRead.ReadUInt16(); - vtsRead.ReadUInt16(); - uint endaddr = vtsRead.ReadUInt32(); - uint[] offsets = new uint[numTitles]; - for (ushort titleNum = 0; titleNum < numTitles; titleNum++) - { - offsets[titleNum] = vtsRead.ReadUInt32(); - } - - for (uint titleNum = 0; titleNum < numTitles; titleNum++) - { - uint chapNum = 1; - vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin); - var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1)); - if (t == null) - { - continue; - } - - do - { - t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum)); - if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) - { - break; - } - - chapNum++; - } - while (vtsFs.Position < (baseAddr + endaddr)); - } - - // Read VTS_PGCI - vtsFs.Seek(0xCC, SeekOrigin.Begin); - uint vtsPgciSecPtr = vtsRead.ReadUInt32(); - vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin); - - long startByte = vtsFs.Position; - - ushort numPgcs = vtsRead.ReadUInt16(); - vtsFs.Seek(6, SeekOrigin.Current); - for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++) - { - byte pgcCat = vtsRead.ReadByte(); - bool entryPgc = (pgcCat & 0x80) != 0; - uint titleNum = (uint)(pgcCat & 0x7F); - - vtsFs.Seek(3, SeekOrigin.Current); - uint vtsPgcOffset = vtsRead.ReadUInt32(); - - var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum)); - if (t != null) - { - t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum); - } - } - } - } - } - } -} diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs deleted file mode 100644 index d231406106..0000000000 --- a/DvdLib/Ifo/DvdTime.cs +++ /dev/null @@ -1,39 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace DvdLib.Ifo -{ - public class DvdTime - { - public readonly byte Hour, Minute, Second, Frames, FrameRate; - - public DvdTime(byte[] data) - { - Hour = GetBCDValue(data[0]); - Minute = GetBCDValue(data[1]); - Second = GetBCDValue(data[2]); - Frames = GetBCDValue((byte)(data[3] & 0x3F)); - - if ((data[3] & 0x80) != 0) - { - FrameRate = 30; - } - else if ((data[3] & 0x40) != 0) - { - FrameRate = 25; - } - } - - private static byte GetBCDValue(byte data) - { - return (byte)((((data & 0xF0) >> 4) * 10) + (data & 0x0F)); - } - - public static explicit operator TimeSpan(DvdTime time) - { - int ms = (int)(((1.0 / (double)time.FrameRate) * time.Frames) * 1000.0); - return new TimeSpan(0, time.Hour, time.Minute, time.Second, ms); - } - } -} diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs deleted file mode 100644 index 3d94fa7dc1..0000000000 --- a/DvdLib/Ifo/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace DvdLib.Ifo -{ - public class Program - { - public IReadOnlyList<Cell> Cells { get; } - - public Program(List<Cell> cells) - { - Cells = cells; - } - } -} diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs deleted file mode 100644 index 83c0051b90..0000000000 --- a/DvdLib/Ifo/ProgramChain.cs +++ /dev/null @@ -1,121 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace DvdLib.Ifo -{ - public enum ProgramPlaybackMode - { - Sequential, - Random, - Shuffle - } - - public class ProgramChain - { - private byte _programCount; - public readonly List<Program> Programs; - - private byte _cellCount; - public readonly List<Cell> Cells; - - public DvdTime PlaybackTime { get; private set; } - - public UserOperation ProhibitedUserOperations { get; private set; } - - public byte[] AudioStreamControl { get; private set; } // 8*2 entries - public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries - - private ushort _nextProgramNumber; - - private ushort _prevProgramNumber; - - private ushort _goupProgramNumber; - - public ProgramPlaybackMode PlaybackMode { get; private set; } - - public uint ProgramCount { get; private set; } - - public byte StillTime { get; private set; } - - public byte[] Palette { get; private set; } // 16*4 entries - - private ushort _commandTableOffset; - - private ushort _programMapOffset; - private ushort _cellPlaybackOffset; - private ushort _cellPositionOffset; - - public readonly uint VideoTitleSetIndex; - - internal ProgramChain(uint vtsPgcNum) - { - VideoTitleSetIndex = vtsPgcNum; - Cells = new List<Cell>(); - Programs = new List<Program>(); - } - - internal void ParseHeader(BinaryReader br) - { - long startPos = br.BaseStream.Position; - - br.ReadUInt16(); - _programCount = br.ReadByte(); - _cellCount = br.ReadByte(); - PlaybackTime = new DvdTime(br.ReadBytes(4)); - ProhibitedUserOperations = (UserOperation)br.ReadUInt32(); - AudioStreamControl = br.ReadBytes(16); - SubpictureStreamControl = br.ReadBytes(128); - - _nextProgramNumber = br.ReadUInt16(); - _prevProgramNumber = br.ReadUInt16(); - _goupProgramNumber = br.ReadUInt16(); - - StillTime = br.ReadByte(); - byte pbMode = br.ReadByte(); - if (pbMode == 0) - { - PlaybackMode = ProgramPlaybackMode.Sequential; - } - else - { - PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle; - } - - ProgramCount = (uint)(pbMode & 0x7F); - - Palette = br.ReadBytes(64); - _commandTableOffset = br.ReadUInt16(); - _programMapOffset = br.ReadUInt16(); - _cellPlaybackOffset = br.ReadUInt16(); - _cellPositionOffset = br.ReadUInt16(); - - // read position info - br.BaseStream.Seek(startPos + _cellPositionOffset, SeekOrigin.Begin); - for (int cellNum = 0; cellNum < _cellCount; cellNum++) - { - var c = new Cell(); - c.ParsePosition(br); - Cells.Add(c); - } - - br.BaseStream.Seek(startPos + _cellPlaybackOffset, SeekOrigin.Begin); - for (int cellNum = 0; cellNum < _cellCount; cellNum++) - { - Cells[cellNum].ParsePlayback(br); - } - - br.BaseStream.Seek(startPos + _programMapOffset, SeekOrigin.Begin); - var cellNumbers = new List<int>(); - for (int progNum = 0; progNum < _programCount; progNum++) cellNumbers.Add(br.ReadByte() - 1); - - for (int i = 0; i < cellNumbers.Count; i++) - { - int max = (i + 1 == cellNumbers.Count) ? _cellCount : cellNumbers[i + 1]; - Programs.Add(new Program(Cells.Where((c, idx) => idx >= cellNumbers[i] && idx < max).ToList())); - } - } - } -} diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs deleted file mode 100644 index 29a0b95c72..0000000000 --- a/DvdLib/Ifo/Title.cs +++ /dev/null @@ -1,70 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.IO; - -namespace DvdLib.Ifo -{ - public class Title - { - public uint TitleNumber { get; private set; } - - public uint AngleCount { get; private set; } - - public ushort ChapterCount { get; private set; } - - public byte VideoTitleSetNumber { get; private set; } - - private ushort _parentalManagementMask; - private byte _titleNumberInVTS; - private uint _vtsStartSector; // relative to start of entire disk - - public ProgramChain EntryProgramChain { get; private set; } - - public readonly List<ProgramChain> ProgramChains; - - public readonly List<Chapter> Chapters; - - public Title(uint titleNum) - { - ProgramChains = new List<ProgramChain>(); - Chapters = new List<Chapter>(); - Chapters = new List<Chapter>(); - TitleNumber = titleNum; - } - - public bool IsVTSTitle(uint vtsNum, uint vtsTitleNum) - { - return (vtsNum == VideoTitleSetNumber && vtsTitleNum == _titleNumberInVTS); - } - - internal void ParseTT_SRPT(BinaryReader br) - { - byte titleType = br.ReadByte(); - // TODO parse Title Type - - AngleCount = br.ReadByte(); - ChapterCount = br.ReadUInt16(); - _parentalManagementMask = br.ReadUInt16(); - VideoTitleSetNumber = br.ReadByte(); - _titleNumberInVTS = br.ReadByte(); - _vtsStartSector = br.ReadUInt32(); - } - - internal void AddPgc(BinaryReader br, long startByte, bool entryPgc, uint pgcNum) - { - long curPos = br.BaseStream.Position; - br.BaseStream.Seek(startByte, SeekOrigin.Begin); - - var pgc = new ProgramChain(pgcNum); - pgc.ParseHeader(br); - ProgramChains.Add(pgc); - if (entryPgc) - { - EntryProgramChain = pgc; - } - - br.BaseStream.Seek(curPos, SeekOrigin.Begin); - } - } -} diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs deleted file mode 100644 index 5d111ebc06..0000000000 --- a/DvdLib/Ifo/UserOperation.cs +++ /dev/null @@ -1,37 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace DvdLib.Ifo -{ - [Flags] - public enum UserOperation - { - None = 0, - TitleOrTimePlay = 1, - ChapterSearchOrPlay = 2, - TitlePlay = 4, - Stop = 8, - GoUp = 16, - TimeOrChapterSearch = 32, - PrevOrTopProgramSearch = 64, - NextProgramSearch = 128, - ForwardScan = 256, - BackwardScan = 512, - TitleMenuCall = 1024, - RootMenuCall = 2048, - SubpictureMenuCall = 4096, - AudioMenuCall = 8192, - AngleMenuCall = 16384, - ChapterMenuCall = 32768, - Resume = 65536, - ButtonSelectOrActive = 131072, - StillOff = 262144, - PauseOn = 524288, - AudioStreamChange = 1048576, - SubpictureStreamChange = 2097152, - AngleChange = 4194304, - KaraokeAudioPresentationModeChange = 8388608, - VideoPresentationModeChange = 16777216, - } -} diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs deleted file mode 100644 index 6acd571d68..0000000000 --- a/DvdLib/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DvdLib")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/Jellyfin.sln b/Jellyfin.sln index ee772cbe40..cad23fc5ee 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing", "src\Jel EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" @@ -137,10 +135,6 @@ Global {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index a0f97df40d..6a40833d7f 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -8,7 +8,6 @@ <ItemGroup> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> - <ProjectReference Include="..\DvdLib\DvdLib.csproj" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index f2f6d8a124..0ec7b368b4 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -9,7 +9,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using DvdLib.Ifo; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -689,33 +688,5 @@ namespace MediaBrowser.Providers.MediaInfo return chapters; } - - private string[] FetchFromDvdLib(Video item) - { - var path = item.Path; - var dvd = new Dvd(path); - - var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault(); - - byte? titleNumber = null; - - if (primaryTitle is not null) - { - titleNumber = primaryTitle.VideoTitleSetNumber; - item.RunTimeTicks = GetRuntime(primaryTitle); - } - - return _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, titleNumber) - .Select(Path.GetFileName) - .ToArray(); - } - - private long GetRuntime(Title title) - { - return title.ProgramChains - .Select(i => (TimeSpan)i.PlaybackTime) - .Select(i => i.Ticks) - .Sum(); - } } } From f2b7f664aa9b3ade38a2402faf95ba9b6989fc41 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 20:16:45 +0100 Subject: [PATCH 07/12] Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index cb73ad7657..1ba7395508 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -325,7 +325,7 @@ public class TranscodingJobHelper : IDisposable await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) { - var path = Path.GetDirectoryName(job.Path) + "/" + job.MediaSource.Id + ".concat"; + var path = Path.Join(job.Path, "/" + job.MediaSource.Id + ".concat"); File.Delete(path); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 7c4892ea50..075e33cb82 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -943,10 +943,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = options.TranscodingTempPath + "/" + state.MediaSource.Id + ".concat"; + var tmpConcatPath = Path.Join(options.TranscodingTempPath, "/" + state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); - arg.Append(" -f concat -safe 0 "); - arg.Append(" -i " + tmpConcatPath + " "); + arg.Append(" -f concat -safe 0 ") + .Append(" -i ") + .Append(tmpConcatPath) + .Append(' '); } else { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 0bf89ec477..347e1de50a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -450,7 +450,7 @@ namespace MediaBrowser.MediaEncoding.Encoder prefix = "bluray"; } - return EncodingUtils.GetInputArgument(prefix, new List<string>() { inputFile }, mediaSource.Protocol); + return EncodingUtils.GetInputArgument(prefix, new[] { inputFile }, mediaSource.Protocol); } /// <inheritdoc /> @@ -458,7 +458,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { const string Prefix = "file"; - return EncodingUtils.GetInputArgument(Prefix, new List<string> { inputFile }, MediaProtocol.File); + return EncodingUtils.GetInputArgument(Prefix, new[] { inputFile }, MediaProtocol.File); } /// <summary> From 979964ef4bba2e4a1e2f67d805dd5b5d638c2850 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 4 Feb 2023 21:39:52 +0100 Subject: [PATCH 08/12] Force transcode/remux for DVDs and BDs --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 62d67c0257..f6c930f5c8 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -635,6 +635,13 @@ namespace MediaBrowser.Model.Dlna var isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || !bitrateLimitExceeded); TranscodeReason transcodeReasons = 0; + // Force transcode or remux for BD/DVD folders + if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay) + { + isEligibleForDirectPlay = false; + isEligibleForDirectStream = false; + } + if (bitrateLimitExceeded) { transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit; From 2403a0a36792d060d913abbd86bec03816da750b Mon Sep 17 00:00:00 2001 From: Shadowghost <Shadowghost@users.noreply.github.com> Date: Sun, 5 Feb 2023 17:24:13 +0100 Subject: [PATCH 09/12] Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 2 +- .../Encoder/MediaEncoder.cs | 12 +-- MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 - .../MediaInfo/FFProbeVideoInfo.cs | 81 +++++++++---------- 5 files changed, 48 insertions(+), 50 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 1ba7395508..3bb3ad358f 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -325,7 +325,7 @@ public class TranscodingJobHelper : IDisposable await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) { - var path = Path.Join(job.Path, "/" + job.MediaSource.Id + ".concat"); + var path = Path.Join(job.Path, job.MediaSource.Id + ".concat"); File.Delete(path); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 075e33cb82..3789bcac90 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -943,7 +943,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = Path.Join(options.TranscodingTempPath, "/" + state.MediaSource.Id + ".concat"); + var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); arg.Append(" -f concat -safe 0 ") .Append(" -i ") diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 347e1de50a..49c81923b4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common; @@ -896,7 +897,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Check for multiple big titles (> 900 MB) var titles = allVobs .Where(vob => vob.Length >= 900 * 1024 * 1024) - .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]) + .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()) .GroupBy(x => x) .Select(y => y.First()) .ToList(); @@ -904,12 +905,12 @@ namespace MediaBrowser.MediaEncoding.Encoder // Fall back to first title if no big title is found if (titles.FirstOrDefault() == null) { - titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).Split('_')[1]); + titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } // Aggregate all VOBs of the titles return allVobs - .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1])) + .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())) .Select(i => i.FullName) .ToList(); } @@ -917,7 +918,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) { var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; - var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/"); + var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")); return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) @@ -941,7 +942,6 @@ namespace MediaBrowser.MediaEncoding.Encoder foreach (var path in files) { - var fileinfo = _fileSystem.GetFileInfo(path); var mediaInfoResult = GetMediaInfo( new MediaInfoRequest { @@ -961,7 +961,7 @@ namespace MediaBrowser.MediaEncoding.Encoder lines.Add("duration " + duration); } - File.WriteAllLinesAsync(concatFilePath, lines, CancellationToken.None).GetAwaiter().GetResult(); + File.WriteAllLines(concatFilePath, lines); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index f6c930f5c8..ef73096b43 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -639,7 +639,6 @@ namespace MediaBrowser.Model.Dlna if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay) { isEligibleForDirectPlay = false; - isEligibleForDirectStream = false; } if (bitrateLimitExceeded) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 0ec7b368b4..f75de47f30 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -328,54 +328,56 @@ namespace MediaBrowser.Providers.MediaInfo { var video = (Video)item; - // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output - if (blurayInfo.Files.Length > 1) + if (blurayInfo.Files.Length <= 1) { - int? currentHeight = null; - int? currentWidth = null; - int? currentBitRate = null; + return; + } - var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output + int? currentHeight = null; + int? currentWidth = null; + int? currentBitRate = null; - // Grab the values that ffprobe recorded - if (videoStream is not null) - { - currentBitRate = videoStream.BitRate; - currentWidth = videoStream.Width; - currentHeight = videoStream.Height; - } + var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - // Fill video properties from the BDInfo result - mediaStreams.Clear(); - mediaStreams.AddRange(blurayInfo.MediaStreams); + // Grab the values that ffprobe recorded + if (videoStream is not null) + { + currentBitRate = videoStream.BitRate; + currentWidth = videoStream.Width; + currentHeight = videoStream.Height; + } - if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) - { - video.RunTimeTicks = blurayInfo.RunTimeTicks; - } + // Fill video properties from the BDInfo result + mediaStreams.Clear(); + mediaStreams.AddRange(blurayInfo.MediaStreams); - if (blurayInfo.Chapters is not null) + if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) + { + video.RunTimeTicks = blurayInfo.RunTimeTicks; + } + + if (blurayInfo.Chapters is not null) + { + double[] brChapter = blurayInfo.Chapters; + chapters = new ChapterInfo[brChapter.Length]; + for (int i = 0; i < brChapter.Length; i++) { - double[] brChapter = blurayInfo.Chapters; - chapters = new ChapterInfo[brChapter.Length]; - for (int i = 0; i < brChapter.Length; i++) + chapters[i] = new ChapterInfo { - chapters[i] = new ChapterInfo - { - StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks - }; - } + StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks + }; } + } - videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - // Use the ffprobe values if these are empty - if (videoStream is not null) - { - videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; - videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; - videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; - } + // Use the ffprobe values if these are empty + if (videoStream is not null) + { + videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; + videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; + videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; } } @@ -391,10 +393,7 @@ namespace MediaBrowser.Providers.MediaInfo /// <returns>VideoStream.</returns> private BlurayDiscInfo GetBDInfo(string path) { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } + ArgumentException.ThrowIfNullOrEmpty(path); try { From cd852d43c16f0e038ba547053bd4c80794ba990c Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sun, 5 Feb 2023 22:59:58 +0100 Subject: [PATCH 10/12] Add more comments and logging, streamline code --- .../MediaEncoding/EncodingHelper.cs | 2 +- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Encoder/MediaEncoder.cs | 21 +++++++---- .../MediaInfo/FFProbeVideoInfo.cs | 35 +++++++++++++------ 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 3789bcac90..2b39c74e23 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.BluRay) { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2TsFiles(state.MediaPath, null).ToList(), state.MediaSource); + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource); } return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 716adc8f0b..83be267a7a 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -208,9 +208,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets the primary playlist of .m2ts files. /// </summary> /// <param name="path">The to the .m2ts files.</param> - /// <param name="titleNumber">The title number to start with.</param> /// <returns>A playlist.</returns> - IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); + IEnumerable<string> GetPrimaryPlaylistM2tsFiles(string path); /// <summary> /// Generates a FFmpeg concat config for the source. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 49c81923b4..05ea7a86d4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -873,7 +873,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { - // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title VOBs ending with _0.VOB + // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title .vob files ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) .Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase)) .Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase)) @@ -891,7 +891,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return vobs.Select(i => i.FullName); } - _logger.LogWarning("Could not determine VOB file list for title {Title} of {Path}.", titleNumber, path); + _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path); } // Check for multiple big titles (> 900 MB) @@ -908,18 +908,22 @@ namespace MediaBrowser.MediaEncoding.Encoder titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } - // Aggregate all VOBs of the titles + // Aggregate all .vob files of the titles return allVobs .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())) .Select(i => i.FullName) .ToList(); } - public IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) + public IEnumerable<string> GetPrimaryPlaylistM2tsFiles(string path) { + // Get all playable .m2ts files var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; + + // Get all files from the BDMV/STREAMING directory var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")); + // Only return playable local .m2ts files return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) .Select(f => f.FullName); @@ -927,6 +931,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) { + // Get all playable files var files = new List<string>(); var videoType = source.VideoType; if (videoType == VideoType.Dvd) @@ -935,11 +940,11 @@ namespace MediaBrowser.MediaEncoding.Encoder } else if (videoType == VideoType.BluRay) { - files = GetPrimaryPlaylistM2TsFiles(source.Path, null).ToList(); + files = GetPrimaryPlaylistM2tsFiles(source.Path).ToList(); } + // Generate concat configuration entries for each file var lines = new List<string>(); - foreach (var path in files) { var mediaInfoResult = GetMediaInfo( @@ -957,10 +962,14 @@ namespace MediaBrowser.MediaEncoding.Encoder var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + // Add file path stanza to concat configuration lines.Add("file " + "'" + path + "'"); + + // Add duration stanza to concat configuration lines.Add("duration " + duration); } + // Write concat configuration File.WriteAllLines(concatFilePath, lines); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index f75de47f30..7200d674c2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -91,8 +91,17 @@ namespace MediaBrowser.Providers.MediaInfo { if (item.VideoType == VideoType.Dvd) { - // Fetch metadata of first VOB + // Get list of playable .vob files var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + + // Return if no playable .vob files are found + if (vobs.Count == 0) + { + _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe."); + return ItemUpdateType.MetadataImport; + } + + // Fetch metadata of first .vob file mediaInfoResult = await GetMediaInfo( new Video { @@ -100,10 +109,10 @@ namespace MediaBrowser.Providers.MediaInfo }, cancellationToken).ConfigureAwait(false); - // Remove first VOB + // Remove first .vob file vobs.RemoveAt(0); - // Add runtime from all other VOBs + // Sum up the runtime of all .vob files foreach (var vob in vobs) { var tmpMediaInfo = await GetMediaInfo( @@ -118,20 +127,26 @@ namespace MediaBrowser.Providers.MediaInfo } else if (item.VideoType == VideoType.BluRay) { + // Get BD disc information blurayDiscInfo = GetBDInfo(item.Path); - var m2ts = _mediaEncoder.GetPrimaryPlaylistM2TsFiles(item.Path, null).ToList(); + + // Get playable .m2ts files + var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path).ToList(); + + // Return if no playable .m2ts files are found + if (blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0) + { + _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe."); + return ItemUpdateType.MetadataImport; + } + + // Fetch metadata of first .m2ts file mediaInfoResult = await GetMediaInfo( new Video { Path = m2ts.First() }, cancellationToken).ConfigureAwait(false); - - if (blurayDiscInfo.Files.Length == 0) - { - _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } } else { From 47aa07c3424ce0041e0a79eea1ab7f6621485b94 Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Fri, 17 Feb 2023 00:26:03 +0100 Subject: [PATCH 11/12] Fix DLNA playback of DVD and BD folders --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 8 ++++++-- .../Probing/ProbeResultNormalizer.cs | 13 ++++++++++++- MediaBrowser.Model/Dlna/StreamInfo.cs | 6 ++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 3bb3ad358f..ee210117e6 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -325,8 +325,12 @@ public class TranscodingJobHelper : IDisposable await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) { - var path = Path.Join(job.Path, job.MediaSource.Id + ".concat"); - File.Delete(path); + var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat"); + if (File.Exists(concatFilePath)) + { + _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); + _fileSystem.DeleteFile(concatFilePath); + } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 99310a75dd..dc15e169fa 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -251,12 +251,23 @@ namespace MediaBrowser.MediaEncoding.Probing return null; } + // Handle MPEG-1 container if (string.Equals(format, "mpegvideo", StringComparison.OrdinalIgnoreCase)) { return "mpeg"; } - format = format.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); + // Handle MPEG-2 container + if (string.Equals(format, "mpeg", StringComparison.OrdinalIgnoreCase)) + { + return "ts"; + } + + // Handle matroska container + if (string.Equals(format, "matroska", StringComparison.OrdinalIgnoreCase)) + { + return "mkv"; + } return format; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3b55099079..fdf3afe973 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -108,8 +108,10 @@ namespace MediaBrowser.Model.Dlna public string MediaSourceId => MediaSource?.Id; public bool IsDirectStream => - PlayMethod == PlayMethod.DirectStream || - PlayMethod == PlayMethod.DirectPlay; + !(MediaSource?.VideoType == VideoType.Dvd + || MediaSource?.VideoType == VideoType.BluRay) + && (PlayMethod == PlayMethod.DirectStream + || PlayMethod == PlayMethod.DirectPlay); /// <summary> /// Gets the audio stream that will be used. From 0da5255f1291ba510f829d36a3ca1a9eb65590dc Mon Sep 17 00:00:00 2001 From: Shadowghost <Ghost_of_Stone@web.de> Date: Sat, 18 Feb 2023 14:42:35 +0100 Subject: [PATCH 12/12] Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 23 +- .../MediaEncoding/IMediaEncoder.cs | 4 +- .../BdInfo/BdInfoDirectoryInfo.cs | 160 ++++++---- .../BdInfo/BdInfoExaminer.cs | 283 +++++++++--------- .../BdInfo/BdInfoFileInfo.cs | 81 +++-- .../Encoder/MediaEncoder.cs | 75 ++--- MediaBrowser.Model/Dlna/StreamInfo.cs | 7 +- .../MediaInfo/BlurayDiscInfo.cs | 56 ++-- .../MediaInfo/IBlurayExaminer.cs | 21 +- .../MediaInfo/FFProbeVideoInfo.cs | 30 +- 11 files changed, 395 insertions(+), 347 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index ee210117e6..73e8f34ada 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -329,7 +329,7 @@ public class TranscodingJobHelper : IDisposable if (File.Exists(concatFilePath)) { _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); - _fileSystem.DeleteFile(concatFilePath); + File.Delete(concatFilePath); } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2b39c74e23..a4e4648b10 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -533,19 +533,12 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetInputPathArgument(EncodingJobInfo state) { - var mediaPath = state.MediaPath ?? string.Empty; - - if (state.MediaSource.VideoType == VideoType.Dvd) + return state.MediaSource.VideoType switch { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource); - } - - if (state.MediaSource.VideoType == VideoType.BluRay) - { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource); - } - - return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); + VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource), + VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource), + _ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource) + }; } /// <summary> @@ -945,10 +938,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); - arg.Append(" -f concat -safe 0 ") - .Append(" -i ") - .Append(tmpConcatPath) - .Append(' '); + arg.Append(" -f concat -safe 0 -i ") + .Append(tmpConcatPath); } else { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 83be267a7a..f830b9f298 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -202,14 +202,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// <param name="path">The to the .vob files.</param> /// <param name="titleNumber">The title number to start with.</param> /// <returns>A playlist.</returns> - IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); + IReadOnlyList<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); /// <summary> /// Gets the primary playlist of .m2ts files. /// </summary> /// <param name="path">The to the .m2ts files.</param> /// <returns>A playlist.</returns> - IEnumerable<string> GetPrimaryPlaylistM2tsFiles(string path); + IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path); /// <summary> /// Generates a FFmpeg concat config for the source. diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index 7e026b42e3..ea520b1d6b 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -1,83 +1,123 @@ -#pragma warning disable CS1591 - using System; +using System.IO; using System.Linq; using BDInfo.IO; using MediaBrowser.Model.IO; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoDirectoryInfo. +/// </summary> +public class BdInfoDirectoryInfo : IDirectoryInfo { - public class BdInfoDirectoryInfo : IDirectoryInfo - { - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; - private readonly FileSystemMetadata _impl; + private readonly FileSystemMetadata _impl; - public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) - { - _fileSystem = fileSystem; - _impl = _fileSystem.GetDirectoryInfo(path); - } + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoDirectoryInfo" /> class. + /// </summary> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="path">The path.</param> + public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) + { + _fileSystem = fileSystem; + _impl = _fileSystem.GetDirectoryInfo(path); + } - private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) - { - _fileSystem = fileSystem; - _impl = impl; - } + private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) + { + _fileSystem = fileSystem; + _impl = impl; + } - public string Name => _impl.Name; + /// <summary> + /// Gets the name. + /// </summary> + public string Name => _impl.Name; - public string FullName => _impl.FullName; + /// <summary> + /// Gets the full name. + /// </summary> + public string FullName => _impl.FullName; - public IDirectoryInfo? Parent + /// <summary> + /// Gets the parent directory information. + /// </summary> + public IDirectoryInfo? Parent + { + get { - get + var parentFolder = Path.GetDirectoryName(_impl.FullName); + if (parentFolder is not null) { - var parentFolder = System.IO.Path.GetDirectoryName(_impl.FullName); - if (parentFolder is not null) - { - return new BdInfoDirectoryInfo(_fileSystem, parentFolder); - } - - return null; + return new BdInfoDirectoryInfo(_fileSystem, parentFolder); } - } - public IDirectoryInfo[] GetDirectories() - { - return Array.ConvertAll( - _fileSystem.GetDirectories(_impl.FullName).ToArray(), - x => new BdInfoDirectoryInfo(_fileSystem, x)); + return null; } + } - public IFileInfo[] GetFiles() - { - return Array.ConvertAll( - _fileSystem.GetFiles(_impl.FullName).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// <summary> + /// Gets the directories. + /// </summary> + /// <returns>An array with all directories.</returns> + public IDirectoryInfo[] GetDirectories() + { + return _fileSystem.GetDirectories(_impl.FullName) + .Select(x => new BdInfoDirectoryInfo(_fileSystem, x)) + .ToArray(); + } - public IFileInfo[] GetFiles(string searchPattern) - { - return Array.ConvertAll( - _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// <summary> + /// Gets the files. + /// </summary> + /// <returns>All files of the directory.</returns> + public IFileInfo[] GetFiles() + { + return _fileSystem.GetFiles(_impl.FullName) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } - public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption) - { - return Array.ConvertAll( - _fileSystem.GetFiles( - _impl.FullName, - new[] { searchPattern }, - false, - (searchOption & System.IO.SearchOption.AllDirectories) == System.IO.SearchOption.AllDirectories).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// <summary> + /// Gets the files matching a pattern. + /// </summary> + /// <param name="searchPattern">The search pattern.</param> + /// <returns>All files of the directory matchign the search pattern.</returns> + public IFileInfo[] GetFiles(string searchPattern) + { + return _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } - public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) - { - return new BdInfoDirectoryInfo(fs, path); - } + /// <summary> + /// Gets the files matching a pattern and search options. + /// </summary> + /// <param name="searchPattern">The search pattern.</param> + /// <param name="searchOption">The search optin.</param> + /// <returns>All files of the directory matchign the search pattern and options.</returns> + public IFileInfo[] GetFiles(string searchPattern, SearchOption searchOption) + { + return _fileSystem.GetFiles( + _impl.FullName, + new[] { searchPattern }, + false, + (searchOption & SearchOption.AllDirectories) == SearchOption.AllDirectories) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } + + /// <summary> + /// Gets the bdinfo of a file system path. + /// </summary> + /// <param name="fs">The file system.</param> + /// <param name="path">The path.</param> + /// <returns>The BD directory information of the path on the file system.</returns> + public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) + { + return new BdInfoDirectoryInfo(fs, path); } } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 3e53cbf29f..8ebb59c59e 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -6,189 +6,182 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoExaminer. +/// </summary> +public class BdInfoExaminer : IBlurayExaminer { + private readonly IFileSystem _fileSystem; + /// <summary> - /// Class BdInfoExaminer. + /// Initializes a new instance of the <see cref="BdInfoExaminer" /> class. /// </summary> - public class BdInfoExaminer : IBlurayExaminer + /// <param name="fileSystem">The filesystem.</param> + public BdInfoExaminer(IFileSystem fileSystem) { - private readonly IFileSystem _fileSystem; + _fileSystem = fileSystem; + } - /// <summary> - /// Initializes a new instance of the <see cref="BdInfoExaminer" /> class. - /// </summary> - /// <param name="fileSystem">The filesystem.</param> - public BdInfoExaminer(IFileSystem fileSystem) + /// <summary> + /// Gets the disc info. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + public BlurayDiscInfo GetDiscInfo(string path) + { + if (string.IsNullOrWhiteSpace(path)) { - _fileSystem = fileSystem; + throw new ArgumentNullException(nameof(path)); } - /// <summary> - /// Gets the disc info. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BlurayDiscInfo.</returns> - public BlurayDiscInfo GetDiscInfo(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); + var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); - bdrom.Scan(); + bdrom.Scan(); - // Get the longest playlist - var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); + // Get the longest playlist + var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); - var outputStream = new BlurayDiscInfo - { - MediaStreams = Array.Empty<MediaStream>() - }; + var outputStream = new BlurayDiscInfo + { + MediaStreams = Array.Empty<MediaStream>() + }; - if (playlist is null) - { - return outputStream; - } + if (playlist is null) + { + return outputStream; + } - outputStream.Chapters = playlist.Chapters.ToArray(); + outputStream.Chapters = playlist.Chapters.ToArray(); - outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; + outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; - var mediaStreams = new List<MediaStream>(); + var sortedStreams = playlist.SortedStreams; + var mediaStreams = new List<MediaStream>(sortedStreams.Count); - foreach (var stream in playlist.SortedStreams) + foreach (var stream in sortedStreams) + { + switch (stream) { - if (stream is TSVideoStream videoStream) - { + case TSVideoStream videoStream: AddVideoStream(mediaStreams, videoStream); - continue; - } - - if (stream is TSAudioStream audioStream) - { + break; + case TSAudioStream audioStream: AddAudioStream(mediaStreams, audioStream); - continue; - } - - if (stream is TSTextStream textStream) - { + break; + case TSTextStream textStream: AddSubtitleStream(mediaStreams, textStream); - continue; - } - - if (stream is TSGraphicsStream graphicsStream) - { - AddSubtitleStream(mediaStreams, graphicsStream); - } + break; + case TSGraphicsStream graphicStream: + AddSubtitleStream(mediaStreams, graphicStream); + break; } + } - outputStream.MediaStreams = mediaStreams.ToArray(); - - outputStream.PlaylistName = playlist.Name; + outputStream.MediaStreams = mediaStreams.ToArray(); - if (playlist.StreamClips is not null && playlist.StreamClips.Any()) - { - // Get the files in the playlist - outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray(); - } + outputStream.PlaylistName = playlist.Name; - return outputStream; + if (playlist.StreamClips is not null && playlist.StreamClips.Count > 0) + { + // Get the files in the playlist + outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray(); } - /// <summary> - /// Adds the video stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="videoStream">The video stream.</param> - private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream) - { - var mediaStream = new MediaStream - { - BitRate = Convert.ToInt32(videoStream.BitRate), - Width = videoStream.Width, - Height = videoStream.Height, - Codec = videoStream.CodecShortName, - IsInterlaced = videoStream.IsInterlaced, - Type = MediaStreamType.Video, - Index = streams.Count - }; - - if (videoStream.FrameRateDenominator > 0) - { - float frameRateEnumerator = videoStream.FrameRateEnumerator; - float frameRateDenominator = videoStream.FrameRateDenominator; + return outputStream; + } - mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; - } + /// <summary> + /// Adds the video stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="videoStream">The video stream.</param> + private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream) + { + var mediaStream = new MediaStream + { + BitRate = Convert.ToInt32(videoStream.BitRate), + Width = videoStream.Width, + Height = videoStream.Height, + Codec = videoStream.CodecShortName, + IsInterlaced = videoStream.IsInterlaced, + Type = MediaStreamType.Video, + Index = streams.Count + }; + + if (videoStream.FrameRateDenominator > 0) + { + float frameRateEnumerator = videoStream.FrameRateEnumerator; + float frameRateDenominator = videoStream.FrameRateDenominator; - streams.Add(mediaStream); + mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; } - /// <summary> - /// Adds the audio stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="audioStream">The audio stream.</param> - private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream) - { - var stream = new MediaStream - { - Codec = audioStream.CodecShortName, - Language = audioStream.LanguageCode, - Channels = audioStream.ChannelCount, - SampleRate = audioStream.SampleRate, - Type = MediaStreamType.Audio, - Index = streams.Count - }; - - var bitrate = Convert.ToInt32(audioStream.BitRate); + streams.Add(mediaStream); + } - if (bitrate > 0) - { - stream.BitRate = bitrate; - } + /// <summary> + /// Adds the audio stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="audioStream">The audio stream.</param> + private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream) + { + var stream = new MediaStream + { + Codec = audioStream.CodecShortName, + Language = audioStream.LanguageCode, + Channels = audioStream.ChannelCount, + SampleRate = audioStream.SampleRate, + Type = MediaStreamType.Audio, + Index = streams.Count + }; - if (audioStream.LFE > 0) - { - stream.Channels = audioStream.ChannelCount + 1; - } + var bitrate = Convert.ToInt32(audioStream.BitRate); - streams.Add(stream); + if (bitrate > 0) + { + stream.BitRate = bitrate; } - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) + if (audioStream.LFE > 0) { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); + stream.Channels = audioStream.ChannelCount + 1; } - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) + streams.Add(stream); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) + { + streams.Add(new MediaStream + { + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) + { + streams.Add(new MediaStream { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); } } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs index d55688e3df..9e7a1d50a3 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -1,41 +1,68 @@ -#pragma warning disable CS1591 - using System.IO; using MediaBrowser.Model.IO; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoFileInfo. +/// </summary> +public class BdInfoFileInfo : BDInfo.IO.IFileInfo { - public class BdInfoFileInfo : BDInfo.IO.IFileInfo - { - private FileSystemMetadata _impl; + private FileSystemMetadata _impl; - public BdInfoFileInfo(FileSystemMetadata impl) - { - _impl = impl; - } + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoFileInfo" /> class. + /// </summary> + /// <param name="impl">The <see cref="FileSystemMetadata" />.</param> + public BdInfoFileInfo(FileSystemMetadata impl) + { + _impl = impl; + } - public string Name => _impl.Name; + /// <summary> + /// Gets the name. + /// </summary> + public string Name => _impl.Name; - public string FullName => _impl.FullName; + /// <summary> + /// Gets the full name. + /// </summary> + public string FullName => _impl.FullName; - public string Extension => _impl.Extension; + /// <summary> + /// Gets the extension. + /// </summary> + public string Extension => _impl.Extension; - public long Length => _impl.Length; + /// <summary> + /// Gets the length. + /// </summary> + public long Length => _impl.Length; - public bool IsDir => _impl.IsDirectory; + /// <summary> + /// Gets a value indicating whether this is a directory. + /// </summary> + public bool IsDir => _impl.IsDirectory; - public Stream OpenRead() - { - return new FileStream( - FullName, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - } + /// <summary> + /// Gets a file as file stream. + /// </summary> + /// <returns>A <see cref="FileStream" /> for the file.</returns> + public Stream OpenRead() + { + return new FileStream( + FullName, + FileMode.Open, + FileAccess.Read, + FileShare.Read); + } - public StreamReader OpenText() - { - return new StreamReader(OpenRead()); - } + /// <summary> + /// Gets a files's content with a stream reader. + /// </summary> + /// <returns>A <see cref="StreamReader" /> for the file's content.</returns> + public StreamReader OpenText() + { + return new StreamReader(OpenRead()); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 05ea7a86d4..d2112e5dc2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -871,7 +871,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } /// <inheritdoc /> - public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) + public IReadOnlyList<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title .vob files ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) @@ -888,7 +888,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (vobs.Count > 0) { - return vobs.Select(i => i.FullName); + return vobs.Select(i => i.FullName).ToList(); } _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path); @@ -898,12 +898,11 @@ namespace MediaBrowser.MediaEncoding.Encoder var titles = allVobs .Where(vob => vob.Length >= 900 * 1024 * 1024) .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()) - .GroupBy(x => x) - .Select(y => y.First()) + .Distinct() .ToList(); // Fall back to first title if no big title is found - if (titles.FirstOrDefault() == null) + if (titles.Count == 0) { titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } @@ -915,7 +914,8 @@ namespace MediaBrowser.MediaEncoding.Encoder .ToList(); } - public IEnumerable<string> GetPrimaryPlaylistM2tsFiles(string path) + /// <inheritdoc /> + public IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path) { // Get all playable .m2ts files var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; @@ -926,51 +926,56 @@ namespace MediaBrowser.MediaEncoding.Encoder // Only return playable local .m2ts files return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) - .Select(f => f.FullName); + .Select(f => f.FullName) + .ToList(); } + /// <inheritdoc /> public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) { // Get all playable files - var files = new List<string>(); + IReadOnlyList<string> files; var videoType = source.VideoType; if (videoType == VideoType.Dvd) { - files = GetPrimaryPlaylistVobFiles(source.Path, null).ToList(); + files = GetPrimaryPlaylistVobFiles(source.Path, null); } else if (videoType == VideoType.BluRay) { - files = GetPrimaryPlaylistM2tsFiles(source.Path).ToList(); + files = GetPrimaryPlaylistM2tsFiles(source.Path); + } + else + { + return; } - // Generate concat configuration entries for each file - var lines = new List<string>(); - foreach (var path in files) + // Generate concat configuration entries for each file and write to file + using (StreamWriter sw = new StreamWriter(concatFilePath)) { - var mediaInfoResult = GetMediaInfo( - new MediaInfoRequest - { - MediaType = DlnaProfileType.Video, - MediaSource = new MediaSourceInfo + foreach (var path in files) + { + var mediaInfoResult = GetMediaInfo( + new MediaInfoRequest { - Path = path, - Protocol = MediaProtocol.File, - VideoType = videoType - } - }, - CancellationToken.None).GetAwaiter().GetResult(); - - var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; - - // Add file path stanza to concat configuration - lines.Add("file " + "'" + path + "'"); - - // Add duration stanza to concat configuration - lines.Add("duration " + duration); + MediaType = DlnaProfileType.Video, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File, + VideoType = videoType + } + }, + CancellationToken.None).GetAwaiter().GetResult(); + + var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + + // Add file path stanza to concat configuration + sw.WriteLine("file '{0}'", path); + + // Add duration stanza to concat configuration + sw.WriteLine("duration {0}", duration); + } } - - // Write concat configuration - File.WriteAllLines(concatFilePath, lines); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index fdf3afe973..0e814f036e 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -107,11 +107,8 @@ namespace MediaBrowser.Model.Dlna public string MediaSourceId => MediaSource?.Id; - public bool IsDirectStream => - !(MediaSource?.VideoType == VideoType.Dvd - || MediaSource?.VideoType == VideoType.BluRay) - && (PlayMethod == PlayMethod.DirectStream - || PlayMethod == PlayMethod.DirectPlay); + public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) + && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; /// <summary> /// Gets the audio stream that will be used. diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs index 83f982a5c8..d546ffccdc 100644 --- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -1,39 +1,41 @@ #nullable disable -#pragma warning disable CS1591 using MediaBrowser.Model.Entities; -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// <summary> +/// Represents the result of BDInfo output. +/// </summary> +public class BlurayDiscInfo { /// <summary> - /// Represents the result of BDInfo output. + /// Gets or sets the media streams. /// </summary> - public class BlurayDiscInfo - { - /// <summary> - /// Gets or sets the media streams. - /// </summary> - /// <value>The media streams.</value> - public MediaStream[] MediaStreams { get; set; } + /// <value>The media streams.</value> + public MediaStream[] MediaStreams { get; set; } - /// <summary> - /// Gets or sets the run time ticks. - /// </summary> - /// <value>The run time ticks.</value> - public long? RunTimeTicks { get; set; } + /// <summary> + /// Gets or sets the run time ticks. + /// </summary> + /// <value>The run time ticks.</value> + public long? RunTimeTicks { get; set; } - /// <summary> - /// Gets or sets the files. - /// </summary> - /// <value>The files.</value> - public string[] Files { get; set; } + /// <summary> + /// Gets or sets the files. + /// </summary> + /// <value>The files.</value> + public string[] Files { get; set; } - public string PlaylistName { get; set; } + /// <summary> + /// Gets or sets the playlist name. + /// </summary> + /// <value>The playlist name.</value> + public string PlaylistName { get; set; } - /// <summary> - /// Gets or sets the chapters. - /// </summary> - /// <value>The chapters.</value> - public double[] Chapters { get; set; } - } + /// <summary> + /// Gets or sets the chapters. + /// </summary> + /// <value>The chapters.</value> + public double[] Chapters { get; set; } } diff --git a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs index 5b7d1d03c3..d397253010 100644 --- a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs +++ b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs @@ -1,15 +1,14 @@ -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// <summary> +/// Interface IBlurayExaminer. +/// </summary> +public interface IBlurayExaminer { /// <summary> - /// Interface IBlurayExaminer. + /// Gets the disc info. /// </summary> - public interface IBlurayExaminer - { - /// <summary> - /// Gets the disc info. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BlurayDiscInfo.</returns> - BlurayDiscInfo GetDiscInfo(string path); - } + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + BlurayDiscInfo GetDiscInfo(string path); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7200d674c2..e199db7f2a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.MediaInfo if (item.VideoType == VideoType.Dvd) { // Get list of playable .vob files - var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null); // Return if no playable .vob files are found if (vobs.Count == 0) @@ -105,22 +105,19 @@ namespace MediaBrowser.Providers.MediaInfo mediaInfoResult = await GetMediaInfo( new Video { - Path = vobs.First() + Path = vobs[0] }, cancellationToken).ConfigureAwait(false); - // Remove first .vob file - vobs.RemoveAt(0); - - // Sum up the runtime of all .vob files - foreach (var vob in vobs) + // Sum up the runtime of all .vob files skipping the first .vob + for (var i = 1; i < vobs.Count; i++) { var tmpMediaInfo = await GetMediaInfo( - new Video - { - Path = vob - }, - cancellationToken).ConfigureAwait(false); + new Video + { + Path = vobs[i] + }, + cancellationToken).ConfigureAwait(false); mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks; } @@ -131,7 +128,7 @@ namespace MediaBrowser.Providers.MediaInfo blurayDiscInfo = GetBDInfo(item.Path); // Get playable .m2ts files - var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path).ToList(); + var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path); // Return if no playable .m2ts files are found if (blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0) @@ -144,14 +141,13 @@ namespace MediaBrowser.Providers.MediaInfo mediaInfoResult = await GetMediaInfo( new Video { - Path = m2ts.First() + Path = m2ts[0] }, cancellationToken).ConfigureAwait(false); } else { mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); } cancellationToken.ThrowIfCancellationRequested(); @@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.MediaInfo } } - private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo) + private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo) { - var video = (Video)item; - if (blurayInfo.Files.Length <= 1) { return;