2016-02-08 21:11:03 +01:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Text ;
namespace Nikse.SubtitleEdit.Core.SubtitleFormats
{
/// <summary>
/// The ULTECH caption file format (ULT/ULD file) is a compact binary file that stores captions with embedded EIA-608 control codes
/// http://en.wikipedia.org/wiki/EIA-608
/// </summary>
public class Ultech130 : SubtitleFormat
{
private const string UltechId = "ULTECH\01.30" ;
public override string Extension
{
get { return ".ult" ; }
}
public const string NameOfFormat = "Ultech 1.30 Caption" ;
public override string Name
{
get { return NameOfFormat ; }
}
public override bool IsTimeBased
{
get { return true ; }
}
public static void Save ( string fileName , Subtitle subtitle )
{
using ( var fs = new FileStream ( fileName , FileMode . Create , FileAccess . Write ) )
{
byte [ ] buffer = Encoding . ASCII . GetBytes ( UltechId ) ;
fs . Write ( buffer , 0 , buffer . Length ) ;
buffer = new byte [ ] { 0 , 0 , 2 , 0x1D , 0 } ; // ?
fs . Write ( buffer , 0 , buffer . Length ) ;
int numberOfLines = subtitle . Paragraphs . Count ;
fs . WriteByte ( ( byte ) ( numberOfLines % 256 ) ) ; // paragraphs - low byte
fs . WriteByte ( ( byte ) ( numberOfLines / 256 ) ) ; // paragraphs - high byte
buffer = new byte [ ] { 0 , 0 , 0 , 0 , 0x1 , 0 , 0xF , 0x15 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x1 , 0 , 0xE , 0x15 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x1 , 0 , 0xD , 0x15 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x1 , 0 , 0xC , 0x15 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } ; // ?
fs . Write ( buffer , 0 , buffer . Length ) ;
buffer = Encoding . ASCII . GetBytes ( "Subtitle Edit" ) ;
fs . Write ( buffer , 0 , buffer . Length ) ;
while ( fs . Length < 512 )
fs . WriteByte ( 0 ) ;
var footer = new byte [ ] { 0xF1 , 0x0B , 0x00 , 0x00 , 0x00 , 0x1B , 0x18 , 0x14 , 0x20 , 0x14 , 0x2E , 0x14 , 0x2F , 0x00 } ; // footer
// paragraphs
foreach ( Paragraph p in subtitle . Paragraphs )
{
// convert line breaks
var sb = new StringBuilder ( ) ;
var line = new StringBuilder ( ) ;
int skipCount = 0 ;
int numberOfNewLines = Utilities . GetNumberOfLines ( p . Text ) ;
bool italic = p . Text . StartsWith ( "<i>" ) & & p . Text . EndsWith ( "</i>" ) ;
string text = HtmlUtil . RemoveHtmlTags ( p . Text , true ) ;
if ( italic )
{
sb . Append ( ' \ u0011 ' ) ;
sb . Append ( ' \ u002E ' ) ;
}
int y = 0x74 - ( numberOfNewLines * 0x20 ) ;
for ( int j = 0 ; j < text . Length ; j + + )
{
if ( text . Substring ( j ) . StartsWith ( Environment . NewLine , StringComparison . Ordinal ) )
{
y + = 0x20 ;
if ( line . Length > 0 )
sb . Append ( line ) ;
line . Clear ( ) ;
skipCount = Environment . NewLine . Length - 1 ;
sb . Append ( ' \ u0014 ' ) ;
sb . Append ( Convert . ToChar ( ( byte ) ( y ) ) ) ;
//center
sb . Append ( ' \ u0017 ' ) ;
sb . Append ( ' \ u0021 ' ) ;
if ( italic )
{
sb . Append ( ' \ u0011 ' ) ;
sb . Append ( ' \ u002E ' ) ;
}
}
else if ( skipCount = = 0 )
{
line . Append ( text [ j ] ) ;
}
else
{
skipCount - - ;
}
}
if ( line . Length > 0 )
sb . Append ( line ) ;
text = sb . ToString ( ) ;
// codes?
buffer = new byte [ ] {
0x14 ,
0x20 ,
0x14 ,
0x2E ,
0x14 ,
( byte ) ( 0x74 - ( numberOfNewLines * 0x20 ) ) ,
0x17 , 0x21 , // 0x1721=center, 0x1722=right ?
} ;
//if (text.StartsWith("{\\a6}"))
//{
// text = p.Text.Remove(0, 5);
// buffer[7] = 1; // align top
//}
//else if (text.StartsWith("{\\a1}"))
//{
// text = p.Text.Remove(0, 5);
// buffer[8] = 0x0A; // align left
//}
//else if (text.StartsWith("{\\a3}"))
//{
// text = p.Text.Remove(0, 5);
// buffer[8] = 0x1E; // align right
//}
//else if (text.StartsWith("{\\a5}"))
//{
// text = p.Text.Remove(0, 5);
// buffer[7] = 1; // align top
// buffer[8] = 05; // align left
//}
//else if (text.StartsWith("{\\a7}"))
//{
// text = p.Text.Remove(0, 5);
// buffer[7] = 1; // align top
// buffer[8] = 0xc; // align right
//}
fs . WriteByte ( 0xF1 ) ; //ID of start record
// length
int length = text . Length + 15 ;
fs . WriteByte ( ( byte ) ( length ) ) ;
fs . WriteByte ( 0 ) ;
// start time
WriteTime ( fs , p . StartTime ) ;
fs . Write ( buffer , 0 , buffer . Length ) ;
// text
buffer = Encoding . ASCII . GetBytes ( text ) ;
fs . Write ( buffer , 0 , buffer . Length ) ; // Text starter på index 19 (0 baseret)
fs . WriteByte ( 0x14 ) ;
fs . WriteByte ( 0x2F ) ;
fs . WriteByte ( 0 ) ;
// end time
fs . WriteByte ( 0xF1 ) ; // id of start record
fs . WriteByte ( 7 ) ; // length of end time
fs . WriteByte ( 0 ) ;
WriteTime ( fs , p . EndTime ) ;
fs . WriteByte ( 0x14 ) ;
fs . WriteByte ( 0x2c ) ;
fs . WriteByte ( 0 ) ;
}
buffer = footer ;
fs . Write ( buffer , 0 , buffer . Length ) ;
}
}
private static void WriteTime ( FileStream fs , TimeCode timeCode )
{
fs . WriteByte ( ( byte ) timeCode . Hours ) ;
fs . WriteByte ( ( byte ) timeCode . Minutes ) ;
fs . WriteByte ( ( byte ) timeCode . Seconds ) ;
fs . WriteByte ( ( byte ) MillisecondsToFramesMaxFrameRate ( timeCode . Milliseconds ) ) ;
}
public override bool IsMine ( List < string > lines , string fileName )
{
if ( ! string . IsNullOrEmpty ( fileName ) & & File . Exists ( fileName ) )
{
var fi = new FileInfo ( fileName ) ;
if ( fi . Length > = 200 & & fi . Length < 1024000 ) // not too small or too big
{
if ( fileName . EndsWith ( ".ult" , StringComparison . OrdinalIgnoreCase ) | | fileName . EndsWith ( ".uld" , StringComparison . OrdinalIgnoreCase ) ) // drop frame is often named uld, and ult for non-drop
{
byte [ ] buffer = FileUtil . ReadAllBytesShared ( fileName ) ;
string id = Encoding . ASCII . GetString ( buffer , 0 , UltechId . Length ) ;
return id = = UltechId ;
}
}
}
return false ;
}
public override string ToText ( Subtitle subtitle , string title )
{
return "Not supported!" ;
}
private static TimeCode DecodeTimestamp ( byte [ ] buffer , int index )
{
return new TimeCode ( buffer [ index ] , buffer [ index + 1 ] , buffer [ index + 2 ] , FramesToMillisecondsMax999 ( buffer [ index + 3 ] ) ) ;
}
public override void LoadSubtitle ( Subtitle subtitle , List < string > lines , string fileName )
{
subtitle . Paragraphs . Clear ( ) ;
subtitle . Header = null ;
byte [ ] buffer = FileUtil . ReadAllBytesShared ( fileName ) ;
int i = 512 ;
Paragraph last = null ;
var sb = new StringBuilder ( ) ;
while ( i < buffer . Length - 25 )
{
var p = new Paragraph ( ) ;
int length = buffer [ i + 1 ] ;
p . StartTime = DecodeTimestamp ( buffer , i + 3 ) ;
2016-06-07 06:31:03 +02:00
if ( last ! = null & & Math . Abs ( last . EndTime . TotalMilliseconds ) < 0.001 )
2016-02-08 21:11:03 +01:00
last . EndTime . TotalMilliseconds = p . StartTime . TotalMilliseconds - 1 ;
if ( length > 22 )
{
int start = i + 7 ;
sb . Clear ( ) ;
int skipCount = 0 ;
bool italics = false ;
//bool font = false;
for ( int k = start ; k < length + i ; k + + )
{
byte b = buffer [ k ] ;
if ( skipCount > 0 )
{
skipCount - - ;
}
else if ( b < 0x1F )
{
byte b2 = buffer [ k + 1 ] ;
skipCount = 1 ;
if ( sb . Length > 0 & & ! sb . ToString ( ) . EndsWith ( Environment . NewLine ) & & ! sb . EndsWith ( '>' ) )
{
//if (font)
// sb.Append("</font>");
if ( italics )
sb . Append ( "</i>" ) ;
sb . AppendLine ( ) ;
//font = false;
italics = false ;
}
//string code = VobSub.Helper.IntToBin(buffer[k] * 256 + buffer[k+1], 16);
//var codeBytes = new List<char>();
//if (b == 0x11 && b2 == 0x28)
//{
// sb.Append("<font color=\"red\">");
// font = true;
//}
//else
if ( b = = 0x11 & & b2 = = 0x2e )
{
sb . Append ( "<i>" ) ;
italics = true ;
}
//foreach (char ch in code)
// codeBytes.Insert(0, ch);
//if (codeBytes[13] == '0' && codeBytes[14] == '0' && codeBytes[12] == '1' && codeBytes[6] == '1')
//{ // preamble address code
// if (code.Substring(11, 4) == "1000")
// {
// sb.Append("<font color=\"green\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0010")
// {
// sb.Append("<font color=\"blue\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0011")
// {
// sb.Append("<font color=\"cyan\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0100")
// {
// sb.Append("<font color=\"red\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0101")
// {
// sb.Append("<font color=\"yellow\">");
// font = true;
// }
// //else if (code.Substring(11, 4) == "0110")
// //{
// // sb.Append("<font color=\"magenta\">");
// // font = true;
// //}
//}
//else if (codeBytes[14] == '0' && codeBytes[13] == '0' && codeBytes[10] == '0' && codeBytes[9] == '0' && codeBytes[6] == '0' &&
// codeBytes[12] == '1' && codeBytes[8] == '1' && codeBytes[6] == '1')
//{ // midrow code
// if (code.Substring(11, 4) == "1000")
// {
// sb.Append("<font color=\"green\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0010")
// {
// sb.Append("<font color=\"blue\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0011")
// {
// sb.Append("<font color=\"cyan\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0100")
// {
// sb.Append("<font color=\"red\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0101")
// {
// sb.Append("<font color=\"yellow\">");
// font = true;
// }
// //else if (code.Substring(11, 4) == "0110")
// //{
// // sb.Append("<font color=\"magenta\">");
// // font = true;
// //}
//}
//else if ((codeBytes[14] == '0' && codeBytes[13] == '0' && codeBytes[9] == '0' && codeBytes[6] == '0' && codeBytes[4] == '0' &&
// codeBytes[12] == '1' && codeBytes[10] == '1' && codeBytes[5] == '1') || b == 0x11)
//{ // codeBytes[10] == 0 ???
// //control codes
// if (code.Substring(11, 4) == "0111" && buffer[k] == 0x11)
// {
// sb.Append("<i>");
// italics = true;
// }
// else if (code.Substring(11, 4) == "1000")
// {
// sb.Append("<font color=\"green\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0010")
// {
// sb.Append("<font color=\"blue\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0011")
// {
// sb.Append("<font color=\"cyan\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0100")
// {
// sb.Append("<font color=\"red\">");
// font = true;
// }
// else if (code.Substring(11, 4) == "0101")
// {
// sb.Append("<font color=\"yellow\">");
// font = true;
// }
// //else if (code.Substring(11, 4) == "0110")
// //{
// // sb.Append("<font color=\"magenta\">");
// // font = true;
// //}
//}
//else
//{
// if (code.Substring(11, 4) == "0111" && buffer[k] == 0x11)
// {
// sb.Append("<i>");
// italics = true;
// }
// else if (code.Substring(11, 4) == "0101" && b == 0x11)
// {
// sb.Append("<font color=\"yellow\">");
// font = true;
// }
//// if (code.Substring(11, 4) == "0111")
//// {
//// //System.Windows.Forms.MessageBox.Show(code);
//// sb.Append("<i>");
//// }
//// else if (code.Substring(11, 4) == "0101")
//// sb.Append("<font color=\"yellow\">");
//}
}
else if ( b = = 0x80 )
{
//if (sb.Length == 0)
// break;
//if (sb.Length > 0 && !sb.ToString().EndsWith(Environment.NewLine))
//{
// if (font)
// sb.Append("</font>");
// if (italics)
// sb.Append("</i>");
// sb.AppendLine();
// font = false;
// italics = false;
//}
}
else
{
sb . Append ( Encoding . GetEncoding ( 1252 ) . GetString ( buffer , k , 1 ) ) ;
}
}
p . Text = sb . ToString ( ) . Trim ( ) ;
//if (font)
// p.Text += "</font>";
if ( italics )
p . Text + = "</i>" ;
p . Text = p . Text . Replace ( Environment . NewLine + Environment . NewLine , Environment . NewLine ) ;
p . Text = p . Text . Replace ( Environment . NewLine + Environment . NewLine , Environment . NewLine ) ;
p . Text = p . Text . Replace ( Environment . NewLine + Environment . NewLine , Environment . NewLine ) ;
subtitle . Paragraphs . Add ( p ) ;
}
else if ( last ! = null )
{
last . EndTime . TotalMilliseconds = p . StartTime . TotalMilliseconds ;
}
last = p ;
i + = length + 3 ;
}
if ( last ! = null )
{
2016-06-07 06:31:03 +02:00
if ( Math . Abs ( last . EndTime . TotalMilliseconds ) < 0.001 )
2016-02-08 21:11:03 +01:00
last . EndTime . TotalMilliseconds = last . StartTime . TotalMilliseconds + 2500 ;
if ( last . Duration . TotalMilliseconds > Configuration . Settings . General . SubtitleMaximumDisplayMilliseconds )
last . EndTime . TotalMilliseconds = last . StartTime . TotalMilliseconds + Utilities . GetOptimalDisplayMilliseconds ( last . Text ) ;
}
subtitle . Renumber ( ) ;
}
public override List < string > AlternateExtensions
{
get
{
return new List < string > ( ) { ".uld" } ; // Ultech drop frame
}
}
}
}