⛏️ index : haiku.git

/*
 * 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
{
	// binary search index
	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;
}