* Copyright 2010, Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "SubTitlesSRT.h"
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <AutoDeleter.h>
#include <File.h>
#include <StringList.h>
#include <TextEncoding.h>
static void
ReadLines(BFile* file, BStringList& lines)
{
if (file == NULL)
return;
if (file->InitCheck() != B_OK)
return;
off_t size;
if (file->GetSize(&size) != B_OK || size == 0)
return;
ArrayDeleter<char> buffer(new(std::nothrow) char[size + 1]);
if (!buffer.IsSet())
return;
if (file->Read(buffer.Get(), size) < size)
return;
buffer[size] = '\0';
BPrivate::BTextEncoding decoder(buffer.Get(), size);
size_t decodedLength = size * 4;
BString content;
char* decoded = content.LockBuffer(decodedLength);
size_t consumed = size;
decoder.Decode(buffer.Get(), consumed, decoded, decodedLength);
content.UnlockBuffer(decodedLength);
content.Split("\n", false, lines);
}
SubTitlesSRT::SubTitlesSRT(BFile* file, const char* name)
:
SubTitles(),
fName(name),
fSubTitles(64)
{
BStringList lines;
ReadLines(file, lines);
int32 totalLines = lines.CountStrings();
enum {
EXPECT_SEQUENCE_NUMBER = 0,
EXPECT_TIME_CODE,
EXPECT_TEXT
};
SubTitle subTitle;
int32 lastSequenceNumber = 0;
int32 state = EXPECT_SEQUENCE_NUMBER;
for (int32 currentLine = 0; currentLine < totalLines; currentLine++) {
BString line(lines.StringAt(currentLine));
line.RemoveAll("\r");
switch (state) {
case EXPECT_SEQUENCE_NUMBER:
{
if (line.IsEmpty())
continue;
line.Trim();
int32 sequenceNumber = atoi(line.String());
if (sequenceNumber != lastSequenceNumber + 1) {
fprintf(stderr, "Warning: Wrong sequence number in SRT "
"file: %" B_PRId32 ", expected: %" B_PRId32 ", line %"
B_PRId32 "\n", sequenceNumber, lastSequenceNumber + 1,
currentLine);
}
state = EXPECT_TIME_CODE;
lastSequenceNumber = sequenceNumber;
break;
}
case EXPECT_TIME_CODE:
{
line.Trim();
int32 separatorPos = line.FindFirst(" --> ");
if (separatorPos < 0) {
fprintf(stderr, "Error: Time code expected on line %"
B_PRId32 ", got '%s'\n", currentLine, line.String());
return;
}
BString timeCode(line.String(), separatorPos);
if (separatorPos != 12) {
fprintf(stderr, "Warning: Time code broken on line %"
B_PRId32 " (%s)?\n", currentLine, timeCode.String());
}
int hours;
int minutes;
int seconds;
int milliSeconds;
if (sscanf(timeCode.String(), "%d:%d:%d,%d", &hours, &minutes,
&seconds, &milliSeconds) != 4) {
fprintf(stderr, "Error: Failed to parse start time on "
"line %" B_PRId32 "\n", currentLine);
return;
}
subTitle.startTime = (bigtime_t)hours * 60 * 60 * 1000000LL
+ (bigtime_t)minutes * 60 * 1000000LL
+ (bigtime_t)seconds * 1000000LL
+ (bigtime_t)milliSeconds * 1000;
int32 endTimePos = separatorPos + 5;
timeCode.SetTo(line.String() + endTimePos);
if (sscanf(timeCode.String(), "%d:%d:%d,%d", &hours, &minutes,
&seconds, &milliSeconds) != 4) {
fprintf(stderr, "Error: Failed to parse end time on "
"line %" B_PRId32 "\n", currentLine);
return;
}
bigtime_t endTime = (bigtime_t)hours * 60 * 60 * 1000000LL
+ (bigtime_t)minutes * 60 * 1000000LL
+ (bigtime_t)seconds * 1000000LL
+ (bigtime_t)milliSeconds * 1000;
subTitle.duration = endTime - subTitle.startTime;
state = EXPECT_TEXT;
break;
}
case EXPECT_TEXT:
if (line.IsEmpty()) {
int32 index = _IndexFor(subTitle.startTime);
SubTitle* clone = new(std::nothrow) SubTitle(subTitle);
if (clone == NULL || !fSubTitles.AddItem(clone, index)) {
delete clone;
return;
}
subTitle.text = "";
subTitle.placement = BPoint(-1, -1);
subTitle.startTime = 0;
subTitle.duration = 0;
state = EXPECT_SEQUENCE_NUMBER;
} else {
subTitle.text << line << '\n';
}
break;
}
}
}
SubTitlesSRT::~SubTitlesSRT()
{
for (int32 i = fSubTitles.CountItems() - 1; i >= 0; i--)
delete reinterpret_cast<SubTitle*>(fSubTitles.ItemAtFast(i));
}
const char*
SubTitlesSRT::Name() const
{
return fName.String();
}
const SubTitle*
SubTitlesSRT::SubTitleAt(bigtime_t time) const
{
int32 index = _IndexFor(time) - 1;
SubTitle* subTitle = reinterpret_cast<SubTitle*>(fSubTitles.ItemAt(index));
if (subTitle != NULL && subTitle->startTime + subTitle->duration > time)
return subTitle;
return NULL;
}
int32
SubTitlesSRT::_IndexFor(bigtime_t startTime) const
{
int32 lower = 0;
int32 upper = fSubTitles.CountItems();
while (lower < upper) {
int32 mid = (lower + upper) / 2;
SubTitle* subTitle = reinterpret_cast<SubTitle*>(
fSubTitles.ItemAtFast(mid));
if (startTime < subTitle->startTime)
upper = mid;
else
lower = mid + 1;
}
return lower;
}