/* 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 . * */ #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(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