manyfest 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@
3
3
  * @author <steven@velozo.com>
4
4
  */
5
5
  let libSimpleLog = require('./Manyfest-LogToConsole.js');
6
+ let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
6
7
 
7
8
  /**
8
9
  * Manyfest object address-based descriptions and manipulations.
@@ -11,15 +12,31 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
11
12
  */
12
13
  class Manyfest
13
14
  {
14
- constructor(pManifest, pInfoLog, pErrorLog)
15
+ constructor(pManifest, pInfoLog, pErrorLog, pOptions)
15
16
  {
16
17
  // Wire in logging
17
18
  this.logInfo = (typeof(pInfoLog) === 'function') ? pInfoLog : libSimpleLog;
18
19
  this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
19
20
 
21
+ // Create an object address resolver and map in the functions
22
+ this.objectAddressResolver = new libObjectAddressResolver(this.logInfo, this.logError);
23
+
20
24
  this.options = (
21
25
  {
22
- strict: false
26
+ strict: false,
27
+ defaultValues:
28
+ {
29
+ "String": "",
30
+ "Number": 0,
31
+ "Float": 0.0,
32
+ "Integer": 0,
33
+ "Boolean": false,
34
+ "Binary": 0,
35
+ "DateTime": 0,
36
+ "Array": [],
37
+ "Object": {},
38
+ "Null": null
39
+ }
23
40
  });
24
41
 
25
42
  this.scope = undefined;
@@ -72,12 +89,12 @@ class Manyfest
72
89
  }
73
90
  else
74
91
  {
75
- this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`);
92
+ this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`, pManifest);
76
93
  }
77
94
  }
78
95
  else
79
96
  {
80
- this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`);
97
+ this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`, pManifest);
81
98
  }
82
99
 
83
100
  if (pManifest.hasOwnProperty('Descriptors'))
@@ -92,12 +109,12 @@ class Manyfest
92
109
  }
93
110
  else
94
111
  {
95
- this.logError(`(${this.scope}) Error loading description object from manifest object. Expecting an object in 'Manifest.Descriptors' but the property was type ${typeof(pManifest.Description)}.`);
112
+ this.logError(`(${this.scope}) Error loading description object from manifest object. Expecting an object in 'Manifest.Descriptors' but the property was type ${typeof(pManifest.Descriptors)}.`, pManifest);
96
113
  }
97
114
  }
98
115
  else
99
116
  {
100
- this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the object.`);
117
+ this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the Manifest object.`, pManifest);
101
118
  }
102
119
  }
103
120
 
@@ -175,60 +192,47 @@ class Manyfest
175
192
  /*************************************************************************
176
193
  * Beginning of Object Manipulation (read & write) Functions
177
194
  */
178
- // Get the value of an element by its hash
179
- getValueByHash (pObject, pHash)
195
+ // Check if an element exists by its hash
196
+ checkAddressExistsByHash (pObject, pHash)
180
197
  {
181
198
  if (this.elementHashes.hasOwnProperty(pHash))
182
199
  {
183
- return this.getValueAtAddress(pObject, this.elementHashes[pHash]);
200
+ return this.checkAddressExists(pObject, this.elementHashes[pHash]);
184
201
  }
185
202
  else
186
203
  {
187
- this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`);
204
+ this.logError(`(${this.scope}) Error in checkAddressExistsByHash; the Hash ${pHash} doesn't exist in the schema.`);
188
205
  return undefined;
189
206
  }
190
207
  }
191
208
 
192
- // Get the value of an element at an address
193
- getValueAtAddress (pObject, pAddress)
209
+ // Check if an element exists at an address
210
+ checkAddressExists (pObject, pAddress)
194
211
  {
195
- // Make sure pObject is an object
196
- if (!typeof(pObject) === 'object') return undefined;
197
- // Make sure pAddress is a string
198
- if (!typeof(pAddress) === 'string') return undefined;
212
+ return this.objectAddressResolver.checkAddressExists(pObject, pAddress);
213
+ }
199
214
 
200
- let tmpSeparatorIndex = pAddress.indexOf('.');
201
215
 
202
- if (tmpSeparatorIndex === -1)
216
+ // Get the value of an element by its hash
217
+ getValueByHash (pObject, pHash)
218
+ {
219
+ if (this.elementHashes.hasOwnProperty(pHash))
203
220
  {
204
- // Now is the point in recursion to return the value in the address
205
- return pObject[pAddress];
221
+ return this.getValueAtAddress(pObject, this.elementHashes[pHash]);
206
222
  }
207
223
  else
208
224
  {
209
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
210
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
211
-
212
- // If there is an object property already named for the sub object, but it isn't an object
213
- // then the system can't set the value in there. Error and abort!
214
- if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
215
- {
216
- return undefined;
217
- }
218
- else if (pObject.hasOwnProperty(tmpSubObjectName))
219
- {
220
- // If there is already a subobject pass that to the recursive thingy
221
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
222
- }
223
- else
224
- {
225
- // Create a subobject and then pass that
226
- pObject[tmpSubObjectName] = {};
227
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
228
- }
225
+ this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`);
226
+ return undefined;
229
227
  }
230
228
  }
231
229
 
230
+ // Get the value of an element at an address
231
+ getValueAtAddress (pObject, pAddress)
232
+ {
233
+ return this.objectAddressResolver.getValueAtAddress(pObject, pAddress);
234
+ }
235
+
232
236
  // Set the value of an element by its hash
233
237
  setValueByHash(pObject, pHash, pValue)
234
238
  {
@@ -243,79 +247,13 @@ class Manyfest
243
247
  }
244
248
  }
245
249
 
250
+
246
251
  // Set the value of an element at an address
247
252
  setValueAtAddress (pObject, pAddress, pValue)
248
253
  {
249
- // Make sure pObject is an object
250
- if (!typeof(pObject) === 'object') return false;
251
- // Make sure pAddress is a string
252
- if (!typeof(pAddress) === 'string') return false;
253
-
254
- let tmpSeparatorIndex = pAddress.indexOf('.');
255
-
256
- if (tmpSeparatorIndex === -1)
257
- {
258
- // Now is the time to set the value in the object
259
- pObject[pAddress] = pValue;
260
- return true;
261
- }
262
- else
263
- {
264
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
265
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
266
-
267
- // If there is an object property already named for the sub object, but it isn't an object
268
- // then the system can't set the value in there. Error and abort!
269
- if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
270
- {
271
- if (!pObject.hasOwnProperty('__ERROR'))
272
- pObject['__ERROR'] = {};
273
- // Put it in an error object so data isn't lost
274
- pObject['__ERROR'][pAddress] = pValue;
275
- return false;
276
- }
277
- else if (pObject.hasOwnProperty(tmpSubObjectName))
278
- {
279
- // If there is already a subobject pass that to the recursive thingy
280
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
281
- }
282
- else
283
- {
284
- // Create a subobject and then pass that
285
- pObject[tmpSubObjectName] = {};
286
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
287
- }
288
- }
289
- }
290
-
291
- setValueAtAddressInContainer(pRecordObject, pFormContainerAddress, pFormContainerIndex, pFormValueAddress, pFormValue)
292
- {
293
- // First see if there *is* a container object
294
- let tmpContainerObject = this.getValueAtAddress(pRecordObject, pFormContainerAddress);
295
-
296
- if (typeof(pFormContainerAddress) !== 'string') return false;
297
-
298
- let tmpFormContainerIndex = parseInt(pFormContainerIndex, 10);
299
- if (isNaN(tmpFormContainerIndex)) return false;
300
-
301
- if ((typeof(tmpContainerObject) !== 'object') || (!Array.isArray(tmpContainerObject)))
302
- {
303
- // Check if there is a value here and we want to store it in the "__OverwrittenData" thing
304
- tmpContainerObject = [];
305
- this.setValueAtAddress(pRecordObject, pFormContainerAddress, tmpContainerObject);
306
- }
307
-
308
- for (let i = 0; (tmpContainerObject.length + i) <= (tmpFormContainerIndex+1); i++)
309
- {
310
- // Add objects to this container until it has enough
311
- tmpContainerObject.push({});
312
- }
313
-
314
- // Now set the value *in* the container object
315
- return this.setValueAtAddress(tmpContainerObject[tmpFormContainerIndex], pFormValueAddress, pFormValue);
254
+ return this.objectAddressResolver.setValueAtAddress(pObject, pAddress, pValue);
316
255
  }
317
256
 
318
-
319
257
  // Validate the consistency of an object against the schema
320
258
  validate(pObject)
321
259
  {
@@ -332,7 +270,13 @@ class Manyfest
332
270
  tmpValidationData.Errors.push(`Expected passed in object to be type object but was passed in ${typeof(pObject)}`);
333
271
  }
334
272
 
335
- // Now enumerate through the values and check for anomalies
273
+ let addValidationError = (pAddress, pErrorMessage) =>
274
+ {
275
+ tmpValidationData.Error = true;
276
+ tmpValidationData.Errors.push(`Element at address "${pAddress}" ${pErrorMessage}.`);
277
+ };
278
+
279
+ // Now enumerate through the values and check for anomalies based on the schema
336
280
  for (let i = 0; i < this.elementAddresses.length; i++)
337
281
  {
338
282
  let tmpDescriptor = this.getDescriptor(this.elementAddresses[i]);
@@ -340,18 +284,144 @@ class Manyfest
340
284
 
341
285
  if (typeof(tmpValue) == 'undefined')
342
286
  {
287
+ // This will technically mean that `Object.Some.Value = undefined` will end up showing as "missing"
288
+ // TODO: Do we want to do a different message based on if the property exists but is undefined?
343
289
  tmpValidationData.MissingElements.push(tmpDescriptor.Address);
344
-
345
290
  if (tmpDescriptor.Required || this.options.strict)
346
291
  {
347
- tmpValidationData.Error = true;
348
- tmpValidationData.Errors.push(`Element at address '${tmpDescriptor.Address}' is flagged Required but is not present.`);
292
+ addValidationError(tmpDescriptor.Address, 'is flagged REQUIRED but is not set in the object');
293
+ }
294
+ }
295
+
296
+ // Now see if there is a data type specified for this element
297
+ if (tmpDescriptor.DataType)
298
+ {
299
+ let tmpElementType = typeof(tmpValue);
300
+ switch(tmpDescriptor.DataType.toString().trim().toLowerCase())
301
+ {
302
+ case 'string':
303
+ if (tmpElementType != 'string')
304
+ {
305
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
306
+ }
307
+ break;
308
+
309
+ case 'number':
310
+ if (tmpElementType != 'number')
311
+ {
312
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
313
+ }
314
+ break;
315
+
316
+ case 'integer':
317
+ if (tmpElementType != 'number')
318
+ {
319
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
320
+ }
321
+ else
322
+ {
323
+ let tmpValueString = tmpValue.toString();
324
+ if (tmpValueString.indexOf('.') > -1)
325
+ {
326
+ // TODO: Is this an error?
327
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but has a decimal point in the number.`);
328
+ }
329
+ }
330
+ break;
331
+
332
+ case 'float':
333
+ if (tmpElementType != 'number')
334
+ {
335
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
336
+ }
337
+ break;
338
+
339
+ case 'DateTime':
340
+ let tmpValueDate = new Date(tmpValue);
341
+ if (tmpValueDate.toString() == 'Invalid Date')
342
+ {
343
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is not parsable as a Date by Javascript`);
344
+ }
345
+
346
+ default:
347
+ // Check if this is a string, in the default case
348
+ // Note this is only when a DataType is specified and it is an unrecognized data type.
349
+ if (tmpElementType != 'string')
350
+ {
351
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} (which auto-converted to String because it was unrecognized) but is of the type ${tmpElementType}`);
352
+ }
353
+ break;
349
354
  }
350
355
  }
351
356
  }
352
357
 
353
358
  return tmpValidationData;
354
359
  }
360
+
361
+ // Returns a default value, or, the default value for the data type (which is overridable with configuration)
362
+ getDefaultValue(pDescriptor)
363
+ {
364
+ if (pDescriptor.hasOwnProperty('Default'))
365
+ {
366
+ return pDescriptor.Default;
367
+ }
368
+ else
369
+ {
370
+ // Default to a null if it doesn't have a type specified.
371
+ // This will ensure a placeholder is created but isn't misinterpreted.
372
+ let tmpDataType = (pDescriptor.hasOwnProperty('DataType')) ? pDescriptor.DataType : 'String';
373
+ if (this.options.defaultValues.hasOwnProperty(tmpDataType))
374
+ {
375
+ return this.options.defaultValues[tmpDataType];
376
+ }
377
+ else
378
+ {
379
+ // give up and return null
380
+ return null;
381
+ }
382
+ }
383
+ }
384
+
385
+ // Enumerate through the schema and populate default values if they don't exist.
386
+ populateDefaults(pObject, pOverwriteProperties)
387
+ {
388
+ return this.populateObject(pObject, pOverwriteProperties,
389
+ // This just sets up a simple filter to see if there is a default set.
390
+ (pDescriptor) =>
391
+ {
392
+ return pDescriptor.hasOwnProperty('Default');
393
+ });
394
+ }
395
+
396
+ // Forcefully populate all values even if they don't have defaults.
397
+ // Based on type, this can do unexpected things.
398
+ populateObject(pObject, pOverwriteProperties, fFilter)
399
+ {
400
+ // Automatically create an object if one isn't passed in.
401
+ let tmpObject = (typeof(pObject) === 'object') ? pObject : {};
402
+ // Default to *NOT OVERWRITING* properties
403
+ let tmpOverwriteProperties = (typeof(pOverwriteProperties) == 'undefined') ? false : pOverwriteProperties;
404
+ // This is a filter function, which is passed the schema and allows complex filtering of population
405
+ // The default filter function just returns true, populating everything.
406
+ let tmpFilterFunction = (typeof(fFilter) == 'function') ? fFilter : (pDescriptor) => { return true; };
407
+
408
+ this.elementAddresses.forEach(
409
+ (pAddress) =>
410
+ {
411
+ let tmpDescriptor = this.getDescriptor(pAddress);
412
+ // Check the filter function to see if this is an address we want to set the value for.
413
+ if (tmpFilterFunction(tmpDescriptor))
414
+ {
415
+ // If we are overwriting properties OR the property does not exist
416
+ if (tmpOverwriteProperties || !this.checkAddressExists(tmpObject, pAddress))
417
+ {
418
+ this.setValueAtAddress(tmpObject, pAddress, this.getDefaultValue(tmpDescriptor));
419
+ }
420
+ }
421
+ });
422
+
423
+ return tmpObject;
424
+ }
355
425
  };
356
426
 
357
427
  module.exports = Manyfest;
@@ -0,0 +1,246 @@
1
+ {
2
+ "created": 1664830085,
3
+ "d1": "ia600202.us.archive.org",
4
+ "d2": "ia800202.us.archive.org",
5
+ "dir": "/7/items/FrankenberryCountChoculaTevevisionCommercial1971",
6
+ "files": [
7
+ {
8
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000001.jpg",
9
+ "source": "derivative",
10
+ "format": "Thumbnail",
11
+ "original": "frankerberry_countchockula_1971.0001.mpg",
12
+ "mtime": "1296336956",
13
+ "size": "838",
14
+ "md5": "e47269cd5a82db9594f265a65785ec12",
15
+ "crc32": "165c668b",
16
+ "sha1": "383303d9546c381267569ad4e33aff691f0bb8c7"
17
+ },
18
+ {
19
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000004.jpg",
20
+ "source": "derivative",
21
+ "format": "Thumbnail",
22
+ "original": "frankerberry_countchockula_1971.0001.mpg",
23
+ "mtime": "1296336957",
24
+ "size": "6843",
25
+ "md5": "c93fa52000ab4665e69b25c403e11aff",
26
+ "crc32": "9444e6f6",
27
+ "sha1": "716b4f9950b8147f51d3265f9c62ff86451308d5"
28
+ },
29
+ {
30
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000009.jpg",
31
+ "source": "derivative",
32
+ "format": "Thumbnail",
33
+ "original": "frankerberry_countchockula_1971.0001.mpg",
34
+ "mtime": "1296336957",
35
+ "size": "8388",
36
+ "md5": "30eb3eb4cbbdfa08d531a0a74da7c000",
37
+ "crc32": "be874a9e",
38
+ "sha1": "0c392d777609e967b6022be27edad678c5ae74e2"
39
+ },
40
+ {
41
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000014.jpg",
42
+ "source": "derivative",
43
+ "format": "Thumbnail",
44
+ "original": "frankerberry_countchockula_1971.0001.mpg",
45
+ "mtime": "1296336958",
46
+ "size": "5993",
47
+ "md5": "4e9ebc3d076bec8cf7dfe76795f8c769",
48
+ "crc32": "912ec98c",
49
+ "sha1": "01dc49c852e1bbb421199450dd902935c62b06de"
50
+ },
51
+ {
52
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000019.jpg",
53
+ "source": "derivative",
54
+ "format": "Thumbnail",
55
+ "original": "frankerberry_countchockula_1971.0001.mpg",
56
+ "mtime": "1296336958",
57
+ "size": "4951",
58
+ "md5": "59f190f0c5b0a048415b26412860b6dd",
59
+ "crc32": "a70a30b1",
60
+ "sha1": "a284af9757cb24d28f96ec88ec1b1c23a8cea9fe"
61
+ },
62
+ {
63
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000024.jpg",
64
+ "source": "derivative",
65
+ "format": "Thumbnail",
66
+ "original": "frankerberry_countchockula_1971.0001.mpg",
67
+ "mtime": "1296336959",
68
+ "size": "3383",
69
+ "md5": "be2a908acd563b896e7758b598295148",
70
+ "crc32": "ed467831",
71
+ "sha1": "94c001e72ebc86d837a78c61a004db9ab9d597bd"
72
+ },
73
+ {
74
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000029.jpg",
75
+ "source": "derivative",
76
+ "format": "Thumbnail",
77
+ "original": "frankerberry_countchockula_1971.0001.mpg",
78
+ "mtime": "1296336960",
79
+ "size": "3503",
80
+ "md5": "c82199d09be07633000fd07b363dd8a3",
81
+ "crc32": "a1fd79cb",
82
+ "sha1": "2bc8e761edb24a441fa5906dda1c424e1f98a47a"
83
+ },
84
+ {
85
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971_archive.torrent",
86
+ "source": "metadata",
87
+ "btih": "de6b371e7cc3c83db1cc08150500753eae533409",
88
+ "mtime": "1542761794",
89
+ "size": "4093",
90
+ "md5": "a275d3b4028cccb5bea8b47a88c838af",
91
+ "crc32": "5ffa7334",
92
+ "sha1": "af8222637b574cba1360d0ea77e231640ffd43c4",
93
+ "format": "Archive BitTorrent"
94
+ },
95
+ {
96
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971_files.xml",
97
+ "source": "metadata",
98
+ "format": "Metadata",
99
+ "md5": "3a7e87b08bed1e203a5858b31352c110"
100
+ },
101
+ {
102
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971_meta.xml",
103
+ "source": "metadata",
104
+ "format": "Metadata",
105
+ "mtime": "1542761793",
106
+ "size": "1371",
107
+ "md5": "0b9c9bf21b9a26aea43a2f735b404624",
108
+ "crc32": "41077288",
109
+ "sha1": "22e6f2c73bf63072f671d846355da2785db51dbd"
110
+ },
111
+ {
112
+ "name": "FrankenberryCountChoculaTevevisionCommercial1971_reviews.xml",
113
+ "source": "original",
114
+ "mtime": "1466898697",
115
+ "size": "620",
116
+ "md5": "260bfba5d696772445dcc7ff6e6d5bdb",
117
+ "crc32": "25ea3229",
118
+ "sha1": "7d541f18fcd5ad9c6e593afe5a80f18771f23b32",
119
+ "format": "Metadata"
120
+ },
121
+ {
122
+ "name": "__ia_thumb.jpg",
123
+ "source": "original",
124
+ "mtime": "1539115881",
125
+ "size": "7481",
126
+ "md5": "8cec324fa0016fd77cc04e6a4b2ebb00",
127
+ "crc32": "d9e1b316",
128
+ "sha1": "4dab42952fe0405a3b7f80146636b33d7b1bd01e",
129
+ "format": "Item Tile",
130
+ "rotation": "0"
131
+ },
132
+ {
133
+ "name": "frankerberry_countchockula_1971.0001.gif",
134
+ "source": "derivative",
135
+ "format": "Animated GIF",
136
+ "original": "frankerberry_countchockula_1971.0001.mpg",
137
+ "mtime": "1296336965",
138
+ "size": "101114",
139
+ "md5": "b78a13094030f104900eb996bafe2b7d",
140
+ "crc32": "6650cd8",
141
+ "sha1": "669798c037205cac14f70592deef6f7831b3d4a1"
142
+ },
143
+ {
144
+ "name": "frankerberry_countchockula_1971.0001.mpg",
145
+ "source": "original",
146
+ "format": "MPEG2",
147
+ "mtime": "1296335803",
148
+ "size": "31625216",
149
+ "md5": "762ba18b026b85b3f074523e7fcb4db0",
150
+ "crc32": "42347f78",
151
+ "sha1": "41162dc2d1a91b618124c84628d0c231544a02be",
152
+ "length": "31.14",
153
+ "height": "480",
154
+ "width": "640"
155
+ },
156
+ {
157
+ "name": "frankerberry_countchockula_1971.0001.mpg.idx",
158
+ "source": "derivative",
159
+ "format": "Video Index",
160
+ "original": "frankerberry_countchockula_1971.0001.mpg",
161
+ "mtime": "1296336956",
162
+ "size": "31141",
163
+ "md5": "49423e072726e4ea3cdd8ebdd26c7dfc",
164
+ "crc32": "ae969a68",
165
+ "sha1": "805782cd2d0f9002555816daadf3b8607e621f79"
166
+ },
167
+ {
168
+ "name": "frankerberry_countchockula_1971.0001.ogv",
169
+ "source": "derivative",
170
+ "format": "Ogg Video",
171
+ "original": "frankerberry_countchockula_1971.0001.mpg",
172
+ "mtime": "1296336994",
173
+ "size": "2248166",
174
+ "md5": "f1b933e97ce63594fb28a0a019ff3436",
175
+ "crc32": "a2a0e5e9",
176
+ "sha1": "a6bf0aec9f006baeca37c03f586686ebe685d59b",
177
+ "length": "31.15",
178
+ "height": "300",
179
+ "width": "400"
180
+ },
181
+ {
182
+ "name": "frankerberry_countchockula_1971.0001_512kb.mp4",
183
+ "source": "derivative",
184
+ "format": "512Kb MPEG4",
185
+ "original": "frankerberry_countchockula_1971.0001.mpg",
186
+ "mtime": "1296336977",
187
+ "size": "2378677",
188
+ "md5": "a7750839519c61ba3bb99fc66b32011d",
189
+ "crc32": "4dbd37c8",
190
+ "sha1": "3929314c192dec006fac2739bcb4730788e8c068",
191
+ "length": "31.13",
192
+ "height": "240",
193
+ "width": "320"
194
+ }
195
+ ],
196
+ "files_count": 17,
197
+ "item_last_updated": 1542761794,
198
+ "item_size": 36431778,
199
+ "metadata": {
200
+ "identifier": "FrankenberryCountChoculaTevevisionCommercial1971",
201
+ "title": "Franken Berry / Count Chocula : Tevevision Commercial 1971",
202
+ "creator": "General Mills",
203
+ "mediatype": "movies",
204
+ "collection": [
205
+ "classic_tv_commercials",
206
+ "television"
207
+ ],
208
+ "description": "Count Chocula and Franken Berry were both introduced in 1971. Boo Berry Cereal appeared in 1973 followed by Fruit Brute in 1974. Yummy Mummy appeared more than a decade later in 1988 - completing the the group known as the General Mills Monster Cereals.",
209
+ "subject": "Third Eye Cinema; Classic Television Commercials; animation; cartoons;General Mills",
210
+ "licenseurl": "http://creativecommons.org/publicdomain/mark/1.0/",
211
+ "publicdate": "2011-01-29 21:36:42",
212
+ "addeddate": "2011-01-29 21:35:38",
213
+ "uploader": "bolexman@msn.com",
214
+ "updater": [
215
+ "Bolexman",
216
+ "Bolexman",
217
+ "Jeff Kaplan"
218
+ ],
219
+ "updatedate": [
220
+ "2011-01-29 21:45:38",
221
+ "2011-01-29 21:55:46",
222
+ "2011-01-29 23:04:55"
223
+ ],
224
+ "sound": "sound",
225
+ "color": "color",
226
+ "runtime": "0:31",
227
+ "backup_location": "ia903608_22",
228
+ "ia_orig__runtime": "31 seconds"
229
+ },
230
+ "reviews": [
231
+ {
232
+ "reviewbody": "Sugar cereal cartoon Karloff and Lugosi argue self-importance pre Lorre ghost. Interesting how kids still know the voices without any idea of the origins.",
233
+ "reviewtitle": "pre booberry",
234
+ "reviewer": "outofthebox",
235
+ "reviewdate": "2016-06-25 23:51:36",
236
+ "createdate": "2016-06-25 23:51:36",
237
+ "stars": "4"
238
+ }
239
+ ],
240
+ "server": "ia800202.us.archive.org",
241
+ "uniq": 1957612749,
242
+ "workable_servers": [
243
+ "ia800202.us.archive.org",
244
+ "ia600202.us.archive.org"
245
+ ]
246
+ }