Initial commit
This commit is contained in:
261
engines/wintermute/video/video_subtitler.cpp
Normal file
261
engines/wintermute/video/video_subtitler.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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() {
|
||||
_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);
|
||||
}
|
||||
|
||||
uint32 fileSize;
|
||||
char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(newFile, &fileSize);
|
||||
|
||||
if (buffer == nullptr) {
|
||||
return false; // no subtitles
|
||||
}
|
||||
|
||||
/* This is where we parse .sub files.
|
||||
* Subtitles cards are in the form
|
||||
* {StartFrame}{EndFrame} FirstLine | SecondLine \n
|
||||
*/
|
||||
uint32 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 = &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(_game, cardText, start, end));
|
||||
}
|
||||
|
||||
pos += lineLength + 1;
|
||||
}
|
||||
|
||||
delete[] buffer;
|
||||
// Succeeded loading subtitles!
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoSubtitler::display() {
|
||||
if (_showSubtitle) {
|
||||
|
||||
BaseFont *font;
|
||||
|
||||
if (_game->_videoFont == nullptr) {
|
||||
font = _game->_systemFont;
|
||||
} else {
|
||||
font = _game->_videoFont;
|
||||
}
|
||||
|
||||
int textHeight = font->getTextHeight(
|
||||
(const byte *)_subtitles[_currentSubtitle].getText().c_str(),
|
||||
_game->_renderer->getWidth());
|
||||
|
||||
font->drawText(
|
||||
(const byte *)_subtitles[_currentSubtitle].getText().c_str(),
|
||||
0,
|
||||
(_game->_renderer->getHeight() - textHeight - 5),
|
||||
(_game->_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
|
||||
Reference in New Issue
Block a user