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