/* TODO: buffer <- replace strcpy with strncpy etc.
*
*
*/
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <math.h>
#ifdef _WIN32
#include <direct.h>
#define COMPARESTRING strnicmp
#else
#include <unistd.h>
#define COMPARESTRING strncasecmp
#endif
#define VERSION "2.76" // program version
#define BUFFER_SIZE 4096 // input line buffer size
class line; // prototype of line class
char const *params[]={ // parameters table
"if", // 0
"t", // 1
"e", // 2
"o", // 3
"n", // 4
"of", // 5
"v", // 6
"i", // 7
"s", // 8
"j", // 9
"c", // 10
"tol", // 11
"m", // 12
"m0", // 13
"tt", // 14
"m1", // 15
"l", // 16
"f", // 17
#ifdef _WIN32
"g", // 18
#endif
"o1", // 19
"o2", // 20
"i1", // 21
"i2" // 22
};
// Subtitle stat types
enum what {chars, lines, subs, length, subchars};
// Work mode of program
enum state {normal, info, generate};
// Subtitle formats
enum format {unknown, micro, sam};
char buffer[BUFFER_SIZE];
line* root; // beginning of the text (pointer on the first line)
double iframerate=25.0; // default framerate for input subtitle
double oframerate=iframerate; // default framerate for output subtitle
double a=0.0, b=3.0, c=0.05; // default time conversion ratios
long int tolerance1; // time tolerance for moving subtitles
long int tolerance2; // time tolerance for squeezing subtitles
double offset=0; // default offset - don't move
unsigned int nlines=0; // number of read lines
long int splittime; // split time in fames
long int jointime; // join time in frames for 2nd subtitle file
long int cut1time, cut2time; // cut times in frames
int compactlines=2, compactchars=60; // number of lines and characters during compacting
int compactoffset=2; // 'window size' during compacting subtitles
int llength, clength; // length and number of lines which are displayed in report
state pmode=normal; // mode
format iformat; // input format (autodetect)
format oformat=unknown; // output format
#ifdef _WIN32
char* language; // language name during generation of .mvd file
#endif
class flags { // work modes
public:
unsigned int endtime : 1;
unsigned int calculate : 1;
unsigned int oframerate : 1;
unsigned int verbose : 1;
unsigned int split : 1;
unsigned int join : 1;
unsigned int cut : 1;
unsigned int tolerance : 1;
unsigned int compact0 : 1;
unsigned int compact1 : 1;
unsigned int compact2 : 1;
unsigned int length : 1;
unsigned int forcecompact : 1;
unsigned int autodetect : 1;
flags() {endtime=0; calculate=0; oframerate=0; verbose=1; split=1; join=1; cut=1; tolerance=1; compact0=1; compact1=1; compact2=1; length=1; forcecompact=1; autodetect=0; };
};
flags* disable=new flags();
char* input; // input filename
char* output; // output filename
char* join; // joining subtitle filename
// error message function
void error(const int num, const void* message1,const void* message2)
{
std::cerr << "Error: ";
switch(num){
case 1:
std::cerr <<"Cannot open file '"<<(char*)message1<<"'.\n";
break;
case 2:
std::cerr <<"Cannot write to file '"<<(char*)message1<<"'\n.";
break;
case 4:
std::cerr <<"Wrong switch '"<<(char*)message1<<"'.\n";
break;
case 9:
std::cerr <<"Wrong number of arguments.\n";
break;
case 16:
std::cerr <<"Switches -j and -i/-g are exclusive.\n";
break;
case 17:
std::cerr <<"<time2> should be greater than <time1>.\n";
break;
case 21:
std::cerr <<"Minimal available offset is "<<*(double *)message1<<" .\n";
break;
case 50:
std::cerr <<"Missing "<<(char *)message1<<" argument.\n";
break;
case 51:
std::cerr <<"Bad "<<(char *)message1<<" argument.\n";
break;
case 52:
std::cerr <<"Bad "<<(char*)message1<<"argument of "<<(char *)message2<<" switch.\n";
break;
case 53:
std::cerr <<(char *)message1<<" parameter should be "<<(char *)message2<<".\n";
break;
default:
std::cerr <<"General error.\n";
break;
}
exit (num);
}
// warning message function
void warning(const int num, const void* message1, const void* message2, const void* message3)
{
std::cerr << "Warning: ";
switch(num){
case 1:
std::cerr <<"Skipping line "<<*(unsigned int *)message1<<" of '"<<(char *)message2<<"' ("<<(char *)message3<<").\n";
break;
case 2:
std::cerr <<"Line "<<*(unsigned int *)message1<<" of '"<<(char *)message2<<"' contains more than 5 lines.\n";
break;
case 3:
std::cerr <<"Enabling "<<(char *)message1<<" ("<<(char *)message2<<" set).\n";
break;
default:
std::cerr <<"Something strange.\n";
break;
}
}
// short help function
void help(char* _filename)
{
std::cout << "microAdjust "<<VERSION<<" (C)2000-2001,2003 Gringo\n";
std::cout << "----------------\n";
std::cout << "Program allows changing duration time, offset, framerate,\n";
std::cout << "cut, join, split and compact MicroDVD or SAM format subtitles.\n";
std::cout << "Usage:\n";
std::cout << _filename <<" [<options>] <input file> <output file>\n";
std::cout << _filename << " -j <time> [<options>] <input file1> <input file2> <output file>\n";
#ifdef _WIN32
std::cout << _filename << " -g <language> <subtitle file> <video file> <output file>\n";
#endif
std::cout << _filename << " -i <input file>\n\n";
std::cout << "Options:\n";
std::cout << "-if <framerate>\t\tSet input framerate of subtitles [Default:25]\n";
std::cout << "-of <framerate>\t\tSet output framerate of subtitles\n";
std::cout << "\t\t\t[Default:same as input framerate]\n";
std::cout << "-i<X>\t\t\tForce input format of subtitle.\n";
std::cout << "\t\t\t<X>= 1: MicroDVD, 2: SAM [Default: Autodetect]\n";
std::cout << "-o<X>\t\t\tSet output format of subtitle.\n";
std::cout << "\t\t\t<X>= 1: MicroDVD, 2: SAM [Default:Same as input]\n";
std::cout << "-o <offset>\t\tSet offset for subtitles in seconds [Default:0].\n";
std::cout << "-t <a> <b> <c>\t\tSet time ratios which calculate duration of each\n";
std::cout << "\t\t\tsubtitle with formula: time[s]=a*sqrt(n) + b + c*n\n";
std::cout << "\t\t\t[Default:a=0 b=3.0 c=0.05]\n";
std::cout << "\t\t\tn - length of each subtitle in chars.\n";
std::cout << "-n\t\t\tDon't make time calculations.\n";
std::cout << "\t\t\tIf set parameter -t is ignored.\n";
std::cout << "-tt\t\t\tEnable time tolerance during time calculations\n";
std::cout << "\t\t\t[Default:disabled].\n";
std::cout << "-c <time1> <time2>\tCut subtitles from <time1> to <time2> (time2>time1).\n";
std::cout << "\t\t\t<time> - <minutes>:<seconds>.\n";
std::cout << "-s <time>\t\tSplit subtitle after specified period of time.\n";
std::cout << "-j <time>\t\tJoin two subtitle files after specified period of time.\n";
std::cout << "-m\t\t\tSame as: -m0 -m1 2 60 2 (optimal settings)\n";
std::cout << "-m0\t\t\tCompact subtitles (remove unnecessary spaces).\n";
std::cout << "\t\t\tOption implies time calculations.\n";
std::cout << "-m1 <a> <b> <c>\t\tCompact subtitles into <a=lines>*<b=chars>\n";
std::cout << "\t\t\ttextbox if needed (subtitle appears to short).\n";
std::cout << "\t\t\t<c> set amount of subtitles processed at once (>0).\n";
std::cout << "\t\t\tOption implies time calculations.\n";
std::cout << "-f\t\t\tForce compacting to <a>*<b> textbox if possible.\n";
std::cout << "-tol <a> <b>\t\tTolerance in seconds during: <a> - time calculations\n";
std::cout << "\t\t\t<b> - subtitle compacting (-m1) [Default: a=0.5 b=2.0]\n";
std::cout << "-e\t\t\tPut empty endtime in each subtitle.\n";
std::cout << "\t\t\tIf set parameter -t is ignored.\n";
#ifdef _WIN32
std::cout << "-g <language>\t\tGenerate startup file for MicroDVD.\n";
#endif
std::cout << "-v\t\t\tVerbose mode (Display additional information).\n";
std::cout << "-i\t\t\tDisplay info about subtitle file.\n";
std::cout << "-l <chars> <lines>\tDisplay subtitles with more/less than <chars>*<lines>.\n";
}
// return class of character (type)
int whatsign(char _char)
{
switch(_char){
case ' ':
case '\t':
return 1;
case ',':
case ';':
case ':':
case '!':
case '.':
case '?':
return 2;
case '|':
return 3;
case '\n':
case '\r':
return 4;
default:
return 0;
}
}
// convert time from MM:SS.SS to number of seconds
// MM - minutes; SS.SS - seconds
double gettime(const char* _string, const char* _param, const char* _switch)
{
char* tmp1;
char* tmp2;
double time;
time=strtod(_string,&tmp1);
if(! tmp1) error(52,_param,_switch);
while(*tmp1)
{ switch(*tmp1) {
case ':':
tmp1++;
time=60*time+strtod(tmp1,&tmp2);
if(! tmp2) error(52,_param,_switch);
break;
default:
error(52,_param,_switch);
break;
}
tmp1=tmp2;
}
#ifdef DEBUG
std::cout << "*** Debug: Read time (" << _string << ") = " << time << " sec\n";
#endif
return time;
}
// Remove multiple dots from the beginning and the end of the line
char* nodots(char* _text, int index, int num)
{
char* start=_text;
char* end;
int i=0;
while(*start++=='.');
strcpy(buffer,((index)?start-1:_text));
end=buffer+strlen(buffer);
if (index!=(num-1)){
while(*--end=='.') i++;
if(i>1) *(end+1)='\0';
}
return buffer;
}
// autodetect input file type (MicroDVD/SAM)
format detect(char* _bufor)
{
char* tmp=_bufor;
for(;*tmp; tmp++){
switch(*tmp){
case '{':
case '}':
return micro; // MicroDVD format
case ':':
return sam; // SAM format
}
}
return unknown; // unknown format
}
// return max value
long int max(long int a, long int b)
{ return (a>b)? a: b; }
// return min value
long int min(long int a, long int b)
{ return (a<b)? a: b; }
// return sign of the value
int sign(int num)
{ return (num)? ((num>0)? 1: -1):0; }
// converts number of frames into human readable time
// eg. 1h 3m 10s
void frames2time(std::ostream& s, long int _frames)
{
int hours, minutes, seconds;
long int tmp=(long int)(_frames/oframerate);
seconds=tmp%60;
tmp/=60;
minutes=tmp%60;
tmp/=60;
hours=tmp%60;
s << ((hours>9)? "":"0") << hours << ":";
s << ((minutes>9)? "":"0") << minutes << ":";
s << ((seconds>9)? "":"0") << seconds;
}
//----------------------------------
//--- The main class of subtitle ---
//----------------------------------
class line{ // object stores one line of text
private:
long int start; // begin
long int end; // end
unsigned int timetag : 1; // not enough time tag
char* text; // text string
line* previous; // pointer to the previous object
line* next; // pointer to the next object
line* manager(int,int, int);
int check(const int);
int compress(int,int);
void calc_line(int);
line* go(int);
void add(char*);
void print(std::ofstream&,long int);
public:
line(const unsigned long int, const unsigned long int, const char*, line* = 0, line* = 0);
~line();
void update(const unsigned long int, const unsigned long int, const char*);
void set_offset(double);
void calc_time();
void calc_framerate(double,double);
void clean();
void compact2(int);
int scan(what);
void load(char*, long int);
void save(char*, long int);
void display();
//friend std::ostream& operator<<(std::ostream&, line*);
friend long int max(long int, long int);
friend long int min(long int, long int);
};
line::line(const unsigned long int _start, const unsigned long int _end, const char* _text, line* _previous, line* _next)
{
start=_start;
end=_end;
text=new char[strlen(_text)+1];
strcpy(text,_text);
timetag=0;
previous=(_previous)?_previous:0;
next=(_next)?_next:0;
}
line::~line(void)
{
if (text) delete text;
if (previous) previous->next=next;
if (next) next->previous=previous;
}
/*std::ostream& operator<<(std::ostream& s, line* l)
{
line* tmp=l;
while (tmp) {
if (disable->endtime)
s << "{" << tmp->start << "}{}" << tmp->text<<"\n";
else s << "{" << tmp->start << "}{" << tmp->end << "}" << tmp->text<<"\n";
tmp=tmp->next;
};
return s;
}*/
// Update object values
void line::update(const unsigned long int _start, const unsigned long int _end, const char* _text)
{
start=_start;
end=_end;
delete text;
text=new char[strlen(_text)+1];
strcpy(text,_text);
}
// Move forward and backward between objects(lines)
line* line::go(int num)
{
line* tmp=this;
int i;
switch(sign(num)){
case 1:
for(i=0; i<num; i++)
if (!(tmp=tmp->next)) return 0;
break;
case -1:
for(i=num; i; i++)
if (!(tmp=tmp->previous)) return 0;
break;
}
return tmp;
}
// Concatenate string to existing text
void line::add(char* add)
{
char* new_string=new char[strlen(text)+strlen(add)+2];
strcpy(new_string,text);
strcat(new_string,"|");
strcat(new_string,add);
delete text;
text=new_string;
}
// Load input file
void line::load(char* _filename, long int _jointime)
{
static line* current=0; // pointer to last record (static need when joining subtitles)
line* next; // next line
long int start;
long int end;
unsigned int current_line=0;
unsigned int hours;
unsigned int minutes;
unsigned int seconds;
char* tmp1;
char* tmp2;
#ifdef _WIN32
std::ifstream inputf(_filename, ios::nocreate);
#else
std::ifstream inputf(_filename);
#endif
if (!inputf) error(1,_filename,NULL);
while (inputf.getline(buffer,BUFFER_SIZE))
{
if (!disable->autodetect) iformat=detect(buffer);
current_line++;
switch (iformat){
case micro: // MicroDVD format
tmp1=buffer;
while(*(tmp1++)!='{'&&(*tmp1));
start=strtoul(tmp1,&tmp2,10)+_jointime;
if (tmp1==tmp2) {warning(1,¤t_line,_filename,"Missing startframe"); continue;}
tmp1=tmp2;
while(*(tmp1++)!='{'&&(*tmp1));
if (!*tmp1) {warning(1,¤t_line,_filename,"No endframe"); continue;}
end=strtoul(tmp1,&tmp2,10)+_jointime;
if (!*tmp2||!*(tmp2+1)) {warning(1,¤t_line,_filename,"No text"); continue;}
nlines++;
break;
case sam: // SAM format
tmp1=buffer;
hours=strtoul(tmp1,&tmp2,10);
if (tmp1==tmp2) {warning(1,¤t_line,_filename,"Missing hours"); continue;}
tmp1=tmp2+1;
minutes=strtoul(tmp1,&tmp2,10);
if (tmp1==tmp2) {warning(1,¤t_line,_filename,"Missing minutes"); continue;}
tmp1=tmp2+1;
seconds=strtoul(tmp1,&tmp2,10);
if (tmp1==tmp2) {warning(1,¤t_line,_filename,"Missing seconds"); continue;}
start=(long int)((hours*3600+minutes*60+seconds)*iframerate)+_jointime;
if (current) {
if (start==current->start) {current->add(tmp2+1); continue;}
if (start<current->start) {warning(1,¤t_line,_filename,"Time integrity"); continue;} }
end=0;
nlines++;
break;
default:
warning(1,¤t_line,_filename,"Unknown format");
continue;
}
if ((!_jointime)&&(!disable->join)&&(start>=jointime)) {warning(1,¤t_line,_filename,"Time overlap"); continue;}
next=new line(start,end,tmp2+1);
if (current) { current->next=next; next->previous=current; }
else { root=next; next->previous=0; }
current=next;
}
inputf.close();
}
// Print single line to output file in specified format
void line::print(std::ofstream& file, long int _offset)
{
if (disable->cut || (((this->start>cut1time)&&(this->end<cut2time))||
((cut1time>this->start)&&(cut1time<this->end))||
((cut2time>this->start)&&(cut2time<this->end))) )
if (disable->endtime)
switch(oformat){
case micro:
file <<"{"<<(((disable->cut)?this->start:max(this->start,cut1time))-_offset)<<"}{}"<<this->text<<"\n";
break;
case sam:
frames2time(file,((disable->cut)?this->start:max(this->start,cut1time))-_offset);
file <<":"<<this->text<<"\n";
break;
}
else switch(oformat){
case micro:
file<<"{"<<(((disable->cut)?this->start:max(this->start,cut1time))-_offset)<<"}{"<<(((disable->cut)?this->end:min(this->end,cut2time))-_offset)<<"}"<<this->text<<"\n";
break;
case sam:
frames2time(file,((disable->cut)?this->start:max(this->start,cut1time))-_offset);
file <<":"<<this->text<<"\n";
break;
}
}
// Save file
void line::save(char* _filename, long int _splittime)
{
char* name=new char[strlen(_filename)+3];
line* tmp=this;
unsigned int linenum=0; // line number
if (disable->split) {
std::ofstream outputf(_filename);
if (!outputf) error(2,_filename,NULL);
while (tmp) {
linenum++;
if (tmp->scan(subs)>5) warning(2,&linenum,_filename,"");
tmp->print(outputf,0);
tmp=tmp->next;
};
outputf.close();}
else {
strcpy(name,_filename);
strcat(name,".1");
std::ofstream part1(name);
if (!part1) error(2,name,NULL);
while (tmp && (tmp->start<_splittime)) {
linenum++;
if (tmp->scan(subs)>5) warning(2,&linenum,name,"");
tmp->print(part1,0);
tmp=tmp->next;
};
part1.close();
strcpy(name,_filename);
strcat(name,".2");
std::ofstream part2(name);
if (!part2) error(2,name,NULL);
linenum=0;
while (tmp) {
linenum++;
if (tmp->scan(subs)>5) warning(2,&linenum,name,"");
tmp->print(part2,_splittime);
tmp=tmp->next;
};
}
delete name;
}
// Perform time translations on one line only
// or set timetag depending on situation
void line::calc_line(int calc)
{
long int value;
long int vmin;
int length;
length=strlen(this->text);
value=this->start+(unsigned long int)((oframerate*(b+(a*sqrt(length))+(c*length))));
if (this->next) // if next exists
if ((this->next->start)>value)
{this->timetag=0; if (calc) this->end=value;}
else {this->timetag=1;
if(calc){
this->end=this->next->start-1;
if(!disable->compact1) {// change start time if -tt specified
this->timetag=0;
vmin=min(value-end,min(tolerance1,this->start-this->previous->end));
if (vmin<(value-this->end)) this->timetag=1;
this->start-=vmin-1;}}}
else {this->timetag=0; this->end=value;}
}
// Time transformation subroutine
void line::calc_time(void)
{
line* tmp=this;
while (tmp){
tmp->calc_line(1);
tmp=tmp->next;
};
}
// Change framerate subroutine
void line::calc_framerate(double _iframerate, double _oframerate)
{
line* tmp=this;
while (tmp){
tmp->start=(long int)(tmp->start*_oframerate/_iframerate);
tmp->end=(long int)(tmp->end*_oframerate/_iframerate);
tmp=tmp->next;
};
}
// Change offset subroutine
void line::set_offset(double _offset)
{
line* tmp=this;
double time;
while (tmp){
if ((tmp->start+(long int)(oframerate*_offset))<0) {
time=-tmp->start/oframerate;
error(21,&time,NULL);
}
tmp->start+=(long int)(oframerate*_offset);
tmp->end+=(long int)(oframerate*_offset);
tmp=tmp->next;
};
}
// Move new-line('|') character to optimal place in the sentence
// ( after ,.;:!? characters) if possible
void post(char* _start)
{
char* _end=_start+strlen(_start);
char* tmp=_end;
char* tmp2;
while(tmp>_start){
while((*tmp!='|'||(*tmp=='|'&&(*(tmp+1)=='-')))&&(tmp>_start)) tmp--;
tmp2=tmp;
while((tmp>_start)&&((_end-tmp)<=compactchars))
{
if ((*tmp==' ')&&(whatsign(*(tmp-1)) == 2))
{
*tmp2=' ';
*tmp='|';
tmp2=tmp;
break;
}
tmp--;
}
_end=tmp2;
tmp=tmp2-1;
}
return;
}
// Remove unnecessary white characters
void line::clean(void)
{
line* tmp=this;
char* c;
char* ostring;
char* nstring; // pointer to the new string
int prv; // previous character indicator:
// 1 - regular; 0 - new line; -1 - white
while (tmp){
prv=0;
ostring=tmp->text;
nstring=new char[strlen(tmp->text)+1];
c=nstring;
while (*ostring) {
switch(whatsign(*ostring)){
case 0: // regular
*c++=*ostring;
prv=1;
break;
case 1: // white
if (prv>0) {*c++=' '; prv=-1;}
break;
case 2: // special
if (prv<0)
if (c!=nstring) *(c-1)=*ostring;
else *c++=*ostring;
else *c++=*ostring;
prv=1;
break;
case 3: // new line '|'
if (prv>0) *c++=*ostring;
else if (c!=nstring) *(c-1)=*ostring;
prv=0;
break;
case 4: // new-line characters \n \r
if (prv>0) {*c++=' '; prv=-1;}
break;
}
ostring++;
}
(prv<0)? *(c-1)='\0':*c='\0';
delete tmp->text;
tmp->text=nstring;
tmp=tmp->next;
};
}
// Cut off line with accuracy +- 1 word
char* cutline(char* _start,const int _limit)
{
char* tmp=_start;
char* prv=0;
while(*tmp&&(((tmp-_start)<=_limit)||!prv)){
if((*tmp==' ')||(*tmp=='|'))
if ((*tmp=='|')&&(*(tmp+1)=='-')) {prv=tmp; break;} else
{prv=tmp; *tmp=' ';}
tmp++;}
if(!*tmp&&(tmp-_start<=_limit)) prv=tmp;
return prv;
}
// Compress line. Return pointer to the next line
int line::compress(int num, int mode)
{
line* tmp;
line* tmp2;
char** table; // text table
char* bufor; // bufor with lines being compressed
long int** stable; // pointer table to time table
long int* size; // time table
char* start;
char* end;
int count;
int i,j;
int length;
for (i=0,count=0,length=0,tmp=this; ((i<num)&&tmp); i++)
{count+=tmp->scan(subs);
length+=strlen(nodots(tmp->text,i,num))+1;
tmp=tmp->next;}
table = new char*[count+2];
stable = new long int*[count+2];
bufor = new char[length];
size = new long int[length];
// copy next lines
for (i=0,start=bufor,tmp=this; ((i<num)&&tmp); i++) {
#ifdef DEBUG
// std::cout << "***Debug: In(" << strlen(tmp->text) << "): '" << tmp->text << "'\n";
#endif
strcpy(start,nodots(tmp->text,i,num));
#ifdef DEBUG
// std::cout << "***Debug: Line(" << strlen(buffer) << "): '" << buffer << "'\n";
#endif
for (j=0; j<(strlen(buffer)+1); j++)
size[start-bufor+j]=tmp->start+j*(tmp->end-tmp->start)/(strlen(buffer));
start+=strlen(buffer)+1;
*(start-1)='|';
tmp=tmp->next;
}
*(start-1)='\0';
start=bufor;
end=bufor;
i=0; j=0;
#ifdef DEBUG
//std::cout << "***Debug: Merged(" << strlen(bufor) << "): '" << bufor << "'\n";
#endif
if(count>compactlines)
{ // divide lines by <compactchars> characters
while((end=cutline(end,compactchars))&&(j<count+2))
{
if ((++i<compactlines)&&(*end)) *end='|';
else {
*end='\0';
i=0;
table[j]=start;
stable[j++]=size+(start-bufor);
start=end+1;
}
if (end==(bufor+length-1)) break;
end++;
}
}
else { // when merged lines can fit into one subtitle
table[j]=bufor;
stable[j++]=size;
}
// when compressed text is longer than original one
if (j<count+2)
{
stable[j]=size+length-1;
// Move '|' to the end of the sentence is possible
for(i=0; i<j; i++) post(table[i]);
// when compression was successful or mode=1
if ((j<num)||((j==num)&&mode)) {
#ifdef DEBUG
std::cout << "***Debug: -----j:"<<j<<" num:"<<num<<" mode:"<<mode<<"-----\n";
for (i=0, tmp=this; ((i<num)&&tmp); i++, tmp=tmp->next)
std::cout << "***Debug: <<<{" << tmp->start<<"}{"<<tmp->end<<"}"<<tmp->text <<"\n";
for (i=0; i<j; i++)
std::cout << "***Debug: >>>{" <<*stable[i]<<"}{"<<*stable[i+1]<<"}"<<table[i]<<"\n";
#endif
// Rewrite compresses subtitle into table and free resources
for (i=num-1,tmp=this->go(num-1); i>=0; i--)
{ if (i>=j) // delete
{ tmp2=tmp;
delete tmp2; }
else // rewrite
{ tmp->update(*stable[i],*stable[i+1],table[i]);
tmp->calc_line(0); }
tmp=tmp->previous; }
}
}
// Free memory
delete bufor;
delete size;
delete table;
delete stable;
return (j<num)? 1: 0;
}
// Check if line can/needs to be compressed
int line::check(const int num)
{
line* tmp=this;
int i=0;
while(i++<num){
if(!tmp) return 0;
if(tmp->next)
if((tmp->next->start-tmp->end)>tolerance2) return 0;
tmp=tmp->next; }
return 1;
}
// Compression manager which sets what should be compressed,
// in which order and in what offset
// first - size of first window
// last - size of the last window
// mode - 0: normal, 1: accept only if no lossless compression
line* line::manager(int first, int last, int mode)
{
line* tmp;
int i,j;
for(i=first; i<=last; i++) // offsets
for(j=i-1; j>=0; j--) // offset windows
{ tmp=this->go(-j);
if(!tmp) break;
else if (tmp->check(i)||mode) // do not check if mode=1
if(tmp->compress(i,mode)) return tmp; }
return this->next;
}
void line::compact2(int num)
{
line* tmp=this;
while(tmp){
if ((!disable->forcecompact)&&((tmp->scan(subchars)>compactchars)||(tmp->scan(subs)>compactlines)))
tmp=tmp->manager(1,num,1);
else if (tmp->scan(subs)>compactlines) tmp->compress(1,1);
if(tmp->timetag) tmp=tmp->manager(2,num,0);
else tmp=tmp->next;
}
}
// Generate stats (number of lines, characters etc.)
int line::scan(what _what)
{
line* tmp=this;
char* start;
char* end;
int max_lines=0;
int count_lines;
int max_chars=0;
int count_chars;
int counter=1;
unsigned short tag;
while(tmp){
start=tmp->text;
count_lines=0;
tag=0;
do {
end=strchr(start,'|');
count_chars=(end)?end-start:strlen(start);
if (clength<0)
{if (count_chars<abs(clength)) tag=1;}
else {if (count_chars>clength) tag=1;};
start=end+1;
max_chars=max(count_chars,max_chars);
count_lines++;
} while (end);
if ((_what==length)&&tag)
if (llength<0)
{if (count_lines<abs(llength)) std::cout<<" "<<counter;}
else {if (count_lines>llength) std::cout<<" "<<counter;};
if (_what==subs) return count_lines;
if (_what==subchars) return max_chars;
max_lines=max(count_lines,max_lines);
tmp=tmp->next;
counter++;
};
if (_what==lines) return max_lines;
else return max_chars;
}
void line::display()
{
if (!disable->verbose||(pmode==info)) {
std::cout << "--------------------------------------------------------------------------------\n";
if (pmode!=info) {
std::cout << "Input framerate: "<<iframerate<<" [fr/s]\n";
std::cout << "Output framerate: "<<oframerate<<" [fr/s]\n";
std::cout << "Time: "<<a<<" "<<b<<" "<<c<<"\n";
std::cout << "Offset: "<<offset<<" [s]\n";}
std::cout << "Lines: "<< nlines <<"\n";
std::cout << "Max lines in subtitle: "<<root->scan(lines)<<"\n";
std::cout << "Max chars in line: "<<root->scan(chars)<<"\n";
if (!disable->length)
{std::cout << "Lines "<<((clength<0)? "<":">")<<abs(clength)<<" chars and "<<((llength<0)?"<":">")<<llength<<" subs:";
root->scan(length); std::cout << "\n";}
std::cout << "--------------------------------------------------------------------------------\n";}
}
#ifdef _WIN32
// give intermediate path between base and source
char* path(char* base, char* source)
{
char* start1=base;
char* start2=source;
char* out=buffer;
int size=128;
char* cwd=new char[size];
while (!getcwd(cwd,size)) {delete cwd; cwd=new char[size+=64];}
if ((*start1!='.')&&(*(start1+1)!=':'))
{
start1=new char[strlen(cwd)+strlen(base)+2];
strcpy(start1,cwd);
strcpy(start1+strlen(cwd),"\\");
strcpy(start1+strlen(cwd)+1,base);
}
if ((*start2!='.')&&(*(start2+1)!=':'))
{
start2=new char[strlen(cwd)+strlen(source)+2];
strcpy(start2,cwd);
strcpy(start2+strlen(cwd),"\\");
strcpy(start2+strlen(cwd)+1,source);
}
if (strrchr(start1,'\\')&&strrchr(start2,'\\'))
while(!COMPARESTRING(start1,start2,strchr(start1,'\\')+1-start1))
{start1=strchr(start1,'\\')+1;
start2=strchr(start2,'\\')+1;}
while ((start1=strchr(start1,'\\')))
{strcpy(out,"..\\");
out+=3;
start1++;}
if (strrchr(start2,'\\'))
{strncpy(out,start2,strrchr(start2,'\\')-start2);
*(out+(strrchr(start2,'\\')-start2))='\0';}
else if (out==buffer) strcpy(out,".");
return buffer;
}
#endif
// check if paramaters is known (in parameters table)
// and return its number
int test_param(char* param)
{
int i;
for(i=0; i<(sizeof(params)/sizeof(char*)); i++) if (!strcmp(param,params[i])) return i;
return -1; // not found
}
// Parse input parameters
int parse_input(int argc,char* argv[])
{
int i=0;
int j=3;
int k;
char* tmp;
while(++i<argc)
switch(argv[i][0]){
case '-':
switch(test_param(argv[i]+1)){
case 0:
if ((i+1)<argc){
iframerate=strtod(argv[i+1],&tmp);
if(!disable->oframerate) oframerate=iframerate;
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(51,"-if",NULL);
}
else error(50,"-if",NULL);
i++;
break;
case 1:
if ((i+3)<argc){
a=strtod(argv[i+1],&tmp);
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(52,"first ","-t");
b=strtod(argv[i+2],&tmp);
if (tmp!=argv[i+2]+strlen(argv[i+2])) error(52,"second ","-t");
c=strtod(argv[i+3],&tmp);
if (tmp!=argv[i+3]+strlen(argv[i+3])) error(52,"third ","-t");
}
else error(50,"-t",NULL);
i+=3;
break;
case 2:
disable->endtime=1;
break;
case 3:
if ((i+1)<argc){
offset=strtod(argv[i+1],&tmp);
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(51,"-o",NULL);
} else error(50,"-o",NULL);
i++;
break;
case 4:
disable->calculate=1;
break;
case 5:
if ((i+1)<argc){
oframerate=strtod(argv[i+1],&tmp);
disable->oframerate=1;
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(51,"-of",NULL);
}
else error(50,"-of",NULL);
i++;
break;
case 6:
disable->verbose=0;
break;
case 7:
pmode=info;
break;
case 8:
if ((i+1)<argc){
splittime=(long int)(oframerate*gettime(argv[i+1],"","-s"));
disable->split=0;
}
else error(50,"-s",NULL);
i++;
break;
case 9:
if ((i+1)<argc){
jointime=(long int)(iframerate*gettime(argv[i+1],"","-j"));
disable->join=0;
}
else error(50,"-j",NULL);
i++;
break;
case 10:
if ((i+2)<argc){
cut1time=(long int)(oframerate*gettime(argv[i+1],"first ","-c"));
cut2time=(long int)(oframerate*gettime(argv[i+2],"second ","-c"));
if (cut1time>=cut2time) error(17,NULL,NULL);
disable->cut=0;
}
else error(50,"-c",NULL);
i+=2;
break;
case 11:
if ((i+2)<argc){
tolerance1=(long int)(oframerate*strtod(argv[i+1],&tmp))+1;
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(52,"first ","-tol");
tolerance2=(long int)(oframerate*strtod(argv[i+2],&tmp));
if (tmp!=argv[i+2]+strlen(argv[i+2])) error(52,"second ","-tol");
}
else error(50,"-tol",NULL);
disable->tolerance=0;
i+=2;
break;
case 12:
disable->compact0=0;
disable->compact2=0;
break;
case 13:
disable->compact0=0;
break;
case 14:
disable->compact1=0;
break;
case 15:
if ((i+3)<argc){
compactlines=strtol(argv[i+1],&tmp,10);
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(52,"first ","-m1");
compactchars=strtol(argv[i+2],&tmp,10);
if (tmp!=argv[i+2]+strlen(argv[i+2])) error(52,"second ","-m1");
compactoffset=strtol(argv[i+3],&tmp,10);
if (tmp!=argv[i+3]+strlen(argv[i+3])) error(52,"third ","-m1");
}
else error(50,"-m1",NULL);
disable->compact2=0;
i+=3;
break;
case 16:
if((i+2)<argc){
clength=strtol(argv[i+1],&tmp,10);
if (tmp!=argv[i+1]+strlen(argv[i+1])) error(51,"-l",NULL);
llength=strtol(argv[i+2],&tmp,10);
if (tmp!=argv[i+2]+strlen(argv[i+2])) error(51,"-l",NULL);
}
else error(50,"-l",NULL);
disable->length=0;
i+=2;
break;
case 17:
disable->forcecompact=0;
break;
#ifdef _WIN32
case 18:
if ((i+1)<argc){
language=new char[min(strlen(argv[i+1]),3)+strlen(argv[i+1])+2];
tmp=language;
for (k=0; k<min(strlen(argv[i+1]),3); k++) *(language+k)=toupper(*(argv[i+1]+k));
*(language+k)=' ';
strcpy(language+min(strlen(argv[i+1]),3)+1,argv[i+1]);
pmode=generate;
}
else error(50,"-g",NULL);
i++;
break;
#endif
case 19:
oformat=micro;
break;
case 20:
oformat=sam;
break;
case 21:
disable->autodetect=1;
iformat=micro;
break;
case 22:
disable->autodetect=1;
iformat=sam;
break;
default:
error(4,argv[i],NULL);
break;
}
break;
default:
switch(j--){
case 3:
input=new char[strlen(argv[i])+1];
strcpy(input,argv[i]);
break;
case 2:
if (disable->join&&pmode!=generate) {
output=new char[strlen(argv[i])+1];
strcpy(output,argv[i]);}
else {
join=new char[strlen(argv[i])+1];
strcpy(join,argv[i]);}
break;
case 1:
output=new char[strlen(argv[i])+1];
strcpy(output,argv[i]);
break;
default:
error(9,NULL,NULL);
break;
}
break;
}
return j;
}
void check_input(int j)
{
if ((!disable->compact1)||(!disable->compact2))
if(disable->calculate)
{disable->calculate=0;
warning(3,"time calculations","-m1 or/and -tt","");}
if (iframerate<=0) error(53,"Input framerate","positive");
if (oframerate<=0) error(53,"Output framerate","positive");
if (compactlines<=0) error(53,"First -m1","positive");
if (compactchars<=0) error(53,"Second -m1","positive");
if (compactoffset<=0) error(53,"Third -m1","positive");
if (disable->tolerance)
{tolerance2=(long int)(2*oframerate);
tolerance1=(long int)(0.5*oframerate)+1;}
if ((!disable->length))
if (pmode!=info) {pmode=info; warning(3,"info","-l","");}
if (!disable->forcecompact)
if (disable->compact2)
{disable->compact2=0;
warning(3,"compacting","-f","");}
if ((pmode!=normal)&&(!disable->join)) error(16,NULL,NULL);
if (j-(disable->join&(pmode!=generate))+((pmode!=info)&disable->length)-1) error(9,NULL,NULL);
}
//----------Main-program-----------
int main(int argc, char* argv[])
{ char* tmp;
// Display help when no parameters
if (argc==1) {
if (strrchr(argv[0],'/')) help(strrchr(argv[0],'/')+1);
else if (strrchr(argv[0],'\\')) help(strrchr(argv[0],'\\')+1);
else help(argv[0]);
exit(0);}
// Read and check input parameters
check_input(parse_input(argc,argv));
root->load(input, 0);
if (!disable->join) root->load(join, jointime);
root->display();
if (oformat==unknown) oformat=iformat;
switch(pmode){
case normal:
// calculations
if (!disable->compact0) root->clean();
if (iframerate!=oframerate) root->calc_framerate(iframerate,oframerate);
if (offset) root->set_offset(offset);
if ((!disable->calculate) && (!disable->endtime)) root->calc_time();
if (!disable->compact2) root->compact2(compactoffset);
// Write output to file
root->save(output,splittime);
break;
case info:
break;
#ifdef _WIN32
case generate:
// Check if videofile exists
#ifdef _WIN32
std::ifstream video(join, ios::nocreate);
#else
std::ifstream video(join, ios::nocreate);
#endif
if (!video) error(1,join,NULL);
video.close();
if (!(strstr(output,".mvd")||strstr(output,".MVD")))
{ tmp=new char[strlen(output)+5];
strcpy(tmp,output);
strcpy(tmp+strlen(output),".mvd");
delete output;
output=tmp; }
std::ofstream outputf(output);
if (!outputf) error(2,output,NULL);
outputf << "[Micro DVD Ini File]\n\n";
outputf << "[MAIN]\nTitle=";
if ((tmp=strrchr(input, '\\'))) tmp++;
else tmp=input;
while(*tmp&&*tmp!='.') outputf << (char)toupper(*tmp++);
outputf <<"\n\n";
outputf << "[MOVIE]\nDirectory="<<path(output,join)<<"\nAVIName=";
if ((tmp=strrchr(join,'\\'))){
outputf <<(tmp+1)<<"\n\n";
}
else outputf << join<<"\n\n";
outputf << "[SUBTITLES]\nLines="<<root->scan(lines)<<"\nDirectory="<<path(output,input)<<"\n1="<<language<<"\nFile=";
if ((tmp=strrchr(input,'\\'))){
outputf <<(tmp+1)<<"\n";
}
else outputf <<input<<"\n";
break;
#endif
}
return 0;
}