From 2769d3c2f98ee3147568a8f498b12a62a9732834 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 29 Oct 2023 09:39:23 +0100 Subject: [PATCH] Add some Cea608 --- src/libse/Cea608/CaptionScreen.cs | 186 +++++++++ src/libse/Cea608/CcDataC608Parser.cs | 289 ++++++++++++++ src/libse/Cea608/CcRow.cs | 167 ++++++++ src/libse/Cea608/CcStyle.cs | 11 + src/libse/Cea608/Cea608Channel.cs | 365 ++++++++++++++++++ src/libse/Cea608/Constants.cs | 161 ++++++++ src/libse/Cea608/DataOutput.cs | 11 + src/libse/Cea608/PacData.cs | 9 + src/libse/Cea608/PenState.cs | 93 +++++ src/libse/Cea608/SerializedPenState.cs | 11 + src/libse/Cea608/SerializedRow.cs | 15 + .../Cea608/SerializedStyledUnicodeChar.cs | 8 + src/libse/Cea608/StyledUnicodeChar.cs | 52 +++ 13 files changed, 1378 insertions(+) create mode 100644 src/libse/Cea608/CaptionScreen.cs create mode 100644 src/libse/Cea608/CcDataC608Parser.cs create mode 100644 src/libse/Cea608/CcRow.cs create mode 100644 src/libse/Cea608/CcStyle.cs create mode 100644 src/libse/Cea608/Cea608Channel.cs create mode 100644 src/libse/Cea608/Constants.cs create mode 100644 src/libse/Cea608/DataOutput.cs create mode 100644 src/libse/Cea608/PacData.cs create mode 100644 src/libse/Cea608/PenState.cs create mode 100644 src/libse/Cea608/SerializedPenState.cs create mode 100644 src/libse/Cea608/SerializedRow.cs create mode 100644 src/libse/Cea608/SerializedStyledUnicodeChar.cs create mode 100644 src/libse/Cea608/StyledUnicodeChar.cs diff --git a/src/libse/Cea608/CaptionScreen.cs b/src/libse/Cea608/CaptionScreen.cs new file mode 100644 index 000000000..09c9c3a5e --- /dev/null +++ b/src/libse/Cea608/CaptionScreen.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class CaptionScreen + { + public CcRow[] Rows = { + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + new CcRow(), + }; + + public int CurrentRow { get; set; } = Constants.ScreenRowCount - 1; + public int? NumberOfRollUpRows { get; set; } + + public CaptionScreen() + { + Reset(); + } + + public SerializedRow[] Serialize() + { + var results = new List(); + for (var i = 0; i < Constants.ScreenRowCount; i++) + { + var row = Rows[i]; + if (row.IsEmpty()) + { + continue; + } + + results.Add(new SerializedRow + { + Row = i, + Position = row.FirstNonEmpty(), + Style = row.CurrentPenState.Serialize(), + Columns = row.Chars.Select(SerializeChar).ToArray() + }); + } + return results.ToArray(); + } + + private SerializedStyledUnicodeChar SerializeChar(StyledUnicodeChar character) + { + return new SerializedStyledUnicodeChar + { + Character = character.Uchar, + Style = character.PenState.Serialize(), + }; + } + + public void Reset() + { + for (var i = 0; i < Constants.ScreenRowCount; i++) + { + Rows[i].Clear(); + } + CurrentRow = Constants.ScreenRowCount - 1; + } + + public bool Equals(CaptionScreen other) + { + var equal = true; + for (var i = 0; i < Constants.ScreenRowCount; i++) + { + if (!Rows[i].Equals(other.Rows[i])) + { + equal = false; + break; + } + } + + return equal; + } + + public void Copy(CaptionScreen other) + { + for (var i = 0; i < Constants.ScreenRowCount; i++) + { + Rows[i].Copy(other.Rows[i]); + } + } + + public bool IsEmpty() + { + var empty = true; + for (var i = 0; i < Constants.ScreenRowCount; i++) + { + if (!Rows[i].IsEmpty()) + { + empty = false; + break; + } + } + return empty; + } + + public void BackSpace() + { + Rows[CurrentRow].BackSpace(); + } + + public void ClearToEndOfRow() + { + Rows[CurrentRow].ClearToEndOfRow(); + } + + public void InsertChar(int character) + { + Rows[CurrentRow].InsertChar(character); + } + + public void SetPen(SerializedPenState styles) + { + Rows[CurrentRow].SetPenStyles(styles); + } + + public void MoveCursor(int relPos) + { + Rows[CurrentRow].MoveCursor(relPos); + } + + public void SetPac(PacData pacData) + { + var newRow = pacData.Row - 1; + CurrentRow = newRow; + var row = Rows[CurrentRow]; + if (pacData.Indent != null) + { + var indent = pacData.Indent; + var prevPos = Math.Max(indent.Value - 1, 0); + row.Position = pacData.Indent.Value; + pacData.Color = row.Chars[prevPos].PenState.Foreground; + } + + SetPen(new SerializedPenState + { + Foreground = pacData.Color ?? Constants.ColorWhite, + Underline = pacData.Underline, + Italics = pacData.Italics ?? false, + Background = Constants.ColorBlack, + Flash = false, + }); + } + + public void SetBkgData(SerializedPenState bkgData) + { + BackSpace(); + SetPen(bkgData); + InsertChar(0x20); // Space + } + + public void SetRollUpRows(int nrRows) + { + NumberOfRollUpRows = nrRows; + } + + public void RollUp() + { + // if the row is empty we have nothing to roll-up + if (NumberOfRollUpRows == null || Rows[CurrentRow].IsEmpty()) + { + return; + } + + var rows = Rows.ToList(); + rows.RemoveAt(CurrentRow - NumberOfRollUpRows.Value + 1); + rows.Add(new CcRow()); + Rows = rows.ToArray(); + } + } +} diff --git a/src/libse/Cea608/CcDataC608Parser.cs b/src/libse/Cea608/CcDataC608Parser.cs new file mode 100644 index 000000000..fa0b2889d --- /dev/null +++ b/src/libse/Cea608/CcDataC608Parser.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; + +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class CcDataC608Parser + { + public delegate void DisplayScreenDelegate(DataOutput data); + + public Cea608Channel[] Channels { get; set; } + public int? CurrentChannelNumber { get; set; } + public int? LastTime { get; set; } + + private int? _lastCmdA; + private int? _lastCmdB; + + public DisplayScreenDelegate DisplayScreen { get; set; } + + public CcDataC608Parser() + { + Channels = new[] + { + new Cea608Channel(1, this), + new Cea608Channel(2, this), + }; + + CurrentChannelNumber = -1; + } + + public void AddData(int t, int[] byteList) + { + LastTime = t; + + for (var i = 0; i < byteList.Length; i += 2) + { + var a = byteList[i] & 0x7f; + var b = byteList[i + 1] & 0x7f; + + if (a == 0 && b == 0) + { + continue; + } + + if (!(ParseCmd(a, b) || + ParseMidRow(a, b) || + ParsePac(a, b) || + ParseBackgroundAttributes(a, b))) + { + ParseCharacters(a, b); + } + } + } + + private void ParseCharacters(int ccData1, int ccData2) + { + var charsFound = ParseChars(ccData1, ccData2); + if (charsFound.Length > 0) + { + if (CurrentChannelNumber != null && CurrentChannelNumber >= 0) + { + var channel = Channels[CurrentChannelNumber.Value - 1]; + channel.InsertChars(charsFound); + } + else + { + System.Diagnostics.Debug.WriteLine("No channel found yet. TEXT-MODE?"); + } + } + } + + public int[] ParseChars(int a, int b) + { + var charCodes = new List(); + int charCode1; + + if (a >= 0x19) + { + charCode1 = a - 8; + } + else + { + charCode1 = a; + } + + if (0x11 <= charCode1 && charCode1 <= 0x13) + { + // Special character + int oneCode; + if (charCode1 == 0x11) + { + oneCode = b + 0x50; + } + else if (charCode1 == 0x12) + { + oneCode = b + 0x70; + } + else + { + oneCode = b + 0x90; + } + + charCodes.Add(oneCode); + } + else if (0x20 <= a && a <= 0x7f) + { + charCodes.Add(a); + + if (b > 0) + { + charCodes.Add(b); + } + } + + return charCodes.ToArray(); + } + + private static bool HasCmd(int ccData1, int ccData2) + { + return ((ccData1 == 0x14 || ccData1 == 0x1C) && (0x20 <= ccData2 && ccData2 <= 0x2F)) || + ((ccData1 == 0x17 || ccData1 == 0x1F) && (0x21 <= ccData2 && ccData2 <= 0x23)); + } + + public bool ParseCmd(int a, int b) + { + if (HasCmd(a, b)) + { + // Duplicate CMD commands get skipped once + if (_lastCmdA == a && _lastCmdB == b) + { + _lastCmdA = null; + _lastCmdB = null; + return true; + } + + int chNr; + if (a == 0x14 || a == 0x17) + { + chNr = 1; + } + else + { + chNr = 2; // (a == 0x1C || a== 0x1f) + } + + Channels[chNr - 1].RunCmd(a, b); + CurrentChannelNumber = chNr; + _lastCmdA = a; + _lastCmdB = b; + return true; + } + + return false; + } + + public bool ParseMidRow(int a, int b) + { + if (((a == 0x11) || (a == 0x19)) && 0x20 <= b && b <= 0x2f) + { + int chNr; + if (a == 0x11) + { + chNr = 1; + } + else + { + chNr = 2; + } + var channel = Channels[chNr - 1]; + channel.cc_MidRow(b); + return true; + } + + return false; + } + + private static bool HasPac(int ccData1, int ccData2) + { + return (((0x11 <= ccData1 && ccData1 <= 0x17) || (0x19 <= ccData1 && ccData1 <= 0x1F)) && (0x40 <= ccData2 && ccData2 <= 0x7F)) || + ((ccData1 == 0x10 || ccData1 == 0x18) && (0x40 <= ccData2 && ccData2 <= 0x5F)); + } + + public bool ParsePac(int a, int b) + { + if (HasPac(a, b)) + { + var chNr = (a <= 0x17) ? 1 : 2; + + int row; + if (0x40 <= b && b <= 0x5F) + { + row = (chNr == 1) ? Constants.Channel1RowsMap[a] : Constants.Channel2RowsMap[a]; + } + else + { // 0x60 <= b <= 0x7F + row = (chNr == 1) ? (Constants.Channel1RowsMap[a] + 1) : (Constants.Channel2RowsMap[a] + 1); + } + + var pacData = InterpretPac(row, b); + var channel = Channels[chNr - 1]; + channel.SetPac(pacData); + CurrentChannelNumber = chNr; + return true; + } + return false; + } + + public PacData InterpretPac(int row, int b) + { + var pacData = new PacData + { + Color = null, + Italics = false, + Indent = null, + Underline = false, + Row = row, + }; + + int pacIndex; + if (b > 0x5F) + { + pacIndex = b - 0x60; + } + else + { + pacIndex = b - 0x40; + } + + pacData.Underline = (pacIndex & 1) == 1; + if (pacIndex <= 0xd) + { + pacData.Color = Constants.PacDataColors[(int)Math.Floor(pacIndex / 2.0)]; + } + else if (pacIndex <= 0xf) + { + pacData.Italics = true; + pacData.Color = Constants.ColorWhite; + } + else + { + pacData.Indent = (int)((Math.Floor((pacIndex - 0x10) / 2.0)) * 4); + } + + return pacData; + } + + public bool ParseBackgroundAttributes(int a, int b) + { + var bkgData = new SerializedPenState(); + if (!HasBackgroundAttributes(a, b)) + { + return false; + } + + if (a == 0x10 || a == 0x18) + { + var index = (int)Math.Round(Math.Floor((b - 0x20) / 2.0)); + bkgData.Background = Constants.PacDataColors[index]; + if (b % 2 == 1) + { + bkgData.Background += "_semi"; + } + } + else if (b == 0x2d) + { + bkgData.Background = Constants.ColorTransparent; + } + else + { + bkgData.Foreground = Constants.ColorBlack; + if (b == 0x2f) + { + bkgData.Underline = true; + } + } + var chNr = (a < 0x18) ? 1 : 2; + var channel = Channels[chNr - 1]; + channel.SetBkgData(bkgData); + return true; + } + + private static bool HasBackgroundAttributes(int ccData1, int ccData2) + { + return (((ccData1 == 0x10 || ccData1 == 0x18) && (0x20 <= ccData2 && ccData2 <= 0x2f)) || + ((ccData1 == 0x17 || ccData1 == 0x1f) && (0x2d <= ccData2 && ccData2 <= 0x2f))); + } + } +} + + diff --git a/src/libse/Cea608/CcRow.cs b/src/libse/Cea608/CcRow.cs new file mode 100644 index 000000000..5bff6b836 --- /dev/null +++ b/src/libse/Cea608/CcRow.cs @@ -0,0 +1,167 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class CcRow + { + public int Position { get; set; } + + public PenState CurrentPenState = new PenState(); + + public StyledUnicodeChar[] Chars { get; } = + { + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + new StyledUnicodeChar(), + }; + + public bool Equals(CcRow other) + { + for (var i = 0; i < Constants.ScreenColCount; i++) + { + if (!Chars[i].Equals(other.Chars[i])) + { + if (!Chars[i].Equals(other.Chars[i])) + { + return false; + } + } + } + + return true; + } + + public void Copy(CcRow other) + { + for (var i = 0; i < Constants.ScreenColCount; i++) + { + Chars[i].Copy(other.Chars[i]); + } + } + + public int FirstNonEmpty() + { + for (var i = 0; i < Constants.ScreenColCount; i++) + { + if (!Chars[i].IsEmpty()) + { + return i; + } + } + + return -1; + } + + public bool IsEmpty() + { + for (var i = 0; i < Constants.ScreenColCount; i++) + { + if (!Chars[i].IsEmpty()) + { + return false; + } + } + + return true; + } + + public void MoveCursor(int relPos) + { + var newPos = Position + relPos; + if (relPos > 1) + { + for (var i = Position + 1; i < newPos + 1; i++) + { + Chars[i].SetPenState(CurrentPenState); + } + } + + Position = newPos; + } + + public void BackSpace() + { + MoveCursor(-1); + Chars[Position].SetChar(Constants.EmptyChar, CurrentPenState); + } + + public void InsertChar(int b) + { + if (b >= 0x90) + { // Extended char + BackSpace(); + } + + var ch = GetCharForByte(b); + Chars[Position].SetChar(ch, CurrentPenState); + MoveCursor(1); + } + + /// + /// Get Unicode Character from CEA-608 byte code. + /// + public static string GetCharForByte(int byteValue) + { + if (Constants.ExtendedCharCodes.TryGetValue(byteValue, out var v)) + { + return char.ConvertFromUtf32(v); + } + + return char.ConvertFromUtf32(byteValue); + } + + public void ClearFromPos(int startPos) + { + for (var i = startPos; i < Constants.ScreenColCount; i++) + { + Chars[i].Reset(); + } + } + + public void Clear() + { + ClearFromPos(0); + Position = 0; + CurrentPenState.Reset(); + } + + public void ClearToEndOfRow() + { + ClearFromPos(Position); + } + + public void SetPenStyles(SerializedPenState styles) + { + CurrentPenState.SetStyles(styles); + var currentChar = Chars[Position]; + currentChar.SetPenState(CurrentPenState); + } + } +} diff --git a/src/libse/Cea608/CcStyle.cs b/src/libse/Cea608/CcStyle.cs new file mode 100644 index 000000000..0022d9768 --- /dev/null +++ b/src/libse/Cea608/CcStyle.cs @@ -0,0 +1,11 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class CcStyle + { + public string Foreground { get; set; } + public bool? Underline { get; set; } + public bool? Italics { get; set; } + public string Background { get; set; } + public bool? Flash { get; set; } + } +} diff --git a/src/libse/Cea608/Cea608Channel.cs b/src/libse/Cea608/Cea608Channel.cs new file mode 100644 index 000000000..979189c5d --- /dev/null +++ b/src/libse/Cea608/Cea608Channel.cs @@ -0,0 +1,365 @@ +using System; + +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class Cea608Channel + { + public CaptionScreen DisplayedMemory { get; set; } + public CaptionScreen NonDisplayedMemory { get; set; } + public CaptionScreen LastOutputScreen { get; set; } + public CaptionScreen WriteScreen { get; set; } + public CcRow CurrentRollUpRow { get; set; } + public int? CueStartTime { get; set; } + public string Mode { get; set; } + + public int ChannelNumber { get; set; } + public CcDataC608Parser Parser { get; set; } + + public Cea608Channel() + { + DisplayedMemory = new CaptionScreen(); + NonDisplayedMemory = new CaptionScreen(); + LastOutputScreen = new CaptionScreen(); + } + + public Cea608Channel(int chNr, CcDataC608Parser parser) + { + Parser = parser; + ChannelNumber = chNr; + + DisplayedMemory = new CaptionScreen(); + NonDisplayedMemory = new CaptionScreen(); + LastOutputScreen = new CaptionScreen(); + + CurrentRollUpRow = DisplayedMemory.Rows[Constants.ScreenRowCount - 1]; + WriteScreen = DisplayedMemory; + CueStartTime = null; + } + + private void CmdMap(int cmd) + { + switch (cmd) + { + case 0x20: + cc_RCL(); + break; + case 0x21: + cc_BS(); + break; + case 0x22: + cc_AOF(); + break; + case 0x23: + cc_AON(); + break; + case 0x24: + cc_DER(); + break; + case 0x25: + cc_RU(2); + break; + case 0x26: + cc_RU(3); + break; + case 0x27: + cc_RU(4); + break; + case 0x28: + cc_FON(); + break; + case 0x29: + cc_RDC(); + break; + case 0x2A: + cc_TR(); + break; + case 0x2B: + cc_RTD(); + break; + case 0x2C: + cc_EDM(); + break; + case 0x2D: + cc_CR(); + break; + case 0x2E: + cc_ENM(); + break; + case 0x2F: + cc_EOC(); + break; + default: + Console.WriteLine("Command not found"); + break; + } + } + + public void Reset() + { + Mode = null; + DisplayedMemory.Reset(); + NonDisplayedMemory.Reset(); + LastOutputScreen.Reset(); + CurrentRollUpRow = DisplayedMemory.Rows[Constants.ScreenRowCount - 1]; + WriteScreen = DisplayedMemory; + CueStartTime = null; + } + + public void SetPac(PacData pacData) + { + WriteScreen.SetPac(pacData); + } + + public void RunCmd(int ccData1, int ccData2) + { + if (ccData1 == 0x14 || ccData1 == 0x1C) + { + CmdMap(ccData2); + } + else + { // a == 0x17 || a == 0x1F + cc_TO(ccData2 - 0x20); + } + } + + public void SetBkgData(SerializedPenState bkgData) + { + WriteScreen.SetBkgData(bkgData); + } + + public void SetMode(string newMode) + { + if (newMode == Mode) + { + return; + } + + Mode = newMode; + + if (Mode == "MODE_POP-ON") + { + WriteScreen = NonDisplayedMemory; + } + else + { + WriteScreen = DisplayedMemory; + WriteScreen.Reset(); + } + + if (Mode != "MODE_ROLL-UP") + { + DisplayedMemory.NumberOfRollUpRows = null; + NonDisplayedMemory.NumberOfRollUpRows = null; + } + + Mode = newMode; + } + + public void InsertChars(int[] chars) + { + foreach (var ch in chars) + { + WriteScreen.InsertChar(ch); + } + + if (Mode == "MODE_PAINT-ON" || Mode == "MODE_ROLL-UP") + { + OutputDataUpdate(); + } + } + + /// + /// Resume Caption Loading (switch mode to Pop On). + /// + public void cc_RCL() + { + SetMode("MODE_POP-ON"); + } + + /// + /// BackSpace. + /// + public void cc_BS() + { + if (Mode == "MODE_TEXT") + { + return; + } + + WriteScreen.BackSpace(); + if (WriteScreen == DisplayedMemory) + { + OutputDataUpdate(); + } + } + + /// + /// Reserved (formerly Alarm Off). + /// + public void cc_AOF() + { + } + + /// + /// Reserved (formerly Alarm On). + /// + public void cc_AON() + { + } + + /// + /// Delete to End of Row. + /// + public void cc_DER() + { + WriteScreen.ClearToEndOfRow(); + OutputDataUpdate(); + } + + /// + /// Roll-Up Captions-2,3,or 4 Rows. + /// + public void cc_RU(int nrRows) + { + WriteScreen = DisplayedMemory; + SetMode("MODE_ROLL-UP"); + WriteScreen.SetRollUpRows(nrRows); + } + + /// + /// Flash On. + /// + public void cc_FON() + { + WriteScreen.SetPen(new PacData { Flash = true }); + } + + /// + /// Resume Direct Captioning (switch mode to PaintOn). + /// + public void cc_RDC() + { + SetMode("MODE_PAINT-ON"); + } + + /// + /// Text Restart in text mode (not supported, however). + /// + public void cc_TR() + { + SetMode("MODE_TEXT"); + } + + /// + /// Resume Text Display in Text mode (not supported, however) + /// + public void cc_RTD() + { + SetMode("MODE_TEXT"); + } + + /// + /// Erase Displayed Memory. + /// + public void cc_EDM() + { + DisplayedMemory.Reset(); + OutputDataUpdate(); + } + + /// + /// Carriage Return. + /// + public void cc_CR() + { + WriteScreen.RollUp(); + OutputDataUpdate(true); + } + + /// + /// Erase Non-Displayed Memory. + /// + public void cc_ENM() + { + NonDisplayedMemory.Reset(); + } + + /// + /// End of Caption (Flip Memories). + /// + public void cc_EOC() + { + if (Mode == "MODE_POP-ON") + { + (DisplayedMemory, NonDisplayedMemory) = (NonDisplayedMemory, DisplayedMemory); + WriteScreen = NonDisplayedMemory; + } + + OutputDataUpdate(); + } + + /// + /// Tab Offset 1,2, or 3 columns + /// + public void cc_TO(int nrCols) + { + WriteScreen.MoveCursor(nrCols); + } + + /// + /// Parse MIDROW command + /// + public void cc_MidRow(int secondByte) + { + // + var styles = new SerializedPenState { Flash = false }; + styles.Underline = secondByte % 2 == 1; + styles.Italics = secondByte >= 0x2e; + if (!styles.Italics.Value) + { + var colorIndex = (int)Math.Floor(secondByte / 2.0) - 0x10; + styles.Foreground = Constants.PacDataColors[colorIndex]; + } + else + { + styles.Foreground = Constants.ColorWhite; + } + + WriteScreen.SetPen(styles); + } + + public void OutputDataUpdate(bool rolling = false) + { + var t = Parser.LastTime; + if (t == null) + { + return; + } + + if (CueStartTime == null && !DisplayedMemory.IsEmpty()) + { // Start of a new cue + CueStartTime = t; + } + else if (!DisplayedMemory.Equals(LastOutputScreen)) + { + + if (CueStartTime != null && !LastOutputScreen.IsEmpty()) + { + Parser.DisplayScreen?.Invoke(new DataOutput + { + Channel = ChannelNumber, + Roll = rolling, + Start = CueStartTime ?? 0, + End = t ?? 0, + Screen = LastOutputScreen.Serialize(), + }); + } + + CueStartTime = DisplayedMemory.IsEmpty() ? null : t; + + } + + LastOutputScreen.Copy(DisplayedMemory); + } + } +} diff --git a/src/libse/Cea608/Constants.cs b/src/libse/Cea608/Constants.cs new file mode 100644 index 000000000..76ede5921 --- /dev/null +++ b/src/libse/Cea608/Constants.cs @@ -0,0 +1,161 @@ +using System.Collections.Generic; + +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public static class Constants + { + public const string ColorWhite = "white"; + public const string ColorGreen = "green"; + public const string ColorBlue = "blue"; + public const string ColorCyan = "cyan"; + public const string ColorRed = "red"; + public const string ColorYellow = "yellow"; + public const string ColorMagenta = "magenta"; + public const string ColorBlack = "black"; + public const string ColorTransparent = "transparent"; + + public const string EmptyChar = " "; + + public static string[] PacDataColors = new string[] + { + ColorWhite, + ColorGreen, + ColorBlue, + ColorCyan, + ColorRed, + ColorYellow, + ColorMagenta, + ColorBlack, + ColorTransparent + }; + + public const int ScreenRowCount = 15; + public const int ScreenColCount = 32; + + + public static Dictionary ExtendedCharCodes = new Dictionary + { + { 0x2a, 0xe1 }, // lowercase a, acute accent + { 0x5c, 0xe9 }, // lowercase e, acute accent + { 0x5e, 0xed }, // lowercase i, acute accent + { 0x5f, 0xf3 }, // lowercase o, acute accent + { 0x60, 0xfa }, // lowercase u, acute accent + { 0x7b, 0xe7 }, // lowercase c with cedilla + { 0x7c, 0xf7 }, // division symbol + { 0x7d, 0xd1 }, // uppercase N tilde + { 0x7e, 0xf1 }, // lowercase n tilde + { 0x7f, 0x2588 }, // Full block + // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS + // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F + // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES + { 0x80, 0xae }, // Registered symbol (R) + { 0x81, 0xb0 }, // degree sign + { 0x82, 0xbd }, // 1/2 symbol + { 0x83, 0xbf }, // Inverted (open) question mark + { 0x84, 0x2122 }, // Trademark symbol (TM) + { 0x85, 0xa2 }, // Cents symbol + { 0x86, 0xa3 }, // Pounds sterling + { 0x87, 0x266a }, // Music 8'th note + { 0x88, 0xe0 }, // lowercase a, grave accent + { 0x89, 0x20 }, // transparent space (regular) + { 0x8a, 0xe8 }, // lowercase e, grave accent + { 0x8b, 0xe2 }, // lowercase a, circumflex accent + { 0x8c, 0xea }, // lowercase e, circumflex accent + { 0x8d, 0xee }, // lowercase i, circumflex accent + { 0x8e, 0xf4 }, // lowercase o, circumflex accent + { 0x8f, 0xfb }, // lowercase u, circumflex accent + // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS + // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F + { 0x90, 0xc1 }, // capital letter A with acute + { 0x91, 0xc9 }, // capital letter E with acute + { 0x92, 0xd3 }, // capital letter O with acute + { 0x93, 0xda }, // capital letter U with acute + { 0x94, 0xdc }, // capital letter U with diaresis + { 0x95, 0xfc }, // lowercase letter U with diaeresis + { 0x96, 0x2018 }, // opening single quote + { 0x97, 0xa1 }, // inverted exclamation mark + { 0x98, 0x2a }, // asterisk + { 0x99, 0x2019 }, // closing single quote + { 0x9a, 0x2501 }, // box drawings heavy horizontal + { 0x9b, 0xa9 }, // copyright sign + { 0x9c, 0x2120 }, // Service mark + { 0x9d, 0x2022 }, // (round) bullet + { 0x9e, 0x201c }, // Left double quotation mark + { 0x9f, 0x201d }, // Right double quotation mark + { 0xa0, 0xc0 }, // uppercase A, grave accent + { 0xa1, 0xc2 }, // uppercase A, circumflex + { 0xa2, 0xc7 }, // uppercase C with cedilla + { 0xa3, 0xc8 }, // uppercase E, grave accent + { 0xa4, 0xca }, // uppercase E, circumflex + { 0xa5, 0xcb }, // capital letter E with diaresis + { 0xa6, 0xeb }, // lowercase letter e with diaresis + { 0xa7, 0xce }, // uppercase I, circumflex + { 0xa8, 0xcf }, // uppercase I, with diaresis + { 0xa9, 0xef }, // lowercase i, with diaresis + { 0xaa, 0xd4 }, // uppercase O, circumflex + { 0xab, 0xd9 }, // uppercase U, grave accent + { 0xac, 0xf9 }, // lowercase u, grave accent + { 0xad, 0xdb }, // uppercase U, circumflex + { 0xae, 0xab }, // left-pointing double angle quotation mark + { 0xaf, 0xbb }, // right-pointing double angle quotation mark + // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS + // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F + { 0xb0, 0xc3 }, // Uppercase A, tilde + { 0xb1, 0xe3 }, // Lowercase a, tilde + { 0xb2, 0xcd }, // Uppercase I, acute accent + { 0xb3, 0xcc }, // Uppercase I, grave accent + { 0xb4, 0xec }, // Lowercase i, grave accent + { 0xb5, 0xd2 }, // Uppercase O, grave accent + { 0xb6, 0xf2 }, // Lowercase o, grave accent + { 0xb7, 0xd5 }, // Uppercase O, tilde + { 0xb8, 0xf5 }, // Lowercase o, tilde + { 0xb9, 0x7b }, // Open curly brace + { 0xba, 0x7d }, // Closing curly brace + { 0xbb, 0x5c }, // Backslash + { 0xbc, 0x5e }, // Caret + { 0xbd, 0x5f }, // Underscore + { 0xbe, 0x7c }, // Pipe (vertical line) + { 0xbf, 0x223c }, // Tilde operator + { 0xc0, 0xc4 }, // Uppercase A, umlaut + { 0xc1, 0xe4 }, // Lowercase A, umlaut + { 0xc2, 0xd6 }, // Uppercase O, umlaut + { 0xc3, 0xf6 }, // Lowercase o, umlaut + { 0xc4, 0xdf }, // Esszett (sharp S) + { 0xc5, 0xa5 }, // Yen symbol + { 0xc6, 0xa4 }, // Generic currency sign + { 0xc7, 0x2503 }, // Box drawings heavy vertical + { 0xc8, 0xc5 }, // Uppercase A, ring + { 0xc9, 0xe5 }, // Lowercase A, ring + { 0xca, 0xd8 }, // Uppercase O, stroke + { 0xcb, 0xf8 }, // Lowercase o, strok + { 0xcc, 0x250f }, // Box drawings heavy down and right + { 0xcd, 0x2513 }, // Box drawings heavy down and left + { 0xce, 0x2517 }, // Box drawings heavy up and right + { 0xcf, 0x251b } // Box drawings heavy up and left + }; + + public static Dictionary Channel1RowsMap = new Dictionary + { + { 0x11, 1 }, + { 0x12, 3 }, + { 0x15, 5 }, + { 0x16, 7 }, + { 0x17, 9 }, + { 0x10, 11 }, + { 0x13, 12 }, + { 0x14, 14 } + }; + + public static Dictionary Channel2RowsMap = new Dictionary + { + { 0x19, 1 }, + { 0x1A, 3 }, + { 0x1D, 5 }, + { 0x1E, 7 }, + { 0x1F, 9 }, + { 0x18, 11 }, + { 0x1B, 12 }, + { 0x1C, 14 } + }; + } +} diff --git a/src/libse/Cea608/DataOutput.cs b/src/libse/Cea608/DataOutput.cs new file mode 100644 index 000000000..e12d6729f --- /dev/null +++ b/src/libse/Cea608/DataOutput.cs @@ -0,0 +1,11 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class DataOutput + { + public int Channel { get; set; } + public bool Roll { get; set; } + public int End { get; set; } + public int Start { get; set; } + public SerializedRow[] Screen { get; set; } + } +} diff --git a/src/libse/Cea608/PacData.cs b/src/libse/Cea608/PacData.cs new file mode 100644 index 000000000..d8f5b8db1 --- /dev/null +++ b/src/libse/Cea608/PacData.cs @@ -0,0 +1,9 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class PacData: SerializedPenState + { + public int Row { get; set; } + public string Color { get; set; } + public int? Indent { get; set; } + } +} diff --git a/src/libse/Cea608/PenState.cs b/src/libse/Cea608/PenState.cs new file mode 100644 index 000000000..c42346500 --- /dev/null +++ b/src/libse/Cea608/PenState.cs @@ -0,0 +1,93 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class PenState + { + public string Foreground { get; set; } = Constants.ColorWhite; + public bool Underline; + public bool Italics { get; set; } + public string Background { get; set; } = Constants.ColorBlack; + public bool Flash { get; set; } + + public PenState() + { + } + + public PenState(string foreground, bool underline, bool italics, string background, bool flash1) + { + Foreground = foreground; + Underline = underline; + Italics = italics; + Background = background; + Flash = flash1; + } + + public void Reset() + { + Foreground = Constants.ColorWhite; + Underline = false; + Italics = false; + Background = Constants.ColorBlack; + Flash = false; + } + + public CcStyle Serialize() + { + return new CcStyle + { + + Foreground = Foreground, + Underline = Underline, + Italics = Italics, + Flash = Flash, + Background = Background + }; + } + + public void Copy(PenState newPenState) + { + Foreground = newPenState.Foreground; + Underline = newPenState.Underline; + Italics = newPenState.Italics; + Background = newPenState.Background; + Flash = newPenState.Flash; + } + + public bool Equals(PenState other) + { + return Foreground == other.Foreground && + Underline == other.Underline && + Italics == other.Italics && + Background == other.Background && + Flash == other.Flash; + } + + public bool IsDefault() + { + return Foreground == Constants.ColorWhite && !Underline && !Italics && Background == Constants.ColorBlack && !Flash; + } + + public void SetStyles(SerializedPenState styles) + { + if (styles.Foreground != null) + { + Foreground = styles.Foreground; + } + if (styles.Underline != null) + { + Underline = styles.Underline.Value; + } + if (styles.Background != null) + { + Background = styles.Background; + } + if (styles.Flash != null) + { + Flash = styles.Flash.Value; + } + if (styles.Italics != null) + { + Italics = styles.Italics.Value; + } + } + } +} diff --git a/src/libse/Cea608/SerializedPenState.cs b/src/libse/Cea608/SerializedPenState.cs new file mode 100644 index 000000000..0c00d8a1f --- /dev/null +++ b/src/libse/Cea608/SerializedPenState.cs @@ -0,0 +1,11 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class SerializedPenState + { + public string Foreground { get; set; } + public bool? Underline { get; set; } + public bool? Italics { get; set; } + public string Background { get; set; } + public bool? Flash { get; set; } + } +} diff --git a/src/libse/Cea608/SerializedRow.cs b/src/libse/Cea608/SerializedRow.cs new file mode 100644 index 000000000..26692e634 --- /dev/null +++ b/src/libse/Cea608/SerializedRow.cs @@ -0,0 +1,15 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class SerializedRow + { + public int Row { get; set; } + + /// + /// Column indentation. + /// + public int Position { get; set; } + + public CcStyle Style { get; set; } + public SerializedStyledUnicodeChar[] Columns { get; set; } + } +} diff --git a/src/libse/Cea608/SerializedStyledUnicodeChar.cs b/src/libse/Cea608/SerializedStyledUnicodeChar.cs new file mode 100644 index 000000000..c21447e7c --- /dev/null +++ b/src/libse/Cea608/SerializedStyledUnicodeChar.cs @@ -0,0 +1,8 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class SerializedStyledUnicodeChar + { + public CcStyle Style { get; set; } + public string Character { get; set; } + } +} diff --git a/src/libse/Cea608/StyledUnicodeChar.cs b/src/libse/Cea608/StyledUnicodeChar.cs new file mode 100644 index 000000000..ca8fe94f8 --- /dev/null +++ b/src/libse/Cea608/StyledUnicodeChar.cs @@ -0,0 +1,52 @@ +namespace Nikse.SubtitleEdit.Core.Cea608 +{ + public class StyledUnicodeChar + { + public PenState PenState { get; set; } + + public string Uchar { get; set; } = Constants.EmptyChar; + public string Foreground { get; set; } + public bool? Underline { get; set; } + public bool? Italics { get; set; } + public string Background { get; set; } + public bool? Flash { get; set; } + + public StyledUnicodeChar() + { + PenState = new PenState(null, false, false, null, false); + } + + public void Reset() + { + Uchar = Constants.EmptyChar; + PenState.Reset(); + } + + public void SetChar(string uchar, PenState newPenState) + { + Uchar = uchar; + PenState.Copy(newPenState); + } + + public void SetPenState(PenState newPenState) + { + PenState.Copy(newPenState); + } + + public bool Equals(StyledUnicodeChar other) + { + return Uchar == other.Uchar && PenState.Equals(other.PenState); + } + + public void Copy(StyledUnicodeChar newChar) + { + Uchar = newChar.Uchar; + PenState.Copy(newChar.PenState); + } + + public bool IsEmpty() + { + return Uchar == Constants.EmptyChar && PenState.IsDefault(); + } + } +}