mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-25 04:33:04 +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.Text;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
{
|
||||
@ -63,28 +64,57 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
{
|
||||
CodePageNumber = "437";
|
||||
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()
|
||||
{
|
||||
string result =
|
||||
CodePageNumber +
|
||||
DiskFormatCode +
|
||||
DisplayStandardCode +
|
||||
string result =
|
||||
CodePageNumber +
|
||||
DiskFormatCode +
|
||||
DisplayStandardCode +
|
||||
CharacterCodeTableNumber +
|
||||
LanguageCode +
|
||||
OriginalProgrammeTitle +
|
||||
OriginalEpisodeTitle +
|
||||
TranslatedProgrammeTitle +
|
||||
TranslatedEpisodeTitle +
|
||||
TranslatedEpisodeTitle +
|
||||
TranslatorsName +
|
||||
TranslatorsContactDetails +
|
||||
SubtitleListReferenceCode +
|
||||
CreationDate +
|
||||
RevisionDate +
|
||||
RevisionNumber +
|
||||
TotalNumberOfTextAndTimingInformationBlocks +
|
||||
TotalNumberOfSubtitles +
|
||||
TotalNumberOfTextAndTimingInformationBlocks +
|
||||
TotalNumberOfSubtitles +
|
||||
TotalNumberOfSubtitleGroups +
|
||||
MaximumNumberOfDisplayableCharactersInAnyTextRow +
|
||||
MaximumNumberOfDisplayableRows +
|
||||
@ -96,11 +126,14 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
CountryOfOrigin +
|
||||
Publisher +
|
||||
EditorsName +
|
||||
EditorsContactDetails +
|
||||
EditorsContactDetails +
|
||||
SpareBytes +
|
||||
UserDefinedArea;
|
||||
return result;
|
||||
UserDefinedArea;
|
||||
if (result.Length == 1024)
|
||||
return result;
|
||||
throw new Exception("Length must be 1024");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -125,7 +158,17 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
public byte CommentFlag { 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
|
||||
|
||||
@ -136,16 +179,184 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
buffer[3] = ExtensionBlockNumber;
|
||||
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[14] = JustificationCode;
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@ -173,15 +384,46 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
|
||||
|
||||
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());
|
||||
fs.Write(buffer, 0, buffer.Length);
|
||||
|
||||
|
||||
foreach (Paragraph p in subtitle.Paragraphs)
|
||||
{
|
||||
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.Close();
|
||||
}
|
||||
|
||||
public override bool IsMine(List<string> lines, string fileName)
|
||||
@ -541,7 +783,7 @@ namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
|
||||
const byte TextFieldCRLF = 0x8A;
|
||||
const byte TextFieldTerminator = 0x8F;
|
||||
const byte ItalicsOn = 0x80;
|
||||
const byte ItalicsOff = 0x80;
|
||||
const byte ItalicsOff = 0x81;
|
||||
const byte UnderlineOn = 0x82;
|
||||
const byte UnderlineOff = 0x83;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user