mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-22 11:12:36 +01:00
More EBU updates (incl. save code)
git-svn-id: https://subtitleedit.googlecode.com/svn/trunk@67 99eadd0c-20b8-1223-b5c4-2a2b2df33de2
This commit is contained in:
parent
af92ccef80
commit
876fb12492
@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||||
{
|
{
|
||||||
@ -63,6 +64,35 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
{
|
{
|
||||||
CodePageNumber = "437";
|
CodePageNumber = "437";
|
||||||
DiskFormatCode = "STL25.01";
|
DiskFormatCode = "STL25.01";
|
||||||
|
DisplayStandardCode = "0";
|
||||||
|
CharacterCodeTableNumber = "00";
|
||||||
|
LanguageCode = "0A";
|
||||||
|
OriginalProgrammeTitle = "No Title ";
|
||||||
|
OriginalEpisodeTitle = " ";
|
||||||
|
TranslatedProgrammeTitle = string.Empty.PadLeft(32, ' ');
|
||||||
|
TranslatedEpisodeTitle = string.Empty.PadLeft(32, ' ');
|
||||||
|
TranslatorsName = string.Empty.PadLeft(32, ' ');
|
||||||
|
TranslatorsContactDetails = string.Empty.PadLeft(32, ' ');
|
||||||
|
SubtitleListReferenceCode = "0 ";
|
||||||
|
CreationDate = "101021";
|
||||||
|
RevisionDate = "101021";
|
||||||
|
RevisionNumber = "01";
|
||||||
|
TotalNumberOfTextAndTimingInformationBlocks = "00725";
|
||||||
|
TotalNumberOfSubtitles = "00725";
|
||||||
|
TotalNumberOfSubtitleGroups = "001";
|
||||||
|
MaximumNumberOfDisplayableCharactersInAnyTextRow = "40";
|
||||||
|
MaximumNumberOfDisplayableRows = "23";
|
||||||
|
TimeCodeStatus = "1";
|
||||||
|
TimeCodeStartOfProgramme = "00000000";
|
||||||
|
TimeCodeFirstInCue = "00000001";
|
||||||
|
TotalNumberOfDisks = "1";
|
||||||
|
DiskSequenceNumber = "1";
|
||||||
|
CountryOfOrigin = "USA";
|
||||||
|
Publisher = string.Empty.PadLeft(32, ' ');
|
||||||
|
EditorsName = string.Empty.PadLeft(32, ' ');
|
||||||
|
EditorsContactDetails = string.Empty.PadLeft(32, ' ');
|
||||||
|
SpareBytes = string.Empty.PadLeft(75, ' ');
|
||||||
|
UserDefinedArea = string.Empty.PadLeft(576, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -99,8 +129,11 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
EditorsContactDetails +
|
EditorsContactDetails +
|
||||||
SpareBytes +
|
SpareBytes +
|
||||||
UserDefinedArea;
|
UserDefinedArea;
|
||||||
|
if (result.Length == 1024)
|
||||||
return result;
|
return result;
|
||||||
|
throw new Exception("Length must be 1024");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -125,7 +158,17 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
public byte CommentFlag { get; set; }
|
public byte CommentFlag { get; set; }
|
||||||
public string TextField { get; set; }
|
public string TextField { get; set; }
|
||||||
|
|
||||||
internal byte[] GetBytes()
|
public EbuTextTimingInformation()
|
||||||
|
{
|
||||||
|
SubtitleGroupNumber = 1;
|
||||||
|
ExtensionBlockNumber = 255;
|
||||||
|
CumulativeStatus = 0;
|
||||||
|
VerticalPosition = 0;
|
||||||
|
JustificationCode = 20;
|
||||||
|
CommentFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal byte[] GetBytes(EbuGeneralSubtitleInformation header)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[128]; // Text and Timing Information (TTI) block consists of 128 bytes
|
byte[] buffer = new byte[128]; // Text and Timing Information (TTI) block consists of 128 bytes
|
||||||
|
|
||||||
@ -136,16 +179,184 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
buffer[3] = ExtensionBlockNumber;
|
buffer[3] = ExtensionBlockNumber;
|
||||||
buffer[4] = CumulativeStatus;
|
buffer[4] = CumulativeStatus;
|
||||||
|
|
||||||
//TODO: time codes
|
buffer[5] = (byte)TimeCodeInHours;
|
||||||
|
buffer[6] = (byte)TimeCodeInMinutes;
|
||||||
|
buffer[7] = (byte)TimeCodeInSeconds;
|
||||||
|
buffer[8] = GetFrameFromMilliseconds(TimeCodeInMilliseconds);
|
||||||
|
|
||||||
|
buffer[9] = (byte)TimeCodeOutHours;
|
||||||
|
buffer[10] = (byte)TimeCodeOutMinutes;
|
||||||
|
buffer[11] = (byte)TimeCodeOutSeconds;
|
||||||
|
buffer[12] = GetFrameFromMilliseconds(TimeCodeOutMilliseconds);
|
||||||
|
|
||||||
buffer[13] = VerticalPosition;
|
buffer[13] = VerticalPosition;
|
||||||
buffer[14] = JustificationCode;
|
buffer[14] = JustificationCode;
|
||||||
buffer[15] = CommentFlag;
|
buffer[15] = CommentFlag;
|
||||||
|
|
||||||
//TODO: text field...
|
Encoding encoding = Encoding.Default;
|
||||||
|
if (header.CharacterCodeTableNumber == "00")
|
||||||
|
{
|
||||||
|
encoding = Encoding.GetEncoding(20269);
|
||||||
|
// 0xC1—0xCF combines characters - http://en.wikipedia.org/wiki/ISO/IEC_6937
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc1 }), "ÀÈÌÒÙàèìòù", "AEIOUaeiou");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc2 }), "ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź", "ACEILNORSUYZacegilnorsuyz");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc3 }), "ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ", "ACEGHIJOSUWYaceghijosuwy");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc4 }), "ÃĨÑÕŨãĩñõũ", "AINOUainou");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc5 }), "ĀĒĪŌŪāēīōū", "AEIOUaeiou");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc6 }), "ĂĞŬăğŭ", "AGUagu");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc7 }), "ĊĖĠİŻċėġıż", "CEGIZcegiz");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xc8 }), "ÄËÏÖÜŸäëïöüÿ", "AEIOUYaeiouy");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xca }), "ÅŮåů", "AUau");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xcb }), "ÇĢĶĻŅŖŞŢçķļņŗşţ", "CGKLNRSTcklnrst");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xcd }), "ŐŰőű", "OUou");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xce }), "ĄĘĮŲąęįų", "AEIUaeiu");
|
||||||
|
TextField = ReplaceSpecialCharactersWithTwoByteEncoding(TextField, encoding.GetString(new byte[] { 0xcf }), "ČĎĚĽŇŘŠŤŽčďěľňřšťž", "CDELNRSTZcdelnrstz");
|
||||||
|
}
|
||||||
|
else if (header.CharacterCodeTableNumber == "01") // Latin/Cyrillic alphabet - from ISO 8859/5-1988
|
||||||
|
{
|
||||||
|
encoding = Encoding.GetEncoding("ISO-8859-5");
|
||||||
|
}
|
||||||
|
else if (header.CharacterCodeTableNumber == "02") // Latin/Arabic alphabet - from ISO 8859/6-1987
|
||||||
|
{
|
||||||
|
encoding = Encoding.GetEncoding("ISO-8859-6");
|
||||||
|
}
|
||||||
|
else if (header.CharacterCodeTableNumber == "03") // Latin/Greek alphabet - from ISO 8859/7-1987
|
||||||
|
{
|
||||||
|
encoding = Encoding.GetEncoding("ISO-8859-7"); // or ISO-8859-1 ?
|
||||||
|
}
|
||||||
|
else if (header.CharacterCodeTableNumber == "04") // Latin/Hebrew alphabet - from ISO 8859/8-1988
|
||||||
|
{
|
||||||
|
encoding = Encoding.GetEncoding("ISO-8859-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// italic/underline
|
||||||
|
string italicsOn = encoding.GetString(new byte[] { 0x80 });
|
||||||
|
string italicsOff = encoding.GetString(new byte[] { 0x81 });
|
||||||
|
string underlineOn = encoding.GetString(new byte[] { 0x82 });
|
||||||
|
string underlineOff = encoding.GetString(new byte[] { 0x83 });
|
||||||
|
TextField = TextField.Replace("<i>", italicsOn);
|
||||||
|
TextField = TextField.Replace("<I>", italicsOn);
|
||||||
|
TextField = TextField.Replace("</i>", italicsOff);
|
||||||
|
TextField = TextField.Replace("</I>", italicsOff);
|
||||||
|
TextField = TextField.Replace("<u>", underlineOn);
|
||||||
|
TextField = TextField.Replace("<U>", underlineOn);
|
||||||
|
TextField = TextField.Replace("</u>", underlineOff);
|
||||||
|
TextField = TextField.Replace("</U>", underlineOff);
|
||||||
|
|
||||||
|
//font tags
|
||||||
|
string s = TextField;
|
||||||
|
int start = s.IndexOf("<font ");
|
||||||
|
if (start >= 0)
|
||||||
|
{
|
||||||
|
int end = s.IndexOf(">", start);
|
||||||
|
if (end > 0)
|
||||||
|
{
|
||||||
|
string f = s.Substring(start, end - start);
|
||||||
|
if (f.Contains(" color="))
|
||||||
|
{
|
||||||
|
int colorStart = f.IndexOf(" color=");
|
||||||
|
if (s.IndexOf("\"", colorStart + " color=".Length + 1) > 0)
|
||||||
|
{
|
||||||
|
int colorEnd = f.IndexOf("\"", colorStart + " color=".Length + 1);
|
||||||
|
if (colorStart > 1)
|
||||||
|
{
|
||||||
|
string color = f.Substring(colorStart + 7, colorEnd - (colorStart + 7));
|
||||||
|
color = color.Trim('\'');
|
||||||
|
color = color.Trim('\"');
|
||||||
|
color = color.Trim('#');
|
||||||
|
|
||||||
|
s = s.Remove(start, end - start + 1);
|
||||||
|
s = s.Insert(start, GetNearestEbuColorCode(color, encoding));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField = s;
|
||||||
|
}
|
||||||
|
TextField = Utilities.RemoveHtmlTags(TextField);
|
||||||
|
|
||||||
|
// newline
|
||||||
|
string newline = encoding.GetString(new byte[] { 0x8A, 0x8A });
|
||||||
|
TextField = TextField.Replace(Environment.NewLine, newline);
|
||||||
|
|
||||||
|
// convert text to bytes
|
||||||
|
byte[] bytes = encoding.GetBytes(TextField);
|
||||||
|
for (int i = 0; i < 112; i++)
|
||||||
|
{
|
||||||
|
if (i < bytes.Length)
|
||||||
|
buffer[16 + i] = bytes[i];
|
||||||
|
else if (i == bytes.Length)
|
||||||
|
buffer[16 + i] = 0x8f;
|
||||||
|
else
|
||||||
|
buffer[16 + i] = 0x8a;
|
||||||
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetNearestEbuColorCode(string color, Encoding encoding)
|
||||||
|
{
|
||||||
|
color = color.ToLower();
|
||||||
|
if (color == "black" || color == "000000")
|
||||||
|
return encoding.GetString(new byte[] { 0x00 }); // black
|
||||||
|
else if (color == "red" || color == "ff0000")
|
||||||
|
return encoding.GetString(new byte[] { 0x01 }); // red
|
||||||
|
else if (color == "green" || color == "00ff00")
|
||||||
|
return encoding.GetString(new byte[] { 0x02 }); // green
|
||||||
|
else if (color == "yellow" || color == "ffff00")
|
||||||
|
return encoding.GetString(new byte[] { 0x03 }); // yellow
|
||||||
|
else if (color == "blue" || color == "0000ff")
|
||||||
|
return encoding.GetString(new byte[] { 0x04 }); // blue
|
||||||
|
else if (color == "magenta" || color == "ff00ff")
|
||||||
|
return encoding.GetString(new byte[] { 0x05 }); // magenta
|
||||||
|
else if (color == "cyan" || color == "00ffff")
|
||||||
|
return encoding.GetString(new byte[] { 0x06 }); // cyan
|
||||||
|
else if (color == "white" || color == "ffffff")
|
||||||
|
return encoding.GetString(new byte[] { 0x07 }); // white
|
||||||
|
else if (color.Length == 6)
|
||||||
|
{
|
||||||
|
Regex regExpr = new Regex(@"^[a-f0-9]{6}$", RegexOptions.Compiled);
|
||||||
|
if (regExpr.IsMatch(color))
|
||||||
|
{
|
||||||
|
const int MaxDiff = 130;
|
||||||
|
int r = int.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
|
||||||
|
int g = int.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||||
|
int b = int.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
|
||||||
|
if (r < MaxDiff && g < MaxDiff && b < MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x00 }); // black
|
||||||
|
if (r > 255 - MaxDiff && g < MaxDiff && b < MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x01 }); // red
|
||||||
|
if (r < MaxDiff && g > 255 - MaxDiff && b < MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x02 }); // green
|
||||||
|
if (r > 255 - MaxDiff && g > 255 - MaxDiff && b < MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x03 }); // yellow
|
||||||
|
if (r < MaxDiff && g < MaxDiff && b > 255 - MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x04 }); // blue
|
||||||
|
if (r > 255 - MaxDiff && g < MaxDiff && b > 255 - MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x05 }); // magenta
|
||||||
|
if (r < MaxDiff && g > 255 - MaxDiff && b > 255 - MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x06 }); // cyan
|
||||||
|
if (r > 255-MaxDiff && g > 255-MaxDiff && b > 255-MaxDiff)
|
||||||
|
return encoding.GetString(new byte[] { 0x07 }); // white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReplaceSpecialCharactersWithTwoByteEncoding(string text, string specialCharacter, string originalCharacters, string newCharacters)
|
||||||
|
{
|
||||||
|
if (originalCharacters.Length != newCharacters.Length)
|
||||||
|
throw new ArgumentException("originalCharacters and newCharacters must have equal length");
|
||||||
|
|
||||||
|
for (int i = 0; i < newCharacters.Length; i++)
|
||||||
|
text = text.Replace(originalCharacters[i].ToString(), specialCharacter + newCharacters[i]);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte GetFrameFromMilliseconds(int milliseconds)
|
||||||
|
{
|
||||||
|
int frame = milliseconds / 41;
|
||||||
|
return (byte)frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Extension
|
public override string Extension
|
||||||
@ -173,15 +384,46 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
|
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
|
||||||
|
|
||||||
EbuGeneralSubtitleInformation header = new EbuGeneralSubtitleInformation();
|
EbuGeneralSubtitleInformation header = new EbuGeneralSubtitleInformation();
|
||||||
|
header.TotalNumberOfSubtitles = ((subtitle.Paragraphs.Count+1).ToString()).PadLeft(5, '0'); // seems to be 1 higher than actual number of subtitles
|
||||||
|
header.TotalNumberOfTextAndTimingInformationBlocks = header.TotalNumberOfSubtitles;
|
||||||
|
|
||||||
|
string today = string.Format("{0:00}{1:00}{2:00}", DateTime.Now.Year.ToString().Remove(0, 2), DateTime.Now.Month, DateTime.Now.Day);
|
||||||
|
if (today.Length == 6)
|
||||||
|
{
|
||||||
|
header.CreationDate = today;
|
||||||
|
header.RevisionDate = today;
|
||||||
|
}
|
||||||
|
|
||||||
|
Paragraph firstParagraph = subtitle.GetParagraphOrDefault(0);
|
||||||
|
if (firstParagraph != null)
|
||||||
|
{
|
||||||
|
TimeCode tc = firstParagraph.StartTime;
|
||||||
|
string firstTimeCode = string.Format("{0:00}{1:00}{2:00}{3:00}", tc.Hours, tc.Minutes, tc.Seconds, EbuTextTimingInformation.GetFrameFromMilliseconds(tc.Milliseconds));
|
||||||
|
if (firstTimeCode.Length == 8)
|
||||||
|
header.TimeCodeFirstInCue = firstTimeCode;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] buffer = ASCIIEncoding.ASCII.GetBytes(header.ToString());
|
byte[] buffer = ASCIIEncoding.ASCII.GetBytes(header.ToString());
|
||||||
fs.Write(buffer, 0, buffer.Length);
|
fs.Write(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
|
||||||
foreach (Paragraph p in subtitle.Paragraphs)
|
foreach (Paragraph p in subtitle.Paragraphs)
|
||||||
{
|
{
|
||||||
EbuTextTimingInformation tti = new EbuTextTimingInformation();
|
EbuTextTimingInformation tti = new EbuTextTimingInformation();
|
||||||
buffer = tti.GetBytes();
|
tti.SubtitleNumber = (ushort)p.Number;
|
||||||
|
tti.TextField = p.Text;
|
||||||
|
tti.TimeCodeInHours = p.StartTime.Hours;
|
||||||
|
tti.TimeCodeInMinutes = p.StartTime.Minutes;
|
||||||
|
tti.TimeCodeInSeconds = p.StartTime.Seconds;
|
||||||
|
tti.TimeCodeInMilliseconds = p.StartTime.Milliseconds;
|
||||||
|
tti.TimeCodeOutHours = p.EndTime.Hours;
|
||||||
|
tti.TimeCodeOutMinutes = p.EndTime.Minutes;
|
||||||
|
tti.TimeCodeOutSeconds = p.EndTime.Seconds;
|
||||||
|
tti.TimeCodeOutMilliseconds = p.EndTime.Milliseconds;
|
||||||
|
buffer = tti.GetBytes(header);
|
||||||
fs.Write(buffer, 0, buffer.Length);
|
fs.Write(buffer, 0, buffer.Length);
|
||||||
}
|
}
|
||||||
|
fs.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsMine(List<string> lines, string fileName)
|
public override bool IsMine(List<string> lines, string fileName)
|
||||||
@ -541,7 +783,7 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
|||||||
const byte TextFieldCRLF = 0x8A;
|
const byte TextFieldCRLF = 0x8A;
|
||||||
const byte TextFieldTerminator = 0x8F;
|
const byte TextFieldTerminator = 0x8F;
|
||||||
const byte ItalicsOn = 0x80;
|
const byte ItalicsOn = 0x80;
|
||||||
const byte ItalicsOff = 0x80;
|
const byte ItalicsOff = 0x81;
|
||||||
const byte UnderlineOn = 0x82;
|
const byte UnderlineOn = 0x82;
|
||||||
const byte UnderlineOff = 0x83;
|
const byte UnderlineOff = 0x83;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user