aboutsummaryrefslogtreecommitdiff
path: root/engines/wintermute/video/video_subtitler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/wintermute/video/video_subtitler.cpp')
-rw-r--r--engines/wintermute/video/video_subtitler.cpp266
1 files changed, 266 insertions, 0 deletions
diff --git a/engines/wintermute/video/video_subtitler.cpp b/engines/wintermute/video/video_subtitler.cpp
new file mode 100644
index 0000000000..95d938574b
--- /dev/null
+++ b/engines/wintermute/video/video_subtitler.cpp
@@ -0,0 +1,266 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * This file is based on Wintermute Engine
+ * http://dead-code.org/redir.php?target=wme
+ * Copyright (c) 2011 Jan Nedoma
+ */
+
+#include "engines/wintermute/video/video_subtitler.h"
+#include "engines/wintermute/base/base_file_manager.h"
+#include "engines/wintermute/utils/path_util.h"
+#include "engines/wintermute/base/font/base_font.h"
+#include "engines/wintermute/base/base_game.h"
+#include "engines/wintermute/base/gfx/base_renderer.h"
+
+namespace Wintermute {
+
+VideoSubtitler::VideoSubtitler(BaseGame *inGame): BaseClass(inGame) {
+ _lastSample = -1;
+ _currentSubtitle = 0;
+ _showSubtitle = false;
+}
+
+VideoSubtitler::~VideoSubtitler(void) {
+ _subtitles.clear();
+}
+
+bool VideoSubtitler::loadSubtitles(const Common::String &filename, const Common::String &subtitleFile) {
+ if (filename.size() == 0) {
+ return false;
+ }
+
+ _subtitles.clear();
+
+ _lastSample = -1;
+ _currentSubtitle = 0;
+ _showSubtitle = false;
+
+ Common::String newFile;
+
+ /*
+ * Okay, the expected behaviour is this: either we are
+ * provided with a subtitle file to use by the script when
+ * calling PlayTheora(), or we try to autodetect a suitable
+ * one which, for /some/path/movie/ogg is to be called
+ * /some/path/movie.sub
+ */
+ if (subtitleFile.size() != 0) {
+ newFile = subtitleFile;
+ } else {
+ Common::String path = PathUtil::getDirectoryName(filename);
+ Common::String name = PathUtil::getFileNameWithoutExtension(filename);
+ Common::String ext = ".SUB";
+ newFile = PathUtil::combine(path, name + ext);
+ }
+
+ Common::SeekableReadStream *file = BaseFileManager::getEngineInstance()->openFile(newFile, true, false);
+
+ if (file == nullptr) {
+ return false; // no subtitles
+ }
+
+ int fileSize = file->size();
+ char *buffer = new char[fileSize];
+
+ file->read(buffer, fileSize);
+
+ /* This is where we parse .sub files.
+ * Subtitles cards are in the form
+ * {StartFrame}{EndFrame} FirstLine | SecondLine \n
+ */
+ int pos = 0;
+
+ while (pos < fileSize) {
+ char *tokenStart = 0;
+ int tokenLength = 0;
+ int tokenPos = -1;
+ int lineLength = 0;
+ int start = -1;
+ int end = -1;
+ bool inToken = false;
+
+ while (pos + lineLength < fileSize &&
+ buffer[pos + lineLength] != '\n' &&
+ buffer[pos + lineLength] != '\0') {
+ // Measure the line until we hit EOL, EOS or just hit the boundary
+ lineLength++;
+ }
+
+ int realLength;
+
+ if (pos + lineLength >= fileSize) {
+ realLength = lineLength - 0;
+ } else {
+ // If we got here the above loop exited after hitting "\0" "\n"
+ realLength = lineLength - 1;
+ }
+
+ Common::String cardText;
+ char *fileLine = (char *)&buffer[pos];
+
+ for (int i = 0; i < realLength; i++) {
+ if (fileLine[i] == '{') {
+ if (!inToken) {
+ // We've hit the start of a Start/EndFrame token
+ inToken = true;
+ tokenStart = fileLine + i + 1;
+ tokenLength = 0;
+ tokenPos++;
+ } else {
+ // Actually, we were already inside an (invalid) one.
+ tokenLength++;
+ }
+ } else if (fileLine[i] == '}') {
+ if (inToken) {
+ // we were /inside/ a {.*} token, so this is the end of the block
+ inToken = false;
+ char *token = new char[tokenLength + 1];
+ strncpy(token, tokenStart, tokenLength);
+ token[tokenLength] = '\0';
+ if (tokenPos == 0) {
+ // Was this StartFrame...
+ start = atoi(token);
+ } else if (tokenPos == 1) {
+ // Or the EndFrame?
+ end = atoi(token);
+ }
+ delete[] token;
+ } else {
+ // This char is part of the plain text, just append it
+ cardText += fileLine[i];
+ }
+ } else {
+ if (inToken) {
+ tokenLength++;
+ } else {
+ if (fileLine[i] == '|') {
+ // The pipe character signals a linebreak in the text
+ cardText += '\n';
+ } else {
+ // This char is part of the plain text, just append it
+ cardText += fileLine[i];
+ }
+ }
+ }
+ }
+
+ if (start != -1 && cardText.size() > 0 && (start != 1 || end != 1)){
+ // Add a subtitlecard based on the line we have just parsed
+ _subtitles.push_back(SubtitleCard(_gameRef, cardText, start, end));
+ }
+
+ pos += lineLength + 1;
+ }
+
+ delete[] buffer;
+ // Succeeded loading subtitles!
+
+ return true;
+}
+
+void VideoSubtitler::display() {
+ if (_showSubtitle) {
+
+ BaseFont *font;
+
+ if (_gameRef->getVideoFont() == nullptr) {
+ font = _gameRef->getSystemFont();
+ } else {
+ font = _gameRef->getVideoFont();
+ }
+
+ int textHeight = font->getTextHeight(
+ (const byte *)_subtitles[_currentSubtitle].getText().c_str(),
+ _gameRef->_renderer->getWidth());
+
+ font->drawText(
+ (const byte *)_subtitles[_currentSubtitle].getText().c_str(),
+ 0,
+ (_gameRef->_renderer->getHeight() - textHeight - 5),
+ (_gameRef->_renderer->getWidth()),
+ TAL_CENTER);
+ }
+}
+
+void VideoSubtitler::update(uint32 frame) {
+ if (_subtitles.size() == 0) {
+ // Edge case: we have loaded subtitles early on... from a blank file.
+ return;
+ }
+
+ if ((int32)frame != _lastSample) {
+ /*
+ * If the frame count hasn't advanced the previous state still matches
+ * the current frame (obviously).
+ */
+
+ _lastSample = frame;
+ // Otherwise, we update _lastSample; see above.
+
+ _showSubtitle = false;
+
+ bool overdue = (frame > _subtitles[_currentSubtitle].getEndFrame());
+ bool hasNext = (_currentSubtitle + 1 < _subtitles.size());
+ bool nextStarted = false;
+ if (hasNext) {
+ nextStarted = (_subtitles[_currentSubtitle + 1].getStartFrame() <= frame);
+ }
+
+ while (_currentSubtitle < _subtitles.size() &&
+ overdue && hasNext && nextStarted) {
+ /*
+ * We advance until we get past all overdue subtitles.
+ * We should exit the cycle when we either reach the first
+ * subtitle which is not overdue whose subsequent subtitle
+ * has not started yet (aka the one we must display now or
+ * the one which WILL be displayed when its time comes)
+ * and / or when we reach the last one.
+ */
+
+ _currentSubtitle++;
+
+ overdue = (frame > _subtitles[_currentSubtitle].getEndFrame());
+ hasNext = (_currentSubtitle + 1 < _subtitles.size());
+ if (hasNext) {
+ nextStarted = (_subtitles[_currentSubtitle + 1].getStartFrame() <= frame);
+ } else {
+ nextStarted = false;
+ }
+ }
+
+ bool currentValid = (_subtitles[_currentSubtitle].getEndFrame() != 0);
+ /*
+ * No idea why we do this check, carried over from Mnemonic's code.
+ * Possibly a workaround for buggy subtitles or some kind of sentinel? :-\
+ */
+
+ bool currentStarted = frame >= _subtitles[_currentSubtitle].getStartFrame();
+
+ if (currentStarted && !overdue && currentValid) {
+ _showSubtitle = true;
+ }
+ }
+}
+
+} // End of namespace Wintermute