diff options
Diffstat (limited to 'engines/wintermute/video/video_subtitler.cpp')
-rw-r--r-- | engines/wintermute/video/video_subtitler.cpp | 266 |
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 |