public static void Save(string fileName, Subtitle subtitle, bool batchMode) { var header = new EbuGeneralSubtitleInformation(); if (EbuUiHelper == null) return; if (subtitle.Header != null && subtitle.Header.Length == 1024 && (subtitle.Header.Contains("STL24") || subtitle.Header.Contains("STL25") || subtitle.Header.Contains("STL29") || subtitle.Header.Contains("STL30"))) { header = ReadHeader(Encoding.UTF8.GetBytes(subtitle.Header)); EbuUiHelper.Initialize(header, 0, null, subtitle); } else { EbuUiHelper.Initialize(header, 0, fileName, subtitle); } if (!batchMode && !EbuUiHelper.ShowDialogOk()) return; using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { header.TotalNumberOfSubtitles = subtitle.Paragraphs.Count.ToString("D5"); // seems to be 1 higher than actual number of subtitles header.TotalNumberOfTextAndTimingInformationBlocks = header.TotalNumberOfSubtitles; var today = string.Format("{0:yyMMdd}", DateTime.Now); 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, header.FrameRate)); if (firstTimeCode.Length == 8) header.TimeCodeFirstInCue = firstTimeCode; } byte[] buffer = Encoding.Default.GetBytes(header.ToString()); fs.Write(buffer, 0, buffer.Length); int subtitleNumber = 0; foreach (Paragraph p in subtitle.Paragraphs) { var tti = new EbuTextTimingInformation(); int rows; if (!int.TryParse(header.MaximumNumberOfDisplayableRows, out rows)) rows = 23; if (header.DisplayStandardCode == "1" || header.DisplayStandardCode == "2") // teletext rows = 23; else if (header.DisplayStandardCode == "0" && header.MaximumNumberOfDisplayableRows == "02") // open subtitling rows = 15; if (p.Text.StartsWith("{\\an7}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an8}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an9}", StringComparison.Ordinal)) { tti.VerticalPosition = 1; // top (vertical) } else if (p.Text.StartsWith("{\\an4}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an5}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an6}", StringComparison.Ordinal)) { tti.VerticalPosition = (byte)(rows / 2); // middle (vertical) } else { int startRow = (rows - 1) - Utilities.CountTagInText(p.Text, Environment.NewLine) * 2; if (startRow < 0) startRow = 0; tti.VerticalPosition = (byte)startRow; // bottom (vertical) } tti.JustificationCode = EbuUiHelper.JustificationCode; if (p.Text.StartsWith("{\\an1}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an4}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an7}", StringComparison.Ordinal)) { tti.JustificationCode = 1; // 01h=left-justified text } else if (p.Text.StartsWith("{\\an3}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an6}", StringComparison.Ordinal) || p.Text.StartsWith("{\\an9}", StringComparison.Ordinal)) { tti.JustificationCode = 3; // 03h=right-justified } else // If it's not left- or right-justified, it's centred. { tti.JustificationCode = 2; // 02h=centred text } tti.SubtitleNumber = (ushort)subtitleNumber; tti.TextField = p.Text; int startTag = tti.TextField.IndexOf('}'); if (tti.TextField.StartsWith("{\\", StringComparison.Ordinal) && startTag > 0 && startTag < 10) { tti.TextField = tti.TextField.Remove(0, startTag + 1); } 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); subtitleNumber++; } } }
public void Save(string fileName, Subtitle subtitle) { EbuGeneralSubtitleInformation header = new EbuGeneralSubtitleInformation(); FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write); 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, header.FrameRate)); if (firstTimeCode.Length == 8) { header.TimeCodeFirstInCue = firstTimeCode; } } byte[] buffer = ASCIIEncoding.ASCII.GetBytes(header.ToString()); fs.Write(buffer, 0, buffer.Length); int subtitleNumber = 0; foreach (Paragraph p in subtitle.Paragraphs) { EbuTextTimingInformation tti = new EbuTextTimingInformation(); if (p.Text.Contains(Environment.NewLine)) { tti.VerticalPosition = 0x14; } else { tti.VerticalPosition = 0x16; } tti.JustificationCode = (byte)0; tti.SubtitleNumber = (ushort)subtitleNumber; 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); subtitleNumber++; } fs.Close(); }
/// <summary> /// Read TTI block /// </summary> private IEnumerable<EbuTextTimingInformation> ReadTextAndTiming(byte[] buffer, EbuGeneralSubtitleInformation header) { const int startOfTextAndTimingBlock = 1024; const int ttiSize = 128; const byte textFieldCarriageReturnLineFeed = 0x8A; const byte textFieldTerminator = 0x8F; const byte italicsOn = 0x80; const byte italicsOff = 0x81; const byte underlineOn = 0x82; const byte underlineOff = 0x83; var list = new List<EbuTextTimingInformation>(); int index = startOfTextAndTimingBlock; while (index + ttiSize <= buffer.Length) { var tti = new EbuTextTimingInformation(); tti.SubtitleGroupNumber = buffer[index]; tti.SubtitleNumber = (ushort)(buffer[index + 2] * 256 + buffer[index + 1]); tti.ExtensionBlockNumber = buffer[index + 3]; tti.CumulativeStatus = buffer[index + 4]; tti.TimeCodeInHours = buffer[index + 5 + 0]; tti.TimeCodeInMinutes = buffer[index + 5 + 1]; tti.TimeCodeInSeconds = buffer[index + 5 + 2]; tti.TimeCodeInMilliseconds = (int)(TimeCode.BaseUnit / (header.FrameRate / buffer[index + 5 + 3])); tti.TimeCodeOutHours = buffer[index + 9 + 0]; tti.TimeCodeOutMinutes = buffer[index + 9 + 1]; tti.TimeCodeOutSeconds = buffer[index + 9 + 2]; tti.TimeCodeOutMilliseconds = (int)(1000 / (header.FrameRate / buffer[index + 9 + 3])); tti.VerticalPosition = buffer[index + 13]; VerticalPositions.Add(tti.VerticalPosition); tti.JustificationCode = buffer[index + 14]; JustificationCodes.Add(tti.JustificationCode); tti.CommentFlag = buffer[index + 15]; // build text bool skipNext = false; var sb = new StringBuilder(); string endTags = string.Empty; string color = string.Empty; string lastColor = string.Empty; for (int i = 0; i < 112; i++) // skip fist byte (seems to be always 0xd/32/space - thx Iban) { byte b = buffer[index + 16 + i]; if (skipNext) { skipNext = false; } else if (header.LanguageCode == LanguageCodeChinese) { if (b == textFieldTerminator) { break; } if (index + 16 + i + 1 < buffer.Length) { byte next = buffer[index + 17 + i]; if (b == 0 && next == textFieldTerminator) break; if (b == 0 && next == 138) // new line { sb.AppendLine(); skipNext = true; } else { sb.Append(GetCharacter(out skipNext, header, buffer, index + 16 + i)); } } } else if (b <= 0xf && (i == 0 || i == 2 || i == 3)) { // not used, 0=0xd, 2=0xb, 3=0xb } else { if (b <= 0x17) { switch (b) { case 0x00: case 0x10: color = "Black"; break; case 0x01: case 0x11: color = "Red"; break; case 0x02: case 0x12: color = "Green"; break; case 0x03: case 0x13: color = "Yellow"; break; case 0x04: case 0x14: color = "Blue"; break; case 0x05: case 0x15: color = "Magenta"; break; case 0x06: case 0x16: color = "Cyan"; break; case 0x07: case 0x17: color = "White"; break; } } if (b == textFieldCarriageReturnLineFeed) sb.AppendLine(); else if (b == italicsOn && header.LanguageCode != LanguageCodeChinese) sb.Append("<i>"); else if (b == italicsOff && header.LanguageCode != LanguageCodeChinese) sb.Append("</i>"); else if (b == underlineOn && header.LanguageCode != LanguageCodeChinese) sb.Append("<u>"); else if (b == underlineOff && header.LanguageCode != LanguageCodeChinese) sb.Append("</u>"); else if (b == 0xd3 && header.CharacterCodeTableNumber == "00") // Latin { sb.Append("©"); } else if (b == 0xd4 && header.CharacterCodeTableNumber == "00") // Latin { sb.Append("™"); } else if (b == 0xd5 && header.CharacterCodeTableNumber == "00") // Latin { sb.Append("♪"); } //else if (b == 0xD0) // em-dash // sb.Append('–'); else if (b == textFieldTerminator) break; else if ((b >= 0x20 && b <= 0x7F) || b >= 0xA1 || header.LanguageCode == LanguageCodeChinese) { string ch = GetCharacter(out skipNext, header, buffer, index + 16 + i); if (ch != " ") { if (color != lastColor && color.Length > 0) { endTags = "</font>"; if (lastColor.Length > 0) sb.Append("</font>"); sb.Append("<font color=\"" + color + "\">"); } lastColor = color; } sb.Append(ch); } } } tti.TextField = sb.ToString().Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine).TrimEnd() + endTags; int rows; if (!int.TryParse(header.MaximumNumberOfDisplayableRows, out rows)) rows = 23; if (tti.VerticalPosition < 3) { if (tti.JustificationCode == 1) // left tti.TextField = "{\\an7}" + tti.TextField; else if (tti.JustificationCode == 3) // right tti.TextField = "{\\an9}" + tti.TextField; else tti.TextField = "{\\an8}" + tti.TextField; } else if (tti.VerticalPosition <= rows / 2 + 1) { if (tti.JustificationCode == 1) // left tti.TextField = "{\\an4}" + tti.TextField; else if (tti.JustificationCode == 3) // right tti.TextField = "{\\an6}" + tti.TextField; else tti.TextField = "{\\an5}" + tti.TextField; } else { if (tti.JustificationCode == 1) // left tti.TextField = "{\\an1}" + tti.TextField; else if (tti.JustificationCode == 3) // right tti.TextField = "{\\an3}" + tti.TextField; } index += ttiSize; list.Add(tti); } return list; }
private IEnumerable <EbuTextTimingInformation> ReadTTI(byte[] buffer, EbuGeneralSubtitleInformation header) { const int StartOfTTI = 1024; const int TTISize = 128; const byte TextFieldCRLF = 0x8A; const byte TextFieldTerminator = 0x8F; const byte ItalicsOn = 0x80; const byte ItalicsOff = 0x81; const byte UnderlineOn = 0x82; const byte UnderlineOff = 0x83; List <EbuTextTimingInformation> list = new List <EbuTextTimingInformation>(); int index = StartOfTTI; while (index + TTISize <= buffer.Length) { var tti = new EbuTextTimingInformation(); tti.SubtitleGroupNumber = buffer[index]; tti.SubtitleNumber = (ushort)(buffer[index + 2] * 256 + buffer[index + 1]); tti.ExtensionBlockNumber = buffer[index + 3]; tti.CumulativeStatus = buffer[index + 4]; tti.TimeCodeInHours = buffer[index + 5 + 0]; tti.TimeCodeInMinutes = buffer[index + 5 + 1]; tti.TimeCodeInSeconds = buffer[index + 5 + 2]; tti.TimeCodeInMilliseconds = (int)(1000.0 / (header.FrameRate / buffer[index + 5 + 3])); tti.TimeCodeOutHours = buffer[index + 9 + 0]; tti.TimeCodeOutMinutes = buffer[index + 9 + 1]; tti.TimeCodeOutSeconds = buffer[index + 9 + 2]; tti.TimeCodeOutMilliseconds = (int)(1000 / (header.FrameRate / buffer[index + 9 + 3])); tti.VerticalPosition = buffer[index + 13]; tti.JustificationCode = buffer[index + 14]; tti.CommentFlag = buffer[index + 15]; // build text bool skipNext = false; StringBuilder sb = new StringBuilder(); string endTags = string.Empty; string color = string.Empty; string lastColor = string.Empty; for (int i = 0; i < 112; i++) // skip fist byte (seems to be always 0xd/32/space - thx Iban) { byte b = buffer[index + 16 + i]; if (b <= 0xf && (i == 0 || i == 2 || i == 3)) { // not used, 0=0xd, 2=0xb, 3=0xb } else if (skipNext) { skipNext = false; } else { if (b >= 0 && b <= 0x17) { switch (b) { case 0x00: case 0x10: color = "Black"; break; case 0x01: case 0x11: color = "Red"; break; case 0x02: case 0x12: color = "Green"; break; case 0x03: case 0x13: color = "Yellow"; break; case 0x04: case 0x14: color = "Blue"; break; case 0x05: case 0x15: color = "Magenta"; break; case 0x06: case 0x16: color = "Cyan"; break; case 0x07: case 0x17: color = "White"; break; } } if (b == TextFieldCRLF) { sb.AppendLine(); } else if (b == ItalicsOn) { sb.Append("<i>"); } else if (b == ItalicsOff) { sb.Append("</i>"); } else if (b == UnderlineOn) { sb.Append("<u>"); } else if (b == UnderlineOff) { sb.Append("</u>"); } else if (b == TextFieldTerminator) { break; } else if ((b >= 0x20 && b <= 0x7F) || b >= 0xA1) { string ch = GetCharacter(out skipNext, header, buffer, index + 16 + i); if (ch != " ") { if (color != lastColor && color.Length > 0) { endTags = "</font>"; if (lastColor.Length > 0) { sb.Append("</font>"); } sb.Append("<font color=\"" + color + "\">"); } lastColor = color; } sb.Append(ch); } } } tti.TextField = sb.ToString().Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine).TrimEnd() + endTags; index += TTISize; list.Add(tti); } return(list); }