Aegisub/vsfilter/libssf/SubtitleFile.cpp

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";
}