SubtitleEdit/libse/VobSub/VobSubParser.cs
2016-03-03 15:25:42 +00:00

248 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace Nikse.SubtitleEdit.Core.VobSub
{
public class VobSubParser
{
public bool IsPal { get; private set; }
public List<VobSubPack> VobSubPacks { get; private set; }
public List<Color> IdxPalette = new List<Color>();
public List<string> IdxLanguages = new List<string>();
private const int PacketizedElementaryStreamMaximumLength = 2028;
public VobSubParser(bool isPal)
{
IsPal = isPal;
VobSubPacks = new List<VobSubPack>();
}
public void Open(string fileName)
{
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
Open(fs);
}
}
/// <summary>
/// Can be used with e.g. MemoryStream or FileStream
/// </summary>
/// <param name="ms"></param>
public void Open(Stream ms)
{
VobSubPacks = new List<VobSubPack>();
ms.Position = 0;
var buffer = new byte[0x800]; // 2048
long position = 0;
while (position < ms.Length)
{
ms.Seek(position, SeekOrigin.Begin);
ms.Read(buffer, 0, 0x0800);
if (IsSubtitlePack(buffer))
VobSubPacks.Add(new VobSubPack(buffer, null));
position += 0x800;
}
}
public void OpenSubIdx(string vobSubFileName, string idxFileName)
{
VobSubPacks = new List<VobSubPack>();
if (!string.IsNullOrEmpty(idxFileName) && File.Exists(idxFileName))
{
var idx = new Idx(idxFileName);
IdxPalette = idx.Palette;
IdxLanguages = idx.Languages;
if (idx.IdxParagraphs.Count > 0)
{
var buffer = new byte[0x800]; // 2048
using (var fs = new FileStream(vobSubFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
foreach (var p in idx.IdxParagraphs)
{
if (p.FilePosition + 100 < fs.Length)
{
long position = p.FilePosition;
fs.Seek(position, SeekOrigin.Begin);
fs.Read(buffer, 0, 0x0800);
if (IsSubtitlePack(buffer) || IsPrivateStream1(buffer, 0))
{
var vsp = new VobSubPack(buffer, p);
VobSubPacks.Add(vsp);
if (IsPrivateStream1(buffer, 0))
position += vsp.PacketizedElementaryStream.Length + 6;
else
position += 0x800;
int currentSubPictureStreamId = vsp.PacketizedElementaryStream.SubPictureStreamId.Value;
while (vsp.PacketizedElementaryStream != null &&
vsp.PacketizedElementaryStream.SubPictureStreamId.HasValue &&
(vsp.PacketizedElementaryStream.Length == PacketizedElementaryStreamMaximumLength ||
currentSubPictureStreamId != vsp.PacketizedElementaryStream.SubPictureStreamId.Value) && position < fs.Length)
{
fs.Seek(position, SeekOrigin.Begin);
fs.Read(buffer, 0, 0x800);
vsp = new VobSubPack(buffer, p); // idx position?
if (vsp.PacketizedElementaryStream != null && vsp.PacketizedElementaryStream.SubPictureStreamId.HasValue && currentSubPictureStreamId == vsp.PacketizedElementaryStream.SubPictureStreamId.Value)
{
VobSubPacks.Add(vsp);
if (IsPrivateStream1(buffer, 0))
position += vsp.PacketizedElementaryStream.Length + 6;
else
position += 0x800;
}
else
{
position += 0x800;
fs.Seek(position, SeekOrigin.Begin);
}
}
}
}
}
}
return;
}
}
// No valid idx file found - just open like vob file
Open(vobSubFileName);
}
/// <summary>
/// Demultiplex multiplexed packs together each streamId at a time + removing bad packs + fixing displaytimes
/// </summary>
/// <returns>List of complete packs each with a complete sub image</returns>
public List<VobSubMergedPack> MergeVobSubPacks()
{
var list = new List<VobSubMergedPack>();
var pts = new TimeSpan();
var ms = new MemoryStream();
int streamId = 0;
float ticksPerMillisecond = 90.000F;
if (!IsPal)
ticksPerMillisecond = 90.090F; // TODO: What should this be for NTSC?
// get unique streamIds
var uniqueStreamIds = new List<int>();
foreach (var p in VobSubPacks)
{
if (p.PacketizedElementaryStream != null &&
p.PacketizedElementaryStream.SubPictureStreamId.HasValue &&
!uniqueStreamIds.Contains(p.PacketizedElementaryStream.SubPictureStreamId.Value))
uniqueStreamIds.Add(p.PacketizedElementaryStream.SubPictureStreamId.Value);
}
IdxParagraph lastIdxParagraph = null;
foreach (int uniqueStreamId in uniqueStreamIds) // packets must be merged in streamId order (so they don't get mixed)
{
foreach (var p in VobSubPacks)
{
if (p.PacketizedElementaryStream != null && p.PacketizedElementaryStream.SubPictureStreamId.HasValue &&
p.PacketizedElementaryStream.SubPictureStreamId.Value == uniqueStreamId)
{
if (p.PacketizedElementaryStream.PresentationTimestampDecodeTimestampFlags > 0)
{
if (lastIdxParagraph == null || p.IdxLine.FilePosition != lastIdxParagraph.FilePosition)
{
if (ms.Length > 0)
list.Add(new VobSubMergedPack(ms.ToArray(), pts, streamId, lastIdxParagraph));
ms.Close();
ms = new MemoryStream();
pts = TimeSpan.FromMilliseconds(Convert.ToDouble(p.PacketizedElementaryStream.PresentationTimestamp / ticksPerMillisecond)); //90000F * 1000)); (PAL)
streamId = p.PacketizedElementaryStream.SubPictureStreamId.Value;
}
}
lastIdxParagraph = p.IdxLine;
p.PacketizedElementaryStream.WriteToStream(ms);
}
}
if (ms.Length > 0)
{
list.Add(new VobSubMergedPack(ms.ToArray(), pts, streamId, lastIdxParagraph));
ms.Close();
ms = new MemoryStream();
}
}
ms.Close();
// Remove any bad packs
for (int i = list.Count - 1; i >= 0; i--)
{
VobSubMergedPack pack = list[i];
if (pack.SubPicture == null || pack.SubPicture.ImageDisplayArea.Width <= 3 || pack.SubPicture.ImageDisplayArea.Height <= 2)
list.RemoveAt(i);
else if (pack.EndTime.TotalSeconds - pack.StartTime.TotalSeconds < 0.1 && pack.SubPicture.ImageDisplayArea.Width <= 10 && pack.SubPicture.ImageDisplayArea.Height <= 10)
list.RemoveAt(i);
}
// Fix subs with no duration (completely normal) or negative duration or duration > 10 seconds
for (int i = 0; i < list.Count; i++)
{
VobSubMergedPack pack = list[i];
if (pack.SubPicture.Delay.TotalMilliseconds > 0)
pack.EndTime = pack.StartTime.Add(pack.SubPicture.Delay);
if (pack.EndTime < pack.StartTime || pack.EndTime.TotalSeconds - pack.StartTime.TotalSeconds > 10.0)
{
if (i + 1 < list.Count)
pack.EndTime = TimeSpan.FromMilliseconds(list[i].StartTime.TotalMilliseconds - 100);
else
pack.EndTime = TimeSpan.FromMilliseconds(pack.StartTime.TotalMilliseconds + 3000);
}
}
return list;
}
public static bool IsMpeg2PackHeader(byte[] buffer)
{
return buffer.Length >= 4 &&
buffer[0] == 0 &&
buffer[1] == 0 &&
buffer[2] == 1 &&
buffer[3] == 0xba; // 0xba == 186 - MPEG-2 Pack Header
}
public static bool IsPrivateStream1(byte[] buffer, int index)
{
return buffer.Length >= index + 4 &&
buffer[index + 0] == 0 &&
buffer[index + 1] == 0 &&
buffer[index + 2] == 1 &&
buffer[index + 3] == 0xbd; // 0xbd == 189 - MPEG-2 Private stream 1 (non MPEG audio, subpictures)
}
public static bool IsPrivateStream2(byte[] buffer, int index)
{
return buffer.Length >= index + 4 &&
buffer[index + 0] == 0 &&
buffer[index + 1] == 0 &&
buffer[index + 2] == 1 &&
buffer[index + 3] == 0xbf; // 0xbf == 191 - MPEG-2 Private stream 2
}
public static bool IsSubtitlePack(byte[] buffer)
{
if (IsMpeg2PackHeader(buffer) && IsPrivateStream1(buffer, Mpeg2Header.Length))
{
int pesHeaderDataLength = buffer[Mpeg2Header.Length + 8];
int streamId = buffer[Mpeg2Header.Length + 8 + 1 + pesHeaderDataLength];
if (streamId >= 0x20 && streamId <= 0x3f) // Subtitle IDs allowed (or x3f to x40?)
return true;
}
return false;
}
}
}