Aegisub/vsfilter/subtitles/VobSubFileRipper.cpp
Niels Martin Hansen 1fb4f58ff2 Import VSFilter from guliverkli.
Originally committed to SVN as r1399.
2007-07-09 18:33:44 +00:00

1259 lines
26 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 "vobsubfileripper.h"
#include "../include/decss/VobDec.h"
#include "../subtitles/CCDecoder.h"
//
// CVobSubFileRipper
//
CVobSubFileRipper::CVobSubFileRipper()
: CVobSubFile(NULL)
, m_fThreadActive(false)
, m_fBreakThread(false)
, m_fIndexing(false)
{
m_rd.Reset();
CAMThread::Create();
}
CVobSubFileRipper::~CVobSubFileRipper()
{
CAMThread::CallWorker(CMD_EXIT);
CAMThread::Close();
}
STDMETHODIMP CVobSubFileRipper::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(IVSFRipper)
__super::NonDelegatingQueryInterface(riid, ppv);
}
void CVobSubFileRipper::Log(log_t type, LPCTSTR lpszFormat, ...)
{
CAutoLock cAutoLock(&m_csCallback);
if(!m_pCallback) return;
TCHAR buff[1024];
va_list args;
va_start(args, lpszFormat);
_vstprintf(buff, lpszFormat, args);
va_end(args);
CString msg;
switch(type)
{
default:
case LOG_INFO: msg = _T(""); break;
case LOG_WARNING: msg = _T("WARNING: "); break;
case LOG_ERROR: msg = _T("ERROR: "); break;
}
msg += buff;
m_pCallback->OnMessage(msg);
}
void CVobSubFileRipper::Progress(double progress)
{
CAutoLock cAutoLock(&m_csCallback);
if(!m_pCallback) return;
m_pCallback->OnProgress(progress);
}
void CVobSubFileRipper::Finished(bool fSucceeded)
{
CAutoLock cAutoLock(&m_csCallback);
if(!m_pCallback) return;
m_pCallback->OnFinished(fSucceeded);
}
#define ReadBEb(var) \
f.Read(&((BYTE*)&var)[0], 1); \
#define ReadBEw(var) \
f.Read(&((BYTE*)&var)[1], 1); \
f.Read(&((BYTE*)&var)[0], 1); \
#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 CVobSubFileRipper::LoadIfo(CString fn)
{
CString str;
CFileStatus status;
if(!CFileGetStatus(fn, status) || !status.m_size)
{
Log(LOG_ERROR, _T("Invalid ifo"));
return(false);
}
CFile f;
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
{
Log(LOG_ERROR, _T("Cannot open ifo"));
return(false);
}
Log(LOG_INFO, _T("Opening ifo OK"));
char hdr[13];
f.Read(hdr, 12);
hdr[12] = 0;
if(strcmp(hdr, "DVDVIDEO-VTS"))
{
Log(LOG_ERROR, _T("Not a Video Title Set IFO file!"));
return(false);
}
// lang ids
f.Seek(0x254, CFile::begin);
WORD ids[32];
memset(ids, 0, sizeof(ids));
int len = 0;
ReadBEw(len);
for(int i = 0; i < len; i++)
{
f.Seek(2, CFile::current); // 01 00 ?
ReadBEw(ids[i]);
if(ids[i] == 0) ids[i] = '--';
f.Seek(2, CFile::current); // 00 00 ?
}
/* Video info */
f.Seek(0x200, CFile::begin);
f.Read(&m_rd.vidinfo, 2);
SIZE res[4][2] =
{
{{720,480},{720,576}},
{{704,480},{704,576}},
{{352,480},{352,576}},
{{352,240},{352,288}}
};
m_rd.vidsize = res[m_rd.vidinfo.source_res][m_rd.vidinfo.system&1];
double rate = (m_rd.vidinfo.system == 0) ? 30.0/29.97 : 1.0;
/* PGCs */
{
DWORD offset;
DWORD pgcpos;
f.Seek(0xc0+0x0c, CFile::begin);
ReadBEdw(pgcpos);
pgcpos *= 0x800;
WORD nPGC;
f.Seek(pgcpos, CFile::begin);
ReadBEw(nPGC);
m_rd.pgcs.RemoveAll();
m_rd.pgcs.SetCount(nPGC);
for(int i = 0; i < nPGC; i++)
{
PGC& pgc = m_rd.pgcs[i];
f.Seek(pgcpos + 8 + i*8 + 4, CFile::begin);
ReadBEdw(offset);
offset += pgcpos;
BYTE nProgs, nCells;
f.Seek(offset + 2, CFile::begin);
ReadBEb(nProgs);
ReadBEb(nCells);
//
memcpy(pgc.ids, ids, sizeof(ids));
struct splanginfo {BYTE res1, id1, id2, res2;};
splanginfo splinfo[32];
f.Seek(offset + 0x1c, CFile::begin);
f.Read(splinfo, 32*4);
for(int j = 0; j < 32; j++)
{
if(splinfo[j].id1 || splinfo[i].id2)
{
WORD tmpids[32];
memset(tmpids, 0, sizeof(tmpids));
for(j = 0; j < 32; j++)
{
if(!(splinfo[j].res1 & 0x80)) break;
pgc.ids[splinfo[j].id1] = ids[j];
pgc.ids[splinfo[j].id2] = ids[j];
}
break;
}
}
//
f.Seek(offset + 0xa4, CFile::begin);
for(int j = 0; j < 16; j++)
{
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;
pgc.pal[j].rgbRed = (BYTE)min(max(1.0*y + 1.4022*(u-128), 0), 255);
pgc.pal[j].rgbGreen = (BYTE)min(max(1.0*y - 0.3456*(u-128) - 0.7145*(v-128), 0), 255);
pgc.pal[j].rgbBlue = (BYTE)min(max(1.0*y + 1.7710*(v-128), 0) , 255);
}
//
WORD progoff, celladdroff, vobcelloff;
f.Seek(offset + 0xe6, CFile::begin);
ReadBEw(progoff);
f.Seek(offset + 0xe8, CFile::begin);
ReadBEw(celladdroff);
f.Seek(offset + 0xea, CFile::begin);
ReadBEw(vobcelloff);
//
CAtlArray<BYTE> progs;
progs.SetCount(nProgs);
f.Seek(offset + progoff, CFile::begin);
f.Read(progs.GetData(), nProgs);
//
pgc.angles[0].SetCount(nCells);
pgc.iSelAngle = 0;
//
f.Seek(offset + vobcelloff, CFile::begin);
for(int j = 0; j < nCells; j++)
{
ReadBEw(pgc.angles[0][j].vob);
ReadBEw(pgc.angles[0][j].cell);
}
//
DWORD tOffset = 0, tTotal = 0;
int iAngle = 0;
pgc.nAngles = 0;
f.Seek(offset + celladdroff, CFile::begin);
for(int j = 0; j < nCells; j++)
{
BYTE b;
ReadBEb(b);
switch(b>>6)
{
case 0: iAngle = 0; break; // normal
case 1: iAngle = 1; break; // first angle block
case 2: iAngle++; break; // middle angle block
case 3: iAngle++; break; // last angle block (no more should follow)
}
pgc.angles[0][j].iAngle = iAngle;
pgc.nAngles = max(pgc.nAngles, iAngle);
f.Seek(3, CFile::current);
ReadBEdw(pgc.angles[0][j].tTime);
ReadBEdw(pgc.angles[0][j].start);
f.Seek(8, CFile::current);
ReadBEdw(pgc.angles[0][j].end);
float fps;
switch((pgc.angles[0][j].tTime>>6)&0x3)
{
default:
case 3: fps = 30; break;
case 1: fps = 25; break;
}
int t = pgc.angles[0][j].tTime;
int hh = ((t>>28)&0xf)*10+((t>>24)&0xf);
int mm = ((t>>20)&0xf)*10+((t>>16)&0xf);
int ss = ((t>>12)&0xf)*10+((t>>8)&0xf);
int ms = (int)(1000.0 * (((t>>4)&0x3)*10+((t>>0)&0xf)) / fps);
pgc.angles[0][j].tTime = (DWORD)((((hh*60+mm)*60+ss)*1000+ms)*rate);
// time discontinuity
if(b&0x02) tOffset = tTotal;
pgc.angles[0][j].fDiscontinuity = !!(b&0x02);
pgc.angles[0][j].tTotal = tTotal;
pgc.angles[0][j].tOffset = tOffset;
tTotal += pgc.angles[0][j].tTime;
}
for(iAngle = 1; iAngle <= 9; iAngle++)
{
tOffset = tTotal = 0;
for(int j = 0, k = 0; j < nCells; j++)
{
if(pgc.angles[0][j].iAngle != 0
&& pgc.angles[0][j].iAngle != iAngle)
continue;
pgc.angles[iAngle].Add(pgc.angles[0][j]);
if(pgc.angles[iAngle][k].fDiscontinuity) tOffset = tTotal;
pgc.angles[iAngle][k].tTotal = tTotal;
pgc.angles[iAngle][k].tOffset = tOffset;
tTotal += pgc.angles[iAngle][k].tTime;
k++;
}
}
}
}
Log(LOG_INFO, _T("Parsing ifo OK"));
return(true);
}
bool CVobSubFileRipper::LoadVob(CString fn)
{
Log(LOG_INFO, _T("Searching vobs..."));
/*
CAtlList<CString> m_vobs;
fn = fn.Left(fn.ReverseFind('.')+1);
fn.TrimRight(_T(".0123456789"));
for(int i = 0; i < 100; i++)
{
CString vob;
vob.Format(_T("%s%d.vob"), fn, i);
CFileStatus status;
if(!(CFileGetStatus(vob, status) && status.m_size))
{
if(i > 0) break;
else continue;
}
if(status.m_size&0x7ff)
{
Log(LOG_ERROR, _T("Length of %s is not n*2048!"), vob);
m_vobs.RemoveAll();
break;
}
CString str = _T("Found ") + vob;
if(i == 0)
{
str += _T(" (skipping, if not a menu vob rename it)");
}
else
{
m_vobs.AddTail(vob);
}
Log(LOG_INFO, str);
}
if(m_vobs.GetCount() <= 0)
{
Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
return(false);
}
*/
CAtlList<CString> vobs;
if(!m_vob.Open(fn, vobs/*m_vobs*/))
{
Log(LOG_ERROR, _T("Cannot open vob sequence"));
return(false);
}
if(vobs.GetCount() <= 0)
{
Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
return(false);
}
POSITION pos = vobs.GetHeadPosition();
while(pos) Log(LOG_INFO, _T("Found ") + vobs.GetNext(pos));
if(m_vob.IsDVD())
{
Log(LOG_INFO, _T("DVD detected..."));
BYTE key[5];
if(m_vob.HasDiscKey(key))
Log(LOG_INFO, _T("Disc key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
else
Log(LOG_WARNING, _T("Couldn't get the disc key"));
if(m_vob.HasTitleKey(key))
Log(LOG_INFO, _T("Title key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
else
Log(LOG_WARNING, _T("Couldn't get the title key"));
BYTE buff[2048];
m_vob.Seek(0);
if(!m_vob.Read(buff))
{
Log(LOG_ERROR, _T("Can't read vob, please unlock it with a software player!"));
return(false);
}
m_vob.Seek(0);
}
return(true);
}
DWORD CVobSubFileRipper::ThreadProc()
{
SetThreadPriority(m_hThread, THREAD_PRIORITY_BELOW_NORMAL);
while(1)
{
DWORD cmd = GetRequest();
m_fThreadActive = true;
switch(cmd)
{
case CMD_EXIT:
Reply(S_OK);
return 0;
case CMD_INDEX:
Reply(S_OK);
{
m_fIndexing = true;
bool fSucceeded = Create();
m_fIndexing = false;
Finished(fSucceeded);
}
break;
default:
Reply(E_FAIL);
return -1;
}
m_fBreakThread = false;
m_fThreadActive = false;
}
return 1;
}
static int SubPosSortProc(const void* e1, const void* e2)
{
return((int)(((CVobSubFile::SubPos*)e1)->start - ((CVobSubFile::SubPos*)e2)->start));
}
bool CVobSubFileRipper::Create()
{
CAutoLock cAutoLock(&m_csAccessLock);
if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount())
{
Log(LOG_ERROR, _T("Invalid program chain number (%d)!"), m_rd.iSelPGC);
return(false);
}
PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
if(pgc.iSelAngle < 0 || pgc.iSelAngle > 9 || pgc.angles[pgc.iSelAngle].GetCount() == 0)
{
Log(LOG_ERROR, _T("Invalid angle number (%d)!"), pgc.iSelAngle);
return(false);
}
CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
if(m_rd.selids.GetCount() == 0 && !m_rd.fClosedCaption)
{
Log(LOG_ERROR, _T("No valid stream set to be extacted!"));
return(false);
}
if(m_rd.selvcs.GetCount() == 0)
{
Log(LOG_ERROR, _T("No valid vob/cell id set to be extacted!"));
return(false);
}
Log(LOG_INFO, _T("Indexing..."));
// initalize CVobSubFile
CVobSubFile::Close();
InitSettings();
m_title = m_outfn;
m_size = m_rd.vidsize;
TrimExtension(m_title);
memcpy(m_orgpal, pgc.pal, sizeof(m_orgpal));
m_sub.SetLength(0);
CCDecoder ccdec(m_title + _T(".cc.srt"), m_title + _T(".cc.raw"));
CVobDec vd;
__int64 SCR, PTS, tOffset = 0, tPrevOffset = 0, tTotal = 0, tStart = 0;
int vob = 0, cell = 0;
bool fDiscontinuity = false, fDiscontinuityFixApplied = false, fNavpackFound = false;
int PTSframeoffset = 0, minPTSframeoffset = 0;
if(m_rd.fResetTime)
{
for(int i = 0; i < angle.GetCount() && ((angle[i].vob<<16)|angle[i].cell) != m_rd.selvcs[0]; i++)
tStart += angle[i].tTime;
Log(LOG_INFO, _T("Counting timestamps from %I64dms (v%02dc%02d)"),
tStart, m_rd.selvcs[0]>>16, m_rd.selvcs[0]&0xffff);
}
CAtlMap<DWORD, int> selvcmap;
selvcmap.RemoveAll();
for(int i = 0; i < m_rd.selvcs.GetCount(); i++)
selvcmap[m_rd.selvcs[i]] = 90000;
CAtlArray<vcchunk> chunks, foundchunks, loadedchunks;
if(m_vob.IsDVD())
{
Log(LOG_INFO, _T("Indexing mode: DVD"));
for(int i = 0; i < angle.GetCount(); i++)
{
DWORD vc = (angle[i].vob<<16)|angle[i].cell;
if(!selvcmap.Lookup(vc))
continue;
vcchunk c = {2048i64*angle[i].start, 2048i64*angle[i].end+2048, vc};
chunks.Add(c);
Log(LOG_INFO, _T("Adding: 0x%x - 0x%x (lba) for vob %d cell %d"),
angle[i].start, angle[i].end, angle[i].vob, angle[i].cell);
}
}
else if(LoadChunks(loadedchunks))
{
Log(LOG_INFO, _T("Indexing mode: File"));
for(int i = 0; i < loadedchunks.GetCount(); i++)
{
DWORD vcid = loadedchunks[i].vc;
if(!selvcmap.Lookup(vcid))
continue;
chunks.Add(loadedchunks[i]);
}
Log(LOG_INFO, _T(".chunk file loaded"));
}
else
{
Log(LOG_INFO, _T("Indexing mode: File"));
chunks.RemoveAll();
vcchunk c = {0, 2048i64*m_vob.GetLength(), 0};
chunks.Add(c);
}
__int64 sizedone = 0, sizetotal = 0;
for(int i = 0; i < chunks.GetCount(); i++)
sizetotal += chunks[i].end - chunks[i].start;
for(int i = 0; !m_fBreakThread && i < chunks.GetCount(); i++)
{
__int64 curpos = chunks[i].start, endpos = chunks[i].end;
vcchunk curchunk = {curpos, curpos, chunks[i].vc};
for(m_vob.Seek((int)(curpos/2048)); !m_fBreakThread && curpos < endpos; curpos += 2048, sizedone += 2048)
{
if(!(curpos&0x7ffff))
Progress(1.0 * sizedone / sizetotal);
static BYTE buff[2048];
if(!m_vob.Read(buff))
{
Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
return(false);
}
curchunk.end = curpos;
if(buff[0x14] & 0x30)
{
if(!vd.m_fFoundKey)
{
Log(LOG_INFO, _T("Encrypted sector found, searching key..."));
__int64 savepos = curpos;
m_vob.Seek(0);
for(__int64 pos = 0; !m_fBreakThread && pos < endpos; pos += 2048)
{
if(!m_vob.Read(buff))
{
Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
return(false);
}
if(vd.FindKey(buff))
break;
}
if(m_fBreakThread)
break;
if(!vd.m_fFoundKey)
{
Log(LOG_ERROR, _T("Key not found, can't decrypt!"));
return(false);
}
Log(LOG_INFO, _T("Key found, continuing extraction..."));
m_vob.Seek((int)((curpos = savepos)/2048));
m_vob.Read(buff);
}
vd.Decrypt(buff);
}
if(*((DWORD*)&buff[0]) != 0xba010000)
{
Log(LOG_WARNING, _T("Bad sector header at block %08d!"), (int)(curpos/2048));
if(AfxMessageBox(_T("Bad packet header found, do you want to continue?"), MB_YESNO) == IDNO)
{
Log(LOG_ERROR, _T("Terminated!"));
return(false);
}
}
SCR = (__int64(buff[0x04] & 0x38) >> 3) << 30
| __int64(buff[0x04] & 0x03) << 28
| __int64(buff[0x05]) << 20
| (__int64(buff[0x06] & 0xf8) >> 3) << 15
| __int64(buff[0x06] & 0x03) << 13
| __int64(buff[0x07]) << 5
| (__int64(buff[0x08] & 0xf8) >> 3) << 0;
bool hasPTS = false;
if((*(DWORD*)&buff[0x0e] == 0xe0010000 || *(DWORD*)&buff[0x0e] == 0xbd010000)
&& buff[0x15] & 0x80)
{
PTS = (__int64)(buff[0x17] & 0x0e) << 29 // 32-30 (+marker)
| ((__int64)(buff[0x18]) << 22) // 29-22
| ((__int64)(buff[0x19] & 0xfe) << 14) // 21-15 (+marker)
| ((__int64)(buff[0x1a]) << 7) // 14-07
| ((__int64)(buff[0x1b]) >> 1); // 06-00 (+marker)
hasPTS = true;
}
if(*((DWORD*)&buff[0x0e]) == 0xbb010000)
{
fNavpackFound = true;
if(vob == buff[0x420] && cell == buff[0x422])
continue;
vob = buff[0x420];
cell = buff[0x422];
tOffset = tTotal = 0;
for(int i = 0; i < angle.GetCount(); i++)
{
if(angle[i].vob == vob && angle[i].cell == cell)
{
tPrevOffset = tOffset;
tOffset = (__int64)angle[i].tOffset;
tTotal = (__int64)angle[i].tTotal;
fDiscontinuity = angle[i].fDiscontinuity;
fDiscontinuityFixApplied = false;
break;
}
}
if(curchunk.vc != ((vob<<16)|cell))
{
if(curchunk.vc != 0) foundchunks.Add(curchunk);
curchunk.start = curchunk.end = curpos;
curchunk.vc = (vob<<16)|cell;
}
CString str, str2;
str.Format(_T("v%02d c%02d lba%08d"), vob, cell, (int)(curpos/2048));
UINT vcid = (vob<<16)|cell;
if(!selvcmap.Lookup(vcid, minPTSframeoffset)) str2 = _T(", skipping");
else str2.Format(_T(", total=%I64dms, off=%I64dms, corr=%I64dms, discont.:%d"),
tTotal, tOffset, -tStart, (int)fDiscontinuity);
Log(LOG_INFO, str + str2);
}
DWORD vcid = (vob<<16)|cell;
if(!selvcmap.Lookup(vcid, minPTSframeoffset))
continue;
if(hasPTS && fDiscontinuity && !fDiscontinuityFixApplied)
{
__int64 tDiff = tOffset - tPrevOffset;
if(tDiff > 0 && tDiff < (PTS/90+1000))
{
CString str;
str.Format(_T("False discontinuity detected, correcting time by %I64dms"), -tDiff);
Log(LOG_INFO, str);
tStart += tDiff;
}
fDiscontinuityFixApplied = true;
}
if(*(DWORD*)&buff[0x0e] == 0xe0010000)
{
if(fDiscontinuity)
{
if(PTS < minPTSframeoffset)
{
selvcmap[vcid] = PTSframeoffset = PTS;
}
fDiscontinuity = false;
}
if(m_rd.fClosedCaption)
ccdec.ExtractCC(buff, 2048, tOffset + ((PTS - PTSframeoffset) / 90) - tStart);
}
else if(*(DWORD*)&buff[0x0e] == 0xbd010000)
{
BYTE id = buff[0x17 + buff[0x16]], iLang = id&0x1f;
if((id & 0xe0) == 0x20 && m_rd.selids.Lookup(iLang))
{
if(hasPTS)
{
SubPos sb;
sb.filepos = m_sub.GetPosition();
sb.start = tOffset + ((PTS - PTSframeoffset) / 90) - tStart;
sb.vobid = (char)vob;
sb.cellid = (char)cell;
sb.celltimestamp = tTotal;
sb.fValid = true;
m_langs[iLang].subpos.Add(sb);
}
m_sub.Write(buff, 2048);
}
}
}
if(curchunk.vc != ((vob<<16)|cell))
{
if(curchunk.vc != 0) foundchunks.Add(curchunk);
curchunk.start = curchunk.end = curpos;
curchunk.vc = (vob<<16)|cell;
}
}
if(sizedone < sizetotal)
{
Log(LOG_ERROR, _T("Indexing terminated before reaching the end!"));
Progress(0);
return(false);
}
if(!fNavpackFound)
{
Log(LOG_ERROR, _T("Could not find any system header start code! (0x000001bb)"));
if(!m_vob.IsDVD()) Log(LOG_ERROR, _T("Make sure the ripper doesn't strip these packets."));
Progress(0);
return(false);
}
Log(LOG_INFO, _T("Indexing finished"));
Progress(1);
for(int i = 0; i < 32; i++)
{
if(m_iLang == -1 && m_langs[i].subpos.GetCount() > 0) m_iLang = i;
m_langs[i].id = pgc.ids[i];
m_langs[i].name = m_langs[i].alt = FindLangFromId(m_langs[i].id);
CAtlArray<SubPos>& sp = m_langs[i].subpos;
qsort(sp.GetData(), sp.GetCount(), sizeof(SubPos), SubPosSortProc);
if(m_rd.fForcedOnly)
{
Log(LOG_INFO, _T("Searching for forced subs..."));
Progress(0);
for(int j = 0, len = sp.GetCount(); j < len; j++)
{
Progress(1.0 * j / len);
sp[j].fValid = false;
int packetsize = 0, datasize = 0;
if(BYTE* buff = GetPacket(j, packetsize, datasize, i))
{
m_img.GetPacketInfo(buff, packetsize, datasize);
sp[j].fValid = m_img.fForced;
delete [] buff;
}
}
Progress(1);
}
}
Log(LOG_INFO, _T("Saving files..."));
if(m_iLang != -1)
{
if(!Save(m_title))
{
Log(LOG_ERROR, _T("Could not save output files!"));
return(false);
}
}
Log(LOG_INFO, _T("Subtitles saved"));
if(!m_vob.IsDVD() && loadedchunks.GetCount() == 0)
{
if(SaveChunks(foundchunks))
{
Log(LOG_INFO, _T(".chunk file saved"));
}
}
Log(LOG_INFO, _T("Done!"));
return(true);
}
static const DWORD s_version = 1;
bool CVobSubFileRipper::LoadChunks(CAtlArray<vcchunk>& chunks)
{
CFile f;
CString fn = m_infn;
TrimExtension(fn);
fn += _T(".chunks");
DWORD chksum = 0, chunklen, version;
__int64 voblen;
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
f.Read(&version, sizeof(version));
if(version == 1)
{
f.Read(&chksum, sizeof(chksum));
f.Read(&voblen, sizeof(voblen));
f.Read(&chunklen, sizeof(chunklen));
chunks.SetCount(chunklen);
f.Read(chunks.GetData(), sizeof(vcchunk)*chunks.GetCount());
}
f.Close();
if(voblen != m_vob.GetLength())
{
chunks.RemoveAll();
return(false);
}
if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
DWORD dw, chksum2 = 0;
while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum2 += dw;
f.Close();
if(chksum != chksum2)
{
chunks.RemoveAll();
return(false);
}
return(true);
}
bool CVobSubFileRipper::SaveChunks(CAtlArray<vcchunk>& chunks)
{
CFile f;
CString fn = m_infn;
TrimExtension(fn);
fn += _T(".chunks");
DWORD chksum = 0, chunklen = chunks.GetCount();
__int64 voblen = m_vob.GetLength();
if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
DWORD dw;
while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum += dw;
f.Close();
if(!f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
return(false);
f.Write(&s_version, sizeof(s_version));
f.Write(&chksum, sizeof(chksum));
f.Write(&voblen, sizeof(voblen));
f.Write(&chunklen, sizeof(chunklen));
f.Write(chunks.GetData(), sizeof(vcchunk)*chunklen);
f.Close();
return(true);
}
// IVSFRipper
STDMETHODIMP CVobSubFileRipper::SetCallBack(IVSFRipperCallback* pCallback)
{
CAutoLock cAutoLock(&m_csCallback);
m_pCallback = pCallback;
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::LoadParamFile(CString fn)
{
CAutoLock cAutoLock(&m_csAccessLock);
m_rd.Reset();
CStdioFile f;
if(!f.Open(fn, CFile::modeRead|CFile::typeText))
return E_FAIL;
TCHAR langid[256];
enum {P_INPUT, P_OUTPUT, P_PGC, P_ANGLE, P_LANGS, P_OPTIONS};
int phase = P_INPUT;
CString line;
while(f.ReadString(line))
{
if(line.Trim().IsEmpty() || line[0] == '#') continue;
if(phase == P_INPUT)
{
if(S_OK != SetInput(line)) break;
phase = P_OUTPUT;
}
else if(phase == P_OUTPUT)
{
if(S_OK != SetOutput(line)) break;
phase = P_PGC;
}
else if(phase == P_PGC)
{
m_rd.iSelPGC = _tcstol(line, NULL, 10)-1;
if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount()) break;
phase = P_ANGLE;
}
else if(phase == 3)
{
PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
pgc.iSelAngle = _tcstol(line, NULL, 10);
if(pgc.iSelAngle < 0 || pgc.iSelAngle > max(1, pgc.nAngles) || pgc.iSelAngle > 9) break;
CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
if(line.Find('v') >= 0)
{
int vob = 0, cell = 0;
line += ' ';
TCHAR* s = (LPTSTR)(LPCTSTR)line;
TCHAR* e = s + line.GetLength();
while(s < e)
{
if(*s == 'v' || s == e-1)
{
s++;
if(vob != 0 && cell == 0)
{
for(int i = 0; i < angle.GetCount(); i++)
{
if(angle[i].vob == vob)
m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
}
}
vob = _tcstol(s, &s, 10);
cell = 0;
}
else if(*s == 'c' && vob > 0)
{
s++;
cell = _tcstol(s, &s, 10);
for(int i = 0; i < angle.GetCount(); i++)
{
if(angle[i].vob == vob && angle[i].cell == cell)
{
m_rd.selvcs.Add((vob<<16)|cell);
break;
}
}
}
else
{
s++;
}
}
}
else
{
for(int i = 0; i < angle.GetCount(); i++)
m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
}
phase = P_LANGS;
}
else if(phase == 4)
{
if(!line.CompareNoCase(_T("ALL")))
{
for(int i = 0; i < 32; i++) m_rd.selids[i] = true;
m_rd.fClosedCaption = true;
phase = P_OPTIONS;
}
else
{
line += ' ';
while(line.GetLength() > 0)
{
int n = line.Find(_T(" "));
CString lang = line.Left(n);
line = line.Mid(n);
line.TrimLeft();
n = 0;
int langnum;
if(_istdigit(lang[0]))
{
n = _stscanf(lang, _T("%d"), &langnum);
if(n != 1) break;
m_rd.selids[langnum] = true;
}
else if(_istalpha(lang[0]))
{
n = _stscanf(lang, _T("%s"), langid);
if(n != 1) break;
int id = (langid[0] << 8) + langid[1];
if(id == 'cc')
{
m_rd.fClosedCaption = true;
}
else
{
m_rd.selids[id] = true;
}
}
else break;
if(n != 1) break;
}
if((m_rd.selids.GetCount() > 0 || m_rd.fClosedCaption) && line.IsEmpty())
phase = P_OPTIONS;
}
}
else if(phase == 5 && !line.CompareNoCase(_T("CLOSE")))
m_rd.fClose = true;
else if(phase == 5 && !line.CompareNoCase(_T("BEEP")))
m_rd.fBeep = true;
else if(phase == 5 && !line.CompareNoCase(_T("RESETTIME")))
m_rd.fResetTime = true;
else if(phase == 5 && !line.CompareNoCase(_T("FORCEDONLY")))
m_rd.fForcedOnly = true;
else if(phase == 5 && !line.CompareNoCase(_T("CLOSEIGNOREERRORS")))
m_rd.fCloseIgnoreError = true;
}
m_rd.fAuto = true;
return phase == P_OPTIONS ? S_OK : E_FAIL;
}
STDMETHODIMP CVobSubFileRipper::SetInput(CString infn)
{
CAutoLock cAutoLock(&m_csAccessLock);
m_rd.Reset();
if(!LoadIfo(infn) || !LoadVob(infn))
return E_INVALIDARG;
m_infn = infn;
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::SetOutput(CString outfn)
{
CAutoLock cAutoLock(&m_csAccessLock);
m_outfn = outfn;
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::GetRipperData(VSFRipperData& rd)
{
CAutoLock cAutoLock(&m_csAccessLock);
rd.Copy(m_rd);
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::UpdateRipperData(VSFRipperData& rd)
{
CAutoLock cAutoLock(&m_csAccessLock);
m_rd.Copy(rd);
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::Index()
{
if(m_fIndexing) return E_FAIL;
CAMThread::CallWorker(CMD_INDEX);
return S_OK;
}
STDMETHODIMP CVobSubFileRipper::IsIndexing()
{
return m_fIndexing ? S_OK : S_FALSE;
}
STDMETHODIMP CVobSubFileRipper::Abort(bool fSavePartial)
{
m_fBreakThread = true;
return S_OK;
}
//
void VSFRipperData::Reset()
{
vidsize.SetSize(0,0);
memset(&vidinfo, 0, sizeof(vidinfo));
pgcs.RemoveAll();
iSelPGC = -1;
fResetTime = fClosedCaption = true;
fForcedOnly = false;
fClose = fBeep = fAuto = false;
fCloseIgnoreError = false;
selvcs.RemoveAll();
selids.RemoveAll();
}
void VSFRipperData::Copy(VSFRipperData& rd)
{
Reset();
vidsize = rd.vidsize;
vidinfo = rd.vidinfo;
if(int len = rd.pgcs.GetCount())
{
pgcs.SetCount(len);
for(int i = 0; i < len; i++)
{
PGC& src = rd.pgcs[i];
PGC& dst = pgcs[i];
dst.nAngles = src.nAngles;
for(int i = 0; i < countof(dst.angles); i++)
dst.angles[i].Copy(src.angles[i]);
dst.iSelAngle = src.iSelAngle;
memcpy(dst.pal, src.pal, sizeof(src.pal));
memcpy(dst.ids, src.ids, sizeof(src.ids));
}
}
iSelPGC = rd.iSelPGC;
fResetTime = rd.fResetTime;
fClosedCaption = rd.fClosedCaption;
fForcedOnly = rd.fForcedOnly;
fClose = rd.fClose;
fBeep = rd.fBeep;
fAuto = rd.fAuto;
fCloseIgnoreError = rd.fCloseIgnoreError;
selvcs.Copy(rd.selvcs);
POSITION pos = rd.selids.GetStartPosition();
while(pos)
{
BYTE key;
bool val;
rd.selids.GetNextAssoc(pos, key, val);
selids[key] = val;
}
}