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