MTROPOLIS: Add AVI movie support

This commit is contained in:
elasota 2023-06-13 22:37:30 -04:00
parent d70dfb7210
commit 80a75d7236
8 changed files with 144 additions and 35 deletions

View File

@ -65,6 +65,8 @@ SIAssetFactory *getAssetFactoryForDataObjectType(const Data::DataObjectTypes::Da
return AssetFactory<AudioAsset, Data::AudioAsset>::getInstance();
case Data::DataObjectTypes::kMovieAsset:
return AssetFactory<MovieAsset, Data::MovieAsset>::getInstance();
case Data::DataObjectTypes::kAVIMovieAsset:
return AssetFactory<AVIMovieAsset, Data::AVIMovieAsset>::getInstance();
case Data::DataObjectTypes::kImageAsset:
return AssetFactory<ImageAsset, Data::ImageAsset>::getInstance();
case Data::DataObjectTypes::kMToonAsset:

View File

@ -802,6 +802,21 @@ void MovieAsset::addDamagedFrame(int frame) {
_damagedFrames.push_back(frame);
}
bool AVIMovieAsset::load(AssetLoaderContext &context, const Data::AVIMovieAsset &data) {
_assetID = data.assetID;
_extFileName = data.extFileName;
return true;
}
AssetType AVIMovieAsset::getAssetType() const {
return kAssetTypeAVIMovie;
}
const Common::String &AVIMovieAsset::getExtFileName() const {
return _extFileName;
}
const Common::Array<int> &MovieAsset::getDamagedFrames() const {
return _damagedFrames;
}

View File

@ -230,6 +230,17 @@ private:
Common::Array<int> _damagedFrames;
};
class AVIMovieAsset : public Asset {
public:
bool load(AssetLoaderContext &context, const Data::AVIMovieAsset &data);
AssetType getAssetType() const override;
const Common::String &getExtFileName() const;
private:
Common::String _extFileName;
};
class CachedImage {
public:
CachedImage();

View File

@ -33,6 +33,7 @@ namespace DataObjectTypes {
bool isValidSceneRootElement(DataObjectType type) {
switch (type) {
case kGraphicElement:
case kAVIMovieElement:
case kMovieElement:
case kMToonElement:
case kImageElement:
@ -45,6 +46,7 @@ bool isValidSceneRootElement(DataObjectType type) {
bool isVisualElement(DataObjectType type) {
switch (type) {
case kGraphicElement:
case kAVIMovieElement:
case kMovieElement:
case kMToonElement:
case kImageElement:
@ -67,6 +69,7 @@ bool isNonVisualElement(DataObjectType type) {
bool isElement(DataObjectType type) {
switch (type) {
case kGraphicElement:
case kAVIMovieElement:
case kMovieElement:
case kMToonElement:
case kImageElement:
@ -140,6 +143,7 @@ bool isModifier(DataObjectType type) {
bool isAsset(DataObjectType type) {
switch (type) {
case kMovieAsset:
case kAVIMovieAsset:
case kAudioAsset:
case kColorTableAsset:
case kImageAsset:
@ -950,11 +954,11 @@ DataReadErrorCode SoundElement::load(DataReader& reader) {
return kDataReadErrorNone;
}
MovieElement::MovieElement()
MovieElement::MovieElement(bool avi)
: sizeIncludingTag(0), guid(0), lengthOfName(0), elementFlags(0), layer(0),
unknown3{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
sectionID(0), unknown5{0, 0}, assetID(0), unknown7(0), volume(0), animationFlags(0),
unknown10{0, 0, 0, 0}, unknown11{0, 0, 0, 0}, streamLocator(0), unknown13{0, 0, 0, 0} {
unknown10{0, 0, 0, 0}, unknown11{0, 0, 0, 0}, streamLocator(0), unknown13{0, 0, 0, 0}, isAVI(avi) {
}
DataReadErrorCode MovieElement::load(DataReader &reader) {
@ -2078,6 +2082,25 @@ DataReadErrorCode MovieAsset::load(DataReader &reader) {
return kDataReadErrorNone;
}
AVIMovieAsset::AVIMovieAsset() : unknown1{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, assetID(0), unknown2{0, 0, 0, 0},
extFileNameLength(0) {
for (uint8 &v : unknown3)
v = 0;
}
DataReadErrorCode AVIMovieAsset::load(DataReader &reader) {
if (_revision != 0)
return kDataReadErrorUnsupportedRevision;
if (!reader.readBytes(unknown1) || !reader.readU32(assetID) || !reader.readBytes(unknown2) || !reader.readU16(extFileNameLength) || !reader.readBytes(unknown3))
return kDataReadErrorReadFailed;
if (!reader.readTerminatedStr(extFileName, extFileNameLength))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
}
AudioAsset::AudioAsset()
: persistFlags(0), assetAndDataCombinedSize(0), unknown2{0, 0, 0, 0}, assetID(0),
unknown3{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, sampleRate1(0), bitsPerSample(0),
@ -2421,7 +2444,10 @@ DataReadErrorCode loadDataObject(const PlugInModifierRegistry &registry, DataRea
dataObject = new GraphicElement();
break;
case DataObjectTypes::kMovieElement:
dataObject = new MovieElement();
dataObject = new MovieElement(false);
break;
case DataObjectTypes::kAVIMovieElement:
dataObject = new MovieElement(true);
break;
case DataObjectTypes::kMToonElement:
dataObject = new MToonElement();
@ -2561,6 +2587,10 @@ DataReadErrorCode loadDataObject(const PlugInModifierRegistry &registry, DataRea
dataObject = new ColorTableAsset();
break;
case DataObjectTypes::kAVIMovieAsset:
dataObject = new AVIMovieAsset();
break;
case DataObjectTypes::kMovieAsset:
dataObject = new MovieAsset();
break;

View File

@ -100,6 +100,7 @@ enum DataObjectType : uint {
kSoundElement = 0xa,
kTextLabelElement = 0x15,
kAVIMovieElement = 0x25,
kAliasModifier = 0x27,
kChangeSceneModifier = 0x136,
kReturnModifier = 0x140,
@ -148,6 +149,7 @@ enum DataObjectType : uint {
kImageAsset = 0xe,
kMToonAsset = 0xf,
kTextAsset = 0x1f,
kAVIMovieAsset = 0x24,
kAssetDataChunk = 0xffff,
};
@ -752,7 +754,7 @@ protected:
};
struct MovieElement : public StructuralDef {
MovieElement();
explicit MovieElement(bool avi);
// Possible flags: NotDirectToScreen, CacheBitmap, Hidden, Loop, Loop + Alternate, Paused
uint32 sizeIncludingTag;
@ -776,6 +778,8 @@ struct MovieElement : public StructuralDef {
Common::String name;
bool isAVI;
protected:
DataReadErrorCode load(DataReader &reader) override;
};
@ -1851,6 +1855,21 @@ protected:
DataReadErrorCode load(DataReader &reader) override;
};
struct AVIMovieAsset : public DataObject {
AVIMovieAsset();
uint8 unknown1[12];
uint32 assetID;
uint8 unknown2[4];
uint16 extFileNameLength;
uint8 unknown3[60];
Common::String extFileName;
protected:
DataReadErrorCode load(DataReader &reader) override;
};
struct AudioAsset : public DataObject {
struct MacPart {
uint8 unknown4[4];

View File

@ -64,6 +64,7 @@ SIElementFactory *getElementFactoryForDataObjectType(const Data::DataObjectTypes
switch (dataObjectType) {
case Data::DataObjectTypes::kGraphicElement:
return ElementFactory<GraphicElement, Data::GraphicElement>::getInstance();
case Data::DataObjectTypes::kAVIMovieElement:
case Data::DataObjectTypes::kMovieElement:
return ElementFactory<MovieElement, Data::MovieElement>::getInstance();
case Data::DataObjectTypes::kImageElement:

View File

@ -21,7 +21,9 @@
#include "video/video_decoder.h"
#include "video/qt_decoder.h"
#include "video/avi_decoder.h"
#include "common/file.h"
#include "common/substream.h"
#include "graphics/macgui/macfontmanager.h"
@ -618,42 +620,67 @@ void MovieElement::activate() {
return;
}
if (asset->getAssetType() != kAssetTypeMovie) {
warning("Movie element assigned an asset that isn't a movie");
if (asset->getAssetType() == kAssetTypeMovie) {
MovieAsset *movieAsset = static_cast<MovieAsset *>(asset.get());
size_t streamIndex = movieAsset->getStreamIndex();
int segmentIndex = project->getSegmentForStreamIndex(streamIndex);
project->openSegmentStream(segmentIndex);
Common::SeekableReadStream *stream = project->getStreamForSegment(segmentIndex);
if (!stream) {
warning("Movie element stream could not be opened");
return;
}
Video::QuickTimeDecoder *qtDecoder = new Video::QuickTimeDecoder();
qtDecoder->setChunkBeginOffset(movieAsset->getMovieDataPos());
qtDecoder->setVolume(_volume * 255 / 100);
_videoDecoder.reset(qtDecoder);
_damagedFrames = movieAsset->getDamagedFrames();
Common::SafeSeekableSubReadStream *movieDataStream = new Common::SafeSeekableSubReadStream(stream, movieAsset->getMovieDataPos(), movieAsset->getMovieDataPos() + movieAsset->getMovieDataSize(), DisposeAfterUse::NO);
if (!_videoDecoder->loadStream(movieDataStream))
_videoDecoder.reset();
else {
if (getRuntime()->getHacks().removeQuickTimeEdits)
qtDecoder->flattenEditLists();
_timeScale = qtDecoder->getTimeScale();
_maxTimestamp = qtDecoder->getDuration().convertToFramerate(qtDecoder->getTimeScale()).totalNumberOfFrames();
}
_unloadSignaller = project->notifyOnSegmentUnload(segmentIndex, this);
} else if (asset->getAssetType() == kAssetTypeAVIMovie) {
AVIMovieAsset *aviAsset = static_cast<AVIMovieAsset *>(asset.get());
Common::File *f = new Common::File();
if (!f->open(Common::Path(Common::String("VIDEO/") + aviAsset->getExtFileName()))) {
warning("Movie asset could not be opened");
delete f;
return;
}
Video::AVIDecoder *aviDec = new Video::AVIDecoder();
aviDec->setVolume(_volume * 255 / 100);
_videoDecoder.reset(aviDec);
if (!_videoDecoder->loadStream(f))
_videoDecoder.reset();
else {
_timeScale = 1000;
_maxTimestamp = aviDec->getDuration().convertToFramerate(1000).totalNumberOfFrames();
}
} else {
warning("Movie element referenced a non-movie asset");
return;
}
MovieAsset *movieAsset = static_cast<MovieAsset *>(asset.get());
size_t streamIndex = movieAsset->getStreamIndex();
int segmentIndex = project->getSegmentForStreamIndex(streamIndex);
project->openSegmentStream(segmentIndex);
Common::SeekableReadStream *stream = project->getStreamForSegment(segmentIndex);
if (!stream) {
warning("Movie element stream could not be opened");
return;
}
Video::QuickTimeDecoder *qtDecoder = new Video::QuickTimeDecoder();
qtDecoder->setChunkBeginOffset(movieAsset->getMovieDataPos());
qtDecoder->setVolume(_volume * 255 / 100);
_videoDecoder.reset(qtDecoder);
_damagedFrames = movieAsset->getDamagedFrames();
Common::SafeSeekableSubReadStream *movieDataStream = new Common::SafeSeekableSubReadStream(stream, movieAsset->getMovieDataPos(), movieAsset->getMovieDataPos() + movieAsset->getMovieDataSize(), DisposeAfterUse::NO);
if (!_videoDecoder->loadStream(movieDataStream))
_videoDecoder.reset();
if (getRuntime()->getHacks().removeQuickTimeEdits)
qtDecoder->flattenEditLists();
_timeScale = qtDecoder->getTimeScale();
_unloadSignaller = project->notifyOnSegmentUnload(segmentIndex, this);
_playMediaSignaller = project->notifyOnPlayMedia(this);
_maxTimestamp = qtDecoder->getDuration().convertToFramerate(qtDecoder->getTimeScale()).totalNumberOfFrames();
_playRange = IntRange(0, 0);
_currentTimestamp = 0;
@ -704,6 +731,9 @@ void MovieElement::queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) {
void MovieElement::render(Window *window) {
const IntRange realRange = computeRealRange();
if (!_videoDecoder)
return;
if (_needsReset) {
_videoDecoder->setReverse(_reversed);
_videoDecoder->seek(Audio::Timestamp(0, _timeScale).addFrames(_currentTimestamp));

View File

@ -3040,6 +3040,7 @@ enum AssetType {
kAssetTypeImage,
kAssetTypeText,
kAssetTypeMToon,
kAssetTypeAVIMovie,
};
class AssetHooks {