1/*
2 * Copyright 2010, Stephan A��mus <superstippi@gmx.de>. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "SubTitlesSRT.h"
8
9#include <new>
10
11#include <stdlib.h>
12
13#include <File.h>
14#include <TextEncoding.h>
15
16#include "FileReadWrite.h"
17
18
19SubTitlesSRT::SubTitlesSRT(BFile* file, const char* name)
20	:
21	SubTitles(),
22	fName(name),
23	fSubTitles(64)
24{
25	if (file == NULL)
26		return;
27	if (file->InitCheck() != B_OK)
28		return;
29
30	FileReadWrite lineProvider(file);
31	BString line;
32	enum {
33		EXPECT_SEQUENCE_NUMBER = 0,
34		EXPECT_TIME_CODE,
35		EXPECT_TEXT
36	};
37
38	SubTitle subTitle;
39	int32 lastSequenceNumber = 0;
40	int32 currentLine = 0;
41
42	BPrivate::BTextEncoding* decoder = NULL;
43
44	int32 state = EXPECT_SEQUENCE_NUMBER;
45	while (lineProvider.Next(line)) {
46		line.RemoveAll("\n");
47		line.RemoveAll("\r");
48		switch (state) {
49			case EXPECT_SEQUENCE_NUMBER:
50			{
51				if (line.IsEmpty())
52					continue;
53
54				line.Trim();
55				int32 sequenceNumber = atoi(line.String());
56				if (sequenceNumber != lastSequenceNumber + 1) {
57					fprintf(stderr, "Warning: Wrong sequence number in SRT "
58						"file: %" B_PRId32 ", expected: %" B_PRId32 ", line %"
59						B_PRId32 "\n", sequenceNumber, lastSequenceNumber + 1,
60						currentLine);
61				}
62				state = EXPECT_TIME_CODE;
63				lastSequenceNumber = sequenceNumber;
64				break;
65			}
66
67			case EXPECT_TIME_CODE:
68			{
69				line.Trim();
70				int32 separatorPos = line.FindFirst(" --> ");
71				if (separatorPos < 0) {
72					fprintf(stderr, "Error: Time code expected on line %"
73						B_PRId32 ", got '%s'\n", currentLine, line.String());
74					return;
75				}
76				BString timeCode(line.String(), separatorPos);
77				if (separatorPos != 12) {
78					fprintf(stderr, "Warning: Time code broken on line %"
79						B_PRId32 " (%s)?\n", currentLine, timeCode.String());
80				}
81				int hours;
82				int minutes;
83				int seconds;
84				int milliSeconds;
85				if (sscanf(timeCode.String(), "%d:%d:%d,%d", &hours, &minutes,
86					&seconds, &milliSeconds) != 4) {
87					fprintf(stderr, "Error: Failed to parse start time on "
88						"line %" B_PRId32 "\n", currentLine);
89					return;
90				}
91				subTitle.startTime = (bigtime_t)hours * 60 * 60 * 1000000LL
92					+ (bigtime_t)minutes * 60 * 1000000LL
93					+ (bigtime_t)seconds * 1000000LL
94					+ (bigtime_t)milliSeconds * 1000;
95
96				int32 endTimePos = separatorPos + 5;
97				timeCode.SetTo(line.String() + endTimePos);
98				if (sscanf(timeCode.String(), "%d:%d:%d,%d", &hours, &minutes,
99					&seconds, &milliSeconds) != 4) {
100					fprintf(stderr, "Error: Failed to parse end time on "
101						"line %" B_PRId32 "\n", currentLine);
102					return;
103				}
104				bigtime_t endTime = (bigtime_t)hours * 60 * 60 * 1000000LL
105					+ (bigtime_t)minutes * 60 * 1000000LL
106					+ (bigtime_t)seconds * 1000000LL
107					+ (bigtime_t)milliSeconds * 1000;
108
109				subTitle.duration = endTime - subTitle.startTime;
110
111				state = EXPECT_TEXT;
112				break;
113			}
114
115			case EXPECT_TEXT:
116				if (line.IsEmpty()) {
117					int32 index = _IndexFor(subTitle.startTime);
118					SubTitle* clone = new(std::nothrow) SubTitle(subTitle);
119					if (clone == NULL || !fSubTitles.AddItem(clone, index)) {
120						delete clone;
121						return;
122					}
123					subTitle.text = "";
124					subTitle.placement = BPoint(-1, -1);
125					subTitle.startTime = 0;
126					subTitle.duration = 0;
127
128					state = EXPECT_SEQUENCE_NUMBER;
129				} else {
130					if (decoder == NULL) {
131						// We try to guess the encoding from the first line of
132						// text in the subtitle file.
133						decoder = new BPrivate::BTextEncoding(line.String(),
134							line.Length());
135					}
136					char buffer[line.Length() * 4];
137					size_t inLength = line.Length();
138					size_t outLength = line.Length() * 4;
139					decoder->Decode(line.String(), inLength, buffer, outLength);
140					buffer[outLength] = 0;
141					subTitle.text << buffer << '\n';
142				}
143				break;
144		}
145		line.SetTo("");
146		currentLine++;
147	}
148
149	delete decoder;
150}
151
152
153SubTitlesSRT::~SubTitlesSRT()
154{
155	for (int32 i = fSubTitles.CountItems() - 1; i >= 0; i--)
156		delete reinterpret_cast<SubTitle*>(fSubTitles.ItemAtFast(i));
157}
158
159
160const char*
161SubTitlesSRT::Name() const
162{
163	return fName.String();
164}
165
166
167const SubTitle*
168SubTitlesSRT::SubTitleAt(bigtime_t time) const
169{
170	int32 index = _IndexFor(time);
171	SubTitle* subTitle
172		= reinterpret_cast<SubTitle*>(fSubTitles.ItemAt(index));
173	if (subTitle != NULL && subTitle->startTime > time)
174		subTitle = reinterpret_cast<SubTitle*>(fSubTitles.ItemAt(index - 1));
175	if (subTitle != NULL && subTitle->startTime <= time
176		&& subTitle->startTime + subTitle->duration > time) {
177		return subTitle;
178	}
179	return NULL;
180}
181
182
183int32
184SubTitlesSRT::_IndexFor(bigtime_t startTime) const
185{
186	// binary search index
187	int32 lower = 0;
188	int32 upper = fSubTitles.CountItems();
189	while (lower < upper) {
190		int32 mid = (lower + upper) / 2;
191		SubTitle* subTitle = reinterpret_cast<SubTitle*>(
192			fSubTitles.ItemAtFast(mid));
193		if (startTime < subTitle->startTime)
194			upper = mid;
195		else
196			lower = mid + 1;
197	}
198	return lower;
199}
200
201
202
203