2018-02-16 00:44:23 -05:00
|
|
|
const JSZip = require('jszip');
|
|
|
|
const log = require('../util/log');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deserializes sound from file into storage cache so that it can
|
|
|
|
* be loaded into the runtime.
|
|
|
|
* @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`
|
2018-03-25 18:14:30 -04:00
|
|
|
* @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])
|
2018-02-16 00:44:23 -05:00
|
|
|
* @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.
|
|
|
|
*/
|
2018-03-25 18:14:30 -04:00
|
|
|
const deserializeSound = function (sound, runtime, zip, assetFileName) {
|
|
|
|
const fileName = assetFileName ? assetFileName : sound.md5;
|
2018-02-16 00:44:23 -05:00
|
|
|
const storage = runtime.storage;
|
|
|
|
if (!storage) {
|
|
|
|
log.error('No storage module present; cannot load sound asset: ', fileName);
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
2018-04-06 15:04:03 -04:00
|
|
|
if (!zip) { // Zip will not be provided if loading project json from server
|
2018-02-22 16:46:24 -05:00
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
2018-10-18 07:39:28 -04:00
|
|
|
|
2018-02-16 00:44:23 -05:00
|
|
|
const soundFile = zip.file(fileName);
|
|
|
|
if (!soundFile) {
|
|
|
|
log.error(`Could not find sound file associated with the ${sound.name} sound.`);
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
2018-10-18 07:39:28 -04:00
|
|
|
|
2018-02-16 00:44:23 -05:00
|
|
|
if (!JSZip.support.uint8array) {
|
|
|
|
log.error('JSZip uint8array is not supported in this browser.');
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
2018-10-18 07:39:28 -04:00
|
|
|
const dataFormat = sound.dataFormat.toLowerCase() === 'mp3' ?
|
|
|
|
storage.DataFormat.MP3 : storage.DataFormat.WAV;
|
|
|
|
return soundFile.async('uint8array').then(data => storage.createAsset(
|
|
|
|
storage.AssetType.Sound,
|
|
|
|
dataFormat,
|
|
|
|
data,
|
|
|
|
sound.assetId
|
|
|
|
));
|
2018-02-16 00:44:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deserializes costume from file into storage cache so that it can
|
|
|
|
* be loaded into the runtime.
|
|
|
|
* @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`
|
2018-03-25 18:14:30 -04:00
|
|
|
* @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])
|
2018-11-05 18:07:56 -05:00
|
|
|
* @param {string} textLayerFileName Optional file name for the given asset's text layer
|
|
|
|
* (sb2 only; files have filenames of the form [int].png)
|
2018-02-16 00:44:23 -05:00
|
|
|
* @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.
|
|
|
|
*/
|
2018-11-05 18:07:56 -05:00
|
|
|
const deserializeCostume = function (costume, runtime, zip, assetFileName, textLayerFileName) {
|
2018-02-16 00:44:23 -05:00
|
|
|
const storage = runtime.storage;
|
|
|
|
const assetId = costume.assetId;
|
2018-03-25 18:14:30 -04:00
|
|
|
const fileName = assetFileName ? assetFileName :
|
|
|
|
`${assetId}.${costume.dataFormat}`;
|
2018-02-16 00:44:23 -05:00
|
|
|
|
|
|
|
if (!storage) {
|
|
|
|
log.error('No storage module present; cannot load costume asset: ', fileName);
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
2018-11-01 14:28:54 -04:00
|
|
|
if (costume.asset) {
|
|
|
|
// When uploading a sprite from an image file, the asset data will be provided
|
|
|
|
// @todo Cache the asset data somewhere and pull it out here
|
|
|
|
return Promise.resolve(storage.createAsset(
|
|
|
|
costume.asset.assetType,
|
|
|
|
costume.asset.dataFormat,
|
|
|
|
new Uint8Array(Object.keys(costume.asset.data).map(key => costume.asset.data[key])),
|
|
|
|
costume.asset.assetId
|
2018-11-05 15:50:28 -05:00
|
|
|
))
|
|
|
|
.then(asset => {
|
|
|
|
costume.asset = asset;
|
|
|
|
});
|
2018-11-01 14:28:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!zip) {
|
|
|
|
// Zip will not be provided if loading project json from server
|
2018-02-22 16:46:24 -05:00
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
2018-02-16 00:44:23 -05:00
|
|
|
const costumeFile = zip.file(fileName);
|
|
|
|
if (!costumeFile) {
|
|
|
|
log.error(`Could not find costume file associated with the ${costume.name} costume.`);
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
let assetType = null;
|
|
|
|
const costumeFormat = costume.dataFormat.toLowerCase();
|
|
|
|
if (costumeFormat === 'svg') {
|
|
|
|
assetType = storage.AssetType.ImageVector;
|
2018-04-04 17:31:03 -04:00
|
|
|
} else if (['png', 'bmp', 'jpeg', 'jpg', 'gif'].indexOf(costumeFormat) >= 0) {
|
2018-02-16 00:44:23 -05:00
|
|
|
assetType = storage.AssetType.ImageBitmap;
|
|
|
|
} else {
|
|
|
|
log.error(`Unexpected file format for costume: ${costumeFormat}`);
|
|
|
|
}
|
|
|
|
if (!JSZip.support.uint8array) {
|
|
|
|
log.error('JSZip uint8array is not supported in this browser.');
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
2018-11-05 18:07:56 -05:00
|
|
|
// textLayerMD5 exists if there is a text layer, which is a png of text from Scratch 1.4
|
|
|
|
// that was opened in Scratch 2.0. In this case, set costume,textLayerAsset.
|
|
|
|
let textLayerFilePromise;
|
|
|
|
if (costume.textLayerMD5) {
|
|
|
|
textLayerFileName = textLayerFileName ? textLayerFileName :
|
|
|
|
`${costume.textLayerMD5}.png`;
|
|
|
|
const textLayerFile = zip.file(textLayerFileName);
|
|
|
|
if (!textLayerFile) {
|
|
|
|
log.error(`Could not find text layer file associated with the ${costume.name} costume.`);
|
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
textLayerFilePromise = textLayerFile.async('uint8array')
|
|
|
|
.then(data => storage.createAsset(
|
|
|
|
storage.AssetType.ImageBitmap,
|
|
|
|
'png',
|
|
|
|
data,
|
|
|
|
costume.textLayerMD5
|
|
|
|
))
|
|
|
|
.then(asset => {
|
|
|
|
costume.textLayerAsset = asset;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
textLayerFilePromise = Promise.resolve(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all([textLayerFilePromise,
|
|
|
|
costumeFile.async('uint8array')
|
|
|
|
.then(data => storage.createAsset(
|
|
|
|
assetType,
|
|
|
|
// TODO eventually we want to map non-png's to their actual file types?
|
|
|
|
costumeFormat,
|
|
|
|
data,
|
|
|
|
assetId
|
|
|
|
))
|
|
|
|
.then(asset => {
|
|
|
|
costume.asset = asset;
|
|
|
|
})
|
|
|
|
]);
|
2018-02-16 00:44:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
deserializeSound,
|
|
|
|
deserializeCostume
|
|
|
|
};
|