using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Nikse.SubtitleEdit.Core.SubtitleFormats
{
///
/// ARIB B-36 - see http://www.arib.or.jp/english/html/overview/doc/2-STD-B36v2_3.pdf (ARIB = Japan Association of Radio Industries and Businesses)
/// For the text decodinfg see http://www.arib.or.jp/english/html/overview/doc/6-STD-B24v5_2-1p3-E1.pdf
/// Also see https://github.com/johnoneil/arib
///
public class AribB36 : SubtitleFormat
{
public class ProgramManagement
{
public string ProductionDepartmentDisplay { get; set; } // 6 bytes
public string MaterialNumber { get; set; } // 27 bytes
public string ProgramName { get; set; } // 40 bytes
public string ProgramSubtitle { get; set; } // 40 bytes
public string MaterialType { get; set; } // 1 byte
public string RegistrationMode { get; set; } // 1 byte
public string LanguageCode { get; set; } // 3 bytes - ISO639
public string DmfReceptionDisplay { get; set; } // 2 bytes
public string IndependenceCompletionSubtitles { get; set; } // 1 byte
public string Sound { get; set; } // 1 byte
public string TotalNumberOfPages { get; set; } // 4 bytes
public string ProgramDataAmount { get; set; } // 8 bytes
public string TimingPresent { get; set; } // 1 byte - Space: No, *: Yes
public string TimingType { get; set; } // 2 bytes
public string TimingUnit { get; set; } // 1 byte - T: Time UnitF: Frame
public ProgramManagement(byte[] buffer, int index)
{
ProductionDepartmentDisplay = Encoding.ASCII.GetString(buffer, index, 6);
MaterialNumber = Encoding.ASCII.GetString(buffer, index + 6, 27);
ProgramName = Encoding.ASCII.GetString(buffer, index + 33, 40);
ProgramSubtitle = Encoding.ASCII.GetString(buffer, index + 73, 40);
MaterialType = Encoding.ASCII.GetString(buffer, index + 113, 1);
RegistrationMode = Encoding.ASCII.GetString(buffer, index + 114, 1);
LanguageCode = Encoding.ASCII.GetString(buffer, index + 115, 3);
DmfReceptionDisplay = Encoding.ASCII.GetString(buffer, index + 118, 2);
IndependenceCompletionSubtitles = Encoding.ASCII.GetString(buffer, index + 120, 1);
Sound = Encoding.ASCII.GetString(buffer, index + 121, 1);
TotalNumberOfPages = Encoding.ASCII.GetString(buffer, index + 122, 4);
ProgramDataAmount = Encoding.ASCII.GetString(buffer, index + 126, 8);
TimingPresent = Encoding.ASCII.GetString(buffer, index + 134, 1);
TimingType = Encoding.ASCII.GetString(buffer, index + 135, 2);
TimingUnit = Encoding.ASCII.GetString(buffer, index + 137, 1);
}
}
public class PageManagement
{
public string PageNumber { get; set; } // 6 bytes
public string PageMaterialType { get; set; } // 1 byte
public string TransmissionTimingType { get; set; } // 2 bytes
public string SpecifiedTimingUnit { get; set; } // 1 byte
public string TransmissionTiming { get; set; } // 9 bytes
public string EraseTiming { get; set; } // 9 byte
public string TimeControlMode { get; set; } // 2 bytes
public string DeleteScreen { get; set; } // 3 bytes
public string PresentationFormat { get; set; } // 3 bytes
public string DisplayVideoEffectiveRatio { get; set; } // 1 byte
public string WindowDisplayArea { get; set; } // 16 bytes
public string Scroll { get; set; } // 1 byte
public string ScrollDirection { get; set; } // 1 byte
public string Sound { get; set; } // 1 byte
public string PageAmountOfData { get; set; } // 5 bytes
public string PageDeleteSpecified { get; set; } // 3 bytes
public string Memo { get; set; } // 20 bytes
public string Reserve { get; set; } // 32 bytes
public string PageCompletedMark { get; set; } // 3 bytes
public string UserAreaIdentification { get; set; } // 1 bytes
public string UserArea { get; set; } // ? bytes
public PageManagement(byte[] buffer, int index)
{
PageNumber = Encoding.ASCII.GetString(buffer, index, 6);
PageMaterialType = Encoding.ASCII.GetString(buffer, index + 6, 1);
TransmissionTimingType = Encoding.ASCII.GetString(buffer, index + 7, 2);
SpecifiedTimingUnit = Encoding.ASCII.GetString(buffer, index + 9, 1);
TransmissionTiming = Encoding.ASCII.GetString(buffer, index + 10, 9);
EraseTiming = Encoding.ASCII.GetString(buffer, index + 19, 9);
TimeControlMode = Encoding.ASCII.GetString(buffer, index + 28, 2);
DeleteScreen = Encoding.ASCII.GetString(buffer, index + 30, 3);
PresentationFormat = Encoding.ASCII.GetString(buffer, index + 33, 3);
DisplayVideoEffectiveRatio = Encoding.ASCII.GetString(buffer, index + 36, 1);
WindowDisplayArea = Encoding.ASCII.GetString(buffer, index + 37, 16);
Scroll = Encoding.ASCII.GetString(buffer, index + 53, 1);
ScrollDirection = Encoding.ASCII.GetString(buffer, index + 54, 1);
Sound = Encoding.ASCII.GetString(buffer, index + 55, 1);
PageAmountOfData = Encoding.ASCII.GetString(buffer, index + 56, 5);
PageDeleteSpecified = Encoding.ASCII.GetString(buffer, index + 61, 3);
Memo = Encoding.ASCII.GetString(buffer, index + 64, 20);
Reserve = Encoding.ASCII.GetString(buffer, index + 84, 32);
PageCompletedMark = Encoding.ASCII.GetString(buffer, index + 116, 1);
UserAreaIdentification = Encoding.ASCII.GetString(buffer, index + 117, 1);
}
private static TimeCode GetTime(string s, string timingUnit)
{
if (s.Length == 9)
{
var hours = int.Parse(s.Substring(0, 2));
var minutes = int.Parse(s.Substring(2, 2));
var seconds = int.Parse(s.Substring(4, 2));
if (timingUnit == "T")
{
//HHMMSSXX0 (last '0' is hardcoded, last 3 bytes is milliseconds)
var milliseconds = int.Parse(s.Substring(6, 3));
return new TimeCode(hours, minutes, seconds, milliseconds);
}
else
{
//HHMMSSXXF (last 'F' is hardcoded, XX=frames)
var frames = int.Parse(s.Substring(6, 2));
return new TimeCode(hours, minutes, seconds, FramesToMillisecondsMax999(frames));
}
}
return new TimeCode();
}
public TimeCode GetStartTime()
{
return GetTime(TransmissionTiming, SpecifiedTimingUnit);
}
public TimeCode GetEndTime()
{
string trimmed = EraseTiming.Trim();
if (trimmed == "F" || trimmed == "0" || trimmed.Length == 0)
{
return new TimeCode();
}
return GetTime(EraseTiming, SpecifiedTimingUnit);
}
}
public class CaptionTextPageManagement
{
public byte DataGroupId { get; set; }
public byte DataGroupVersion { get; set; }
public byte DataGroupLinkNumber { get; set; }
public byte LastDataGroupLinkNumber { get; set; }
public int DataGroupSize { get; set; }
public byte TimeControlMode { get; set; }
public string OffsetTimeMode { get; set; }
public byte NumberOfLanguages { get; set; }
public byte LanguageTag { get; set; }
public string DisplayMode { get; set; }
public byte DisplayModeControl { get; set; }
public string Iso639Languagecode { get; set; }
public byte Format { get; set; }
public string CharacterEncoding { get; set; }
public string RollupMode { get; set; }
public CaptionTextPageManagement(byte[] buffer, int index)
{
DataGroupId = (byte)(buffer[index] >> 2);
DataGroupVersion = (byte)(buffer[index] & VobSub.Helper.B00000011);
DataGroupLinkNumber = buffer[index + 1];
LastDataGroupLinkNumber = buffer[index + 2];
DataGroupSize = (buffer[index + 3] << 8) + buffer[index + 4];
TimeControlMode = (byte)(buffer[index + 5] >> 6);
NumberOfLanguages = buffer[index + 12];
LanguageTag = (byte)(buffer[index + 13] >> 5);
DisplayModeControl = buffer[index + 13];
Iso639Languagecode = Encoding.ASCII.GetString(buffer, index + 14, 3);
Format = (byte)(buffer[index + 18] >> 4);
}
}
public class CaptionTextUnit
{
public byte UnitSeparator { get; set; }
public byte DataUnitParameter { get; set; }
public int DataUnitSize { get; set; }
public AribText AribText { get; set; }
}
public class AribTextATag
{
public Point Location { get; set; }
public byte[] Data { get; set; }
public string Text { get; set; }
}
public class AribText
{
private static readonly Regex ATag = new Regex(@"\d+;\d+ a", RegexOptions.Compiled);
public double DurationInSeconds { get; set; }
public Point AreaSize { get; set; }
public Point AreaLocation { get; set; }
public Point FontWidthAndHeight { get; set; }
public int CharacterSpacing { get; set; }
public int LineSpacing { get; set; }
public List Texts { get; set; }
public AribText(byte[] buffer, int index, string languageCode, int length)
{
Texts = new List();
var sb = new StringBuilder();
int startBuffer = index;
for (int i = 0; i <= length; i++)
{
var b = buffer[index + i];
if (b == 0x9b || i == length) // 0x9b = separator
{
var code = sb.ToString();
var match = ATag.Match(code);
if (match.Success)
{
code = match.Value;
var arr = code.TrimEnd('a').TrimEnd().Split(';');
int x, y;
if (arr.Length == 2 && int.TryParse(arr[0], out x) && int.TryParse(arr[1], out y))
{
int start = startBuffer + match.Length;
int len = index + i - start;
var decodedText = AribB24Decoder.AribToString(buffer, start, len);
Texts.Add(new AribTextATag
{
Location = new Point(x, y),
Text = decodedText
});
}
}
else if (code.EndsWith(" S"))
{
double d;
if (double.TryParse(code.TrimEnd('S'), out d))
{
DurationInSeconds = d;
}
}
else if (code.EndsWith(" V"))
{
var arr = code.TrimEnd('V').TrimEnd().Split(';');
int x, y;
if (arr.Length == 2 && int.TryParse(arr[0], out x) && int.TryParse(arr[1], out y))
{
AreaSize = new Point(x, y);
}
}
else if (code.EndsWith(" _"))
{
var arr = code.TrimEnd('_').TrimEnd().Split(';');
int x, y;
if (arr.Length == 2 && int.TryParse(arr[0], out x) && int.TryParse(arr[1], out y))
{
AreaLocation = new Point(x, y);
}
}
else if (code.EndsWith(" W"))
{
var arr = code.TrimEnd('W').TrimEnd().Split(';');
int x, y;
if (arr.Length == 2 && int.TryParse(arr[0], out x) && int.TryParse(arr[1], out y))
{
FontWidthAndHeight = new Point(x, y);
}
}
else if (code.EndsWith(" X"))
{
var s = code.TrimEnd('X').TrimEnd();
int x;
if (int.TryParse(s, out x))
{
CharacterSpacing = x;
}
}
else if (code.EndsWith(" Y"))
{
var s = code.TrimEnd('Y').TrimEnd();
int y;
if (int.TryParse(s, out y))
{
LineSpacing = y;
}
}
sb.Clear();
startBuffer = index + i + 1;
}
else
{
sb.Append(Encoding.ASCII.GetString(buffer, index + i, 1));
}
}
}
}
public class CaptionText
{
public byte DataGroupId { get; set; }
public byte DataGroupVersion { get; set; }
public byte DataGroupLinkNumber { get; set; }
public byte LastDataGroupLinkNumber { get; set; }
public int DataGroupSize { get; set; }
public int DataUnitLoopLength { get; set; }
public List CaptionTextUnits { get; set; }
public CaptionText(byte[] buffer, int index, string languageCode)
{
DataGroupId = (byte)(buffer[index] >> 2);
DataGroupVersion = (byte)(buffer[index] & VobSub.Helper.B00000011);
DataGroupLinkNumber = buffer[index + 1];
LastDataGroupLinkNumber = buffer[index + 2];
DataGroupSize = (buffer[index + 3] << 8) + buffer[index + 4];
DataUnitLoopLength = (buffer[index + 12] << 16) + (buffer[index + 13] << 8) + buffer[index + 14];
CaptionTextUnits = new List(1);
var unitIndex = index + 15;
int end = unitIndex + DataUnitLoopLength;
while (unitIndex < end)
{
var unit = new CaptionTextUnit();
unit.UnitSeparator = buffer[unitIndex++];
unit.DataUnitParameter = buffer[unitIndex++];
unit.DataUnitSize = (buffer[unitIndex++] << 16) + (buffer[unitIndex++] << 8) + buffer[unitIndex++];
unit.AribText = new AribText(buffer, unitIndex, languageCode, unit.DataUnitSize);
unitIndex += unit.DataUnitSize;
if (unit.UnitSeparator == 0x1f && unit.DataUnitParameter == 0x20 && unit.AribText.Texts.Count > 0) // DataUnitParameter 0x20=Text, 0x35=Bitmap data
{
CaptionTextUnits.Add(unit);
}
}
}
}
public override string Extension
{
get { return ".1HD"; }
}
public override string Name
{
get { return "ARIB"; }
}
public override bool IsTimeBased
{
get { return true; }
}
public override bool IsMine(List lines, string fileName)
{
if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName))
{
var fi = new FileInfo(fileName);
if (fi.Length >= 3000 && fi.Length < 1024000) // not too small or too big
{
string fileExt = Path.GetExtension(fileName).ToUpperInvariant();
if (fileExt != Extension && !AlternateExtensions.Contains(fileExt))
{
return false;
}
return base.IsMine(lines, fileName);
}
}
return false;
}
public override string ToText(Subtitle subtitle, string title)
{
throw new NotImplementedException();
}
public void Save(string fileName, string videoFileName, Subtitle subtitle)
{
}
int RoundUp(int number, int multiple)
{
if (multiple == 0)
{
return number;
}
int remainder = number % multiple;
if (remainder == 0)
{
return number;
}
return number + multiple - remainder;
}
public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName)
{
const int startPosition = 256 * 3; // First 256 bytes block is just id, two next blocks are ProgramManagementInformation, then comes the list of PageManagementInformations
_errorCount = 0;
subtitle.Paragraphs.Clear();
subtitle.Header = null;
var buffer = FileUtil.ReadAllBytesShared(fileName);
int index = startPosition;
string label = Encoding.ASCII.GetString(buffer, 0, 8);
if (label != "DCAPTION" && label != "BCAPTION" && label != "MCAPTION")
{
return;
}
var programManagementInformation = new ProgramManagement(buffer, 256 + 4);
while (index + 255 < buffer.Length)
{
if (buffer[index + 4] == 0x2a) // Page management information
{
int blockstart = index;
int length = (buffer[index + 2] << 8) + buffer[index + 3];
index += 5;
var pageManagementInformationLength = (buffer[index++] << 8) + buffer[index++];
var pageManagementInformation = new PageManagement(buffer, index);
index += pageManagementInformationLength;
if (buffer[index] == 0x3a) // Caption text page management data
{
index++;
var captionTextPageManagementLength = (buffer[index++] << 8) + buffer[index++];
var captionTextPageManagement = new CaptionTextPageManagement(buffer, index);
index += captionTextPageManagementLength;
if (buffer[index] == 0x4a) // Caption text data
{
index++;
var subtitleDataLength = (buffer[index++] << 8) + buffer[index++];
try
{
var captionText = new CaptionText(buffer, index, captionTextPageManagement.Iso639Languagecode);
var p = new Paragraph
{
StartTime = pageManagementInformation.GetStartTime(),
EndTime = pageManagementInformation.GetEndTime()
};
foreach (var unit in captionText.CaptionTextUnits)
{
foreach (var text in unit.AribText.Texts)
{
p.Text = (p.Text + Environment.NewLine + text.Text).Trim();
}
}
subtitle.Paragraphs.Add(p);
}
catch
{
_errorCount++;
}
}
}
index = RoundUp(blockstart + length, 256);
}
else
{
index += 256;
}
}
for (int i = 0; i < subtitle.Paragraphs.Count - 1; i++)
{
var paragraph = subtitle.Paragraphs[i];
if (Math.Abs(paragraph.EndTime.TotalMilliseconds) < 0.001)
{
var next = subtitle.Paragraphs[i + 1];
paragraph.EndTime.TotalMilliseconds = next.StartTime.TotalMilliseconds - Configuration.Settings.General.MinimumMillisecondsBetweenLines;
}
}
if (subtitle.Paragraphs.Count > 0 && Math.Abs(subtitle.Paragraphs[subtitle.Paragraphs.Count - 1].EndTime.TotalMilliseconds) < 0.001)
{
var p = subtitle.Paragraphs[subtitle.Paragraphs.Count - 1];
p.EndTime.TotalMilliseconds = p.StartTime.TotalMilliseconds + Utilities.GetOptimalDisplayMilliseconds(p.Text);
}
subtitle.Renumber();
}
// 1HD = first HD subtitle, 2SD = second SD subtitle
public override List AlternateExtensions => new List { ".2HD", ".1SD", ".2SD" };
}
}