Added preliminary subtitle handling.
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 50s

This commit is contained in:
fey 2026-09-02 22:38:45 +02:00
parent c6551dbd52
commit ddd4181dc3
4 changed files with 385 additions and 5 deletions

View File

@ -163,6 +163,7 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd
throw new NotImplementedException();
}
private bool timePresent;
public void OnTdtTime(DateTime utcTime)
{
throw new NotImplementedException();
@ -220,7 +221,7 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd
continue;
case StreamType.IsdbSubtitles:
case StreamType.IsdbTeletext:
DvbContext.RegisterPacketProcessor(pmtStream.ElementaryPid, new PesDecoder(new IsdbSubtitlePesParser(this)));
DvbContext.RegisterPacketProcessor(pmtStream.ElementaryPid, new PesDecoder(new IsdbSubtitlePesParser(this, pmtStream.ElementaryPid)));
continue;
case StreamType.TryAutodetect:
DvbContext.RegisterPacketProcessor(pmtStream.ElementaryPid, new StreamTypeAutodetection(pmtStream.ElementaryPid, this));
@ -305,4 +306,12 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd
{
throw new NotImplementedException();
}
public void OnCaption(int pid, IsdbCaption plaintextCaption)
{
if (!timePresent)
return;
throw new NotImplementedException();
}
}

View File

@ -2,5 +2,5 @@ namespace skyscraper8.Isdb.Subtitles;
public interface IIsdbSubtitleHandler
{
void OnCaption(int pid, IsdbCaption plaintextCaption);
}

View File

@ -0,0 +1,261 @@
using System.Drawing;
using System.Text;
using log4net;
using skyscraper5.Dvb.DataBroadcasting.IntModel;
using skyscraper5.Skyscraper.IO;
namespace skyscraper8.Isdb.Subtitles;
public class IsdbCaption
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(IsdbCaption));
public IsdbCaption(byte[] buffer)
{
byte[] outBuffer = new byte[buffer.Length];
int inPointer = 0;
int outPointer = 0;
while (inPointer < buffer.Length)
{
byte b = buffer[inPointer++];
switch (b)
{
case 0x00: //NUL
break;
case 0x07: //BEL
throw new NotImplementedException("BEL");
case 0x08: //APB
throw new NotImplementedException("APB");
case 0x09: //APF
throw new NotImplementedException("APF");
case 0x0A: //APD
throw new NotImplementedException("APD");
case 0x0B: //APU
throw new NotImplementedException("APU");
case 0x0C: //CS
this.ClearScreen = true;
break;
case 0x0D: //APR
throw new NotImplementedException("APR");
case 0x0E: //LS1
throw new NotImplementedException("LS1");
case 0x0F: //LS0
throw new NotImplementedException("LS0");
case 0x16: //PAPF
throw new NotImplementedException("PAPF");
case 0x18: //CAN
throw new NotImplementedException("CAN");
case 0x19: //SS2
throw new NotImplementedException("SS2");
case 0x1B: //ESC
throw new NotImplementedException("ESC");
case 0x1C: //APS
int y = buffer[inPointer++];
int x = buffer[inPointer++];
x -= 0x40;
y -= 0x40;
ActivePositionSet = new Point(x, y);
break;
case 0x1D:
throw new NotImplementedException("SS3");
case 0x1E:
throw new NotImplementedException("RS");
case 0x1F:
throw new NotImplementedException("US");
case 0x20: //SP (Space)
outBuffer[outPointer++] = 0x20;
break;
case 0x7F: //DEL
throw new NotImplementedException("DEL");
case 0x80: //BKF
throw new NotImplementedException("BKF");
case 0x81: //RDF
throw new NotImplementedException("RDF");
case 0x82: //GRF
throw new NotImplementedException("GRF");
case 0x83: //YLF
throw new NotImplementedException("YLF");
case 0x84: //BLF
throw new NotImplementedException("BLF");
case 0x85: //MGF
throw new NotImplementedException("MGF");
case 0x86: //CNF
throw new NotImplementedException("CNF");
case 0x87: //WHF
ForegroundColor = Color.White;
break;
case 0x88: //SSZ
CharacterSize = Subtitles.CharacterSize.Small;
break;
case 0x89: //MSZ
throw new NotImplementedException("MSZ");
case 0x8A: //NSZ
throw new NotImplementedException("NSZ");
case 0x8B:
throw new NotImplementedException("SZX");
case 0x90: //COL
byte colP1 = buffer[inPointer++];
switch (colP1)
{
case 0x50: BackgroundColor = Color.Black; break;
case 0x58: BackgroundColor = Color.Transparent; break;
default:
//see 6-STD-B24v6_4-1p3-E1.pdf, page 108
throw new NotImplementedException(String.Format("colP1 0x{0:X2}", colP1));
}
break;
case 0x91: //FLC
throw new NotImplementedException("FLC");
case 0x92: //CDC
throw new NotImplementedException("CDC");
case 0x93: //POL
throw new NotImplementedException("POL");
case 0x94: //WLF
throw new NotImplementedException("WLF");
case 0x95: //MACRO
throw new NotImplementedException("MACRO");
case 0x97: //HLC
throw new NotImplementedException("HLC");
case 0x98: //RPC
throw new NotImplementedException("RPC");
case 0x99: //SPL
throw new NotImplementedException("SPL");
case 0x9A: //STL
throw new NotImplementedException("STL");
case 0x9B: //CSI
ControlSequenceIntroduced = true;
string csiArguments = "";
while (inPointer < buffer.Length)
{
byte csiByte = buffer[inPointer++];
if (csiByte >= 0x20 && csiByte <= 0x3F)
{
csiArguments += (char)csiByte;
}
else if (csiByte >= 0x40 && csiByte <= 0x7E)
{
char command = (char)csiByte;
HandleCsiCommand(command, csiArguments);
break;
}
}
break;
case 0x9D: //TIME
throw new NotImplementedException("TIME");
case 0xA0: //10/0
throw new NotImplementedException("10/0");
case 0xFF:
throw new NotImplementedException("15/15");
default:
outBuffer[outPointer++] = b;
break;
}
}
Text = Encoding.UTF8.GetString(outBuffer, 0, outPointer);
}
private void HandleCsiCommand(char command, string csiArguments)
{
//Um den passenden CMD zu finden, die 6-STD-B24v6_4-1p3-E1.pdf durchsuchen und Table 7-17 (ab Seite 113) nach Final character durchsuchen.
//Wenn cmd zum Beispiel 0x53 ist, dann nach 05/3 suchen.
byte cmd = (byte)command;
csiArguments = csiArguments.TrimEnd();
switch (cmd)
{
case 0x53:
WritingFormat = (WritingFormat)int.Parse(csiArguments);
break;
case 0x56:
string[] sdfArgs = csiArguments.Split(';');
DisplayFormat = new Size(int.Parse(sdfArgs[0]), int.Parse(sdfArgs[1]));
break;
case 0x5f:
string[] sdpArgs = csiArguments.Split(';');
DisplayPosition = new Point(int.Parse(sdpArgs[0]), int.Parse(sdpArgs[1]));
break;
case 0x57:
string[] ssmArgs = csiArguments.Split(';');
CharacterCompositionDots = new Size(int.Parse(ssmArgs[0]), int.Parse(ssmArgs[1]));
break;
case 0x58:
HorizontalSpacing = int.Parse(csiArguments);
break;
case 0x59:
VerticalSpace = int.Parse(csiArguments);
break;
case 0x6e:
int rcsColor = int.Parse(csiArguments);
bool isTransparent = rcsColor > 8;
if (isTransparent)
rcsColor -= 8;
switch (rcsColor)
{
case 0: RasterColour = Color.Black; break;
case 1: RasterColour = Color.Red; break;
case 2: RasterColour = Color.Green; break;
case 3: RasterColour = Color.Yellow; break;
case 4: RasterColour = Color.Blue; break;
case 5: RasterColour = Color.Magenta; break;
case 6: RasterColour = Color.Cyan; break;
case 7: RasterColour = Color.White; break;
case 8: RasterColour = Color.Transparent; break;
}
if (isTransparent)
RasterColour = Color.FromArgb(RasterColour.Value.A,RasterColour.Value.R / 2,RasterColour.Value.G / 2,RasterColour.Value.B / 2);
break;
default:
throw new NotImplementedException(String.Format("cmd 0x{0:X2}", cmd));
}
}
public Color? RasterColour { get; set; }
public int? VerticalSpace { get; set; }
public int? HorizontalSpacing { get; set; }
public Size? CharacterCompositionDots { get; set; }
public Point? DisplayPosition { get; set; }
public Size? DisplayFormat { get; set; }
public WritingFormat? WritingFormat { get; set; }
public string Text { get; private set; }
public bool ControlSequenceIntroduced { get; private set; }
public bool ClearScreen { get; private set; }
public CharacterSize? CharacterSize { get; private set; }
public Color? ForegroundColor { get; private set; }
public Color? BackgroundColor { get; private set; }
public Point? ActivePositionSet { get; private set; }
}
public enum CharacterSize
{
Small,
Medium,
Large
}
public enum WritingFormat
{
HorizontalStandardDensity = 0,
VerticalStandardDensity = 1,
HorizontalHighDensity = 2,
VerticalHighDensity = 3,
HorizontalWritingWestern = 4,
HorizontalWriting1920_1080 = 5,
VerticalWriting1920_1080 = 6,
HorizontalWriting960_540 = 7,
VerticalWriting960_540 = 8,
HorizontalWriting720_480 = 9,
VerticalWriting720_480 = 10,
HorizontalWriting1280_720 = 11,
VerticalWriting1280_720 = 12,
}

View File

@ -1,5 +1,6 @@
using log4net;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;
namespace skyscraper8.Isdb.Subtitles;
@ -9,13 +10,122 @@ class IsdbSubtitlePesParser : IPesProcessor
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
public IsdbSubtitlePesParser(IIsdbSubtitleHandler handler)
public IsdbSubtitlePesParser(IIsdbSubtitleHandler handler, int pmtStreamElementaryPid)
{
Handler = handler;
Pid = pmtStreamElementaryPid;
}
public int Pid { get; private set; }
public void ProcessPesPacket(PesPacket packet)
{
throw new NotImplementedException();
//Peel back the outer layer, as stated by 6-STD-B37v2_4-E1.pdf, clause 2.2.3.6, Table 2-25
byte[] payload = packet.Payload;
MemoryStream ms = new MemoryStream(payload, false);
byte dataIdentifier = ms.ReadUInt8();
byte privateStreamId = ms.ReadUInt8();
byte byte3 = ms.ReadUInt8();
int reserved = (byte3 & 0xf0) >> 4;
int pesDataPacketHeaderLength = (byte3 & 0x0f);
byte[] pesDataPacketHeader = ms.ReadBytes(pesDataPacketHeaderLength);
if (pesDataPacketHeaderLength > 0)
{
throw new NotImplementedException("PES packet header length not implemented");
}
//Finally, we can start parsing the data group 6-STD-B24v6_4-1p3-E1.pdf, clause 9.2
byte byte4 = ms.ReadUInt8();
int dataGroupId = (byte4 & 0xfc) >> 2;
int dataGroupVersion = (byte4 & 0x03);
byte dataGroupLinkNumber = ms.ReadUInt8();
byte lastDataGroupLinkNumber = ms.ReadUInt8();
ushort dataGroupSize = ms.ReadUInt16BE();
long expectedRemaining = ms.GetAvailableBytes() - 2;
if (dataGroupSize != expectedRemaining)
{
throw new IsdbException("Unexpected data group size: " + dataGroupSize);
}
byte[] dataGroupSizeBytes = ms.ReadBytes(dataGroupSize);
switch (dataGroupId)
{
case 0:
ParseCaptionManagement(dataGroupSizeBytes);
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
ParseCaptionStatement(dataGroupSizeBytes);
break;
}
ushort crc16 = ms.ReadUInt16BE();
}
private void ParseCaptionManagement(byte[] dataGroupSizeBytes)
{
throw new NotImplementedException("Caption management not implemented");
}
private void ParseCaptionStatement(byte[] dataGroupSizeBytes)
{
MemoryStream ms = new MemoryStream(dataGroupSizeBytes, false);
byte byteA = ms.ReadUInt8();
int tmd = (byteA & 0xc0) >> 6;
int reserved = (byteA & 0x3f);
if (tmd == 1 || tmd == 2)
{
byte[] stm = ms.ReadBytes(5);
}
uint dataUnitLoopLength = ms.ReadUInt24BE();
long expectedDataUnitLoopLength = ms.GetAvailableBytes();
if (dataUnitLoopLength != expectedDataUnitLoopLength)
{
throw new IsdbException("Unexpected data unit-loop length: " + dataUnitLoopLength);
}
byte[] dataUnit = ms.ReadBytes(dataUnitLoopLength);
ParseDataUnit(dataUnit);
}
private void ParseDataUnit(byte[] dataUnit)
{
MemoryStream ms = new MemoryStream(dataUnit, false);
byte unitSeperator = ms.ReadUInt8();
if (unitSeperator != 0x1f)
{
throw new IsdbException("Unexpected data unit-seperator: " + unitSeperator);
}
byte dataUnitParameter = ms.ReadUInt8();
uint dataUnitSize = ms.ReadUInt24BE();
long expectedDataUnitSize = ms.GetAvailableBytes();
if (dataUnitSize != expectedDataUnitSize)
{
throw new IsdbException("Unexpected data unit-size: " + dataUnitSize);
}
byte[] dataUnitData = ms.ReadBytes(dataUnitSize);
switch (dataUnitParameter)
{
case 0x20:
IsdbCaption plaintextCaption = new IsdbCaption(dataUnitData);
logger.InfoFormat("Caption on PID 0x{0:X4} - {1}", Pid, plaintextCaption.Text);
Handler.OnCaption(Pid, plaintextCaption);
break;
default:
throw new IsdbException("Unexpected data unit-parameter: " + dataUnitParameter);
}
}
}