Hookup sb2 import code to deserialize assets from local sb2 file.

This commit is contained in:
Karishma Chadha 2018-03-25 18:14:30 -04:00
parent 13641297fd
commit b8e67a7727
3 changed files with 45 additions and 18 deletions

View file

@ -7,12 +7,15 @@ const log = require('../util/log');
* @param {object} sound Descriptor for sound from sb3 file
* @param {Runtime} runtime The runtime containing the storage to cache the sounds in
* @param {JSZip} zip The zip containing the sound file being described by `sound`
* @param {string} assetFileName Optional file name for the given asset
* (sb2 files have filenames of the form [int].[ext],
* sb3 files have filenames of the form [md5].[ext])
* @return {Promise} Promise that resolves after the described sound has been stored
* into the runtime storage cache, the sound was already stored, or an error has
* occurred.
*/
const deserializeSound = function (sound, runtime, zip) {
const fileName = sound.md5; // The md5 property has the full file name
const deserializeSound = function (sound, runtime, zip, assetFileName) {
const fileName = assetFileName ? assetFileName : sound.md5;
const storage = runtime.storage;
if (!storage) {
log.error('No storage module present; cannot load sound asset: ', fileName);
@ -64,16 +67,18 @@ const deserializeSound = function (sound, runtime, zip) {
* @param {object} costume Descriptor for costume from sb3 file
* @param {Runtime} runtime The runtime containing the storage to cache the costumes in
* @param {JSZip} zip The zip containing the costume file being described by `costume`
* @param {string} assetFileName Optional file name for the given asset
* (sb2 files have filenames of the form [int].[ext],
* sb3 files have filenames of the form [md5].[ext])
* @return {Promise} Promise that resolves after the described costume has been stored
* into the runtime storage cache, the costume was already stored, or an error has
* occurred.
*/
const deserializeCostume = function (costume, runtime, zip) {
const deserializeCostume = function (costume, runtime, zip, assetFileName) {
const storage = runtime.storage;
const assetId = costume.assetId;
const fileName = costume.md5 ?
costume.md5 :
`${assetId}.${costume.dataFormat}`; // The md5 property has the full file name
const fileName = assetFileName ? assetFileName :
`${assetId}.${costume.dataFormat}`;
if (!storage) {
log.error('No storage module present; cannot load costume asset: ', fileName);

View file

@ -11,11 +11,13 @@ const Sprite = require('../sprites/sprite');
const Color = require('../util/color');
const log = require('../util/log');
const uid = require('../util/uid');
const StringUtil = require('../util/string-util');
const specMap = require('./sb2_specmap');
const Variable = require('../engine/variable');
const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.js');
const {deserializeCostume, deserializeSound} = require('./deserialize-assets.js');
/**
* Convert a Scratch 2.0 procedure string (e.g., "my_procedure %s %b %n")
@ -198,9 +200,10 @@ const globalBroadcastMsgStateGenerator = (function () {
* @param {!Runtime} runtime - Runtime object to load all structures into.
* @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here.
* @param {boolean} topLevel - Whether this is the top-level object (stage).
* @param {?object} zip - Optional zipped assets for local file import
* @return {!Promise.<Array.<Target>>} Promise for the loaded targets when ready, or null for unsupported objects.
*/
const parseScratchObject = function (object, runtime, extensions, topLevel) {
const parseScratchObject = function (object, runtime, extensions, topLevel, zip) {
if (!object.hasOwnProperty('objName')) {
// Watcher/monitor - skip this object until those are implemented in VM.
// @todo
@ -231,9 +234,18 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
md5: costumeSource.baseLayerMD5,
skinId: null
};
// TODO need to add deserializeCostume here so that assets from
// actual .sb2s get loaded in
costumePromises.push(loadCostume(costume.md5, costume, runtime));
const md5ext = costumeSource.baseLayerMD5;
const idParts = StringUtil.splitFirst(md5ext, '.');
const md5 = idParts[0];
const ext = idParts[1].toLowerCase();
costume.dataFormat = ext;
costume.assetId = md5;
// If there is no internet connection, or if the asset is not in storage
// for some reason, and we are doing a local .sb2 import, (e.g. zip is provided)
// the file name of the costume should be the baseLayerID followed by the file ext
const assetFileName = `${costumeSource.baseLayerID}.${ext}`;
costumePromises.push(deserializeCostume(costume, runtime, zip, assetFileName)
.then(() => loadCostume(costume.md5, costume, runtime)));
}
}
// Sounds from JSON
@ -246,7 +258,6 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
format: soundSource.format,
rate: soundSource.rate,
sampleCount: soundSource.sampleCount,
soundID: soundSource.soundID,
// TODO we eventually want this next property to be called
// md5ext to reflect what it actually contains, however this
// will be a very extensive change across many repositories
@ -256,9 +267,19 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
md5: soundSource.md5,
data: null
};
// TODO need to add deserializeSound here so that assets from
// actual .sb2s get loaded in
soundPromises.push(loadSound(sound, runtime));
const md5ext = soundSource.md5;
const idParts = StringUtil.splitFirst(md5ext, '.');
const md5 = idParts[0];
const ext = idParts[1].toLowerCase();
sound.dataFormat = ext;
sound.assetId = md5;
// If there is no internet connection, or if the asset is not in storage
// for some reason, and we are doing a local .sb2 import, (e.g. zip is provided)
// the file name of the sound should be the soundID (provided from the project.json)
// followed by the file ext
const assetFileName = `${soundSource.soundID}.${ext}`;
soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName)
.then(() => loadSound(sound, runtime)));
}
}
@ -356,7 +377,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
const childrenPromises = [];
if (object.children) {
for (let m = 0; m < object.children.length; m++) {
childrenPromises.push(parseScratchObject(object.children[m], runtime, extensions, false));
childrenPromises.push(parseScratchObject(object.children[m], runtime, extensions, false, zip));
}
}
@ -421,14 +442,15 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
* @param {!object} json SB2-format JSON to load.
* @param {!Runtime} runtime Runtime object to load all structures into.
* @param {boolean=} optForceSprite If set, treat as sprite (Sprite2).
* @param {?object} zip Optional zipped assets for local file import
* @return {Promise.<ImportedProject>} Promise that resolves to the loaded targets when ready.
*/
const sb2import = function (json, runtime, optForceSprite) {
const sb2import = function (json, runtime, optForceSprite, zip) {
const extensions = {
extensionIDs: new Set(),
extensionURLs: new Map()
};
return parseScratchObject(json, runtime, extensions, !optForceSprite)
return parseScratchObject(json, runtime, extensions, !optForceSprite, zip)
.then(targets => ({
targets,
extensions