Files
2026-02-02 04:50:13 +01:00

734 lines
19 KiB
C++

/* 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/>.
*
*/
#include "audio/decoders/aiff.h"
#include "common/platform.h"
#include "common/stream.h"
#include "common/macresman.h"
#include "graphics/paletteman.h"
#include "graphics/surface.h"
#include "graphics/macgui/macwidget.h"
#include "video/video_decoder.h"
#include "video/avi_decoder.h"
#include "video/qt_decoder.h"
#include "director/director.h"
#include "director/cast.h"
#include "director/channel.h"
#include "director/images.h"
#include "director/movie.h"
#include "director/window.h"
#include "director/castmember/digitalvideo.h"
#include "director/lingo/lingo-the.h"
namespace Director {
class NoVideoAIFFDecoder : public Video::VideoDecoder {
protected:
class AIFFAudioTrack : public Video::VideoDecoder::AudioTrack {
private:
Audio::RewindableAudioStream *_audioStream = nullptr;
public:
AIFFAudioTrack(Audio::Mixer::SoundType soundType, Audio::RewindableAudioStream *stream) : AudioTrack(soundType) {
_audioStream = stream;
}
virtual Audio::AudioStream *getAudioStream() const {
return _audioStream;
}
};
public:
bool loadFile(const Common::Path &filename) override {
Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(filename);
if (!file) {
delete file;
return false;
}
bool result = loadStream(file);
if (!result)
delete file;
return result;
}
virtual bool loadStream(Common::SeekableReadStream *stream) override {
addTrack(new AIFFAudioTrack(Audio::Mixer::SoundType::kSFXSoundType, Audio::makeAIFFStream(stream, DisposeAfterUse::Flag::NO)));
return true;
}
};
DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId)
: CastMember(cast, castId) {
_type = kCastDigitalVideo;
_video = nullptr;
_lastFrame = nullptr;
_channel = nullptr;
_getFirstFrame = false;
_duration = 0;
_vflags = 0;
_frameRate = 0;
_frameRateType = kFrameRateDefault;
_videoType = kDVUnknown;
_qtmovie = true;
_avimovie = false;
_preload = false;
_enableVideo = true;
_pausedAtStart = false;
_showControls = false;
_directToStage = false;
_looping = false;
_enableSound = true;
_crop = false;
_center = false;
_dirty = false;
_emptyFile = false;
memset(_ditheringPalette, 0, 256*3);
}
DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
: CastMember(cast, castId, stream) {
_type = kCastDigitalVideo;
_video = nullptr;
_lastFrame = nullptr;
_channel = nullptr;
_getFirstFrame = false;
_duration = 0;
_initialRect = Movie::readRect(stream);
_vflags = stream.readUint32();
_frameRate = (_vflags >> 24) & 0xff;
_frameRateType = kFrameRateDefault;
_videoType = kDVUnknown;
if (_vflags & 0x0800) {
_frameRateType = (FrameRateType)((_vflags & 0x3000) >> 12);
}
_qtmovie = _vflags & 0x8000;
_avimovie = _vflags & 0x4000;
_preload = _vflags & 0x0400;
_enableVideo = !(_vflags & 0x0200);
_pausedAtStart = _vflags & 0x0100;
_showControls = _vflags & 0x40;
_directToStage = _vflags & 0x20;
_looping = _vflags & 0x10;
_enableSound = _vflags & 0x08;
_crop = !(_vflags & 0x02);
_center = _vflags & 0x01;
_dirty = false;
_emptyFile = false;
memset(_ditheringPalette, 0, 256*3);
if (debugChannelSet(2, kDebugLoading))
_initialRect.debugPrint(2, "DigitalVideoCastMember(): rect:");
debugC(2, kDebugLoading, "DigitalVideoCastMember(): flags: (%d 0x%04x)", _vflags, _vflags);
debugC(2, kDebugLoading, "_frameRate: %d", _frameRate);
debugC(2, kDebugLoading, "_frameRateType: %d, _preload: %d, _enableVideo %d, _pausedAtStart %d",
_frameRateType, _preload, _enableVideo, _pausedAtStart);
debugC(2, kDebugLoading, "_showControls: %d, _looping: %d, _enableSound: %d, _crop %d, _center: %d, _directToStage: %d",
_showControls, _looping, _enableSound, _crop, _center, _directToStage);
debugC(2, kDebugLoading, "_avimovie: %d, _qtmovie: %d", _avimovie, _qtmovie);
}
DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, DigitalVideoCastMember &source)
: CastMember(cast, castId) {
_type = kCastDigitalVideo;
_loaded = source._loaded;
_initialRect = source._initialRect;
_boundingRect = source._boundingRect;
if (cast == source._cast)
_children = source._children;
_filename = source._filename;
_vflags = source._vflags;
_looping = source._looping;
_pausedAtStart = source._pausedAtStart;
_enableVideo = source._enableVideo;
_enableSound = source._enableSound;
_crop = source._crop;
_center = source._center;
_preload = source._preload;
_showControls = source._showControls;
_directToStage = source._directToStage;
_avimovie = source._avimovie;
_qtmovie = source._qtmovie;
_dirty = source._dirty;
_frameRateType = source._frameRateType;
_videoType = source._videoType;
_frameRate = source._frameRate;
_getFirstFrame = source._getFirstFrame;
_duration = source._duration;
_video = nullptr;
_lastFrame = nullptr;
_channel = nullptr;
}
DigitalVideoCastMember::~DigitalVideoCastMember() {
if (_lastFrame) {
_lastFrame->free();
delete _lastFrame;
}
if (_video)
delete _video;
}
bool DigitalVideoCastMember::loadVideoFromCast() {
Common::String path = getCast()->getVideoPath(_castId);
if (!path.empty())
return loadVideo(path);
return false;
}
bool DigitalVideoCastMember::loadVideo(Common::String path) {
if (_filename == path) {
// we've already loaded this video, or not. no point trying again.
return _video ? true : false;
}
if (_video) {
delete _video;
_video = nullptr;
}
_filename = path;
Common::Path location = findPath(path);
if (location.empty()) {
warning("DigitalVideoCastMember::loadVideo(): unable to resolve path %s", path.c_str());
return false;
}
Common::SeekableReadStream *copiedStream = Common::MacResManager::openFileOrDataFork(location);
if (!copiedStream) {
warning("DigitalVideoCastMember::loadVideo Failed to open %s", path.c_str());
return false;
}
uint32 magic1 = copiedStream->readUint32BE();
uint32 magic2 = copiedStream->readUint32BE();
uint32 magic3 = copiedStream->readUint32BE();
delete copiedStream;
bool result = false;
debugC(2, kDebugLoading, "Loading video %s -> %s", path.c_str(), location.toString(Common::Path::kNativeSeparator).c_str());
if (magic1 == MKTAG('F', 'O', 'R', 'M') &&
(magic3 == MKTAG('A', 'I', 'F', 'F') || magic3 == MKTAG('A', 'I', 'F', 'C'))) {
_video = new NoVideoAIFFDecoder();
result = _video->loadFile(location);
if (!result) {
delete _video;
_video = nullptr;
return false;
} else {
// Pretend that this is our friend QuickTime
_videoType = kDVQuickTime;
}
} else if (magic2 == MKTAG('m', 'o', 'o', 'v') || magic2 == MKTAG('m', 'd', 'a', 't')) {
_video = new Video::QuickTimeDecoder();
result = _video->loadFile(location);
if (!result) {
delete _video;
_video = nullptr;
// Probe for empty file
Common::MacResManager mgr;
if (mgr.open(location)) {
if (!mgr.hasDataFork()) {
debugC(8, kDebugLevelGVideo, "DigitalVideoCastMember::loadVideo(): skipping empty stream");
_emptyFile = true;
}
return false;
}
} else {
_videoType = kDVQuickTime;
}
} else if (magic1 == MKTAG('R', 'I', 'F', 'F') && (magic3 == MKTAG('A', 'V', 'I', ' '))) {
_video = new Video::AVIDecoder();
result = _video->loadFile(location);
if (!result) {
warning("DigitalVideoCastMember::loadVideo(): format not supported, skipping video '%s'", path.c_str());
delete _video;
_video = nullptr;
return false;
} else {
_videoType = kDVVideoForWindows;
}
} else {
warning("DigitalVideoCastMember::loadVideo: Unknown file format for video '%s', skipping", path.c_str());
}
if (result && g_director->_pixelformat.bytesPerPixel == 1) {
// Director supports playing back RGB and paletted video in 256 colour mode.
// In both cases they are dithered to match the Director palette.
memcpy(_ditheringPalette, g_director->getPalette(), 256*3);
// In Windows, the first 8 and last 8 colors are reserved for the system palette.
// Generally you don't want these as part of the video, and Video for Windows
// seems to deliberately exclude them.
// Keep colour 0 and 255 as they are pure white and pure black, respectively.
if (g_director->_vfwPaletteHack && g_director->getPlatform() == Common::kPlatformWindows) {
for (int i = 1; i < 8; i++) {
_ditheringPalette[i*3+0] = _ditheringPalette[0];
_ditheringPalette[i*3+1] = _ditheringPalette[1];
_ditheringPalette[i*3+2] = _ditheringPalette[2];
}
for (int i = 248; i < 255; i++) {
_ditheringPalette[i*3+0] = _ditheringPalette[0];
_ditheringPalette[i*3+1] = _ditheringPalette[1];
_ditheringPalette[i*3+2] = _ditheringPalette[2];
}
}
_video->setDitheringPalette(_ditheringPalette);
}
_duration = getMovieTotalTime();
if (_video) {
// Setting the initial rect to the actual movie dimensions
_initialRect.setWidth(_video->getWidth());
_initialRect.setHeight(_video->getHeight());
}
return result;
}
bool DigitalVideoCastMember::isModified() {
if (!_video || !_video->isVideoLoaded())
return true;
if (_dirty) {
_dirty = false;
return true;
}
// Inelegant, but necessary. isModified will get called on
// every screen update, so use it to keep the playback
// status up to date.
if (_video->endOfVideo()) {
if (_looping) {
_video->rewind();
} else if (_channel) {
_channel->_movieRate = 0.0;
}
}
if (_getFirstFrame)
return true;
if (_channel && _channel->_movieRate == 0.0)
return false;
return _video->needsUpdate();
}
void DigitalVideoCastMember::startVideo() {
if (!_video || !_video->isVideoLoaded()) {
warning("DigitalVideoCastMember::startVideo: No video %s", !_video ? "decoder" : "loaded");
return;
}
if (_pausedAtStart) {
_getFirstFrame = true;
} else {
if (_channel && _channel->_movieRate == 0.0)
_channel->_movieRate = 1.0;
}
if (_video->isPlaying())
_video->rewind();
else
_video->start();
debugC(2, kDebugImages, "STARTING VIDEO %s", _filename.c_str());
if (_channel && _channel->_stopTime == 0)
_channel->_stopTime = getMovieTotalTime();
}
void DigitalVideoCastMember::stopVideo() {
if (!_video || !_video->isVideoLoaded()) {
if (!_emptyFile)
warning("DigitalVideoCastMember::stopVideo: No video decoder");
return;
}
_video->stop();
debugC(2, kDebugImages, "STOPPING VIDEO %s", _filename.c_str());
}
void DigitalVideoCastMember::rewindVideo() {
if (!_video || !_video->isVideoLoaded()) {
if (!_emptyFile)
warning("DigitalVideoCastMember::rewindVideo: No video decoder");
return;
}
_video->rewind();
debugC(2, kDebugImages, "REWINDING VIDEO %s", _filename.c_str());
}
Graphics::MacWidget *DigitalVideoCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
if (_emptyFile)
return nullptr;
if (!_video || !_video->isVideoLoaded()) {
// try and load the video if not already
if (!loadVideoFromCast()) {
return nullptr;
}
}
Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
_channel = channel;
// Do not render stopped videos
if (_channel->_movieRate == 0.0 && !_getFirstFrame && _lastFrame) {
widget->getSurface()->blitFrom(*_lastFrame);
return widget;
}
const Graphics::Surface *frame = _video->decodeNextFrame();
// If the video gets stopped, for whatever reason, _video->getPalette() will not work.
// Cache it when possible.
if (g_director->_pixelformat.bytesPerPixel == 4) {
const byte *videoPalette = _video->getPalette();
if (videoPalette) {
memcpy(_ditheringPalette, videoPalette, 256*3);
}
}
debugC(1, kDebugImages, "Video time: %d rate: %f frame: %p dims: %d x %d", _channel->_movieTime, _channel->_movieRate, (const void *)frame, bbox.width(), bbox.height());
if (frame) {
if (_lastFrame) {
_lastFrame->free();
delete _lastFrame;
_lastFrame = nullptr;
}
if (frame->getPixels()) {
// Video should have the dithering palette set, decode using whatever palette we have now
_lastFrame = frame->convertTo(g_director->_pixelformat, _ditheringPalette);
} else {
warning("DigitalVideoCastMember::createWidget(): frame has no pixel data");
}
}
if (_lastFrame)
copyStretchImg(
_lastFrame,
widget->getSurface()->surfacePtr(),
Common::Rect((int16)_video->getWidth(), (int16)_video->getHeight()),
bbox
);
if (_getFirstFrame) {
_video->stop();
_getFirstFrame = false;
}
return widget;
}
uint DigitalVideoCastMember::getDuration() {
if (!_video || !_video->isVideoLoaded()) {
loadVideoFromCast();
}
return _duration;
}
uint DigitalVideoCastMember::getMovieCurrentTime() {
if (!_video)
return 0;
int ticks = 1 + ((_video->getTime() * 60 - 1)/1000);
int stamp = MIN<int>(ticks, getMovieTotalTime());
return stamp;
}
uint DigitalVideoCastMember::getMovieTotalTime() {
if (!_video)
return 0;
int ticks = 1 + ((_video->getDuration().msecs() * 60 - 1)/1000);
return ticks;
}
void DigitalVideoCastMember::seekMovie(int stamp) {
if (!_video)
return;
_channel->_startTime = stamp;
Audio::Timestamp dur = _video->getDuration();
_video->seek(Audio::Timestamp(_channel->_startTime * 1000 / 60, dur.framerate()));
if (_channel->_movieRate == 0.0) {
_getFirstFrame = true;
}
_dirty = true;
}
void DigitalVideoCastMember::setStopTime(int stamp) {
if (!_video)
return;
_channel->_stopTime = stamp;
Audio::Timestamp dur = _video->getDuration();
_video->setEndTime(Audio::Timestamp(_channel->_stopTime * 1000 / 60, dur.framerate()));
}
void DigitalVideoCastMember::setMovieRate(double rate) {
if (!_video)
return;
_channel->_movieRate = rate;
if (rate < 0.0)
warning("STUB: DigitalVideoCastMember::setMovieRate(%g)", rate);
else {
if (_getFirstFrame && rate != 0.0) {
// playback got started before we rendered the first
// frame in pause mode, keep going
_getFirstFrame = false;
}
_video->setRate(Common::Rational((int)(rate * 100.0), 100));
}
if (_video->endOfVideo())
_video->rewind();
}
void DigitalVideoCastMember::setFrameRate(int rate) {
if (!_video)
return;
warning("STUB: DigitalVideoCastMember::setFrameRate(%d)", rate);
}
Common::String DigitalVideoCastMember::formatInfo() {
return Common::String::format(
"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, filename: \"%s\", duration: %d, enableVideo: %d, enableSound: %d, looping: %d, crop: %d, center: %d, showControls: %d",
_initialRect.width(), _initialRect.height(),
_initialRect.left, _initialRect.top,
_boundingRect.width(), _boundingRect.height(),
_boundingRect.left, _boundingRect.top,
_filename.c_str(), _duration,
_enableVideo, _enableSound,
_looping, _crop, _center, _showControls
);
}
Common::Point DigitalVideoCastMember::getRegistrationOffset() {
return Common::Point(_initialRect.width() / 2, _initialRect.height() / 2);
}
Common::Point DigitalVideoCastMember::getRegistrationOffset(int16 width, int16 height) {
return Common::Point(width / 2, height / 2);
}
bool DigitalVideoCastMember::hasField(int field) {
switch (field) {
case kTheCenter:
case kTheController:
case kTheCrop:
case kTheCuePointNames: // D6
case kTheCuePointTimes: // D6
case kTheCurrentTime: // D6
case kTheDigitalVideoType:
case kTheDirectToStage:
case kTheDuration:
case kTheFrameRate:
case kTheLoop:
case kThePausedAtStart:
case kThePreLoad:
case kTheSound:
case kTheTimeScale:
case kTheVideo:
return true;
default:
break;
}
return CastMember::hasField(field);
}
Datum DigitalVideoCastMember::getField(int field) {
Datum d;
switch (field) {
case kTheCenter:
d = _center;
break;
case kTheController:
d = _showControls;
break;
case kTheCrop:
d = _crop;
break;
case kTheDigitalVideoType:
if (_videoType == kDVVideoForWindows) {
d = Datum("videoForWindows");
} else {
// for unknown, just pretend QuickTime
d = Datum("quickTime");
}
d.type = SYMBOL;
break;
case kTheDirectToStage:
d = _directToStage;
break;
case kTheDuration:
// sometimes, we will get duration before we start video.
// _duration is initialized in startVideo, thus we will not get the correct number.
d = (int)getDuration();
break;
case kTheFrameRate:
d = _frameRate;
break;
case kTheLoop:
d = _looping;
break;
case kThePausedAtStart:
d = _pausedAtStart;
break;
case kThePreLoad:
d = _preload;
break;
case kTheSound:
d = _enableSound;
break;
case kTheTimeScale:
warning("STUB: DigitalVideoCastMember::getField(): timeScale not implemented");
d = Datum(600); // quicktime default
break;
case kTheVideo:
d = _enableVideo;
break;
default:
d = CastMember::getField(field);
}
return d;
}
void DigitalVideoCastMember::setField(int field, const Datum &d) {
switch (field) {
case kTheCenter:
_center = (bool)d.asInt();
return;
case kTheController:
_showControls = (bool)d.asInt();
return;
case kTheCrop:
_crop = (bool)d.asInt();
return;
case kTheDigitalVideoType:
warning("DigitalVideoCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
return;
case kTheDirectToStage:
_directToStage = (bool)d.asInt();
return;
case kTheDuration:
warning("DigitalVideoCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
return;
case kTheFrameRate:
_frameRate = d.asInt();
setFrameRate(d.asInt());
return;
case kTheLoop:
_looping = (bool)d.asInt();
if (_looping && _channel && _channel->_movieRate == 0.0) {
setMovieRate(1.0);
}
return;
case kThePausedAtStart:
_pausedAtStart = (bool)d.asInt();
return;
case kThePreLoad:
_preload = (bool)d.asInt();
return;
case kTheSound:
_enableSound = (bool)d.asInt();
return;
case kTheTimeScale:
warning("DigitalVideoCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
return;
case kTheVideo:
_enableVideo = (bool)d.asInt();
return;
default:
break;
}
CastMember::setField(field, d);
}
uint32 DigitalVideoCastMember::getCastDataSize() {
// We're only reading the _initialRect and _vflags from the Cast Data
// _initialRect : 8 bytes + _vflags : 4 bytes + castType and flags1 (see Cast::loadCastData() for Director 4 only) 2 byte
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
// It has been observed that the DigitalVideoCastMember has _flags set to 0x00
return (_flags1 == 0xFF) ? 13 : 14;
} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
return 8 + 4;
}
warning("DigitalVideoCastMember::getCastDataSize(): unhandled or invalid cast version: %d", _cast->_version);
return 0;
}
void DigitalVideoCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
Movie::writeRect(writeStream, _initialRect);
writeStream->writeUint32BE(_vflags);
}
} // End of namespace Director