Revert "Remove DvdLib (#9068)"

This reverts commit db1913b08f.
This commit is contained in:
Shadowghost 2023-02-03 18:49:23 +01:00
parent 6bf34f8e22
commit 519709bf10
26 changed files with 1258 additions and 1 deletions

View File

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

20
DvdLib/DvdLib.csproj Normal file
View File

@ -0,0 +1,20 @@
<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>

23
DvdLib/Ifo/Cell.cs Normal file
View File

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

View File

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

View File

@ -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();
}
}
}

20
DvdLib/Ifo/Chapter.cs Normal file
View File

@ -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;
}
}
}

167
DvdLib/Ifo/Dvd.cs Normal file
View File

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

39
DvdLib/Ifo/DvdTime.cs Normal file
View File

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

16
DvdLib/Ifo/Program.cs Normal file
View File

@ -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;
}
}
}

121
DvdLib/Ifo/ProgramChain.cs Normal file
View File

@ -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()));
}
}
}
}

70
DvdLib/Ifo/Title.cs Normal file
View File

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

View File

@ -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,
}
}

View File

@ -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)]

View File

@ -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>();

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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());
}
}
}

View File

@ -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??

View File

@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BDInfo" />
<PackageReference Include="libse" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="System.Text.Encoding.CodePages" />

View File

@ -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; }
}
}

View File

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

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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,