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