mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-10-27 14:32:35 +01:00
Merge branch 'SubtitleEdit:main' into master
This commit is contained in:
commit
e02778b9c3
186
src/libse/Cea608/CaptionScreen.cs
Normal file
186
src/libse/Cea608/CaptionScreen.cs
Normal file
@ -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<SerializedRow>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
17
src/libse/Cea608/CcData.cs
Normal file
17
src/libse/Cea608/CcData.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Nikse.SubtitleEdit.Core.Cea608
|
||||
{
|
||||
public class CcData
|
||||
{
|
||||
public CcData(int type, int data1, int data2)
|
||||
{
|
||||
Type = type;
|
||||
Data1 = data1;
|
||||
Data2 = data2;
|
||||
}
|
||||
|
||||
public int Type { get; set; }
|
||||
public int Data1 { get; set; }
|
||||
public int Data2 { get; set; }
|
||||
public ulong Time { get; set; }
|
||||
}
|
||||
}
|
289
src/libse/Cea608/CcDataC608Parser.cs
Normal file
289
src/libse/Cea608/CcDataC608Parser.cs
Normal file
@ -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>();
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
167
src/libse/Cea608/CcRow.cs
Normal file
167
src/libse/Cea608/CcRow.cs
Normal file
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Unicode Character from CEA-608 byte code.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
11
src/libse/Cea608/CcStyle.cs
Normal file
11
src/libse/Cea608/CcStyle.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
365
src/libse/Cea608/Cea608Channel.cs
Normal file
365
src/libse/Cea608/Cea608Channel.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume Caption Loading (switch mode to Pop On).
|
||||
/// </summary>
|
||||
public void cc_RCL()
|
||||
{
|
||||
SetMode("MODE_POP-ON");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BackSpace.
|
||||
/// </summary>
|
||||
public void cc_BS()
|
||||
{
|
||||
if (Mode == "MODE_TEXT")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteScreen.BackSpace();
|
||||
if (WriteScreen == DisplayedMemory)
|
||||
{
|
||||
OutputDataUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserved (formerly Alarm Off).
|
||||
/// </summary>
|
||||
public void cc_AOF()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserved (formerly Alarm On).
|
||||
/// </summary>
|
||||
public void cc_AON()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete to End of Row.
|
||||
/// </summary>
|
||||
public void cc_DER()
|
||||
{
|
||||
WriteScreen.ClearToEndOfRow();
|
||||
OutputDataUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Roll-Up Captions-2,3,or 4 Rows.
|
||||
/// </summary>
|
||||
public void cc_RU(int nrRows)
|
||||
{
|
||||
WriteScreen = DisplayedMemory;
|
||||
SetMode("MODE_ROLL-UP");
|
||||
WriteScreen.SetRollUpRows(nrRows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flash On.
|
||||
/// </summary>
|
||||
public void cc_FON()
|
||||
{
|
||||
WriteScreen.SetPen(new PacData { Flash = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume Direct Captioning (switch mode to PaintOn).
|
||||
/// </summary>
|
||||
public void cc_RDC()
|
||||
{
|
||||
SetMode("MODE_PAINT-ON");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text Restart in text mode (not supported, however).
|
||||
/// </summary>
|
||||
public void cc_TR()
|
||||
{
|
||||
SetMode("MODE_TEXT");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume Text Display in Text mode (not supported, however)
|
||||
/// </summary>
|
||||
public void cc_RTD()
|
||||
{
|
||||
SetMode("MODE_TEXT");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erase Displayed Memory.
|
||||
/// </summary>
|
||||
public void cc_EDM()
|
||||
{
|
||||
DisplayedMemory.Reset();
|
||||
OutputDataUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Carriage Return.
|
||||
/// </summary>
|
||||
public void cc_CR()
|
||||
{
|
||||
WriteScreen.RollUp();
|
||||
OutputDataUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erase Non-Displayed Memory.
|
||||
/// </summary>
|
||||
public void cc_ENM()
|
||||
{
|
||||
NonDisplayedMemory.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of Caption (Flip Memories).
|
||||
/// </summary>
|
||||
public void cc_EOC()
|
||||
{
|
||||
if (Mode == "MODE_POP-ON")
|
||||
{
|
||||
(DisplayedMemory, NonDisplayedMemory) = (NonDisplayedMemory, DisplayedMemory);
|
||||
WriteScreen = NonDisplayedMemory;
|
||||
}
|
||||
|
||||
OutputDataUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tab Offset 1,2, or 3 columns
|
||||
/// </summary>
|
||||
public void cc_TO(int nrCols)
|
||||
{
|
||||
WriteScreen.MoveCursor(nrCols);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse MIDROW command
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
161
src/libse/Cea608/Constants.cs
Normal file
161
src/libse/Cea608/Constants.cs
Normal file
@ -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<int, int> ExtendedCharCodes = new Dictionary<int, int>
|
||||
{
|
||||
{ 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<int, int> Channel1RowsMap = new Dictionary<int, int>
|
||||
{
|
||||
{ 0x11, 1 },
|
||||
{ 0x12, 3 },
|
||||
{ 0x15, 5 },
|
||||
{ 0x16, 7 },
|
||||
{ 0x17, 9 },
|
||||
{ 0x10, 11 },
|
||||
{ 0x13, 12 },
|
||||
{ 0x14, 14 }
|
||||
};
|
||||
|
||||
public static Dictionary<int, int> Channel2RowsMap = new Dictionary<int, int>
|
||||
{
|
||||
{ 0x19, 1 },
|
||||
{ 0x1A, 3 },
|
||||
{ 0x1D, 5 },
|
||||
{ 0x1E, 7 },
|
||||
{ 0x1F, 9 },
|
||||
{ 0x18, 11 },
|
||||
{ 0x1B, 12 },
|
||||
{ 0x1C, 14 }
|
||||
};
|
||||
}
|
||||
}
|
11
src/libse/Cea608/DataOutput.cs
Normal file
11
src/libse/Cea608/DataOutput.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
132
src/libse/Cea608/GetCcDataHelper.cs
Normal file
132
src/libse/Cea608/GetCcDataHelper.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Cea608
|
||||
{
|
||||
public static class GetCcDataHelper
|
||||
{
|
||||
public static List<CcData> GetCcData(Stream fs, uint startPos, ulong size)
|
||||
{
|
||||
var fieldData = new List<CcData>();
|
||||
for (var i = startPos; i < startPos + size - 5; i++)
|
||||
{
|
||||
var buffer = new byte[4];
|
||||
fs.Seek(i, SeekOrigin.Begin);
|
||||
fs.Read(buffer, 0, buffer.Length);
|
||||
var nalSize = GetUInt32(buffer, 0);
|
||||
var flag = fs.ReadByte();
|
||||
if (IsRbspNalUnitType(flag & 0x1F))
|
||||
{
|
||||
//parseCCDataFromSEI(getSEIData(raw, i + 5, i + nalSize + 3), fieldData);
|
||||
var seiData = GetSeiData(fs, i + 5, i + nalSize + 3);
|
||||
ParseCcDataFromSei(seiData, fieldData);
|
||||
}
|
||||
|
||||
i += nalSize + 3;
|
||||
}
|
||||
|
||||
return fieldData;
|
||||
}
|
||||
|
||||
private static bool IsRbspNalUnitType(int unitType)
|
||||
{
|
||||
return unitType == 0x06;
|
||||
}
|
||||
|
||||
private static byte[] GetSeiData(Stream fs, uint startPos, uint endPos)
|
||||
{
|
||||
var data = new List<byte>();
|
||||
var buffer = new byte[endPos - startPos];
|
||||
fs.Seek(startPos, SeekOrigin.Begin);
|
||||
fs.Read(buffer, 0, buffer.Length);
|
||||
|
||||
for (var x = startPos; x < endPos; x++)
|
||||
{
|
||||
var idx = x - startPos;
|
||||
|
||||
if (x + 2 < endPos && buffer[idx] == 0x00 && buffer[idx + 1] == 0x00 && buffer[idx + 2] == 0x03)
|
||||
{
|
||||
data.Add(0x00);
|
||||
data.Add(0x00);
|
||||
x += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Add(buffer[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
private static void ParseCcDataFromSei(byte[] buffer, List<CcData> fieldData)
|
||||
{
|
||||
var x = 0;
|
||||
while (x < buffer.Length)
|
||||
{
|
||||
var payloadType = 0;
|
||||
var payloadSize = 0;
|
||||
int now;
|
||||
|
||||
do
|
||||
{
|
||||
now = buffer[x++];
|
||||
payloadType += now;
|
||||
} while (now == 0xFF);
|
||||
|
||||
do
|
||||
{
|
||||
now = buffer[x++];
|
||||
payloadSize += now;
|
||||
} while (now == 0xFF);
|
||||
|
||||
if (IsStartOfCcDataHeader(payloadType, buffer, x))
|
||||
{
|
||||
var pos = x + 10;
|
||||
var ccCount = pos + (buffer[pos - 2] & 0x1F) * 3;
|
||||
for (var i = pos; i < ccCount; i += 3)
|
||||
{
|
||||
var b = buffer[i];
|
||||
if ((b & 0x4) > 0)
|
||||
{
|
||||
var ccType = b & 0x3;
|
||||
if (IsCcType(ccType))
|
||||
{
|
||||
var ccData1 = buffer[i + 1];
|
||||
var ccData2 = buffer[i + 2];
|
||||
if (IsNonEmptyCcData(ccData1, ccData2))
|
||||
{
|
||||
fieldData.Add(new CcData(ccType, ccData1, ccData2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += payloadSize;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCcType(int type)
|
||||
{
|
||||
return type == 0 || type == 1;
|
||||
}
|
||||
|
||||
private static bool IsNonEmptyCcData(int ccData1, int ccData2)
|
||||
{
|
||||
return (ccData1 & 0x7f) > 0 || (ccData2 & 0x7f) > 0;
|
||||
}
|
||||
|
||||
private static bool IsStartOfCcDataHeader(int payloadType, byte[] buffer, int pos)
|
||||
{
|
||||
return payloadType == 4 &&
|
||||
GetUInt32(buffer, pos) == 3036688711 &&
|
||||
GetUInt32(buffer, pos + 4) == 1094267907;
|
||||
}
|
||||
|
||||
private static uint GetUInt32(byte[] buffer, int pos)
|
||||
{
|
||||
return (uint)((buffer[pos + 0] << 24) + (buffer[pos + 1] << 16) + (buffer[pos + 2] << 8) + buffer[pos + 3]);
|
||||
}
|
||||
}
|
||||
}
|
9
src/libse/Cea608/PacData.cs
Normal file
9
src/libse/Cea608/PacData.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
93
src/libse/Cea608/PenState.cs
Normal file
93
src/libse/Cea608/PenState.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/libse/Cea608/SerializedPenState.cs
Normal file
11
src/libse/Cea608/SerializedPenState.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
15
src/libse/Cea608/SerializedRow.cs
Normal file
15
src/libse/Cea608/SerializedRow.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Nikse.SubtitleEdit.Core.Cea608
|
||||
{
|
||||
public class SerializedRow
|
||||
{
|
||||
public int Row { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Column indentation.
|
||||
/// </summary>
|
||||
public int Position { get; set; }
|
||||
|
||||
public CcStyle Style { get; set; }
|
||||
public SerializedStyledUnicodeChar[] Columns { get; set; }
|
||||
}
|
||||
}
|
8
src/libse/Cea608/SerializedStyledUnicodeChar.cs
Normal file
8
src/libse/Cea608/SerializedStyledUnicodeChar.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Nikse.SubtitleEdit.Core.Cea608
|
||||
{
|
||||
public class SerializedStyledUnicodeChar
|
||||
{
|
||||
public CcStyle Style { get; set; }
|
||||
public string Character { get; set; }
|
||||
}
|
||||
}
|
52
src/libse/Cea608/StyledUnicodeChar.cs
Normal file
52
src/libse/Cea608/StyledUnicodeChar.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
{
|
||||
public byte[] Buffer;
|
||||
public ulong Position;
|
||||
public ulong StartPosition;
|
||||
public string Name;
|
||||
public ulong Size;
|
||||
|
||||
@ -21,6 +22,11 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
return (uint)((Buffer[index] << 24) + (Buffer[index + 1] << 16) + (Buffer[index + 2] << 8) + Buffer[index + 3]);
|
||||
}
|
||||
|
||||
public int GetInt(int index)
|
||||
{
|
||||
return (int)((Buffer[index] << 24) + (Buffer[index + 1] << 16) + (Buffer[index + 2] << 8) + Buffer[index + 3]);
|
||||
}
|
||||
|
||||
public ulong GetUInt64(int index)
|
||||
{
|
||||
return (ulong)Buffer[index] << 56 | (ulong)Buffer[index + 1] << 48 | (ulong)Buffer[index + 2] << 40 | (ulong)Buffer[index + 3] << 32 |
|
||||
@ -60,6 +66,11 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
|
||||
internal bool InitializeSizeAndName(System.IO.Stream fs)
|
||||
{
|
||||
if (StartPosition == 0)
|
||||
{
|
||||
StartPosition = (ulong)fs.Position -8;
|
||||
}
|
||||
|
||||
Buffer = new byte[8];
|
||||
var bytesRead = fs.Read(Buffer, 0, Buffer.Length);
|
||||
if (bytesRead < Buffer.Length)
|
||||
|
@ -30,11 +30,11 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
BaseMediaDecodeTime = GetUInt(8);
|
||||
BaseMediaDecodeTime = GetUInt64(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseMediaDecodeTime = GetUInt64(8);
|
||||
BaseMediaDecodeTime = GetUInt(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
{
|
||||
Tfhd = new Tfhd(fs, Size);
|
||||
}
|
||||
else if (Name == "tfdt")
|
||||
{
|
||||
Tfdt = new Tfdt(fs, Size);
|
||||
}
|
||||
|
||||
fs.Seek((long)Position, SeekOrigin.Begin);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
public class Trun : Box
|
||||
{
|
||||
public List<TimeSegment> Samples { get; set; }
|
||||
public uint? DataOffset { get; set; }
|
||||
|
||||
public Trun(Stream fs, ulong maximumLength)
|
||||
{
|
||||
@ -28,6 +29,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
}
|
||||
|
||||
var versionAndFlags = GetUInt(0);
|
||||
var version = versionAndFlags >> 24;
|
||||
var flags = versionAndFlags & 0xFFFFFF;
|
||||
var sampleCount = GetUInt(4);
|
||||
if (sampleCount == 0)
|
||||
@ -35,6 +37,18 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
return;
|
||||
}
|
||||
|
||||
if ((flags & 0x1) > 0)
|
||||
{
|
||||
Buffer = new byte[4];
|
||||
readCount = fs.Read(Buffer, 0, Buffer.Length);
|
||||
if (readCount < Buffer.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DataOffset = GetUInt(0);
|
||||
}
|
||||
|
||||
var sampleLength = Math.Min(sampleCount * 16 + 24, maximumLength);
|
||||
Buffer = new byte[sampleLength];
|
||||
readCount = fs.Read(Buffer, 0, Buffer.Length);
|
||||
@ -45,11 +59,6 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
|
||||
var pos = 0;
|
||||
|
||||
if ((flags & 0x000001) > 0)
|
||||
{
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// skip "first_sample_flags" if present
|
||||
if ((flags & 0x000004) > 0)
|
||||
{
|
||||
@ -99,7 +108,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4.Boxes
|
||||
// read "sample_time_offset" if present
|
||||
if ((flags & 0x000800) > 0)
|
||||
{
|
||||
sample.TimeOffset = GetUInt(pos);
|
||||
sample.TimeOffset = version == 1 ? GetInt(pos) : (long)GetUInt(pos);
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4
|
||||
|
||||
if (presentation.Duration.HasValue)
|
||||
{
|
||||
var startTime = presentation.TimeOffset.HasValue ? presentation.BaseMediaDecodeTime + presentation.TimeOffset.Value : presentation.BaseMediaDecodeTime;
|
||||
var startTime = presentation.TimeOffset.HasValue ? (ulong)((long)presentation.BaseMediaDecodeTime + presentation.TimeOffset.Value) : presentation.BaseMediaDecodeTime;
|
||||
var currentTime = startTime + presentation.Duration.Value;
|
||||
|
||||
// The payload can be null as that would mean that it was a VTTE and
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Nikse.SubtitleEdit.Core.Cea608;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4
|
||||
{
|
||||
@ -21,6 +22,9 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4
|
||||
public Subtitle VttcSubtitle { get; private set; }
|
||||
public string VttcLanguage { get; private set; }
|
||||
|
||||
public Subtitle TrunCea608Subtitle { get; private set; }
|
||||
private List<Cea608.CcData> _trunCea608CcData = new List<Cea608.CcData>();
|
||||
|
||||
public List<Trak> GetSubtitleTracks()
|
||||
{
|
||||
var list = new List<Trak>();
|
||||
@ -161,6 +165,36 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4
|
||||
else if (Name == "moof")
|
||||
{
|
||||
Moof = new Moof(fs, Position);
|
||||
|
||||
if (Moof.Traf?.Trun?.DataOffset != null && Moof.Traf.Tfdt != null)
|
||||
{
|
||||
var dts = Moof.Traf.Tfdt.BaseMediaDecodeTime;
|
||||
var startPosition = (uint)(Moof.StartPosition + Moof.Traf.Trun.DataOffset.Value);
|
||||
for (var index = 0; index < Moof.Traf.Trun.Samples.Count; index++)
|
||||
{
|
||||
var sample = Moof.Traf.Trun.Samples[index];
|
||||
if (sample.Size.HasValue)
|
||||
{
|
||||
var ccData = GetCcDataHelper.GetCcData(fs, startPosition, sample.Size.Value);
|
||||
if (ccData.Count > 0)
|
||||
{
|
||||
if (sample.TimeOffset.HasValue)
|
||||
{
|
||||
ccData[0].Time = (ulong)((long)dts + sample.TimeOffset.Value);
|
||||
}
|
||||
|
||||
_trunCea608CcData.Add(ccData[0]); //TODO: can there be more than one?
|
||||
}
|
||||
|
||||
startPosition += sample.Size.Value;
|
||||
}
|
||||
|
||||
if (sample.Duration.HasValue)
|
||||
{
|
||||
dts += sample.Duration.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Name == "mdat" && Moof != null && Moof?.Traf?.Trun?.Samples?.Count > 0)
|
||||
{
|
||||
@ -216,6 +250,52 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Mp4
|
||||
var merged = MergeLinesSameTextUtils.MergeLinesWithSameTextInSubtitle(VttcSubtitle, false, 250);
|
||||
VttcSubtitle = merged;
|
||||
}
|
||||
|
||||
CheckForTrunCea608();
|
||||
}
|
||||
|
||||
private void CheckForTrunCea608()
|
||||
{
|
||||
try
|
||||
{
|
||||
TrunCea608Subtitle = new Subtitle();
|
||||
var sortedData = _trunCea608CcData.OrderBy(p => p.Time).ToList();
|
||||
var parser = new CcDataC608Parser();
|
||||
parser.DisplayScreen += DisplayScreen;
|
||||
foreach (var cc in sortedData)
|
||||
{
|
||||
parser.AddData((int)cc.Time, new[] { cc.Data1, cc.Data2 });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SeLogger.Error(e, "Error while parsing MP4 TRUN CEA 608");
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayScreen(DataOutput data)
|
||||
{
|
||||
var timeScale = Moov?.Mvhd?.TimeScale ?? 1000.0;
|
||||
var startMs = data.Start / timeScale * 1000.0;
|
||||
var endMs = data.End / timeScale * 1000.0;
|
||||
var p = new Paragraph(GetText(data.Screen), startMs, endMs);
|
||||
TrunCea608Subtitle.Paragraphs.Add(p);
|
||||
}
|
||||
|
||||
private static string GetText(SerializedRow[] dataScreen)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var row in dataScreen)
|
||||
{
|
||||
foreach (var column in row.Columns)
|
||||
{
|
||||
sb.Append(column.Character);
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
private void ReadVttWithSize(Mdat mdat, List<TimeSegment> trunSamples, ref double timeTotalMs)
|
||||
|
@ -4,7 +4,7 @@
|
||||
{
|
||||
public uint? Duration { get; set; }
|
||||
public uint? Size { get; set; }
|
||||
public uint? TimeOffset { get; set; }
|
||||
public long? TimeOffset { get; set; }
|
||||
public ulong BaseMediaDecodeTime { get; set; }
|
||||
}
|
||||
}
|
@ -1198,7 +1198,7 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
|
||||
if (Settings.RemoveIfOnlyMusicSymbols)
|
||||
{
|
||||
if (text.Replace('♪', ' ').Replace('♫', ' ').Trim().Length == 0)
|
||||
if (string.IsNullOrWhiteSpace(HtmlUtil.RemoveHtmlTags(text, true).RemoveChar('♪', '♫')))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
@ -133,15 +133,24 @@ namespace Nikse.SubtitleEdit.Forms.AudioToText
|
||||
public static void InitializeWhisperEngines(NikseComboBox cb)
|
||||
{
|
||||
cb.Items.Clear();
|
||||
|
||||
var is64BitOs = IntPtr.Size * 8 == 64;
|
||||
if (!is64BitOs)
|
||||
{
|
||||
cb.Items.Add(WhisperChoice.Cpp);
|
||||
cb.SelectedIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var engines = new List<string> { WhisperChoice.OpenAi };
|
||||
if (Configuration.IsRunningOnWindows && IntPtr.Size * 8 == 64)
|
||||
if (Configuration.IsRunningOnWindows)
|
||||
{
|
||||
engines.Add(WhisperChoice.PurfviewFasterWhisper);
|
||||
engines.Add(WhisperChoice.ConstMe);
|
||||
}
|
||||
engines.Add(WhisperChoice.Cpp);
|
||||
engines.Add(WhisperChoice.CTranslate2);
|
||||
// engines.Add(WhisperChoice.StableTs);
|
||||
engines.Add(WhisperChoice.StableTs);
|
||||
engines.Add(WhisperChoice.WhisperX);
|
||||
|
||||
foreach (var engine in engines)
|
||||
|
@ -15599,6 +15599,23 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mp4Parser.TrunCea608Subtitle?.Paragraphs.Count > 0)
|
||||
{
|
||||
MakeHistoryForUndo(_language.BeforeImportFromMatroskaFile);
|
||||
_subtitleListViewIndex = -1;
|
||||
FileNew();
|
||||
_subtitle = mp4Parser.TrunCea608Subtitle;
|
||||
UpdateSourceView();
|
||||
SubtitleListview1.Fill(_subtitle, _subtitleOriginal);
|
||||
_subtitleListViewIndex = -1;
|
||||
SubtitleListview1.FirstVisibleIndex = -1;
|
||||
SubtitleListview1.SelectIndexAndEnsureVisible(0, true);
|
||||
_fileName = Utilities.GetPathAndFileNameWithoutExtension(fileName) + GetCurrentSubtitleFormat().Extension;
|
||||
_converted = true;
|
||||
SetTitle();
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageBox.Show(_language.NoSubtitlesFound);
|
||||
return false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user