404 lines
8.8 KiB
C++
404 lines
8.8 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 "SubtitleFile.h"
|
||
|
|
||
|
namespace ssf
|
||
|
{
|
||
|
SubtitleFile::SubtitleFile()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
SubtitleFile::~SubtitleFile()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void SubtitleFile::Parse(InputStream& s)
|
||
|
{
|
||
|
m_segments.RemoveAll();
|
||
|
|
||
|
__super::Parse(s, s_predef);
|
||
|
|
||
|
// TODO: check file.format == "ssf" and file.version == 1
|
||
|
|
||
|
CAtlList<Definition*> defs;
|
||
|
GetRootRef()->GetChildDefs(defs, L"subtitle");
|
||
|
|
||
|
StringMapW<float> offset;
|
||
|
|
||
|
POSITION pos = defs.GetHeadPosition();
|
||
|
while(pos)
|
||
|
{
|
||
|
Definition* pDef = defs.GetNext(pos);
|
||
|
|
||
|
try
|
||
|
{
|
||
|
Definition::Time time;
|
||
|
|
||
|
if(pDef->GetAsTime(time, offset) && (*pDef)[L"@"].IsValue())
|
||
|
{
|
||
|
m_segments.Insert(time.start.value, time.stop.value, pDef);
|
||
|
}
|
||
|
}
|
||
|
catch(Exception&)
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SubtitleFile::Append(InputStream& s, float start, float stop, bool fSetTime)
|
||
|
{
|
||
|
Reference* pRootRef = GetRootRef();
|
||
|
|
||
|
ParseDefs(s, pRootRef);
|
||
|
|
||
|
CAtlList<Definition*> defs;
|
||
|
GetNewDefs(defs);
|
||
|
|
||
|
POSITION pos = defs.GetHeadPosition();
|
||
|
while(pos)
|
||
|
{
|
||
|
Definition* pDef = defs.GetNext(pos);
|
||
|
|
||
|
if(pDef->m_parent == pRootRef && pDef->m_type == L"subtitle" && (*pDef)[L"@"].IsValue())
|
||
|
{
|
||
|
m_segments.Insert(start, stop, pDef);
|
||
|
|
||
|
if(fSetTime)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
Definition::Time time;
|
||
|
StringMapW<float> offset;
|
||
|
pDef->GetAsTime(time, offset);
|
||
|
if(time.start.value == start && time.stop.value == stop)
|
||
|
continue;
|
||
|
}
|
||
|
catch(Exception&)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
CStringW str;
|
||
|
str.Format(L"%.3f", start);
|
||
|
pDef->SetChildAsNumber(L"time.start", str, L"s");
|
||
|
str.Format(L"%.3f", stop);
|
||
|
pDef->SetChildAsNumber(L"time.stop", str, L"s");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Commit();
|
||
|
}
|
||
|
|
||
|
bool SubtitleFile::Lookup(float at, CAutoPtrList<Subtitle>& subs)
|
||
|
{
|
||
|
if(!subs.IsEmpty()) {ASSERT(0); return false;}
|
||
|
|
||
|
CAtlList<SegmentItem> sis;
|
||
|
m_segments.Lookup(at, sis);
|
||
|
|
||
|
POSITION pos = sis.GetHeadPosition();
|
||
|
while(pos)
|
||
|
{
|
||
|
SegmentItem& si = sis.GetNext(pos);
|
||
|
|
||
|
CAutoPtr<Subtitle> s(new Subtitle(this));
|
||
|
|
||
|
if(s->Parse(si.pDef, si.start, si.stop, at))
|
||
|
{
|
||
|
for(POSITION pos = subs.GetHeadPosition(); pos; subs.GetNext(pos))
|
||
|
{
|
||
|
if(s->m_layer < subs.GetAt(pos)->m_layer)
|
||
|
{
|
||
|
subs.InsertBefore(pos, s);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(s)
|
||
|
{
|
||
|
subs.AddTail(s);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return !subs.IsEmpty();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
|
||
|
SubtitleFile::Segment::Segment(float start, float stop, const SegmentItem* si)
|
||
|
{
|
||
|
m_start = start;
|
||
|
m_stop = stop;
|
||
|
if(si) AddTail(*si);
|
||
|
}
|
||
|
|
||
|
SubtitleFile::Segment::Segment(const Segment& s)
|
||
|
{
|
||
|
*this = s;
|
||
|
}
|
||
|
|
||
|
void SubtitleFile::Segment::operator = (const Segment& s)
|
||
|
{
|
||
|
m_start = s.m_start;
|
||
|
m_stop = s.m_stop;
|
||
|
RemoveAll();
|
||
|
AddTailList(&s);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
|
||
|
void SubtitleFile::SegmentList::RemoveAll()
|
||
|
{
|
||
|
__super::RemoveAll();
|
||
|
m_index.RemoveAll();
|
||
|
}
|
||
|
|
||
|
void SubtitleFile::SegmentList::Insert(float start, float stop, Definition* pDef)
|
||
|
{
|
||
|
if(start >= stop) {ASSERT(0); return;}
|
||
|
|
||
|
m_index.RemoveAll();
|
||
|
|
||
|
SegmentItem si = {pDef, start, stop};
|
||
|
|
||
|
if(IsEmpty())
|
||
|
{
|
||
|
AddTail(Segment(start, stop, &si));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Segment& head = GetHead();
|
||
|
Segment& tail = GetTail();
|
||
|
|
||
|
if(start >= tail.m_stop && stop > tail.m_stop)
|
||
|
{
|
||
|
if(start > tail.m_stop) AddTail(Segment(tail.m_stop, start));
|
||
|
AddTail(Segment(start, stop, &si));
|
||
|
}
|
||
|
else if(start < head.m_start && stop <= head.m_start)
|
||
|
{
|
||
|
if(stop < head.m_start) AddHead(Segment(stop, head.m_start));
|
||
|
AddHead(Segment(start, stop, &si));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(start < head.m_start)
|
||
|
{
|
||
|
AddHead(Segment(start, head.m_start, &si));
|
||
|
start = head.m_start;
|
||
|
}
|
||
|
|
||
|
if(stop > tail.m_stop)
|
||
|
{
|
||
|
AddTail(Segment(tail.m_stop, stop, &si));
|
||
|
stop = tail.m_stop;
|
||
|
}
|
||
|
|
||
|
for(POSITION pos = GetHeadPosition(); pos; GetNext(pos))
|
||
|
{
|
||
|
Segment& s = GetAt(pos);
|
||
|
|
||
|
if(start >= s.m_stop) continue;
|
||
|
if(stop <= s.m_start) break;
|
||
|
|
||
|
if(s.m_start < start && start < s.m_stop)
|
||
|
{
|
||
|
Segment s2 = s;
|
||
|
s2.m_start = start;
|
||
|
InsertAfter(pos, s2);
|
||
|
s.m_stop = start;
|
||
|
}
|
||
|
else if(s.m_start == start)
|
||
|
{
|
||
|
if(stop > s.m_stop)
|
||
|
{
|
||
|
start = s.m_stop;
|
||
|
}
|
||
|
else if(stop < s.m_stop)
|
||
|
{
|
||
|
Segment s2 = s;
|
||
|
s2.m_start = stop;
|
||
|
InsertAfter(pos, s2);
|
||
|
s.m_stop = stop;
|
||
|
}
|
||
|
|
||
|
s.AddTail(si);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size_t SubtitleFile::SegmentList::Index(bool fForce)
|
||
|
{
|
||
|
if(m_index.IsEmpty() || fForce)
|
||
|
{
|
||
|
m_index.RemoveAll();
|
||
|
POSITION pos = GetHeadPosition();
|
||
|
while(pos) m_index.Add(&GetNext(pos));
|
||
|
}
|
||
|
|
||
|
return m_index.GetCount();
|
||
|
}
|
||
|
|
||
|
void SubtitleFile::SegmentList::Lookup(float at, CAtlList<SegmentItem>& sis)
|
||
|
{
|
||
|
sis.RemoveAll();
|
||
|
|
||
|
size_t k;
|
||
|
if(Lookup(at, k))
|
||
|
{
|
||
|
sis.AddTailList(GetSegment(k));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SubtitleFile::SegmentList::Lookup(float at, size_t& k)
|
||
|
{
|
||
|
if(!Index()) return false;
|
||
|
|
||
|
size_t i = 0, j = m_index.GetCount()-1;
|
||
|
|
||
|
if(m_index[i]->m_start <= at && at < m_index[j]->m_stop)
|
||
|
do
|
||
|
{
|
||
|
k = (i+j)/2;
|
||
|
if(m_index[k]->m_start <= at && at < m_index[k]->m_stop) {return true;}
|
||
|
else if(at < m_index[k]->m_start) {if(j == k) k--; j = k;}
|
||
|
else if(at >= m_index[k]->m_stop) {if(i == k) k++; i = k;}
|
||
|
}
|
||
|
while(i <= j);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const SubtitleFile::Segment* SubtitleFile::SegmentList::GetSegment(size_t k)
|
||
|
{
|
||
|
return 0 <= k && k < m_index.GetCount() ? m_index[k] : NULL;
|
||
|
}
|
||
|
|
||
|
// TODO: this should be overridable from outside
|
||
|
|
||
|
LPCWSTR SubtitleFile::s_predef =
|
||
|
L"color#white {a: 255; r: 255; g: 255; b: 255;}; \n"
|
||
|
L"color#black {a: 255; r: 0; g: 0; b: 0;}; \n"
|
||
|
L"color#gray {a: 255; r: 128; g: 128; b: 128;}; \n"
|
||
|
L"color#red {a: 255; r: 255; g: 0; b: 0;}; \n"
|
||
|
L"color#green {a: 255; r: 0; g: 255; b: 0;}; \n"
|
||
|
L"color#blue {a: 255; r: 0; g: 0; b: 255;}; \n"
|
||
|
L"color#cyan {a: 255; r: 0; g: 255; b: 255;}; \n"
|
||
|
L"color#yellow {a: 255; r: 255; g: 255; b: 0;}; \n"
|
||
|
L"color#magenta {a: 255; r: 255; g: 0; b: 255;}; \n"
|
||
|
L" \n"
|
||
|
L"align#topleft {v: \"top\"; h: \"left\";}; \n"
|
||
|
L"align#topcenter {v: \"top\"; h: \"center\";}; \n"
|
||
|
L"align#topright {v: \"top\"; h: \"right\";}; \n"
|
||
|
L"align#middleleft {v: \"middle\"; h: \"left\";}; \n"
|
||
|
L"align#middlecenter {v: \"middle\"; h: \"center\";}; \n"
|
||
|
L"align#middleright {v: \"middle\"; h: \"right\";}; \n"
|
||
|
L"align#bottomleft {v: \"bottom\"; h: \"left\";}; \n"
|
||
|
L"align#bottomcenter {v: \"bottom\"; h: \"center\";}; \n"
|
||
|
L"align#bottomright {v: \"bottom\"; h: \"right\";}; \n"
|
||
|
L" \n"
|
||
|
L"time#time {scale: 1;}; \n"
|
||
|
L"time#startstop {start: \"start\"; stop: \"stop\";}; \n"
|
||
|
L" \n"
|
||
|
L"#b {font.weight: \"bold\"}; \n"
|
||
|
L"#i {font.italic: \"true\"}; \n"
|
||
|
L"#u {font.underline: \"true\"}; \n"
|
||
|
L"#s {font.strikethrough: \"true\"}; \n"
|
||
|
L" \n"
|
||
|
L"#nobr {linebreak: \"none\"}; \n"
|
||
|
L" \n"
|
||
|
L"subtitle#subtitle \n"
|
||
|
L"{ \n"
|
||
|
L" frame \n"
|
||
|
L" { \n"
|
||
|
L" reference: \"video\"; \n"
|
||
|
L" resolution: {cx: 640; cy: 480;}; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" direction \n"
|
||
|
L" { \n"
|
||
|
L" primary: \"right\"; \n"
|
||
|
L" secondary: \"down\"; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" wrap: \"normal\"; \n"
|
||
|
L" \n"
|
||
|
L" layer: 0; \n"
|
||
|
L" \n"
|
||
|
L" style \n"
|
||
|
L" { \n"
|
||
|
L" linebreak: \"word\"; \n"
|
||
|
L" \n"
|
||
|
L" placement \n"
|
||
|
L" { \n"
|
||
|
L" clip: \"none\"; \n"
|
||
|
L" margin: {t: 0; r: 0; b: 0; l: 0;}; \n"
|
||
|
L" align: bottomcenter; \n"
|
||
|
L" pos: \"auto\" \n"
|
||
|
L" offset: {x: 0; y: 0;}; \n"
|
||
|
L" angle: {x: 0; y: 0; z: 0;}; \n"
|
||
|
L" org: \"auto\" \n"
|
||
|
L" path: \"\"; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" font \n"
|
||
|
L" { \n"
|
||
|
L" face: \"Arial\"; \n"
|
||
|
L" size: 20; \n"
|
||
|
L" weight: \"bold\"; \n"
|
||
|
L" color: white; \n"
|
||
|
L" underline: \"false\"; \n"
|
||
|
L" strikethrough: \"false\"; \n"
|
||
|
L" italic: \"false\"; \n"
|
||
|
L" spacing: 0; \n"
|
||
|
L" scale: {cx: 1; cy: 1;}; \n"
|
||
|
L" kerning: \"true\"; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" background \n"
|
||
|
L" { \n"
|
||
|
L" color: black; \n"
|
||
|
L" size: 2; \n"
|
||
|
L" type: \"outline\"; \n"
|
||
|
L" blur: 0; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" shadow \n"
|
||
|
L" { \n"
|
||
|
L" color: black {a: 128;}; \n"
|
||
|
L" depth: 2; \n"
|
||
|
L" angle: -45; \n"
|
||
|
L" blur: 0; \n"
|
||
|
L" }; \n"
|
||
|
L" \n"
|
||
|
L" fill \n"
|
||
|
L" { \n"
|
||
|
L" color: yellow; \n"
|
||
|
L" width: 0; \n"
|
||
|
L" }; \n"
|
||
|
L" }; \n"
|
||
|
L"}; \n";
|
||
|
}
|