xref: /haiku/src/apps/mediaplayer/supplier/SubTitlesSRT.cpp (revision b31cb92f29fe89eaca84d173d0f70d38bf0c6a3d)
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 
19 SubTitlesSRT::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 
153 SubTitlesSRT::~SubTitlesSRT()
154 {
155 	for (int32 i = fSubTitles.CountItems() - 1; i >= 0; i--)
156 		delete reinterpret_cast<SubTitle*>(fSubTitles.ItemAtFast(i));
157 }
158 
159 
160 const char*
161 SubTitlesSRT::Name() const
162 {
163 	return fName.String();
164 }
165 
166 
167 const SubTitle*
168 SubTitlesSRT::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 
183 int32
184 SubTitlesSRT::_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