Aegisub/vsfilter/subtitles/VobSubFile.cpp

2369 lines
57 KiB
C++

/*
* Copyright (C) 2003-2006 Gabest
* http://www.gabest.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
#include "stdafx.h"
#include <winioctl.h>
#include "TextFile.h"
#include "..\include\unrar\unrar.h"
#include "VobSubFile.h"
//
struct lang_type {unsigned short id; LPCSTR lang_long;} lang_tbl[] =
{
{'--', "(Not detected)"},
{'cc', "Closed Caption"},
{'aa', "Afar"},
{'ab', "Abkhazian"},
{'af', "Afrikaans"},
{'am', "Amharic"},
{'ar', "Arabic"},
{'as', "Assamese"},
{'ay', "Aymara"},
{'az', "Azerbaijani"},
{'ba', "Bashkir"},
{'be', "Byelorussian"},
{'bg', "Bulgarian"},
{'bh', "Bihari"},
{'bi', "Bislama"},
{'bn', "Bengali; Bangla"},
{'bo', "Tibetan"},
{'br', "Breton"},
{'ca', "Catalan"},
{'co', "Corsican"},
{'cs', "Czech"},
{'cy', "Welsh"},
{'da', "Dansk"},
{'de', "Deutsch"},
{'dz', "Bhutani"},
{'el', "Greek"},
{'en', "English"},
{'eo', "Esperanto"},
{'es', "Espanol"},
{'et', "Estonian"},
{'eu', "Basque"},
{'fa', "Persian"},
{'fi', "Finnish"},
{'fj', "Fiji"},
{'fo', "Faroese"},
{'fr', "Francais"},
{'fy', "Frisian"},
{'ga', "Irish"},
{'gd', "Scots Gaelic"},
{'gl', "Galician"},
{'gn', "Guarani"},
{'gu', "Gujarati"},
{'ha', "Hausa"},
{'he', "Hebrew"},
{'hi', "Hindi"},
{'hr', "Hrvatski"},
{'hu', "Hungarian"},
{'hy', "Armenian"},
{'ia', "Interlingua"},
{'id', "Indonesian"},
{'ie', "Interlingue"},
{'ik', "Inupiak"},
{'in', "Indonesian"},
{'is', "Islenska"},
{'it', "Italiano"},
{'iu', "Inuktitut"},
{'iw', "Hebrew"},
{'ja', "Japanese"},
{'ji', "Yiddish"},
{'jw', "Javanese"},
{'ka', "Georgian"},
{'kk', "Kazakh"},
{'kl', "Greenlandic"},
{'km', "Cambodian"},
{'kn', "Kannada"},
{'ko', "Korean"},
{'ks', "Kashmiri"},
{'ku', "Kurdish"},
{'ky', "Kirghiz"},
{'la', "Latin"},
{'ln', "Lingala"},
{'lo', "Laothian"},
{'lt', "Lithuanian"},
{'lv', "Latvian, Lettish"},
{'mg', "Malagasy"},
{'mi', "Maori"},
{'mk', "Macedonian"},
{'ml', "Malayalam"},
{'mn', "Mongolian"},
{'mo', "Moldavian"},
{'mr', "Marathi"},
{'ms', "Malay"},
{'mt', "Maltese"},
{'my', "Burmese"},
{'na', "Nauru"},
{'ne', "Nepali"},
{'nl', "Nederlands"},
{'no', "Norsk"},
{'oc', "Occitan"},
{'om', "(Afan) Oromo"},
{'or', "Oriya"},
{'pa', "Punjabi"},
{'pl', "Polish"},
{'ps', "Pashto, Pushto"},
{'pt', "Portugues"},
{'qu', "Quechua"},
{'rm', "Rhaeto-Romance"},
{'rn', "Kirundi"},
{'ro', "Romanian"},
{'ru', "Russian"},
{'rw', "Kinyarwanda"},
{'sa', "Sanskrit"},
{'sd', "Sindhi"},
{'sg', "Sangho"},
{'sh', "Serbo-Croatian"},
{'si', "Sinhalese"},
{'sk', "Slovak"},
{'sl', "Slovenian"},
{'sm', "Samoan"},
{'sn', "Shona"},
{'so', "Somali"},
{'sq', "Albanian"},
{'sr', "Serbian"},
{'ss', "Siswati"},
{'st', "Sesotho"},
{'su', "Sundanese"},
{'sv', "Svenska"},
{'sw', "Swahili"},
{'ta', "Tamil"},
{'te', "Telugu"},
{'tg', "Tajik"},
{'th', "Thai"},
{'ti', "Tigrinya"},
{'tk', "Turkmen"},
{'tl', "Tagalog"},
{'tn', "Setswana"},
{'to', "Tonga"},
{'tr', "Turkish"},
{'ts', "Tsonga"},
{'tt', "Tatar"},
{'tw', "Twi"},
{'ug', "Uighur"},
{'uk', "Ukrainian"},
{'ur', "Urdu"},
{'uz', "Uzbek"},
{'vi', "Vietnamese"},
{'vo', "Volapuk"},
{'wo', "Wolof"},
{'xh', "Xhosa"},
{'yi', "Yiddish"}, // formerly ji
{'yo', "Yoruba"},
{'za', "Zhuang"},
{'zh', "Chinese"},
{'zu', "Zulu"},
};
int find_lang(unsigned short id)
{
int mid, lo = 0, hi = countof(lang_tbl) - 1;
while(lo < hi)
{
mid = (lo + hi) >> 1;
if(id < lang_tbl[mid].id) hi = mid;
else if(id > lang_tbl[mid].id) lo = mid + 1;
else return(mid);
}
return(id == lang_tbl[lo].id ? lo : 0);
}
CString FindLangFromId(WORD id)
{
return CString(lang_tbl[find_lang(id)].lang_long);
}
//
// CVobSubFile
//
CVobSubFile::CVobSubFile(CCritSec* pLock)
: ISubPicProviderImpl(pLock)
, m_sub(1024*1024)
{
}
CVobSubFile::~CVobSubFile()
{
}
//
bool CVobSubFile::Copy(CVobSubFile& vsf)
{
Close();
*(CVobSubSettings*)this = *(CVobSubSettings*)&vsf;
m_title = vsf.m_title;
m_iLang = vsf.m_iLang;
m_sub.SetLength(vsf.m_sub.GetLength());
m_sub.SeekToBegin();
for(int i = 0; i < 32; i++)
{
SubLang& src = vsf.m_langs[i];
SubLang& dst = m_langs[i];
dst.id = src.id;
dst.name = src.name;
dst.alt = src.alt;
for(int j = 0; j < src.subpos.GetCount(); j++)
{
SubPos& sp = src.subpos[j];
if(!sp.fValid) continue;
if(sp.filepos != vsf.m_sub.Seek(sp.filepos, CFile::begin))
continue;
sp.filepos = m_sub.GetPosition();
BYTE buff[2048];
vsf.m_sub.Read(buff, 2048);
m_sub.Write(buff, 2048);
WORD packetsize = (buff[buff[0x16]+0x18]<<8) | buff[buff[0x16]+0x19];
for(int k = 0, size, sizeleft = packetsize - 4;
k < packetsize - 4;
k += size, sizeleft -= size)
{
int hsize = buff[0x16]+0x18 + ((buff[0x15]&0x80) ? 4 : 0);
size = min(sizeleft, 2048 - hsize);
if(size != sizeleft)
{
while(vsf.m_sub.Read(buff, 2048))
{
if(!(buff[0x15]&0x80) && buff[buff[0x16]+0x17] == (i|0x20)) break;
}
m_sub.Write(buff, 2048);
}
}
dst.subpos.Add(sp);
}
}
m_sub.SetLength(m_sub.GetPosition());
return(true);
}
//
void CVobSubFile::TrimExtension(CString& fn)
{
int i = fn.ReverseFind('.');
if(i > 0)
{
CString ext = fn.Mid(i).MakeLower();
if(ext == _T(".ifo") || ext == _T(".idx") || ext == _T(".sub")
|| ext == _T(".sst") || ext == _T(".son") || ext == _T(".rar"))
fn = fn.Left(i);
}
}
bool CVobSubFile::Open(CString fn)
{
TrimExtension(fn);
do
{
Close();
int ver;
if(!ReadIdx(fn + _T(".idx"), ver))
break;
if(ver < 6 && !ReadIfo(fn + _T(".ifo")))
break;
if(!ReadSub(fn + _T(".sub")) && !ReadRar(fn + _T(".rar")))
break;
m_title = fn;
for(int i = 0; i < 32; i++)
{
CAtlArray<SubPos>& sp = m_langs[i].subpos;
for(int j = 0; j < sp.GetCount(); j++)
{
sp[j].stop = sp[j].start;
sp[j].fForced = false;
int packetsize = 0, datasize = 0;
BYTE* buff = GetPacket(j, packetsize, datasize, i);
if(!buff) continue;
m_img.delay = j < (sp.GetCount()-1) ? sp[j+1].start - sp[j].start : 3000;
m_img.GetPacketInfo(buff, packetsize, datasize);
if(j < (sp.GetCount()-1)) m_img.delay = min(m_img.delay, sp[j+1].start - sp[j].start);
sp[j].stop = sp[j].start + m_img.delay;
sp[j].fForced = m_img.fForced;
if(j > 0 && sp[j-1].stop > sp[j].start)
sp[j-1].stop = sp[j].start;
delete [] buff;
}
}
return(true);
}
while(false);
Close();
return(false);
}
bool CVobSubFile::Save(CString fn, SubFormat sf)
{
TrimExtension(fn);
CVobSubFile vsf(NULL);
if(!vsf.Copy(*this))
return(false);
switch(sf)
{
case VobSub: return vsf.SaveVobSub(fn); break;
case WinSubMux: return vsf.SaveWinSubMux(fn); break;
case Scenarist: return vsf.SaveScenarist(fn); break;
case Maestro: return vsf.SaveMaestro(fn); break;
default: break;
}
return(false);
}
void CVobSubFile::Close()
{
InitSettings();
m_title.Empty();
m_sub.SetLength(0);
m_img.Invalidate();
m_iLang = -1;
for(int i = 0; i < 32; i++)
{
m_langs[i].id = 0;
m_langs[i].name.Empty();
m_langs[i].alt.Empty();
m_langs[i].subpos.RemoveAll();
}
}
//
bool CVobSubFile::ReadIdx(CString fn, int& ver)
{
CWebTextFile f;
if(!f.Open(fn))
return(false);
bool fError = false;
int id = -1, delay = 0, vobid = -1, cellid = -1;
__int64 celltimestamp = 0;
CString str;
for(int line = 0; !fError && f.ReadString(str); line++)
{
str.Trim();
if(line == 0)
{
TCHAR buff[] = _T("VobSub index file, v");
const TCHAR* s = str;
int i = str.Find(buff);
if(i < 0 || _stscanf(&s[i+_tcslen(buff)], _T("%d"), &ver) != 1
|| ver > VOBSUBIDXVER)
{
AfxMessageBox(_T("Wrong file version!"));
fError = true;
continue;
}
}
else if(!str.GetLength())
{
continue;
}
else if(str[0] == _T('#'))
{
TCHAR buff[] = _T("Vob/Cell ID:");
const TCHAR* s = str;
int i = str.Find(buff);
if(i >= 0)
{
_stscanf(&s[i+_tcslen(buff)], _T("%d, %d (PTS: %d)"), &vobid, &cellid, &celltimestamp);
}
continue;
}
int i = str.Find(':');
if(i <= 0) continue;
CString entry = str.Left(i).MakeLower();
str = str.Mid(i+1);
str.Trim();
if(str.IsEmpty()) continue;
if(entry == _T("size"))
{
int x, y;
if(_stscanf(str, _T("%dx%d"), &x, &y) != 2) fError = true;
m_size.cx = x;
m_size.cy = y;
}
else if(entry == _T("org"))
{
if(_stscanf(str, _T("%d,%d"), &m_x, &m_y) != 2) fError = true;
else m_org = CPoint(m_x, m_y);
}
else if(entry == _T("scale"))
{
if(ver < 5)
{
int scale = 100;
if(_stscanf(str, _T("%d%%"), &scale) != 1) fError = true;
m_scale_x = m_scale_y = scale;
}
else
{
if(_stscanf(str, _T("%d%%,%d%%"), &m_scale_x, &m_scale_y) != 2) fError = true;
}
}
else if(entry == _T("alpha"))
{
if(_stscanf(str, _T("%d"), &m_alpha) != 1) fError = true;
}
else if(entry == _T("smooth"))
{
str.MakeLower();
if(str.Find(_T("old")) >= 0 || str.Find(_T("2")) >= 0) m_fSmooth = 2;
else if(str.Find(_T("on")) >= 0 || str.Find(_T("1")) >= 0) m_fSmooth = 1;
else if(str.Find(_T("off")) >= 0 || str.Find(_T("0")) >= 0) m_fSmooth = 0;
else fError = true;
}
else if(entry == _T("fadein/out"))
{
if(_stscanf(str, _T("%d,%d"), &m_fadein, &m_fadeout) != 2) fError = true;
}
else if(entry == _T("align"))
{
str.MakeLower();
int i = 0, j = 0;
for(CString token = str.Tokenize(_T(" "), i);
j < 3 && !fError && !token.IsEmpty();
token = str.Tokenize(_T(" "), i), j++)
{
if(j == 0)
{
if(token == _T("on") || token == _T("1")) m_fAlign = true;
else if(token == _T("off") || token == _T("0")) m_fAlign = false;
else fError = true;
}
else if(j == 1)
{
if(token == _T("at")) {j--; continue;}
if(token == _T("left")) m_alignhor = 0;
else if(token == _T("center")) m_alignhor = 1;
else if(token == _T("right")) m_alignhor = 2;
else fError = true;
}
else if(j == 2)
{
if(token == _T("top")) m_alignver = 0;
else if(token == _T("center")) m_alignver = 1;
else if(token == _T("bottom")) m_alignver = 2;
else fError = true;
}
}
}
else if(entry == _T("time offset"))
{
bool fNegative = false;
if(str[0] == '-') fNegative = true;
str.TrimLeft(_T("+-"));
TCHAR c;
int hh, mm, ss, ms;
int n = _stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms);
m_toff = n == 1
? hh * (fNegative ? -1 : 1)
: n == 4+3
? (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1)
: fError = true, 0;
}
else if(entry == _T("forced subs"))
{
str.MakeLower();
if(str.Find(_T("on")) >= 0 || str.Find(_T("1")) >= 0) m_fOnlyShowForcedSubs = true;
else if(str.Find(_T("off")) >= 0 || str.Find(_T("0")) >= 0) m_fOnlyShowForcedSubs = false;
else fError = true;
}
else if(entry == _T("langidx"))
{
if(_stscanf(str, _T("%d"), &m_iLang) != 1) fError = true;
}
else if(entry == _T("palette"))
{
if(_stscanf(str, _T("%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x"),
&m_orgpal[0], &m_orgpal[1], &m_orgpal[2], &m_orgpal[3],
&m_orgpal[4], &m_orgpal[5], &m_orgpal[6], &m_orgpal[7],
&m_orgpal[8], &m_orgpal[9], &m_orgpal[10], &m_orgpal[11],
&m_orgpal[12], &m_orgpal[13], &m_orgpal[14], &m_orgpal[15]
) != 16) fError = true;
}
else if(entry == _T("custom colors"))
{
str.MakeLower();
if(str.Find(_T("on")) == 0 || str.Find(_T("1")) == 0) m_fCustomPal = true;
else if(str.Find(_T("off")) == 0 || str.Find(_T("0")) == 0) m_fCustomPal = false;
else fError = true;
i = str.Find(_T("tridx:"));
if(i < 0) {fError = true; continue;}
str = str.Mid(i + (int)_tcslen(_T("tridx:")));
int tridx;
if(_stscanf(str, _T("%x"), &tridx) != 1) {fError = true; continue;}
tridx = ((tridx&0x1000)>>12) | ((tridx&0x100)>>7) | ((tridx&0x10)>>2) | ((tridx&1)<<3);
i = str.Find(_T("colors:"));
if(i < 0) {fError = true; continue;}
str = str.Mid(i + (int)_tcslen(_T("colors:")));
RGBQUAD pal[4];
if(_stscanf(str, _T("%x,%x,%x,%x"), &pal[0], &pal[1], &pal[2], &pal[3]) != 4) {fError = true; continue;}
SetCustomPal(pal, tridx);
}
else if(entry == _T("id"))
{
str.MakeLower();
int langid = ((str[0]&0xff)<<8)|(str[1]&0xff);
i = str.Find(_T("index:"));
if(i < 0) {fError = true; continue;}
str = str.Mid(i + (int)_tcslen(_T("index:")));
if(_stscanf(str, _T("%d"), &id) != 1 || id < 0 || id >= 32) {fError = true; continue;}
m_langs[id].id = langid;
m_langs[id].name = lang_tbl[find_lang(langid)].lang_long;
m_langs[id].alt = lang_tbl[find_lang(langid)].lang_long;
delay = 0;
}
else if(id >= 0 && entry == _T("alt"))
{
m_langs[id].alt = str;
}
else if(id >= 0 && entry == _T("delay"))
{
bool fNegative = false;
if(str[0] == '-') fNegative = true;
str.TrimLeft(_T("+-"));
TCHAR c;
int hh, mm, ss, ms;
if(_stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms) != 4+3) {fError = true; continue;}
delay += (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1);
}
else if(id >= 0 && entry == _T("timestamp"))
{
SubPos sb;
sb.vobid = vobid;
sb.cellid = cellid;
sb.celltimestamp = celltimestamp;
sb.fValid = true;
bool fNegative = false;
if(str[0] == '-') fNegative = true;
str.TrimLeft(_T("+-"));
TCHAR c;
int hh, mm, ss, ms;
if(_stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms) != 4+3) {fError = true; continue;}
sb.start = (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1) + delay;
i = str.Find(_T("filepos:"));
if(i < 0) {fError = true; continue;}
str = str.Mid(i + (int)_tcslen(_T("filepos:")));
if(_stscanf(str, _T("%I64x"), &sb.filepos) != 1) {fError = true; continue;}
if(delay < 0 && m_langs[id].subpos.GetCount() > 0)
{
__int64 ts = m_langs[id].subpos[m_langs[id].subpos.GetCount()-1].start;
if(sb.start < ts)
{
delay += (int)(ts - sb.start);
sb.start = ts;
}
}
m_langs[id].subpos.Add(sb);
}
else fError = true;
}
return(!fError);
}
bool CVobSubFile::ReadSub(CString fn)
{
CFile f;
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
m_sub.SetLength(f.GetLength());
m_sub.SeekToBegin();
int len;
BYTE buff[2048];
while((len = f.Read(buff, sizeof(buff))) > 0 && *(DWORD*)buff == 0xba010000)
m_sub.Write(buff, len);
return(true);
}
static unsigned char* RARbuff = NULL;
static unsigned int RARpos = 0;
static int PASCAL MyProcessDataProc(unsigned char* Addr, int Size)
{
ASSERT(RARbuff);
memcpy(&RARbuff[RARpos], Addr, Size);
RARpos += Size;
return(1);
}
bool CVobSubFile::ReadRar(CString fn)
{
HMODULE h = LoadLibrary(_T("unrar.dll"));
if(!h) return(false);
RAROpenArchiveEx OpenArchiveEx = (RAROpenArchiveEx)GetProcAddress(h, "RAROpenArchiveEx");
RARCloseArchive CloseArchive = (RARCloseArchive)GetProcAddress(h, "RARCloseArchive");
RARReadHeaderEx ReadHeaderEx = (RARReadHeaderEx)GetProcAddress(h, "RARReadHeaderEx");
RARProcessFile ProcessFile = (RARProcessFile)GetProcAddress(h, "RARProcessFile");
RARSetChangeVolProc SetChangeVolProc = (RARSetChangeVolProc)GetProcAddress(h, "RARSetChangeVolProc");
RARSetProcessDataProc SetProcessDataProc = (RARSetProcessDataProc)GetProcAddress(h, "RARSetProcessDataProc");
RARSetPassword SetPassword = (RARSetPassword)GetProcAddress(h, "RARSetPassword");
if(!(OpenArchiveEx && CloseArchive && ReadHeaderEx && ProcessFile
&& SetChangeVolProc && SetProcessDataProc && SetPassword))
{
FreeLibrary(h);
return(false);
}
struct RAROpenArchiveDataEx ArchiveDataEx;
memset(&ArchiveDataEx, 0, sizeof(ArchiveDataEx));
#ifdef UNICODE
ArchiveDataEx.ArcNameW = (LPTSTR)(LPCTSTR)fn;
char fnA[MAX_PATH];
if(wcstombs(fnA, fn, fn.GetLength()+1) == -1) fnA[0] = 0;
ArchiveDataEx.ArcName = fnA;
#else
ArchiveDataEx.ArcName = (LPTSTR)(LPCTSTR)fn;
#endif
ArchiveDataEx.OpenMode = RAR_OM_EXTRACT;
ArchiveDataEx.CmtBuf = 0;
HANDLE hrar = OpenArchiveEx(&ArchiveDataEx);
if(!hrar)
{
FreeLibrary(h);
return(false);
}
SetProcessDataProc(hrar, MyProcessDataProc);
struct RARHeaderDataEx HeaderDataEx;
HeaderDataEx.CmtBuf = NULL;
while(ReadHeaderEx(hrar, &HeaderDataEx) == 0)
{
#ifdef UNICODE
CString subfn(HeaderDataEx.FileNameW);
#else
CString subfn(HeaderDataEx.FileName);
#endif
if(!subfn.Right(4).CompareNoCase(_T(".sub")))
{
CAutoVectorPtr<BYTE> buff;
if(!buff.Allocate(HeaderDataEx.UnpSize))
{
CloseArchive(hrar);
FreeLibrary(h);
return(false);
}
RARbuff = buff;
RARpos = 0;
if(ProcessFile(hrar, RAR_TEST, NULL, NULL))
{
CloseArchive(hrar);
FreeLibrary(h);
return(false);
}
m_sub.SetLength(HeaderDataEx.UnpSize);
m_sub.SeekToBegin();
m_sub.Write(buff, HeaderDataEx.UnpSize);
m_sub.SeekToBegin();
RARbuff = NULL;
RARpos = 0;
break;
}
ProcessFile(hrar, RAR_SKIP, NULL, NULL);
}
CloseArchive(hrar);
FreeLibrary(h);
return(true);
}
#define ReadBEdw(var) \
f.Read(&((BYTE*)&var)[3], 1); \
f.Read(&((BYTE*)&var)[2], 1); \
f.Read(&((BYTE*)&var)[1], 1); \
f.Read(&((BYTE*)&var)[0], 1); \
bool CVobSubFile::ReadIfo(CString fn)
{
CFile f;
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
/* PGC1 */
f.Seek(0xc0+0x0c, SEEK_SET);
DWORD pos;
ReadBEdw(pos);
f.Seek(pos*0x800 + 0x0c, CFile::begin);
DWORD offset;
ReadBEdw(offset);
/* Subpic palette */
f.Seek(pos*0x800 + offset + 0xa4, CFile::begin);
for(int i = 0; i < 16; i++)
{
BYTE y, u, v, tmp;
f.Read(&tmp, 1);
f.Read(&y, 1);
f.Read(&u, 1);
f.Read(&v, 1);
y = (y-16)*255/219;
m_orgpal[i].rgbRed = (BYTE)min(max(1.0*y + 1.4022*(u-128), 0), 255);
m_orgpal[i].rgbGreen = (BYTE)min(max(1.0*y - 0.3456*(u-128) - 0.7145*(v-128), 0), 255);
m_orgpal[i].rgbBlue = (BYTE)min(max(1.0*y + 1.7710*(v-128), 0) , 255);
}
return(true);
}
bool CVobSubFile::WriteIdx(CString fn)
{
CTextFile f;
if(!f.Save(fn, CTextFile::ASCII))
return(false);
CString str;
str.Format(_T("# VobSub index file, v%d (do not modify this line!)\n"), VOBSUBIDXVER);
f.WriteString(str);
f.WriteString(_T("# \n"));
f.WriteString(_T("# To repair desyncronization, you can insert gaps this way:\n"));
f.WriteString(_T("# (it usually happens after vob id changes)\n"));
f.WriteString(_T("# \n"));
f.WriteString(_T("#\t delay: [sign]hh:mm:ss:ms\n"));
f.WriteString(_T("# \n"));
f.WriteString(_T("# Where:\n"));
f.WriteString(_T("#\t [sign]: +, - (optional)\n"));
f.WriteString(_T("#\t hh: hours (0 <= hh)\n"));
f.WriteString(_T("#\t mm/ss: minutes/seconds (0 <= mm/ss <= 59)\n"));
f.WriteString(_T("#\t ms: milliseconds (0 <= ms <= 999)\n"));
f.WriteString(_T("# \n"));
f.WriteString(_T("#\t Note: You can't position a sub before the previous with a negative value.\n"));
f.WriteString(_T("# \n"));
f.WriteString(_T("# You can also modify timestamps or delete a few subs you don't like.\n"));
f.WriteString(_T("# Just make sure they stay in increasing order.\n"));
f.WriteString(_T("\n"));
f.WriteString(_T("\n"));
// Settings
f.WriteString(_T("# Settings\n\n"));
f.WriteString(_T("# Original frame size\n"));
str.Format(_T("size: %dx%d\n\n"), m_size.cx, m_size.cy);
f.WriteString(str);
f.WriteString(_T("# Origin, relative to the upper-left corner, can be overloaded by aligment\n"));
str.Format(_T("org: %d, %d\n\n"), m_x, m_y);
f.WriteString(str);
f.WriteString(_T("# Image scaling (hor,ver), origin is at the upper-left corner or at the alignment coord (x, y)\n"));
str.Format(_T("scale: %d%%, %d%%\n\n"), m_scale_x, m_scale_y);
f.WriteString(str);
f.WriteString(_T("# Alpha blending\n"));
str.Format(_T("alpha: %d%%\n\n"), m_alpha);
f.WriteString(str);
f.WriteString(_T("# Smoothing for very blocky images (use OLD for no filtering)\n"));
str.Format(_T("smooth: %s\n\n"), m_fSmooth == 0 ? _T("OFF") : m_fSmooth == 1 ? _T("ON") : _T("OLD"));
f.WriteString(str);
f.WriteString(_T("# In millisecs\n"));
str.Format(_T("fadein/out: %d, %d\n\n"), m_fadein, m_fadeout);
f.WriteString(str);
f.WriteString(_T("# Force subtitle placement relative to (org.x, org.y)\n"));
str.Format(_T("align: %s %s %s\n\n"),
m_fAlign ? _T("ON at") : _T("OFF at"),
m_alignhor == 0 ? _T("LEFT") : m_alignhor == 1 ? _T("CENTER") : m_alignhor == 2 ? _T("RIGHT") : _T(""),
m_alignver == 0 ? _T("TOP") : m_alignver == 1 ? _T("CENTER") : m_alignver == 2 ? _T("BOTTOM") : _T(""));
f.WriteString(str);
f.WriteString(_T("# For correcting non-progressive desync. (in millisecs or hh:mm:ss:ms)\n"));
f.WriteString(_T("# Note: Not effective in DirectVobSub, use \"delay: ... \" instead.\n"));
str.Format(_T("time offset: %d\n\n"), m_toff);
f.WriteString(str);
f.WriteString(_T("# ON: displays only forced subtitles, OFF: shows everything\n"));
str.Format(_T("forced subs: %s\n\n"), m_fOnlyShowForcedSubs ? _T("ON") : _T("OFF"));
f.WriteString(str);
f.WriteString(_T("# The original palette of the DVD\n"));
str.Format(_T("palette: %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x\n\n"),
*((unsigned int*)&m_orgpal[0])&0xffffff,
*((unsigned int*)&m_orgpal[1])&0xffffff,
*((unsigned int*)&m_orgpal[2])&0xffffff,
*((unsigned int*)&m_orgpal[3])&0xffffff,
*((unsigned int*)&m_orgpal[4])&0xffffff,
*((unsigned int*)&m_orgpal[5])&0xffffff,
*((unsigned int*)&m_orgpal[6])&0xffffff,
*((unsigned int*)&m_orgpal[7])&0xffffff,
*((unsigned int*)&m_orgpal[8])&0xffffff,
*((unsigned int*)&m_orgpal[9])&0xffffff,
*((unsigned int*)&m_orgpal[10])&0xffffff,
*((unsigned int*)&m_orgpal[11])&0xffffff,
*((unsigned int*)&m_orgpal[12])&0xffffff,
*((unsigned int*)&m_orgpal[13])&0xffffff,
*((unsigned int*)&m_orgpal[14])&0xffffff,
*((unsigned int*)&m_orgpal[15])&0xffffff);
f.WriteString(str);
int tridx = (!!(m_tridx&1))*0x1000 + (!!(m_tridx&2))*0x100 + (!!(m_tridx&4))*0x10 + (!!(m_tridx&8));
f.WriteString(_T("# Custom colors (transp idxs and the four colors)\n"));
str.Format(_T("custom colors: %s, tridx: %04x, colors: %06x, %06x, %06x, %06x\n\n"),
m_fCustomPal ? _T("ON") : _T("OFF"),
tridx,
*((unsigned int*)&m_cuspal[0])&0xffffff,
*((unsigned int*)&m_cuspal[1])&0xffffff,
*((unsigned int*)&m_cuspal[2])&0xffffff,
*((unsigned int*)&m_cuspal[3])&0xffffff);
f.WriteString(str);
f.WriteString(_T("# Language index in use\n"));
str.Format(_T("langidx: %d\n\n"), m_iLang);
f.WriteString(str);
// Subs
for(int i = 0; i < 32; i++)
{
SubLang& sl = m_langs[i];
CAtlArray<SubPos>& sp = sl.subpos;
if(sp.IsEmpty() && !sl.id) continue;
str.Format(_T("# %s\n"), sl.name);
f.WriteString(str);
ASSERT(sl.id);
if(!sl.id) sl.id = '--';
str.Format(_T("id: %c%c, index: %d\n"), sl.id>>8, sl.id&0xff, i);
f.WriteString(str);
str.Format(_T("# Decomment next line to activate alternative name in DirectVobSub / Windows Media Player 6.x\n"));
f.WriteString(str);
str.Format(_T("alt: %s\n"), sl.alt);
if(sl.name == sl.alt) str = _T("# ") + str;
f.WriteString(str);
char vobid = -1, cellid = -1;
for(int j = 0; j < sp.GetCount(); j++)
{
if(!sp[j].fValid) continue;
if(sp[j].vobid != vobid || sp[j].cellid != cellid)
{
str.Format(_T("# Vob/Cell ID: %d, %d (PTS: %d)\n"), sp[j].vobid, sp[j].cellid, sp[j].celltimestamp);
f.WriteString(str);
vobid = sp[j].vobid;
cellid = sp[j].cellid;
}
str.Format(_T("timestamp: %s%02d:%02d:%02d:%03d, filepos: %09I64x\n"),
sp[j].start < 0 ? _T("-") : _T(""),
abs(int((sp[j].start/1000/60/60)%60)),
abs(int((sp[j].start/1000/60)%60)),
abs(int((sp[j].start/1000)%60)),
abs(int((sp[j].start)%1000)),
sp[j].filepos);
f.WriteString(str);
}
f.WriteString(_T("\n"));
}
return(true);
}
bool CVobSubFile::WriteSub(CString fn)
{
CFile f;
if(!f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
if(m_sub.GetLength() == 0)
return(true); // nothing to do...
m_sub.SeekToBegin();
int len;
BYTE buff[2048];
while((len = m_sub.Read(buff, sizeof(buff))) > 0 && *(DWORD*)buff == 0xba010000)
f.Write(buff, len);
return(true);
}
//
BYTE* CVobSubFile::GetPacket(int idx, int& packetsize, int& datasize, int iLang)
{
BYTE* ret = NULL;
if(iLang < 0 || iLang >= 32) iLang = m_iLang;
CAtlArray<SubPos>& sp = m_langs[iLang].subpos;
do
{
if(idx < 0 || idx >= sp.GetCount())
break;
if(m_sub.Seek(sp[idx].filepos, CFile::begin) != sp[idx].filepos)
break;
BYTE buff[0x800];
if(sizeof(buff) != m_sub.Read(buff, sizeof(buff)))
break;
BYTE offset = buff[0x16];
// let's check a few things to make sure...
if(*(DWORD*)&buff[0x00] != 0xba010000
|| *(DWORD*)&buff[0x0e] != 0xbd010000
|| !(buff[0x15] & 0x80)
|| (buff[0x17] & 0xf0) != 0x20
|| (buff[buff[0x16] + 0x17] & 0xe0) != 0x20
|| (buff[buff[0x16] + 0x17] & 0x1f) != iLang)
break;
packetsize = (buff[buff[0x16] + 0x18] << 8) + buff[buff[0x16] + 0x19];
datasize = (buff[buff[0x16] + 0x1a] << 8) + buff[buff[0x16] + 0x1b];
ret = new BYTE[packetsize];
if(!ret) break;
int i = 0, sizeleft = packetsize;
for(int size;
i < packetsize;
i += size, sizeleft -= size)
{
int hsize = 0x18 + buff[0x16];
size = min(sizeleft, 0x800 - hsize);
memcpy(&ret[i], &buff[hsize], size);
if(size != sizeleft)
{
while(m_sub.Read(buff, sizeof(buff)))
{
if(/*!(buff[0x15] & 0x80) &&*/ buff[buff[0x16] + 0x17] == (iLang|0x20))
break;
}
}
}
if(i != packetsize || sizeleft > 0)
delete [] ret, ret = NULL;
}
while(false);
return(ret);
}
bool CVobSubFile::GetFrame(int idx, int iLang)
{
if(iLang < 0 || iLang >= 32) iLang = m_iLang;
CAtlArray<SubPos>& sp = m_langs[iLang].subpos;
if(idx < 0 || idx >= sp.GetCount())
return(false);
if(m_img.iLang != iLang || m_img.iIdx != idx)
{
int packetsize = 0, datasize = 0;
CAutoVectorPtr<BYTE> buff;
buff.Attach(GetPacket(idx, packetsize, datasize, iLang));
if(!buff || packetsize <= 0 || datasize <= 0) return(false);
m_img.start = sp[idx].start;
m_img.delay = idx < (sp.GetCount()-1)
? sp[idx+1].start - sp[idx].start
: 3000;
bool ret = m_img.Decode(buff, packetsize, datasize, m_fCustomPal, m_tridx, m_orgpal, m_cuspal, true);
if(idx < (sp.GetCount()-1))
m_img.delay = min(m_img.delay, sp[idx+1].start - m_img.start);
if(!ret) return(false);
m_img.iIdx = idx;
m_img.iLang = iLang;
}
return(m_fOnlyShowForcedSubs ? m_img.fForced : true);
}
bool CVobSubFile::GetFrameByTimeStamp(__int64 time)
{
return(GetFrame(GetFrameIdxByTimeStamp(time)));
}
int CVobSubFile::GetFrameIdxByTimeStamp(__int64 time)
{
if(m_iLang < 0 || m_iLang >= 32)
return(-1);
CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
int i = 0, j = (int)sp.GetCount() - 1, ret = -1;
if(j >= 0 && time >= sp[j].start)
return(j);
while(i < j)
{
int mid = (i + j) >> 1;
int midstart = (int)sp[mid].start;
if(time == midstart) {ret = mid; break;}
else if(time < midstart) {ret = -1; if(j == mid) mid--; j = mid;}
else if(time > midstart) {ret = mid; if(i == mid) mid++; i = mid;}
}
return(ret);
}
//
STDMETHODIMP CVobSubFile::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
CheckPointer(ppv, E_POINTER);
*ppv = NULL;
return
QI(IPersist)
QI(ISubStream)
QI(ISubPicProvider)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPicProvider
// TODO: return segments for the fade-in/out time (with animated set to "true" of course)
STDMETHODIMP_(POSITION) CVobSubFile::GetStartPosition(REFERENCE_TIME rt, double fps)
{
rt /= 10000;
int i = GetFrameIdxByTimeStamp(rt);
if(!GetFrame(i))
return(NULL);
if(rt >= (m_img.start + m_img.delay))
{
if(!GetFrame(++i))
return(NULL);
}
return((POSITION)(i+1));
}
STDMETHODIMP_(POSITION) CVobSubFile::GetNext(POSITION pos)
{
int i = (int)pos;
return(GetFrame(i) ? (POSITION)(i+1) : NULL);
}
STDMETHODIMP_(REFERENCE_TIME) CVobSubFile::GetStart(POSITION pos, double fps)
{
int i = (int)pos-1;
return(GetFrame(i) ? 10000i64*m_img.start : 0);
}
STDMETHODIMP_(REFERENCE_TIME) CVobSubFile::GetStop(POSITION pos, double fps)
{
int i = (int)pos-1;
return(GetFrame(i) ? 10000i64*(m_img.start + m_img.delay) : 0);
}
STDMETHODIMP_(bool) CVobSubFile::IsAnimated(POSITION pos)
{
return(false);
}
STDMETHODIMP CVobSubFile::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
{
if(spd.bpp != 32) return E_INVALIDARG;
rt /= 10000;
if(!GetFrame(GetFrameIdxByTimeStamp(rt)))
return E_FAIL;
if(rt >= (m_img.start + m_img.delay))
return E_FAIL;
return __super::Render(spd, bbox);
}
// IPersist
STDMETHODIMP CVobSubFile::GetClassID(CLSID* pClassID)
{
return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
}
// ISubStream
STDMETHODIMP_(int) CVobSubFile::GetStreamCount()
{
int iStreamCount = 0;
for(int i = 0; i < 32; i++)
if(m_langs[i].subpos.GetCount()) iStreamCount++;
return(iStreamCount);
}
STDMETHODIMP CVobSubFile::GetStreamInfo(int iStream, WCHAR** ppName, LCID* pLCID)
{
for(int i = 0; i < 32; i++)
{
SubLang& sl = m_langs[i];
if(sl.subpos.IsEmpty() || iStream-- > 0)
continue;
if(ppName)
{
if(!(*ppName = (WCHAR*)CoTaskMemAlloc((sl.alt.GetLength()+1)*sizeof(WCHAR))))
return E_OUTOFMEMORY;
wcscpy(*ppName, CStringW(sl.alt));
}
if(pLCID)
{
*pLCID = 0; // TODO: make lcid out of "sl.id"
}
return S_OK;
}
return E_FAIL;
}
STDMETHODIMP_(int) CVobSubFile::GetStream()
{
int iStream = 0;
for(int i = 0; i < m_iLang; i++)
if(!m_langs[i].subpos.IsEmpty()) iStream++;
return(iStream);
}
STDMETHODIMP CVobSubFile::SetStream(int iStream)
{
for(int i = 0; i < 32; i++)
{
CAtlArray<SubPos>& sp = m_langs[i].subpos;
if(sp.IsEmpty() || iStream-- > 0)
continue;
m_iLang = i;
m_img.Invalidate();
break;
}
return iStream < 0 ? S_OK : E_FAIL;
}
STDMETHODIMP CVobSubFile::Reload()
{
CFileStatus s;
if(!CFile::GetStatus(m_title + _T(".idx"), s)) return E_FAIL;
return !m_title.IsEmpty() && Open(m_title) ? S_OK : E_FAIL;
}
// StretchBlt
static void PixelAtBiLinear(RGBQUAD& c, int x, int y, CVobSubImage& src)
{
int w = src.rect.Width(),
h = src.rect.Height();
int x1 = (x >> 16), y1 = (y >> 16) * w,
x2 = min(x1 + 1, w-1), y2 = min(y1 + w, (h-1)*w);
RGBQUAD* ptr = src.lpPixels;
RGBQUAD c11 = ptr[y1 + x1], c12 = ptr[y1 + x2],
c21 = ptr[y2 + x1], c22 = ptr[y2 + x2];
__int64 u2 = x & 0xffff,
v2 = y & 0xffff,
u1 = 0x10000 - u2,
v1 = 0x10000 - v2;
int v1u1 = int(v1*u1 >> 16) * c11.rgbReserved,
v1u2 = int(v1*u2 >> 16) * c12.rgbReserved,
v2u1 = int(v2*u1 >> 16) * c21.rgbReserved,
v2u2 = int(v2*u2 >> 16) * c22.rgbReserved;
c.rgbRed = (c11.rgbRed * v1u1 + c12.rgbRed * v1u2
+ c21.rgbRed * v2u1 + c22.rgbRed * v2u2) >> 24;
c.rgbGreen = (c11.rgbGreen * v1u1 + c12.rgbGreen * v1u2
+ c21.rgbGreen * v2u1 + c22.rgbGreen * v2u2) >> 24;
c.rgbBlue = (c11.rgbBlue * v1u1 + c12.rgbBlue * v1u2
+ c21.rgbBlue * v2u1 + c22.rgbBlue * v2u2) >> 24;
c.rgbReserved = (v1u1 + v1u2
+ v2u1 + v2u2) >> 16;
}
static void StretchBlt(SubPicDesc& spd, CRect dstrect, CVobSubImage& src)
{
if(dstrect.IsRectEmpty()) return;
if((dstrect & CRect(0, 0, spd.w, spd.h)).IsRectEmpty()) return;
int sw = src.rect.Width(),
sh = src.rect.Height(),
dw = dstrect.Width(),
dh = dstrect.Height();
int srcx = 0,
srcy = 0,
srcdx = (sw << 16) / dw >> 1,
srcdy = (sh << 16) / dh >> 1;
if(dstrect.left < 0) {srcx = -dstrect.left * (srcdx<<1); dstrect.left = 0;}
if(dstrect.top < 0) {srcy = -dstrect.top * (srcdy<<1); dstrect.top = 0;}
if(dstrect.right > spd.w) {dstrect.right = spd.w;}
if(dstrect.bottom > spd.h) {dstrect.bottom = spd.h;}
if((dstrect & CRect(0, 0, spd.w, spd.h)).IsRectEmpty()) return;
dw = dstrect.Width();
dh = dstrect.Height();
for(int y = dstrect.top; y < dstrect.bottom; y++, srcy += (srcdy<<1))
{
RGBQUAD* ptr = (RGBQUAD*)&((BYTE*)spd.bits)[y*spd.pitch] + dstrect.left;
RGBQUAD* endptr = ptr + dw;
for(int sx = srcx; ptr < endptr; sx += (srcdx<<1), ptr++)
{
// PixelAtBiLinear(*ptr, sx, srcy, src);
////
RGBQUAD cc[4];
PixelAtBiLinear(cc[0], sx, srcy, src);
PixelAtBiLinear(cc[1], sx+srcdx, srcy, src);
PixelAtBiLinear(cc[2], sx, srcy+srcdy, src);
PixelAtBiLinear(cc[3], sx+srcdx, srcy+srcdy, src);
ptr->rgbRed = (cc[0].rgbRed + cc[1].rgbRed + cc[2].rgbRed + cc[3].rgbRed) >> 2;
ptr->rgbGreen = (cc[0].rgbGreen + cc[1].rgbGreen + cc[2].rgbGreen + cc[3].rgbGreen) >> 2;
ptr->rgbBlue = (cc[0].rgbBlue + cc[1].rgbBlue + cc[2].rgbBlue + cc[3].rgbBlue) >> 2;
ptr->rgbReserved = (cc[0].rgbReserved + cc[1].rgbReserved + cc[2].rgbReserved + cc[3].rgbReserved) >> 2;
////
ptr->rgbRed = ptr->rgbRed * ptr->rgbReserved >> 8;
ptr->rgbGreen = ptr->rgbGreen * ptr->rgbReserved >> 8;
ptr->rgbBlue = ptr->rgbBlue * ptr->rgbReserved >> 8;
ptr->rgbReserved = ~ptr->rgbReserved;
}
}
}
//
// CVobSubSettings
//
void CVobSubSettings::InitSettings()
{
m_size.SetSize(720, 480);
m_toff = m_x = m_y = 0;
m_org.SetPoint(0, 0);
m_scale_x = m_scale_y = m_alpha = 100;
m_fadein = m_fadeout = 50;
m_fSmooth = 0;
m_fAlign = false;
m_alignhor = m_alignver = 0;
m_fOnlyShowForcedSubs = false;
m_fCustomPal = false;
m_tridx = 0;
memset(m_orgpal, 0, sizeof(m_orgpal));
memset(m_cuspal, 0, sizeof(m_cuspal));
}
bool CVobSubSettings::GetCustomPal(RGBQUAD* cuspal, int& tridx)
{
memcpy(cuspal, m_cuspal, sizeof(RGBQUAD)*4);
tridx = m_tridx;
return(m_fCustomPal);
}
void CVobSubSettings::SetCustomPal(RGBQUAD* cuspal, int tridx)
{
memcpy(m_cuspal, cuspal, sizeof(RGBQUAD)*4);
m_tridx = tridx & 0xf;
for(int i = 0; i < 4; i++) m_cuspal[i].rgbReserved = (tridx&(1<<i)) ? 0 : 0xff;
m_img.Invalidate();
}
void CVobSubSettings::GetDestrect(CRect& r)
{
int w = MulDiv(m_img.rect.Width(), m_scale_x, 100);
int h = MulDiv(m_img.rect.Height(), m_scale_y, 100);
if(!m_fAlign)
{
r.left = MulDiv(m_img.rect.left, m_scale_x, 100);
r.right = MulDiv(m_img.rect.right, m_scale_x, 100);
r.top = MulDiv(m_img.rect.top, m_scale_y, 100);
r.bottom = MulDiv(m_img.rect.bottom, m_scale_y, 100);
}
else
{
switch(m_alignhor)
{
case 0: r.left = 0; r.right = w; break; // left
case 1: r.left = -(w>>1); r.right = -(w>>1) + w; break; // center
case 2: r.left = -w; r.right = 0; break; // right
default:
r.left = MulDiv(m_img.rect.left, m_scale_x, 100);
r.right = MulDiv(m_img.rect.right, m_scale_x, 100);
break;
}
switch(m_alignver)
{
case 0: r.top = 0; r.bottom = h; break; // top
case 1: r.top = -(h>>1); r.bottom = -(h>>1) + h; break; // center
case 2: r.top = -h; r.bottom = 0; break; // bottom
default:
r.top = MulDiv(m_img.rect.top, m_scale_y, 100);
r.bottom = MulDiv(m_img.rect.bottom, m_scale_y, 100);
break;
}
}
r += m_org;
}
void CVobSubSettings::GetDestrect(CRect& r, int w, int h)
{
GetDestrect(r);
r.left = MulDiv(r.left, w, m_size.cx);
r.right = MulDiv(r.right, w, m_size.cx);
r.top = MulDiv(r.top, h, m_size.cy);
r.bottom = MulDiv(r.bottom, h, m_size.cy);
}
void CVobSubSettings::SetAlignment(bool fAlign, int x, int y, int hor, int ver)
{
if(m_fAlign = fAlign)
{
m_org.x = MulDiv(m_size.cx, x, 100);
m_org.y = MulDiv(m_size.cy, y, 100);
m_alignhor = min(max(hor, 0), 2);
m_alignver = min(max(ver, 0), 2);
}
else
{
m_org.x = m_x;
m_org.y = m_y;
}
}
#include "RTS.h"
HRESULT CVobSubSettings::Render(SubPicDesc& spd, RECT& bbox)
{
CRect r;
GetDestrect(r, spd.w, spd.h);
StretchBlt(spd, r, m_img);
/*
CRenderedTextSubtitle rts(NULL);
rts.CreateDefaultStyle(DEFAULT_CHARSET);
rts.m_dstScreenSize.SetSize(m_size.cx, m_size.cy);
CStringW assstr;
m_img.Polygonize(assstr, false);
REFERENCE_TIME rtStart = 10000i64*m_img.start, rtStop = 10000i64*(m_img.start+m_img.delay);
rts.Add(assstr, true, rtStart, rtStop);
rts.Render(spd, (rtStart+rtStop)/2, 25, r);
*/
r &= CRect(CPoint(0, 0), CSize(spd.w, spd.h));
bbox = r;
return !r.IsRectEmpty() ? S_OK : S_FALSE;
}
/////////////////////////////////////////////////////////
static bool CompressFile(CString fn)
{
if(GetVersion() < 0)
return(false);
BOOL b = FALSE;
HANDLE h = CreateFile(fn, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if(h != INVALID_HANDLE_VALUE)
{
USHORT us = COMPRESSION_FORMAT_DEFAULT;
DWORD nBytesReturned;
b = DeviceIoControl(h, FSCTL_SET_COMPRESSION, (LPVOID)&us, 2, NULL, 0, (LPDWORD)&nBytesReturned, NULL);
CloseHandle(h);
}
return(!!b);
}
bool CVobSubFile::SaveVobSub(CString fn)
{
return WriteIdx(fn + _T(".idx")) && WriteSub(fn + _T(".sub"));
}
bool CVobSubFile::SaveWinSubMux(CString fn)
{
TrimExtension(fn);
CStdioFile f;
if(!f.Open(fn + _T(".sub"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
return(false);
m_img.Invalidate();
CAutoVectorPtr<BYTE> p4bpp;
if(!p4bpp.Allocate(720*576/2))
return(false);
CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
for(int i = 0; i < sp.GetCount(); i++)
{
if(!GetFrame(i)) continue;
int pal[4] = {0, 1, 2, 3};
for(int j = 0; j < 5; j++)
{
if(j == 4 || !m_img.pal[j].tr)
{
j &= 3;
memset(p4bpp, (j<<4)|j, 720*576/2);
pal[j] ^= pal[0], pal[0] ^= pal[j], pal[j] ^= pal[0];
break;
}
}
int tr[4] = {m_img.pal[pal[0]].tr, m_img.pal[pal[1]].tr, m_img.pal[pal[2]].tr, m_img.pal[pal[3]].tr};
DWORD uipal[4+12];
if(!m_fCustomPal)
{
uipal[0] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[0]].pal]);
uipal[1] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[1]].pal]);
uipal[2] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[2]].pal]);
uipal[3] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[3]].pal]);
}
else
{
uipal[0] = *((DWORD*)&m_img.cuspal[pal[0]]) & 0xffffff;
uipal[1] = *((DWORD*)&m_img.cuspal[pal[1]]) & 0xffffff;
uipal[2] = *((DWORD*)&m_img.cuspal[pal[2]]) & 0xffffff;
uipal[3] = *((DWORD*)&m_img.cuspal[pal[3]]) & 0xffffff;
}
CAtlMap<DWORD,BYTE> palmap;
palmap[uipal[0]] = 0;
palmap[uipal[1]] = 1;
palmap[uipal[2]] = 2;
palmap[uipal[3]] = 3;
uipal[0] = 0xff; // blue background
int w = m_img.rect.Width()-2;
int h = m_img.rect.Height()-2;
int pitch = (((w+1)>>1) + 3) & ~3;
for(int y = 0; y < h; y++)
{
DWORD* p = (DWORD*)&m_img.lpPixels[(y+1)*(w+2)+1];
for(int x = 0; x < w; x++, p++)
{
BYTE c = 0;
if(*p & 0xff000000)
{
DWORD uic = *p & 0xffffff;
palmap.Lookup(uic, c);
}
BYTE& c4bpp = p4bpp[(h-y-1)*pitch+(x>>1)];
c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
}
}
int t1 = m_img.start, t2 = t1 + m_img.delay /*+ (m_size.cy==480?(1000/29.97+1):(1000/25))*/;
ASSERT(t2>t1);
if(t2 <= 0) continue;
if(t1 < 0) t1 = 0;
CString bmpfn;
bmpfn.Format(_T("%s_%06d.bmp"), fn, i+1);
CString str;
str.Format(_T("%s\t%02d:%02d:%02d:%02d %02d:%02d:%02d:%02d\t%03d %03d %03d %03d %d %d %d %d\n"),
bmpfn,
t1/1000/60/60, (t1/1000/60)%60, (t1/1000)%60, (t1%1000)/10,
t2/1000/60/60, (t2/1000/60)%60, (t2/1000)%60, (t2%1000)/10,
m_img.rect.Width(), m_img.rect.Height(), m_img.rect.left, m_img.rect.top,
(tr[0]<<4)|tr[0], (tr[1]<<4)|tr[1], (tr[2]<<4)|tr[2], (tr[3]<<4)|tr[3]);
f.WriteString(str);
BITMAPFILEHEADER fhdr =
{
0x4d42,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + pitch*h,
0, 0,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
};
BITMAPINFOHEADER ihdr =
{
sizeof(BITMAPINFOHEADER),
w, h, 1, 4, 0,
0,
pitch*h, 0,
16, 4
};
CFile bmp;
if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
{
bmp.Write(&fhdr, sizeof(fhdr));
bmp.Write(&ihdr, sizeof(ihdr));
bmp.Write(uipal, sizeof(RGBQUAD)*16);
bmp.Write(p4bpp, pitch*h);
bmp.Close();
CompressFile(bmpfn);
}
}
return(true);
}
bool CVobSubFile::SaveScenarist(CString fn)
{
TrimExtension(fn);
CStdioFile f;
if(!f.Open(fn + _T(".sst"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
return(false);
m_img.Invalidate();
fn.Replace('\\', '/');
CString title = fn.Mid(fn.ReverseFind('/')+1);
TCHAR buff[MAX_PATH], * pFilePart = buff;
if(GetFullPathName(fn, MAX_PATH, buff, &pFilePart) == 0)
return(false);
CString fullpath = CString(buff).Left(pFilePart - buff);
fullpath.TrimRight(_T("\\/"));
if(fullpath.IsEmpty())
return(false);
CString str, str2;
str += _T("st_format\t2\n");
str += _T("Display_Start\t%s\n");
str += _T("TV_Type\t\t%s\n");
str += _T("Tape_Type\tNON_DROP\n");
str += _T("Pixel_Area\t(0 %d)\n");
str += _T("Directory\t%s\n");
str += _T("Subtitle\t%s\n");
str += _T("Display_Area\t(0 2 719 %d)\n");
str += _T("Contrast\t(15 15 15 0)\n");
str += _T("\n");
str += _T("PA\t(0 0 255 - - - )\n");
str += _T("E1\t(255 0 0 - - - )\n");
str += _T("E2\t(0 0 0 - - - )\n");
str += _T("BG\t(255 255 255 - - - )\n");
str += _T("\n");
str += _T("SP_NUMBER\tSTART\tEND\tFILE_NAME\n");
str2.Format(str,
!m_fOnlyShowForcedSubs ? _T("non_forced") : _T("forced"),
m_size.cy == 480 ? _T("NTSC") : _T("PAL"),
m_size.cy-3,
fullpath,
title,
m_size.cy == 480 ? 479 : 574);
f.WriteString(str2);
f.Flush();
RGBQUAD pal[16] =
{
{255, 0, 0, 0},
{0, 0, 255, 0},
{0, 0, 0, 0},
{255, 255, 255, 0},
{0, 255, 0, 0},
{255, 0, 255, 0},
{0, 255, 255, 0},
{125, 125, 0, 0},
{125, 125, 125, 0},
{225, 225, 225, 0},
{0, 0, 125, 0},
{0, 125, 0, 0},
{125, 0, 0, 0},
{255, 0, 222, 0},
{0, 125, 222, 0},
{125, 0, 125, 0},
};
BITMAPFILEHEADER fhdr =
{
0x4d42,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + 360*(m_size.cy-2),
0, 0,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
};
BITMAPINFOHEADER ihdr =
{
sizeof(BITMAPINFOHEADER),
720, m_size.cy-2, 1, 4, 0,
360*(m_size.cy-2),
0, 0,
16, 4
};
bool fCustomPal = m_fCustomPal;
m_fCustomPal = true;
RGBQUAD tempCusPal[4], newCusPal[4+12] = {{255, 0, 0, 0}, {0, 0, 255, 0}, {0, 0, 0, 0}, {255, 255, 255, 0}};
memcpy(tempCusPal, m_cuspal, sizeof(tempCusPal));
memcpy(m_cuspal, newCusPal, sizeof(m_cuspal));
CAutoVectorPtr<BYTE> p4bpp;
if(!p4bpp.Allocate((m_size.cy-2)*360))
return(false);
BYTE colormap[16];
for(int i = 0; i < 16; i++)
{
int idx = 0, maxdif = 255*255*3+1;
for(int j = 0; j < 16 && maxdif; j++)
{
int rdif = pal[j].rgbRed - m_orgpal[i].rgbRed;
int gdif = pal[j].rgbGreen - m_orgpal[i].rgbGreen;
int bdif = pal[j].rgbBlue - m_orgpal[i].rgbBlue;
int dif = rdif*rdif + gdif*gdif + bdif*bdif;
if(dif < maxdif) {maxdif = dif; idx = j;}
}
colormap[i] = idx+1;
}
int pc[4] = {1, 1, 1, 1}, pa[4] = {15, 15, 15, 0};
CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
for(int i = 0, k = 0; i < sp.GetCount(); i++)
{
if(!GetFrame(i)) continue;
for(int j = 0; j < 5; j++)
{
if(j == 4 || !m_img.pal[j].tr)
{
j &= 3;
memset(p4bpp, (j<<4)|j, (m_size.cy-2)*360);
break;
}
}
for(int y = max(m_img.rect.top+1, 2); y < m_img.rect.bottom-1; y++)
{
ASSERT(m_size.cy-y-1 >= 0);
if(m_size.cy-y-1 < 0) break;
DWORD* p = (DWORD*)&m_img.lpPixels[(y-m_img.rect.top)*m_img.rect.Width()+1];
for(int x = m_img.rect.left+1; x < m_img.rect.right-1; x++, p++)
{
DWORD rgb = *p&0xffffff;
BYTE c = rgb == 0x0000ff ? 0 : rgb == 0xff0000 ? 1 : rgb == 0x000000 ? 2 : 3;
BYTE& c4bpp = p4bpp[(m_size.cy-y-1)*360+(x>>1)];
c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
}
}
CString bmpfn;
bmpfn.Format(_T("%s_%04d.bmp"), fn, i+1);
title = bmpfn.Mid(bmpfn.ReverseFind('/')+1);
// E1, E2, P, Bg
int c[4] = {colormap[m_img.pal[1].pal], colormap[m_img.pal[2].pal], colormap[m_img.pal[0].pal], colormap[m_img.pal[3].pal]};
c[0]^=c[1], c[1]^=c[0], c[0]^=c[1];
if(memcmp(pc, c, sizeof(c)))
{
memcpy(pc, c, sizeof(c));
str.Format(_T("Color\t (%d %d %d %d)\n"), c[0], c[1], c[2], c[3]);
f.WriteString(str);
}
// E1, E2, P, Bg
int a[4] = {m_img.pal[1].tr, m_img.pal[2].tr, m_img.pal[0].tr, m_img.pal[3].tr};
a[0]^=a[1], a[1]^=a[0], a[0]^=a[1];
if(memcmp(pa, a, sizeof(a)))
{
memcpy(pa, a, sizeof(a));
str.Format(_T("Contrast (%d %d %d %d)\n"), a[0], a[1], a[2], a[3]);
f.WriteString(str);
}
int t1 = sp[i].start;
int h1 = t1/1000/60/60, m1 = (t1/1000/60)%60, s1 = (t1/1000)%60;
int f1 = (int)((m_size.cy==480?29.97:25)*(t1%1000)/1000);
int t2 = sp[i].stop;
int h2 = t2/1000/60/60, m2 = (t2/1000/60)%60, s2 = (t2/1000)%60;
int f2 = (int)((m_size.cy==480?29.97:25)*(t2%1000)/1000);
if(t2 <= 0) continue;
if(t1 < 0) t1 = 0;
if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
{
f2++;
if(f2 == (m_size.cy==480?30:25)) {f2 = 0; s2++; if(s2 == 60) {s2 = 0; m2++; if(m2 == 60) {m2 = 0; h2++;}}}
}
if(i < sp.GetCount()-1)
{
int t3 = sp[i+1].start;
int h3 = t3/1000/60/60, m3 = (t3/1000/60)%60, s3 = (t3/1000)%60;
int f3 = (int)((m_size.cy==480?29.97:25)*(t3%1000)/1000);
if(h3 == h2 && m3 == m2 && s3 == s2 && f3 == f2)
{
f2--;
if(f2 == -1) {f2 = (m_size.cy==480?29:24); s2--; if(s2 == -1) {s2 = 59; m2--; if(m2 == -1) {m2 = 59; if(h2 > 0) h2--;}}}
}
}
if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
continue;
str.Format(_T("%04d\t%02d:%02d:%02d:%02d\t%02d:%02d:%02d:%02d\t%s\n"),
++k,
h1, m1, s1, f1,
h2, m2, s2, f2,
title);
f.WriteString(str);
CFile bmp;
if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::modeRead|CFile::typeBinary))
{
bmp.Write(&fhdr, sizeof(fhdr));
bmp.Write(&ihdr, sizeof(ihdr));
bmp.Write(newCusPal, sizeof(RGBQUAD)*16);
bmp.Write(p4bpp, 360*(m_size.cy-2));
bmp.Close();
CompressFile(bmpfn);
}
}
m_fCustomPal = fCustomPal;
memcpy(m_cuspal, tempCusPal, sizeof(m_cuspal));
return(true);
}
bool CVobSubFile::SaveMaestro(CString fn)
{
TrimExtension(fn);
CStdioFile f;
if(!f.Open(fn + _T(".son"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
return(false);
m_img.Invalidate();
fn.Replace('\\', '/');
CString title = fn.Mid(fn.ReverseFind('/')+1);
TCHAR buff[MAX_PATH], * pFilePart = buff;
if(GetFullPathName(fn, MAX_PATH, buff, &pFilePart) == 0)
return(false);
CString fullpath = CString(buff).Left(pFilePart - buff);
fullpath.TrimRight(_T("\\/"));
if(fullpath.IsEmpty())
return(false);
CString str, str2;
str += _T("st_format\t2\n");
str += _T("Display_Start\t%s\n");
str += _T("TV_Type\t\t%s\n");
str += _T("Tape_Type\tNON_DROP\n");
str += _T("Pixel_Area\t(0 %d)\n");
str += _T("Directory\t%s\n");
str += _T("Subtitle\t%s\n");
str += _T("Display_Area\t(0 2 719 %d)\n");
str += _T("Contrast\t(15 15 15 0)\n");
str += _T("\n");
str += _T("SP_NUMBER\tSTART\tEND\tFILE_NAME\n");
str2.Format(str,
!m_fOnlyShowForcedSubs ? _T("non_forced") : _T("forced"),
m_size.cy == 480 ? _T("NTSC") : _T("PAL"),
m_size.cy-3,
fullpath,
title,
m_size.cy == 480 ? 479 : 574);
f.WriteString(str2);
f.Flush();
RGBQUAD pal[16] =
{
{255, 0, 0, 0},
{0, 0, 255, 0},
{0, 0, 0, 0},
{255, 255, 255, 0},
{0, 255, 0, 0},
{255, 0, 255, 0},
{0, 255, 255, 0},
{125, 125, 0, 0},
{125, 125, 125, 0},
{225, 225, 225, 0},
{0, 0, 125, 0},
{0, 125, 0, 0},
{125, 0, 0, 0},
{255, 0, 222, 0},
{0, 125, 222, 0},
{125, 0, 125, 0},
};
BITMAPFILEHEADER fhdr =
{
0x4d42,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + 360*(m_size.cy-2),
0, 0,
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
};
BITMAPINFOHEADER ihdr =
{
sizeof(BITMAPINFOHEADER),
720, m_size.cy-2, 1, 4, 0,
360*(m_size.cy-2),
0, 0,
16, 4
};
bool fCustomPal = m_fCustomPal;
m_fCustomPal = true;
RGBQUAD tempCusPal[4], newCusPal[4+12] = {{255, 0, 0, 0}, {0, 0, 255, 0}, {0, 0, 0, 0}, {255, 255, 255, 0}};
memcpy(tempCusPal, m_cuspal, sizeof(tempCusPal));
memcpy(m_cuspal, newCusPal, sizeof(m_cuspal));
CAutoVectorPtr<BYTE> p4bpp;
if(!p4bpp.Allocate((m_size.cy-2)*360))
return(false);
BYTE colormap[16];
for(int i = 0; i < 16; i++)
colormap[i] = i;
CFile spf;
if(spf.Open(fn + _T(".spf"), CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))
{
for(int i = 0; i < 16; i++)
{
COLORREF c = (m_orgpal[i].rgbBlue<<16) | (m_orgpal[i].rgbGreen<<8) | m_orgpal[i].rgbRed;
spf.Write(&c, sizeof(COLORREF));
}
spf.Close();
}
int pc[4] = {1,1,1,1}, pa[4] = {15,15,15,0};
CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
for(int i = 0, k = 0; i < sp.GetCount(); i++)
{
if(!GetFrame(i)) continue;
for(int j = 0; j < 5; j++)
{
if(j == 4 || !m_img.pal[j].tr)
{
j &= 3;
memset(p4bpp, (j<<4)|j, (m_size.cy-2)*360);
break;
}
}
for(int y = max(m_img.rect.top+1, 2); y < m_img.rect.bottom-1; y++)
{
ASSERT(m_size.cy-y-1 >= 0);
if(m_size.cy-y-1 < 0) break;
DWORD* p = (DWORD*)&m_img.lpPixels[(y-m_img.rect.top)*m_img.rect.Width()+1];
for(int x = m_img.rect.left+1; x < m_img.rect.right-1; x++, p++)
{
DWORD rgb = *p&0xffffff;
BYTE c = rgb == 0x0000ff ? 0 : rgb == 0xff0000 ? 1 : rgb == 0x000000 ? 2 : 3;
BYTE& c4bpp = p4bpp[(m_size.cy-y-1)*360+(x>>1)];
c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
}
}
CString bmpfn;
bmpfn.Format(_T("%s_%04d.bmp"), fn, i+1);
title = bmpfn.Mid(bmpfn.ReverseFind('/')+1);
// E1, E2, P, Bg
int c[4] = {colormap[m_img.pal[1].pal], colormap[m_img.pal[2].pal], colormap[m_img.pal[0].pal], colormap[m_img.pal[3].pal]};
if(memcmp(pc, c, sizeof(c)))
{
memcpy(pc, c, sizeof(c));
str.Format(_T("Color\t (%d %d %d %d)\n"), c[0], c[1], c[2], c[3]);
f.WriteString(str);
}
// E1, E2, P, Bg
int a[4] = {m_img.pal[1].tr, m_img.pal[2].tr, m_img.pal[0].tr, m_img.pal[3].tr};
if(memcmp(pa, a, sizeof(a)))
{
memcpy(pa, a, sizeof(a));
str.Format(_T("Contrast (%d %d %d %d)\n"), a[0], a[1], a[2], a[3]);
f.WriteString(str);
}
int t1 = sp[i].start;
int h1 = t1/1000/60/60, m1 = (t1/1000/60)%60, s1 = (t1/1000)%60;
int f1 = (int)((m_size.cy==480?29.97:25)*(t1%1000)/1000);
int t2 = sp[i].stop;
int h2 = t2/1000/60/60, m2 = (t2/1000/60)%60, s2 = (t2/1000)%60;
int f2 = (int)((m_size.cy==480?29.97:25)*(t2%1000)/1000);
if(t2 <= 0) continue;
if(t1 < 0) t1 = 0;
if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
{
f2++;
if(f2 == (m_size.cy==480?30:25)) {f2 = 0; s2++; if(s2 == 60) {s2 = 0; m2++; if(m2 == 60) {m2 = 0; h2++;}}}
}
if(i < sp.GetCount()-1)
{
int t3 = sp[i+1].start;
int h3 = t3/1000/60/60, m3 = (t3/1000/60)%60, s3 = (t3/1000)%60;
int f3 = (int)((m_size.cy==480?29.97:25)*(t3%1000)/1000);
if(h3 == h2 && m3 == m2 && s3 == s2 && f3 == f2)
{
f2--;
if(f2 == -1) {f2 = (m_size.cy==480?29:24); s2--; if(s2 == -1) {s2 = 59; m2--; if(m2 == -1) {m2 = 59; if(h2 > 0) h2--;}}}
}
}
if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
continue;
str.Format(_T("%04d\t%02d:%02d:%02d:%02d\t%02d:%02d:%02d:%02d\t%s\n"),
++k,
h1, m1, s1, f1,
h2, m2, s2, f2,
title);
f.WriteString(str);
CFile bmp;
if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))
{
bmp.Write(&fhdr, sizeof(fhdr));
bmp.Write(&ihdr, sizeof(ihdr));
bmp.Write(newCusPal, sizeof(RGBQUAD)*16);
bmp.Write(p4bpp, 360*(m_size.cy-2));
bmp.Close();
CompressFile(bmpfn);
}
}
m_fCustomPal = fCustomPal;
memcpy(m_cuspal, tempCusPal, sizeof(m_cuspal));
return(true);
}
//
// CVobSubStream
//
CVobSubStream::CVobSubStream(CCritSec* pLock)
: ISubPicProviderImpl(pLock)
{
}
CVobSubStream::~CVobSubStream()
{
}
void CVobSubStream::Open(CString name, BYTE* pData, int len)
{
CAutoLock cAutoLock(&m_csSubPics);
m_name = name;
CAtlList<CString> lines;
Explode(CString(CStringA((CHAR*)pData, len)), lines, '\n');
while(lines.GetCount())
{
CAtlList<CString> sl;
Explode(lines.RemoveHead(), sl, ':', 2);
if(sl.GetCount() != 2) continue;
CString key = sl.GetHead();
CString value = sl.GetTail();
if(key == _T("size"))
_stscanf(value, _T("%dx %d"), &m_size.cx, &m_size.cy);
else if(key == _T("org"))
_stscanf(value, _T("%d, %d"), &m_org.x, &m_org.y);
else if(key == _T("scale"))
_stscanf(value, _T("%d%%, %d%%"), &m_scale_x, &m_scale_y);
else if(key == _T("alpha"))
_stscanf(value, _T("%d%%"), &m_alpha);
else if(key == _T("smooth"))
m_fSmooth =
value == _T("0") || value == _T("OFF") ? 0 :
value == _T("1") || value == _T("ON") ? 1 :
value == _T("2") || value == _T("OLD") ? 2 :
0;
else if(key == _T("align"))
{
Explode(value, sl, ' ');
if(sl.GetCount() == 4) sl.RemoveAt(sl.FindIndex(1));
if(sl.GetCount() == 3)
{
m_fAlign = sl.RemoveHead() == _T("ON");
CString hor = sl.GetHead(), ver = sl.GetTail();
m_alignhor = hor == _T("LEFT") ? 0 : hor == _T("CENTER") ? 1 : hor == _T("RIGHT") ? 2 : 1;
m_alignver = ver == _T("TOP") ? 0 : ver == _T("CENTER") ? 1 : ver == _T("BOTTOM") ? 2 : 2;
}
}
else if(key == _T("fade in/out"))
_stscanf(value, _T("%d%, %d%"), &m_fadein, &m_fadeout);
else if(key == _T("time offset"))
m_toff = _tcstol(value, NULL, 10);
else if(key == _T("forced subs"))
m_fOnlyShowForcedSubs = value == _T("1") || value == _T("ON");
else if(key == _T("palette"))
{
Explode(value, sl, ',', 16);
for(int i = 0; i < 16 && sl.GetCount(); i++)
*(DWORD*)&m_orgpal[i] = _tcstol(sl.RemoveHead(), NULL, 16);
}
else if(key == _T("custom colors"))
{
m_fCustomPal = Explode(value, sl, ',', 3) == _T("ON");
if(sl.GetCount() == 3)
{
sl.RemoveHead();
CAtlList<CString> tridx, colors;
Explode(sl.RemoveHead(), tridx, ':', 2);
if(tridx.RemoveHead() == _T("tridx"))
{
TCHAR tr[4];
_stscanf(tridx.RemoveHead(), _T("%c%c%c%c"), &tr[0], &tr[1], &tr[2], &tr[3]);
for(int i = 0; i < 4; i++)
m_tridx |= ((tr[i]=='1')?1:0)<<i;
}
Explode(sl.RemoveHead(), colors, ':', 2);
if(colors.RemoveHead() == _T("colors"))
{
Explode(colors.RemoveHead(), colors, ',', 4);
for(int i = 0; i < 4 && colors.GetCount(); i++)
*(DWORD*)&m_cuspal[i] = _tcstol(colors.RemoveHead(), NULL, 16);
}
}
}
}
}
void CVobSubStream::Add(REFERENCE_TIME tStart, REFERENCE_TIME tStop, BYTE* pData, int len)
{
if(len <= 4 || ((pData[0]<<8)|pData[1]) != len) return;
CVobSubImage vsi;
vsi.GetPacketInfo(pData, (pData[0]<<8)|pData[1], (pData[2]<<8)|pData[3]);
CAutoPtr<SubPic> p(new SubPic());
p->tStart = tStart;
p->tStop = vsi.delay > 0 ? (tStart + 10000i64*vsi.delay) : tStop;
p->pData.SetCount(len);
memcpy(p->pData.GetData(), pData, p->pData.GetCount());
CAutoLock cAutoLock(&m_csSubPics);
while(m_subpics.GetCount() && m_subpics.GetTail()->tStart >= tStart)
m_subpics.RemoveTail();
m_subpics.AddTail(p);
}
void CVobSubStream::RemoveAll()
{
CAutoLock cAutoLock(&m_csSubPics);
m_subpics.RemoveAll();
}
STDMETHODIMP CVobSubStream::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
CheckPointer(ppv, E_POINTER);
*ppv = NULL;
return
QI(IPersist)
QI(ISubStream)
QI(ISubPicProvider)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPicProvider
STDMETHODIMP_(POSITION) CVobSubStream::GetStartPosition(REFERENCE_TIME rt, double fps)
{
CAutoLock cAutoLock(&m_csSubPics);
POSITION pos = m_subpics.GetTailPosition();
for(; pos; m_subpics.GetPrev(pos))
{
SubPic* sp = m_subpics.GetAt(pos);
if(sp->tStart <= rt)
{
if(sp->tStop <= rt) m_subpics.GetNext(pos);
break;
}
}
return(pos);
}
STDMETHODIMP_(POSITION) CVobSubStream::GetNext(POSITION pos)
{
CAutoLock cAutoLock(&m_csSubPics);
m_subpics.GetNext(pos);
return pos;
}
STDMETHODIMP_(REFERENCE_TIME) CVobSubStream::GetStart(POSITION pos, double fps)
{
CAutoLock cAutoLock(&m_csSubPics);
return m_subpics.GetAt(pos)->tStart;
}
STDMETHODIMP_(REFERENCE_TIME) CVobSubStream::GetStop(POSITION pos, double fps)
{
CAutoLock cAutoLock(&m_csSubPics);
return m_subpics.GetAt(pos)->tStop;
}
STDMETHODIMP_(bool) CVobSubStream::IsAnimated(POSITION pos)
{
return(false);
}
STDMETHODIMP CVobSubStream::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
{
if(spd.bpp != 32) return E_INVALIDARG;
POSITION pos = m_subpics.GetTailPosition();
for(; pos; m_subpics.GetPrev(pos))
{
SubPic* sp = m_subpics.GetAt(pos);
if(sp->tStart <= rt && rt < sp->tStop)
{
if(m_img.iIdx != (int)pos)
{
BYTE* pData = sp->pData.GetData();
m_img.Decode(
pData, (pData[0]<<8)|pData[1], (pData[2]<<8)|pData[3],
m_fCustomPal, m_tridx, m_orgpal, m_cuspal, true);
m_img.iIdx = (int)pos;
}
return __super::Render(spd, bbox);
}
}
return E_FAIL;
}
// IPersist
STDMETHODIMP CVobSubStream::GetClassID(CLSID* pClassID)
{
return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
}
// ISubStream
STDMETHODIMP_(int) CVobSubStream::GetStreamCount()
{
return 1;
}
STDMETHODIMP CVobSubStream::GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID)
{
CAutoLock cAutoLock(&m_csSubPics);
if(ppName)
{
if(!(*ppName = (WCHAR*)CoTaskMemAlloc((m_name.GetLength()+1)*sizeof(WCHAR))))
return E_OUTOFMEMORY;
wcscpy(*ppName, CStringW(m_name));
}
if(pLCID)
{
*pLCID = 0; // TODO
}
return S_OK;
}
STDMETHODIMP_(int) CVobSubStream::GetStream()
{
return 0;
}
STDMETHODIMP CVobSubStream::SetStream(int iStream)
{
return iStream == 0 ? S_OK : E_FAIL;
}