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 {object} sound Descriptor for sound from sb3 file
* @param {Runtime} runtime The runtime containing the storage to cache the sounds in * @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 {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 * @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 * into the runtime storage cache, the sound was already stored, or an error has
* occurred. * occurred.
*/ */
const deserializeSound = function (sound, runtime, zip) { const deserializeSound = function (sound, runtime, zip, assetFileName) {
const fileName = sound.md5; // The md5 property has the full file name const fileName = assetFileName ? assetFileName : sound.md5;
const storage = runtime.storage; const storage = runtime.storage;
if (!storage) { if (!storage) {
log.error('No storage module present; cannot load sound asset: ', fileName); 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 {object} costume Descriptor for costume from sb3 file
* @param {Runtime} runtime The runtime containing the storage to cache the costumes in * @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 {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 * @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 * into the runtime storage cache, the costume was already stored, or an error has
* occurred. * occurred.
*/ */
const deserializeCostume = function (costume, runtime, zip) { const deserializeCostume = function (costume, runtime, zip, assetFileName) {
const storage = runtime.storage; const storage = runtime.storage;
const assetId = costume.assetId; const assetId = costume.assetId;
const fileName = costume.md5 ? const fileName = assetFileName ? assetFileName :
costume.md5 : `${assetId}.${costume.dataFormat}`;
`${assetId}.${costume.dataFormat}`; // The md5 property has the full file name
if (!storage) { if (!storage) {
log.error('No storage module present; cannot load costume asset: ', fileName); 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 Color = require('../util/color');
const log = require('../util/log'); const log = require('../util/log');
const uid = require('../util/uid'); const uid = require('../util/uid');
const StringUtil = require('../util/string-util');
const specMap = require('./sb2_specmap'); const specMap = require('./sb2_specmap');
const Variable = require('../engine/variable'); const Variable = require('../engine/variable');
const {loadCostume} = require('../import/load-costume.js'); const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.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") * 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 {!Runtime} runtime - Runtime object to load all structures into.
* @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here. * @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here.
* @param {boolean} topLevel - Whether this is the top-level object (stage). * @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. * @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')) { if (!object.hasOwnProperty('objName')) {
// Watcher/monitor - skip this object until those are implemented in VM. // Watcher/monitor - skip this object until those are implemented in VM.
// @todo // @todo
@ -231,9 +234,18 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
md5: costumeSource.baseLayerMD5, md5: costumeSource.baseLayerMD5,
skinId: null skinId: null
}; };
// TODO need to add deserializeCostume here so that assets from const md5ext = costumeSource.baseLayerMD5;
// actual .sb2s get loaded in const idParts = StringUtil.splitFirst(md5ext, '.');
costumePromises.push(loadCostume(costume.md5, costume, runtime)); 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 // Sounds from JSON
@ -246,7 +258,6 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
format: soundSource.format, format: soundSource.format,
rate: soundSource.rate, rate: soundSource.rate,
sampleCount: soundSource.sampleCount, sampleCount: soundSource.sampleCount,
soundID: soundSource.soundID,
// TODO we eventually want this next property to be called // TODO we eventually want this next property to be called
// md5ext to reflect what it actually contains, however this // md5ext to reflect what it actually contains, however this
// will be a very extensive change across many repositories // will be a very extensive change across many repositories
@ -256,9 +267,19 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
md5: soundSource.md5, md5: soundSource.md5,
data: null data: null
}; };
// TODO need to add deserializeSound here so that assets from const md5ext = soundSource.md5;
// actual .sb2s get loaded in const idParts = StringUtil.splitFirst(md5ext, '.');
soundPromises.push(loadSound(sound, runtime)); 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 = []; const childrenPromises = [];
if (object.children) { if (object.children) {
for (let m = 0; m < object.children.length; m++) { 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 {!object} json SB2-format JSON to load.
* @param {!Runtime} runtime Runtime object to load all structures into. * @param {!Runtime} runtime Runtime object to load all structures into.
* @param {boolean=} optForceSprite If set, treat as sprite (Sprite2). * @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. * @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 = { const extensions = {
extensionIDs: new Set(), extensionIDs: new Set(),
extensionURLs: new Map() extensionURLs: new Map()
}; };
return parseScratchObject(json, runtime, extensions, !optForceSprite) return parseScratchObject(json, runtime, extensions, !optForceSprite, zip)
.then(targets => ({ .then(targets => ({
targets, targets,
extensions extensions

View file

@ -293,7 +293,7 @@ class VirtualMachine extends EventEmitter {
const deserializePromise = function () { const deserializePromise = function () {
const projectVersion = projectJSON.projectVersion; const projectVersion = projectJSON.projectVersion;
if (projectVersion === 2) { if (projectVersion === 2) {
return sb2.deserialize(projectJSON, runtime); return sb2.deserialize(projectJSON, runtime, false, zip);
} }
if (projectVersion === 3) { if (projectVersion === 3) {
return sb3.deserialize(projectJSON, runtime, zip); return sb3.deserialize(projectJSON, runtime, zip);