Added MVIMG support!
Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
parent
b0c7f61e58
commit
017560fa1a
12
frontend/js/LICENSE
Normal file
12
frontend/js/LICENSE
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
Contents of the 'js' directory are from:
|
||||||
|
|
||||||
|
* JavaScript Load Image Exif Parser
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
|
388
frontend/js/load-image-exif-map.js
Normal file
388
frontend/js/load-image-exif-map.js
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Exif Map
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Exif tags mapping based on
|
||||||
|
* https://github.com/jseidelin/exif-js
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-exif'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'), require('./load-image-exif'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.tags = {
|
||||||
|
// =================
|
||||||
|
// TIFF tags (IFD0):
|
||||||
|
// =================
|
||||||
|
0x0100: 'ImageWidth',
|
||||||
|
0x0101: 'ImageHeight',
|
||||||
|
0x8769: 'ExifIFDPointer',
|
||||||
|
0x8825: 'GPSInfoIFDPointer',
|
||||||
|
0xa005: 'InteroperabilityIFDPointer',
|
||||||
|
0x0102: 'BitsPerSample',
|
||||||
|
0x0103: 'Compression',
|
||||||
|
0x0106: 'PhotometricInterpretation',
|
||||||
|
0x0112: 'Orientation',
|
||||||
|
0x0115: 'SamplesPerPixel',
|
||||||
|
0x011c: 'PlanarConfiguration',
|
||||||
|
0x0212: 'YCbCrSubSampling',
|
||||||
|
0x0213: 'YCbCrPositioning',
|
||||||
|
0x011a: 'XResolution',
|
||||||
|
0x011b: 'YResolution',
|
||||||
|
0x0128: 'ResolutionUnit',
|
||||||
|
0x0111: 'StripOffsets',
|
||||||
|
0x0116: 'RowsPerStrip',
|
||||||
|
0x0117: 'StripByteCounts',
|
||||||
|
0x0201: 'JPEGInterchangeFormat',
|
||||||
|
0x0202: 'JPEGInterchangeFormatLength',
|
||||||
|
0x012d: 'TransferFunction',
|
||||||
|
0x013e: 'WhitePoint',
|
||||||
|
0x013f: 'PrimaryChromaticities',
|
||||||
|
0x0211: 'YCbCrCoefficients',
|
||||||
|
0x0214: 'ReferenceBlackWhite',
|
||||||
|
0x0132: 'DateTime',
|
||||||
|
0x010e: 'ImageDescription',
|
||||||
|
0x010f: 'Make',
|
||||||
|
0x0110: 'Model',
|
||||||
|
0x0131: 'Software',
|
||||||
|
0x013b: 'Artist',
|
||||||
|
0x8298: 'Copyright',
|
||||||
|
// ==================
|
||||||
|
// Exif Sub IFD tags:
|
||||||
|
// ==================
|
||||||
|
0x9000: 'ExifVersion', // EXIF version
|
||||||
|
0xa000: 'FlashpixVersion', // Flashpix format version
|
||||||
|
0xa001: 'ColorSpace', // Color space information tag
|
||||||
|
0xa002: 'PixelXDimension', // Valid width of meaningful image
|
||||||
|
0xa003: 'PixelYDimension', // Valid height of meaningful image
|
||||||
|
0xa500: 'Gamma',
|
||||||
|
0x9101: 'ComponentsConfiguration', // Information about channels
|
||||||
|
0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
|
||||||
|
0x927c: 'MakerNote', // Any desired information written by the manufacturer
|
||||||
|
0x9286: 'UserComment', // Comments by user
|
||||||
|
0xa004: 'RelatedSoundFile', // Name of related sound file
|
||||||
|
0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
|
||||||
|
0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
|
||||||
|
0x9290: 'SubSecTime', // Fractions of seconds for DateTime
|
||||||
|
0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
|
||||||
|
0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
|
||||||
|
0x829a: 'ExposureTime', // Exposure time (in seconds)
|
||||||
|
0x829d: 'FNumber',
|
||||||
|
0x8822: 'ExposureProgram', // Exposure program
|
||||||
|
0x8824: 'SpectralSensitivity', // Spectral sensitivity
|
||||||
|
0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
|
||||||
|
0x8828: 'OECF', // Optoelectric conversion factor
|
||||||
|
0x8830: 'SensitivityType',
|
||||||
|
0x8831: 'StandardOutputSensitivity',
|
||||||
|
0x8832: 'RecommendedExposureIndex',
|
||||||
|
0x8833: 'ISOSpeed',
|
||||||
|
0x8834: 'ISOSpeedLatitudeyyy',
|
||||||
|
0x8835: 'ISOSpeedLatitudezzz',
|
||||||
|
0x9201: 'ShutterSpeedValue', // Shutter speed
|
||||||
|
0x9202: 'ApertureValue', // Lens aperture
|
||||||
|
0x9203: 'BrightnessValue', // Value of brightness
|
||||||
|
0x9204: 'ExposureBias', // Exposure bias
|
||||||
|
0x9205: 'MaxApertureValue', // Smallest F number of lens
|
||||||
|
0x9206: 'SubjectDistance', // Distance to subject in meters
|
||||||
|
0x9207: 'MeteringMode', // Metering mode
|
||||||
|
0x9208: 'LightSource', // Kind of light source
|
||||||
|
0x9209: 'Flash', // Flash status
|
||||||
|
0x9214: 'SubjectArea', // Location and area of main subject
|
||||||
|
0x920a: 'FocalLength', // Focal length of the lens in mm
|
||||||
|
0xa20b: 'FlashEnergy', // Strobe energy in BCPS
|
||||||
|
0xa20c: 'SpatialFrequencyResponse',
|
||||||
|
0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
|
||||||
|
0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
|
||||||
|
0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
|
||||||
|
0xa214: 'SubjectLocation', // Location of subject in image
|
||||||
|
0xa215: 'ExposureIndex', // Exposure index selected on camera
|
||||||
|
0xa217: 'SensingMethod', // Image sensor type
|
||||||
|
0xa300: 'FileSource', // Image source (3 == DSC)
|
||||||
|
0xa301: 'SceneType', // Scene type (1 == directly photographed)
|
||||||
|
0xa302: 'CFAPattern', // Color filter array geometric pattern
|
||||||
|
0xa401: 'CustomRendered', // Special processing
|
||||||
|
0xa402: 'ExposureMode', // Exposure mode
|
||||||
|
0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
|
||||||
|
0xa404: 'DigitalZoomRatio', // Digital zoom ratio
|
||||||
|
0xa405: 'FocalLengthIn35mmFilm',
|
||||||
|
0xa406: 'SceneCaptureType', // Type of scene
|
||||||
|
0xa407: 'GainControl', // Degree of overall image gain adjustment
|
||||||
|
0xa408: 'Contrast', // Direction of contrast processing applied by camera
|
||||||
|
0xa409: 'Saturation', // Direction of saturation processing applied by camera
|
||||||
|
0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
|
||||||
|
0xa40b: 'DeviceSettingDescription',
|
||||||
|
0xa40c: 'SubjectDistanceRange', // Distance to subject
|
||||||
|
0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
|
||||||
|
0xa430: 'CameraOwnerName',
|
||||||
|
0xa431: 'BodySerialNumber',
|
||||||
|
0xa432: 'LensSpecification',
|
||||||
|
0xa433: 'LensMake',
|
||||||
|
0xa434: 'LensModel',
|
||||||
|
0xa435: 'LensSerialNumber',
|
||||||
|
// ==============
|
||||||
|
// GPS Info tags:
|
||||||
|
// ==============
|
||||||
|
0x0000: 'GPSVersionID',
|
||||||
|
0x0001: 'GPSLatitudeRef',
|
||||||
|
0x0002: 'GPSLatitude',
|
||||||
|
0x0003: 'GPSLongitudeRef',
|
||||||
|
0x0004: 'GPSLongitude',
|
||||||
|
0x0005: 'GPSAltitudeRef',
|
||||||
|
0x0006: 'GPSAltitude',
|
||||||
|
0x0007: 'GPSTimeStamp',
|
||||||
|
0x0008: 'GPSSatellites',
|
||||||
|
0x0009: 'GPSStatus',
|
||||||
|
0x000a: 'GPSMeasureMode',
|
||||||
|
0x000b: 'GPSDOP',
|
||||||
|
0x000c: 'GPSSpeedRef',
|
||||||
|
0x000d: 'GPSSpeed',
|
||||||
|
0x000e: 'GPSTrackRef',
|
||||||
|
0x000f: 'GPSTrack',
|
||||||
|
0x0010: 'GPSImgDirectionRef',
|
||||||
|
0x0011: 'GPSImgDirection',
|
||||||
|
0x0012: 'GPSMapDatum',
|
||||||
|
0x0013: 'GPSDestLatitudeRef',
|
||||||
|
0x0014: 'GPSDestLatitude',
|
||||||
|
0x0015: 'GPSDestLongitudeRef',
|
||||||
|
0x0016: 'GPSDestLongitude',
|
||||||
|
0x0017: 'GPSDestBearingRef',
|
||||||
|
0x0018: 'GPSDestBearing',
|
||||||
|
0x0019: 'GPSDestDistanceRef',
|
||||||
|
0x001a: 'GPSDestDistance',
|
||||||
|
0x001b: 'GPSProcessingMethod',
|
||||||
|
0x001c: 'GPSAreaInformation',
|
||||||
|
0x001d: 'GPSDateStamp',
|
||||||
|
0x001e: 'GPSDifferential',
|
||||||
|
0x001f: 'GPSHPositioningError'
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.stringValues = {
|
||||||
|
ExposureProgram: {
|
||||||
|
0: 'Undefined',
|
||||||
|
1: 'Manual',
|
||||||
|
2: 'Normal program',
|
||||||
|
3: 'Aperture priority',
|
||||||
|
4: 'Shutter priority',
|
||||||
|
5: 'Creative program',
|
||||||
|
6: 'Action program',
|
||||||
|
7: 'Portrait mode',
|
||||||
|
8: 'Landscape mode'
|
||||||
|
},
|
||||||
|
MeteringMode: {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Average',
|
||||||
|
2: 'CenterWeightedAverage',
|
||||||
|
3: 'Spot',
|
||||||
|
4: 'MultiSpot',
|
||||||
|
5: 'Pattern',
|
||||||
|
6: 'Partial',
|
||||||
|
255: 'Other'
|
||||||
|
},
|
||||||
|
LightSource: {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Daylight',
|
||||||
|
2: 'Fluorescent',
|
||||||
|
3: 'Tungsten (incandescent light)',
|
||||||
|
4: 'Flash',
|
||||||
|
9: 'Fine weather',
|
||||||
|
10: 'Cloudy weather',
|
||||||
|
11: 'Shade',
|
||||||
|
12: 'Daylight fluorescent (D 5700 - 7100K)',
|
||||||
|
13: 'Day white fluorescent (N 4600 - 5400K)',
|
||||||
|
14: 'Cool white fluorescent (W 3900 - 4500K)',
|
||||||
|
15: 'White fluorescent (WW 3200 - 3700K)',
|
||||||
|
17: 'Standard light A',
|
||||||
|
18: 'Standard light B',
|
||||||
|
19: 'Standard light C',
|
||||||
|
20: 'D55',
|
||||||
|
21: 'D65',
|
||||||
|
22: 'D75',
|
||||||
|
23: 'D50',
|
||||||
|
24: 'ISO studio tungsten',
|
||||||
|
255: 'Other'
|
||||||
|
},
|
||||||
|
Flash: {
|
||||||
|
0x0000: 'Flash did not fire',
|
||||||
|
0x0001: 'Flash fired',
|
||||||
|
0x0005: 'Strobe return light not detected',
|
||||||
|
0x0007: 'Strobe return light detected',
|
||||||
|
0x0009: 'Flash fired, compulsory flash mode',
|
||||||
|
0x000d: 'Flash fired, compulsory flash mode, return light not detected',
|
||||||
|
0x000f: 'Flash fired, compulsory flash mode, return light detected',
|
||||||
|
0x0010: 'Flash did not fire, compulsory flash mode',
|
||||||
|
0x0018: 'Flash did not fire, auto mode',
|
||||||
|
0x0019: 'Flash fired, auto mode',
|
||||||
|
0x001d: 'Flash fired, auto mode, return light not detected',
|
||||||
|
0x001f: 'Flash fired, auto mode, return light detected',
|
||||||
|
0x0020: 'No flash function',
|
||||||
|
0x0041: 'Flash fired, red-eye reduction mode',
|
||||||
|
0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
|
||||||
|
0x0047: 'Flash fired, red-eye reduction mode, return light detected',
|
||||||
|
0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
|
||||||
|
0x004d: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
|
||||||
|
0x004f: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
|
||||||
|
0x0059: 'Flash fired, auto mode, red-eye reduction mode',
|
||||||
|
0x005d: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
|
||||||
|
0x005f: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
|
||||||
|
},
|
||||||
|
SensingMethod: {
|
||||||
|
1: 'Undefined',
|
||||||
|
2: 'One-chip color area sensor',
|
||||||
|
3: 'Two-chip color area sensor',
|
||||||
|
4: 'Three-chip color area sensor',
|
||||||
|
5: 'Color sequential area sensor',
|
||||||
|
7: 'Trilinear sensor',
|
||||||
|
8: 'Color sequential linear sensor'
|
||||||
|
},
|
||||||
|
SceneCaptureType: {
|
||||||
|
0: 'Standard',
|
||||||
|
1: 'Landscape',
|
||||||
|
2: 'Portrait',
|
||||||
|
3: 'Night scene'
|
||||||
|
},
|
||||||
|
SceneType: {
|
||||||
|
1: 'Directly photographed'
|
||||||
|
},
|
||||||
|
CustomRendered: {
|
||||||
|
0: 'Normal process',
|
||||||
|
1: 'Custom process'
|
||||||
|
},
|
||||||
|
WhiteBalance: {
|
||||||
|
0: 'Auto white balance',
|
||||||
|
1: 'Manual white balance'
|
||||||
|
},
|
||||||
|
GainControl: {
|
||||||
|
0: 'None',
|
||||||
|
1: 'Low gain up',
|
||||||
|
2: 'High gain up',
|
||||||
|
3: 'Low gain down',
|
||||||
|
4: 'High gain down'
|
||||||
|
},
|
||||||
|
Contrast: {
|
||||||
|
0: 'Normal',
|
||||||
|
1: 'Soft',
|
||||||
|
2: 'Hard'
|
||||||
|
},
|
||||||
|
Saturation: {
|
||||||
|
0: 'Normal',
|
||||||
|
1: 'Low saturation',
|
||||||
|
2: 'High saturation'
|
||||||
|
},
|
||||||
|
Sharpness: {
|
||||||
|
0: 'Normal',
|
||||||
|
1: 'Soft',
|
||||||
|
2: 'Hard'
|
||||||
|
},
|
||||||
|
SubjectDistanceRange: {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Macro',
|
||||||
|
2: 'Close view',
|
||||||
|
3: 'Distant view'
|
||||||
|
},
|
||||||
|
FileSource: {
|
||||||
|
3: 'DSC'
|
||||||
|
},
|
||||||
|
ComponentsConfiguration: {
|
||||||
|
0: '',
|
||||||
|
1: 'Y',
|
||||||
|
2: 'Cb',
|
||||||
|
3: 'Cr',
|
||||||
|
4: 'R',
|
||||||
|
5: 'G',
|
||||||
|
6: 'B'
|
||||||
|
},
|
||||||
|
Orientation: {
|
||||||
|
1: 'top-left',
|
||||||
|
2: 'top-right',
|
||||||
|
3: 'bottom-right',
|
||||||
|
4: 'bottom-left',
|
||||||
|
5: 'left-top',
|
||||||
|
6: 'right-top',
|
||||||
|
7: 'right-bottom',
|
||||||
|
8: 'left-bottom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.getText = function(id) {
|
||||||
|
var value = this.get(id)
|
||||||
|
switch (id) {
|
||||||
|
case 'LightSource':
|
||||||
|
case 'Flash':
|
||||||
|
case 'MeteringMode':
|
||||||
|
case 'ExposureProgram':
|
||||||
|
case 'SensingMethod':
|
||||||
|
case 'SceneCaptureType':
|
||||||
|
case 'SceneType':
|
||||||
|
case 'CustomRendered':
|
||||||
|
case 'WhiteBalance':
|
||||||
|
case 'GainControl':
|
||||||
|
case 'Contrast':
|
||||||
|
case 'Saturation':
|
||||||
|
case 'Sharpness':
|
||||||
|
case 'SubjectDistanceRange':
|
||||||
|
case 'FileSource':
|
||||||
|
case 'Orientation':
|
||||||
|
return this.stringValues[id][value]
|
||||||
|
case 'ExifVersion':
|
||||||
|
case 'FlashpixVersion':
|
||||||
|
if (!value) return
|
||||||
|
return String.fromCharCode(value[0], value[1], value[2], value[3])
|
||||||
|
case 'ComponentsConfiguration':
|
||||||
|
if (!value) return
|
||||||
|
return (
|
||||||
|
this.stringValues[id][value[0]] +
|
||||||
|
this.stringValues[id][value[1]] +
|
||||||
|
this.stringValues[id][value[2]] +
|
||||||
|
this.stringValues[id][value[3]]
|
||||||
|
)
|
||||||
|
case 'GPSVersionID':
|
||||||
|
if (!value) return
|
||||||
|
return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3]
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
;(function(exifMapPrototype) {
|
||||||
|
var tags = exifMapPrototype.tags
|
||||||
|
var map = exifMapPrototype.map
|
||||||
|
var prop
|
||||||
|
// Map the tag names to tags:
|
||||||
|
for (prop in tags) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(tags, prop)) {
|
||||||
|
map[tags[prop]] = prop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(loadImage.ExifMap.prototype)
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.getAll = function() {
|
||||||
|
var map = {}
|
||||||
|
var prop
|
||||||
|
var id
|
||||||
|
for (prop in this) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this, prop)) {
|
||||||
|
id = this.tags[prop]
|
||||||
|
if (id) {
|
||||||
|
map[id] = this.getText(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
})
|
322
frontend/js/load-image-exif.js
Normal file
322
frontend/js/load-image-exif.js
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Exif Parser
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-meta'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'), require('./load-image-meta'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
loadImage.ExifMap = function() {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.map = {
|
||||||
|
Orientation: 0x0112
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.ExifMap.prototype.get = function(id) {
|
||||||
|
return this[id] || this[this.map[id]]
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.getExifThumbnail = function(dataView, offset, length) {
|
||||||
|
if (!length || offset + length > dataView.byteLength) {
|
||||||
|
console.log('Invalid Exif data: Invalid thumbnail data.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return loadImage.createObjectURL(
|
||||||
|
new Blob([dataView.buffer.slice(offset, offset + length)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.exifTagTypes = {
|
||||||
|
// byte, 8-bit unsigned int:
|
||||||
|
1: {
|
||||||
|
getValue: function(dataView, dataOffset) {
|
||||||
|
return dataView.getUint8(dataOffset)
|
||||||
|
},
|
||||||
|
size: 1
|
||||||
|
},
|
||||||
|
// ascii, 8-bit byte:
|
||||||
|
2: {
|
||||||
|
getValue: function(dataView, dataOffset) {
|
||||||
|
return String.fromCharCode(dataView.getUint8(dataOffset))
|
||||||
|
},
|
||||||
|
size: 1,
|
||||||
|
ascii: true
|
||||||
|
},
|
||||||
|
// short, 16 bit int:
|
||||||
|
3: {
|
||||||
|
getValue: function(dataView, dataOffset, littleEndian) {
|
||||||
|
return dataView.getUint16(dataOffset, littleEndian)
|
||||||
|
},
|
||||||
|
size: 2
|
||||||
|
},
|
||||||
|
// long, 32 bit int:
|
||||||
|
4: {
|
||||||
|
getValue: function(dataView, dataOffset, littleEndian) {
|
||||||
|
return dataView.getUint32(dataOffset, littleEndian)
|
||||||
|
},
|
||||||
|
size: 4
|
||||||
|
},
|
||||||
|
// rational = two long values, first is numerator, second is denominator:
|
||||||
|
5: {
|
||||||
|
getValue: function(dataView, dataOffset, littleEndian) {
|
||||||
|
return (
|
||||||
|
dataView.getUint32(dataOffset, littleEndian) /
|
||||||
|
dataView.getUint32(dataOffset + 4, littleEndian)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
size: 8
|
||||||
|
},
|
||||||
|
// slong, 32 bit signed int:
|
||||||
|
9: {
|
||||||
|
getValue: function(dataView, dataOffset, littleEndian) {
|
||||||
|
return dataView.getInt32(dataOffset, littleEndian)
|
||||||
|
},
|
||||||
|
size: 4
|
||||||
|
},
|
||||||
|
// srational, two slongs, first is numerator, second is denominator:
|
||||||
|
10: {
|
||||||
|
getValue: function(dataView, dataOffset, littleEndian) {
|
||||||
|
return (
|
||||||
|
dataView.getInt32(dataOffset, littleEndian) /
|
||||||
|
dataView.getInt32(dataOffset + 4, littleEndian)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
size: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// undefined, 8-bit byte, value depending on field:
|
||||||
|
loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
|
||||||
|
|
||||||
|
loadImage.getExifValue = function(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
offset,
|
||||||
|
type,
|
||||||
|
length,
|
||||||
|
littleEndian
|
||||||
|
) {
|
||||||
|
var tagType = loadImage.exifTagTypes[type]
|
||||||
|
var tagSize
|
||||||
|
var dataOffset
|
||||||
|
var values
|
||||||
|
var i
|
||||||
|
var str
|
||||||
|
var c
|
||||||
|
if (!tagType) {
|
||||||
|
console.log('Invalid Exif data: Invalid tag type.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tagSize = tagType.size * length
|
||||||
|
// Determine if the value is contained in the dataOffset bytes,
|
||||||
|
// or if the value at the dataOffset is a pointer to the actual data:
|
||||||
|
dataOffset =
|
||||||
|
tagSize > 4
|
||||||
|
? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
|
||||||
|
: offset + 8
|
||||||
|
if (dataOffset + tagSize > dataView.byteLength) {
|
||||||
|
console.log('Invalid Exif data: Invalid data offset.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (length === 1) {
|
||||||
|
return tagType.getValue(dataView, dataOffset, littleEndian)
|
||||||
|
}
|
||||||
|
values = []
|
||||||
|
for (i = 0; i < length; i += 1) {
|
||||||
|
values[i] = tagType.getValue(
|
||||||
|
dataView,
|
||||||
|
dataOffset + i * tagType.size,
|
||||||
|
littleEndian
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (tagType.ascii) {
|
||||||
|
str = ''
|
||||||
|
// Concatenate the chars:
|
||||||
|
for (i = 0; i < values.length; i += 1) {
|
||||||
|
c = values[i]
|
||||||
|
// Ignore the terminating NULL byte(s):
|
||||||
|
if (c === '\u0000') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
str += c
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.parseExifTag = function(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
offset,
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
) {
|
||||||
|
var tag = dataView.getUint16(offset, littleEndian)
|
||||||
|
data.exif[tag] = loadImage.getExifValue(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
offset,
|
||||||
|
dataView.getUint16(offset + 2, littleEndian), // tag type
|
||||||
|
dataView.getUint32(offset + 4, littleEndian), // tag length
|
||||||
|
littleEndian
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.parseExifTags = function(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
dirOffset,
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
) {
|
||||||
|
var tagsNumber, dirEndOffset, i
|
||||||
|
if (dirOffset + 6 > dataView.byteLength) {
|
||||||
|
console.log('Invalid Exif data: Invalid directory offset.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tagsNumber = dataView.getUint16(dirOffset, littleEndian)
|
||||||
|
dirEndOffset = dirOffset + 2 + 12 * tagsNumber
|
||||||
|
if (dirEndOffset + 4 > dataView.byteLength) {
|
||||||
|
console.log('Invalid Exif data: Invalid directory size.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (i = 0; i < tagsNumber; i += 1) {
|
||||||
|
this.parseExifTag(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
dirOffset + 2 + 12 * i, // tag offset
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Return the offset to the next directory:
|
||||||
|
return dataView.getUint32(dirEndOffset, littleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.parseExifData = function(dataView, offset, length, data, options) {
|
||||||
|
if (options.disableExif) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var tiffOffset = offset + 10
|
||||||
|
var littleEndian
|
||||||
|
var dirOffset
|
||||||
|
var thumbnailData
|
||||||
|
// Check for the ASCII code for "Exif" (0x45786966):
|
||||||
|
if (dataView.getUint32(offset + 4) !== 0x45786966) {
|
||||||
|
// No Exif data, might be XMP data instead
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tiffOffset + 8 > dataView.byteLength) {
|
||||||
|
console.log('Invalid Exif data: Invalid segment size.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check for the two null bytes:
|
||||||
|
if (dataView.getUint16(offset + 8) !== 0x0000) {
|
||||||
|
console.log('Invalid Exif data: Missing byte alignment offset.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check the byte alignment:
|
||||||
|
switch (dataView.getUint16(tiffOffset)) {
|
||||||
|
case 0x4949:
|
||||||
|
littleEndian = true
|
||||||
|
break
|
||||||
|
case 0x4d4d:
|
||||||
|
littleEndian = false
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log('Invalid Exif data: Invalid byte alignment marker.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check for the TIFF tag marker (0x002A):
|
||||||
|
if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
|
||||||
|
console.log('Invalid Exif data: Missing TIFF marker.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
|
||||||
|
dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
|
||||||
|
// Create the exif object to store the tags:
|
||||||
|
data.exif = new loadImage.ExifMap()
|
||||||
|
// Parse the tags of the main image directory and retrieve the
|
||||||
|
// offset to the next directory, usually the thumbnail directory:
|
||||||
|
dirOffset = loadImage.parseExifTags(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
tiffOffset + dirOffset,
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
if (dirOffset && !options.disableExifThumbnail) {
|
||||||
|
thumbnailData = { exif: {} }
|
||||||
|
dirOffset = loadImage.parseExifTags(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
tiffOffset + dirOffset,
|
||||||
|
littleEndian,
|
||||||
|
thumbnailData
|
||||||
|
)
|
||||||
|
// Check for JPEG Thumbnail offset:
|
||||||
|
if (thumbnailData.exif[0x0201]) {
|
||||||
|
data.exif.Thumbnail = loadImage.getExifThumbnail(
|
||||||
|
dataView,
|
||||||
|
tiffOffset + thumbnailData.exif[0x0201],
|
||||||
|
thumbnailData.exif[0x0202] // Thumbnail data length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for Exif Sub IFD Pointer:
|
||||||
|
if (data.exif[0x8769] && !options.disableExifSub) {
|
||||||
|
loadImage.parseExifTags(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
tiffOffset + data.exif[0x8769], // directory offset
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Check for GPS Info IFD Pointer:
|
||||||
|
if (data.exif[0x8825] && !options.disableExifGps) {
|
||||||
|
loadImage.parseExifTags(
|
||||||
|
dataView,
|
||||||
|
tiffOffset,
|
||||||
|
tiffOffset + data.exif[0x8825], // directory offset
|
||||||
|
littleEndian,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers the Exif parser for the APP1 JPEG meta data segment:
|
||||||
|
loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
|
||||||
|
|
||||||
|
// Adds the following properties to the parseMetaData callback data:
|
||||||
|
// * exif: The exif tags, parsed by the parseExifData method
|
||||||
|
|
||||||
|
// Adds the following options to the parseMetaData method:
|
||||||
|
// * disableExif: Disables Exif parsing.
|
||||||
|
// * disableExifThumbnail: Disables parsing of the Exif Thumbnail.
|
||||||
|
// * disableExifSub: Disables parsing of the Exif Sub IFD.
|
||||||
|
// * disableExifGps: Disables parsing of the Exif GPS Info IFD.
|
||||||
|
})
|
44
frontend/js/load-image-fetch.js
Normal file
44
frontend/js/load-image-fetch.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Fetch
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2017, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-meta'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'), require('./load-image-meta'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (typeof fetch !== 'undefined' && typeof Request !== 'undefined') {
|
||||||
|
loadImage.fetchBlob = function(url, callback, options) {
|
||||||
|
if (loadImage.hasMetaOption(options)) {
|
||||||
|
return fetch(new Request(url, options))
|
||||||
|
.then(function(response) {
|
||||||
|
return response.blob()
|
||||||
|
})
|
||||||
|
.then(callback)
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err) // eslint-disable-line no-console
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
129
frontend/js/load-image-iptc-map.js
Normal file
129
frontend/js/load-image-iptc-map.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image IPTC Map
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* Copyright 2018, Dave Bevan
|
||||||
|
*
|
||||||
|
* IPTC tags mapping based on
|
||||||
|
* https://github.com/jseidelin/exif-js
|
||||||
|
* https://iptc.org/standards/photo-metadata
|
||||||
|
* http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-iptc'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'), require('./load-image-iptc'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
loadImage.IptcMap.prototype.tags = {
|
||||||
|
// ==========
|
||||||
|
// IPTC tags:
|
||||||
|
// ==========
|
||||||
|
0x03: 'ObjectType',
|
||||||
|
0x04: 'ObjectAttribute',
|
||||||
|
0x05: 'ObjectName',
|
||||||
|
0x07: 'EditStatus',
|
||||||
|
0x08: 'EditorialUpdate',
|
||||||
|
0x0a: 'Urgency',
|
||||||
|
0x0c: 'SubjectRef',
|
||||||
|
0x0f: 'Category',
|
||||||
|
0x14: 'SupplCategory',
|
||||||
|
0x16: 'FixtureID',
|
||||||
|
0x19: 'Keywords',
|
||||||
|
0x1a: 'ContentLocCode',
|
||||||
|
0x1b: 'ContentLocName',
|
||||||
|
0x1e: 'ReleaseDate',
|
||||||
|
0x23: 'ReleaseTime',
|
||||||
|
0x25: 'ExpirationDate',
|
||||||
|
0x26: 'ExpirationTime',
|
||||||
|
0x28: 'SpecialInstructions',
|
||||||
|
0x2a: 'ActionAdvised',
|
||||||
|
0x2d: 'RefService',
|
||||||
|
0x2f: 'RefDate',
|
||||||
|
0x32: 'RefNumber',
|
||||||
|
0x37: 'DateCreated',
|
||||||
|
0x3c: 'TimeCreated',
|
||||||
|
0x3e: 'DigitalCreationDate',
|
||||||
|
0x3f: 'DigitalCreationTime',
|
||||||
|
0x41: 'OriginatingProgram',
|
||||||
|
0x46: 'ProgramVersion',
|
||||||
|
0x4b: 'ObjectCycle',
|
||||||
|
0x50: 'Byline',
|
||||||
|
0x55: 'BylineTitle',
|
||||||
|
0x5a: 'City',
|
||||||
|
0x5c: 'Sublocation',
|
||||||
|
0x5f: 'State',
|
||||||
|
0x64: 'CountryCode',
|
||||||
|
0x65: 'CountryName',
|
||||||
|
0x67: 'OrigTransRef',
|
||||||
|
0x69: 'Headline',
|
||||||
|
0x6e: 'Credit',
|
||||||
|
0x73: 'Source',
|
||||||
|
0x74: 'CopyrightNotice',
|
||||||
|
0x76: 'Contact',
|
||||||
|
0x78: 'Caption',
|
||||||
|
0x7a: 'WriterEditor',
|
||||||
|
0x82: 'ImageType',
|
||||||
|
0x83: 'ImageOrientation',
|
||||||
|
0x87: 'LanguageID'
|
||||||
|
|
||||||
|
// We don't record these tags:
|
||||||
|
//
|
||||||
|
// 0x00: 'RecordVersion',
|
||||||
|
// 0x7d: 'RasterizedCaption',
|
||||||
|
// 0x96: 'AudioType',
|
||||||
|
// 0x97: 'AudioSamplingRate',
|
||||||
|
// 0x98: 'AudioSamplingRes',
|
||||||
|
// 0x99: 'AudioDuration',
|
||||||
|
// 0x9a: 'AudioOutcue',
|
||||||
|
// 0xc8: 'PreviewFileFormat',
|
||||||
|
// 0xc9: 'PreviewFileFormatVer',
|
||||||
|
// 0xca: 'PreviewData'
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.IptcMap.prototype.getText = function(id) {
|
||||||
|
var value = this.get(id)
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
;(function(iptcMapPrototype) {
|
||||||
|
var tags = iptcMapPrototype.tags
|
||||||
|
var map = iptcMapPrototype.map || {}
|
||||||
|
var prop
|
||||||
|
// Map the tag names to tags:
|
||||||
|
for (prop in tags) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(tags, prop)) {
|
||||||
|
map[tags[prop]] = prop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(loadImage.IptcMap.prototype)
|
||||||
|
|
||||||
|
loadImage.IptcMap.prototype.getAll = function() {
|
||||||
|
var map = {}
|
||||||
|
var prop
|
||||||
|
var id
|
||||||
|
for (prop in this) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this, prop)) {
|
||||||
|
id = this.tags[prop]
|
||||||
|
if (id) {
|
||||||
|
map[id] = this.getText(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
})
|
153
frontend/js/load-image-iptc.js
Normal file
153
frontend/js/load-image-iptc.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image IPTC Parser
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* Copyright 2018, Dave Bevan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require, Buffer */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-meta'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'), require('./load-image-meta'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
loadImage.IptcMap = function() {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.IptcMap.prototype.map = {
|
||||||
|
ObjectName: 0x5
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.IptcMap.prototype.get = function(id) {
|
||||||
|
return this[id] || this[this.map[id]]
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.parseIptcTags = function(
|
||||||
|
dataView,
|
||||||
|
startOffset,
|
||||||
|
sectionLength,
|
||||||
|
data
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Retrieves string for the given Buffer and range
|
||||||
|
*
|
||||||
|
* @param {Buffer} buffer IPTC buffer
|
||||||
|
* @param {number} start Range start
|
||||||
|
* @param {number} length Range length
|
||||||
|
* @returns {string} String value
|
||||||
|
*/
|
||||||
|
function getStringFromDB(buffer, start, length) {
|
||||||
|
var outstr = ''
|
||||||
|
for (var n = start; n < start + length; n++) {
|
||||||
|
outstr += String.fromCharCode(buffer.getUint8(n))
|
||||||
|
}
|
||||||
|
return outstr
|
||||||
|
}
|
||||||
|
var fieldValue, dataSize, segmentType
|
||||||
|
var segmentStartPos = startOffset
|
||||||
|
while (segmentStartPos < startOffset + sectionLength) {
|
||||||
|
// we currently handle the 2: class of iptc tag
|
||||||
|
if (
|
||||||
|
dataView.getUint8(segmentStartPos) === 0x1c &&
|
||||||
|
dataView.getUint8(segmentStartPos + 1) === 0x02
|
||||||
|
) {
|
||||||
|
segmentType = dataView.getUint8(segmentStartPos + 2)
|
||||||
|
// only store data for known tags
|
||||||
|
if (segmentType in data.iptc.tags) {
|
||||||
|
dataSize = dataView.getInt16(segmentStartPos + 3)
|
||||||
|
fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize)
|
||||||
|
// Check if we already stored a value with this name
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data.iptc, segmentType)) {
|
||||||
|
// Value already stored with this name, create multivalue field
|
||||||
|
if (data.iptc[segmentType] instanceof Array) {
|
||||||
|
data.iptc[segmentType].push(fieldValue)
|
||||||
|
} else {
|
||||||
|
data.iptc[segmentType] = [data.iptc[segmentType], fieldValue]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.iptc[segmentType] = fieldValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segmentStartPos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.parseIptcData = function(dataView, offset, length, data, options) {
|
||||||
|
if (options.disableIptc) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var markerLength = offset + length
|
||||||
|
// Found '8BIM<EOT><EOT>' ?
|
||||||
|
var isFieldSegmentStart = function(dataView, offset) {
|
||||||
|
return (
|
||||||
|
dataView.getUint32(offset) === 0x3842494d &&
|
||||||
|
dataView.getUint16(offset + 4) === 0x0404
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Hunt forward, looking for the correct IPTC block signature:
|
||||||
|
// Reference: https://metacpan.org/pod/distribution/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/Structures.pod#Structure-of-a-Photoshop-style-APP13-segment
|
||||||
|
// From https://github.com/exif-js/exif-js/blob/master/exif.js ~ line 474 on
|
||||||
|
while (offset + 8 < markerLength) {
|
||||||
|
if (isFieldSegmentStart(dataView, offset)) {
|
||||||
|
var nameHeaderLength = dataView.getUint8(offset + 7)
|
||||||
|
if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1
|
||||||
|
// Check for pre photoshop 6 format
|
||||||
|
if (nameHeaderLength === 0) {
|
||||||
|
// Always 4
|
||||||
|
nameHeaderLength = 4
|
||||||
|
}
|
||||||
|
var startOffset = offset + 8 + nameHeaderLength
|
||||||
|
if (startOffset > markerLength) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Invalid IPTC data: Invalid segment offset.')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength)
|
||||||
|
if (offset + sectionLength > markerLength) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Invalid IPTC data: Invalid segment size.')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Create the iptc object to store the tags:
|
||||||
|
data.iptc = new loadImage.IptcMap()
|
||||||
|
// Parse the tags
|
||||||
|
return loadImage.parseIptcTags(
|
||||||
|
dataView,
|
||||||
|
startOffset,
|
||||||
|
sectionLength,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('No IPTC data at this offset - could be XMP')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers this IPTC parser for the APP13 JPEG meta data segment:
|
||||||
|
loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
|
||||||
|
|
||||||
|
// Adds the following properties to the parseMetaData callback data:
|
||||||
|
// * iptc: The iptc tags, parsed by the parseIptcData method
|
||||||
|
|
||||||
|
// Adds the following options to the parseMetaData method:
|
||||||
|
// * disableIptc: Disables IPTC parsing.
|
||||||
|
})
|
185
frontend/js/load-image-meta.js
Normal file
185
frontend/js/load-image-meta.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Meta
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Image meta data handling implementation
|
||||||
|
* based on the help and contribution of
|
||||||
|
* Achim Stöhr.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require, DataView, Uint8Array */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var hasblobSlice =
|
||||||
|
typeof Blob !== 'undefined' &&
|
||||||
|
(Blob.prototype.slice ||
|
||||||
|
Blob.prototype.webkitSlice ||
|
||||||
|
Blob.prototype.mozSlice)
|
||||||
|
|
||||||
|
loadImage.blobSlice =
|
||||||
|
hasblobSlice &&
|
||||||
|
function() {
|
||||||
|
var slice = this.slice || this.webkitSlice || this.mozSlice
|
||||||
|
return slice.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.metaDataParsers = {
|
||||||
|
jpeg: {
|
||||||
|
0xffe1: [], // APP1 marker
|
||||||
|
0xffed: [] // APP13 marker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses image meta data and calls the callback with an object argument
|
||||||
|
// with the following properties:
|
||||||
|
// * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
|
||||||
|
// The options argument accepts an object and supports the following
|
||||||
|
// properties:
|
||||||
|
// * maxMetaDataSize: Defines the maximum number of bytes to parse.
|
||||||
|
// * disableImageHead: Disables creating the imageHead property.
|
||||||
|
loadImage.parseMetaData = function(file, callback, options, data) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
options = options || {}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
data = data || {}
|
||||||
|
var that = this
|
||||||
|
// 256 KiB should contain all EXIF/ICC/IPTC segments:
|
||||||
|
var maxMetaDataSize = options.maxMetaDataSize || 262144
|
||||||
|
var noMetaData = !(
|
||||||
|
typeof DataView !== 'undefined' &&
|
||||||
|
file &&
|
||||||
|
file.size >= 12 &&
|
||||||
|
file.type === 'image/jpeg' &&
|
||||||
|
loadImage.blobSlice
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
noMetaData ||
|
||||||
|
!loadImage.readFile(
|
||||||
|
loadImage.blobSlice.call(file, 0, maxMetaDataSize),
|
||||||
|
function(e) {
|
||||||
|
if (e.target.error) {
|
||||||
|
// FileReader error
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(e.target.error)
|
||||||
|
callback(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Note on endianness:
|
||||||
|
// Since the marker and length bytes in JPEG files are always
|
||||||
|
// stored in big endian order, we can leave the endian parameter
|
||||||
|
// of the DataView methods undefined, defaulting to big endian.
|
||||||
|
var buffer = e.target.result
|
||||||
|
var dataView = new DataView(buffer)
|
||||||
|
var offset = 2
|
||||||
|
var maxOffset = dataView.byteLength - 4
|
||||||
|
var headLength = offset
|
||||||
|
var markerBytes
|
||||||
|
var markerLength
|
||||||
|
var parsers
|
||||||
|
var i
|
||||||
|
// Check for the JPEG marker (0xffd8):
|
||||||
|
if (dataView.getUint16(0) === 0xffd8) {
|
||||||
|
while (offset < maxOffset) {
|
||||||
|
markerBytes = dataView.getUint16(offset)
|
||||||
|
// Search for APPn (0xffeN) and COM (0xfffe) markers,
|
||||||
|
// which contain application-specific meta-data like
|
||||||
|
// Exif, ICC and IPTC data and text comments:
|
||||||
|
if (
|
||||||
|
(markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
|
||||||
|
markerBytes === 0xfffe
|
||||||
|
) {
|
||||||
|
// The marker bytes (2) are always followed by
|
||||||
|
// the length bytes (2), indicating the length of the
|
||||||
|
// marker segment, which includes the length bytes,
|
||||||
|
// but not the marker bytes, so we add 2:
|
||||||
|
markerLength = dataView.getUint16(offset + 2) + 2
|
||||||
|
if (offset + markerLength > dataView.byteLength) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Invalid meta data: Invalid segment size.')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
parsers = loadImage.metaDataParsers.jpeg[markerBytes]
|
||||||
|
if (parsers) {
|
||||||
|
for (i = 0; i < parsers.length; i += 1) {
|
||||||
|
parsers[i].call(
|
||||||
|
that,
|
||||||
|
dataView,
|
||||||
|
offset,
|
||||||
|
markerLength,
|
||||||
|
data,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += markerLength
|
||||||
|
headLength = offset
|
||||||
|
} else {
|
||||||
|
// Not an APPn or COM marker, probably safe to
|
||||||
|
// assume that this is the end of the meta data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Meta length must be longer than JPEG marker (2)
|
||||||
|
// plus APPn marker (2), followed by length bytes (2):
|
||||||
|
if (!options.disableImageHead && headLength > 6) {
|
||||||
|
if (buffer.slice) {
|
||||||
|
data.imageHead = buffer.slice(0, headLength)
|
||||||
|
} else {
|
||||||
|
// Workaround for IE10, which does not yet
|
||||||
|
// support ArrayBuffer.slice:
|
||||||
|
data.imageHead = new Uint8Array(buffer).subarray(0, headLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Invalid JPEG file: Missing JPEG marker.')
|
||||||
|
}
|
||||||
|
callback(data)
|
||||||
|
},
|
||||||
|
'readAsArrayBuffer'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
callback(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if meta data should be loaded automatically:
|
||||||
|
loadImage.hasMetaOption = function(options) {
|
||||||
|
return options && options.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalTransform = loadImage.transform
|
||||||
|
loadImage.transform = function(img, options, callback, file, data) {
|
||||||
|
if (loadImage.hasMetaOption(options)) {
|
||||||
|
loadImage.parseMetaData(
|
||||||
|
file,
|
||||||
|
function(data) {
|
||||||
|
originalTransform.call(loadImage, img, options, callback, file, data)
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
originalTransform.apply(loadImage, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
188
frontend/js/load-image-orientation.js
Normal file
188
frontend/js/load-image-orientation.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Orientation
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2013, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image', './load-image-scale', './load-image-meta'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(
|
||||||
|
require('./load-image'),
|
||||||
|
require('./load-image-scale'),
|
||||||
|
require('./load-image-meta')
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var originalHasCanvasOption = loadImage.hasCanvasOption
|
||||||
|
var originalHasMetaOption = loadImage.hasMetaOption
|
||||||
|
var originalTransformCoordinates = loadImage.transformCoordinates
|
||||||
|
var originalGetTransformedOptions = loadImage.getTransformedOptions
|
||||||
|
|
||||||
|
// Determines if the target image should be a canvas element:
|
||||||
|
loadImage.hasCanvasOption = function(options) {
|
||||||
|
return (
|
||||||
|
!!options.orientation || originalHasCanvasOption.call(loadImage, options)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if meta data should be loaded automatically:
|
||||||
|
loadImage.hasMetaOption = function(options) {
|
||||||
|
return (
|
||||||
|
(options && options.orientation === true) ||
|
||||||
|
originalHasMetaOption.call(loadImage, options)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform image orientation based on
|
||||||
|
// the given EXIF orientation option:
|
||||||
|
loadImage.transformCoordinates = function(canvas, options) {
|
||||||
|
originalTransformCoordinates.call(loadImage, canvas, options)
|
||||||
|
var ctx = canvas.getContext('2d')
|
||||||
|
var width = canvas.width
|
||||||
|
var height = canvas.height
|
||||||
|
var styleWidth = canvas.style.width
|
||||||
|
var styleHeight = canvas.style.height
|
||||||
|
var orientation = options.orientation
|
||||||
|
if (!orientation || orientation > 8) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (orientation > 4) {
|
||||||
|
canvas.width = height
|
||||||
|
canvas.height = width
|
||||||
|
canvas.style.width = styleHeight
|
||||||
|
canvas.style.height = styleWidth
|
||||||
|
}
|
||||||
|
switch (orientation) {
|
||||||
|
case 2:
|
||||||
|
// horizontal flip
|
||||||
|
ctx.translate(width, 0)
|
||||||
|
ctx.scale(-1, 1)
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
// 180° rotate left
|
||||||
|
ctx.translate(width, height)
|
||||||
|
ctx.rotate(Math.PI)
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
// vertical flip
|
||||||
|
ctx.translate(0, height)
|
||||||
|
ctx.scale(1, -1)
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
// vertical flip + 90 rotate right
|
||||||
|
ctx.rotate(0.5 * Math.PI)
|
||||||
|
ctx.scale(1, -1)
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
// 90° rotate right
|
||||||
|
ctx.rotate(0.5 * Math.PI)
|
||||||
|
ctx.translate(0, -height)
|
||||||
|
break
|
||||||
|
case 7:
|
||||||
|
// horizontal flip + 90 rotate right
|
||||||
|
ctx.rotate(0.5 * Math.PI)
|
||||||
|
ctx.translate(width, -height)
|
||||||
|
ctx.scale(-1, 1)
|
||||||
|
break
|
||||||
|
case 8:
|
||||||
|
// 90° rotate left
|
||||||
|
ctx.rotate(-0.5 * Math.PI)
|
||||||
|
ctx.translate(-width, 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transforms coordinate and dimension options
|
||||||
|
// based on the given orientation option:
|
||||||
|
loadImage.getTransformedOptions = function(img, opts, data) {
|
||||||
|
var options = originalGetTransformedOptions.call(loadImage, img, opts)
|
||||||
|
var orientation = options.orientation
|
||||||
|
var newOptions
|
||||||
|
var i
|
||||||
|
if (orientation === true && data && data.exif) {
|
||||||
|
orientation = data.exif.get('Orientation')
|
||||||
|
}
|
||||||
|
if (!orientation || orientation > 8 || orientation === 1) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
newOptions = {}
|
||||||
|
for (i in options) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(options, i)) {
|
||||||
|
newOptions[i] = options[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOptions.orientation = orientation
|
||||||
|
switch (orientation) {
|
||||||
|
case 2:
|
||||||
|
// horizontal flip
|
||||||
|
newOptions.left = options.right
|
||||||
|
newOptions.right = options.left
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
// 180° rotate left
|
||||||
|
newOptions.left = options.right
|
||||||
|
newOptions.top = options.bottom
|
||||||
|
newOptions.right = options.left
|
||||||
|
newOptions.bottom = options.top
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
// vertical flip
|
||||||
|
newOptions.top = options.bottom
|
||||||
|
newOptions.bottom = options.top
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
// vertical flip + 90 rotate right
|
||||||
|
newOptions.left = options.top
|
||||||
|
newOptions.top = options.left
|
||||||
|
newOptions.right = options.bottom
|
||||||
|
newOptions.bottom = options.right
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
// 90° rotate right
|
||||||
|
newOptions.left = options.top
|
||||||
|
newOptions.top = options.right
|
||||||
|
newOptions.right = options.bottom
|
||||||
|
newOptions.bottom = options.left
|
||||||
|
break
|
||||||
|
case 7:
|
||||||
|
// horizontal flip + 90 rotate right
|
||||||
|
newOptions.left = options.bottom
|
||||||
|
newOptions.top = options.right
|
||||||
|
newOptions.right = options.top
|
||||||
|
newOptions.bottom = options.left
|
||||||
|
break
|
||||||
|
case 8:
|
||||||
|
// 90° rotate left
|
||||||
|
newOptions.left = options.bottom
|
||||||
|
newOptions.top = options.left
|
||||||
|
newOptions.right = options.top
|
||||||
|
newOptions.bottom = options.right
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (newOptions.orientation > 4) {
|
||||||
|
newOptions.maxWidth = options.maxHeight
|
||||||
|
newOptions.maxHeight = options.maxWidth
|
||||||
|
newOptions.minWidth = options.minHeight
|
||||||
|
newOptions.minHeight = options.minWidth
|
||||||
|
newOptions.sourceWidth = options.sourceHeight
|
||||||
|
newOptions.sourceHeight = options.sourceWidth
|
||||||
|
}
|
||||||
|
return newOptions
|
||||||
|
}
|
||||||
|
})
|
293
frontend/js/load-image-scale.js
Normal file
293
frontend/js/load-image-scale.js
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image Scaling
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2011, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, module, require */
|
||||||
|
|
||||||
|
;(function(factory) {
|
||||||
|
'use strict'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// Register as an anonymous AMD module:
|
||||||
|
define(['./load-image'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
factory(require('./load-image'))
|
||||||
|
} else {
|
||||||
|
// Browser globals:
|
||||||
|
factory(window.loadImage)
|
||||||
|
}
|
||||||
|
})(function(loadImage) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var originalTransform = loadImage.transform
|
||||||
|
|
||||||
|
loadImage.transform = function(img, options, callback, file, data) {
|
||||||
|
originalTransform.call(
|
||||||
|
loadImage,
|
||||||
|
loadImage.scale(img, options, data),
|
||||||
|
options,
|
||||||
|
callback,
|
||||||
|
file,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform image coordinates, allows to override e.g.
|
||||||
|
// the canvas orientation based on the orientation option,
|
||||||
|
// gets canvas, options passed as arguments:
|
||||||
|
loadImage.transformCoordinates = function() {}
|
||||||
|
|
||||||
|
// Returns transformed options, allows to override e.g.
|
||||||
|
// maxWidth, maxHeight and crop options based on the aspectRatio.
|
||||||
|
// gets img, options passed as arguments:
|
||||||
|
loadImage.getTransformedOptions = function(img, options) {
|
||||||
|
var aspectRatio = options.aspectRatio
|
||||||
|
var newOptions
|
||||||
|
var i
|
||||||
|
var width
|
||||||
|
var height
|
||||||
|
if (!aspectRatio) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
newOptions = {}
|
||||||
|
for (i in options) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(options, i)) {
|
||||||
|
newOptions[i] = options[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOptions.crop = true
|
||||||
|
width = img.naturalWidth || img.width
|
||||||
|
height = img.naturalHeight || img.height
|
||||||
|
if (width / height > aspectRatio) {
|
||||||
|
newOptions.maxWidth = height * aspectRatio
|
||||||
|
newOptions.maxHeight = height
|
||||||
|
} else {
|
||||||
|
newOptions.maxWidth = width
|
||||||
|
newOptions.maxHeight = width / aspectRatio
|
||||||
|
}
|
||||||
|
return newOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas render method, allows to implement a different rendering algorithm:
|
||||||
|
loadImage.renderImageToCanvas = function(
|
||||||
|
canvas,
|
||||||
|
img,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
destX,
|
||||||
|
destY,
|
||||||
|
destWidth,
|
||||||
|
destHeight
|
||||||
|
) {
|
||||||
|
canvas
|
||||||
|
.getContext('2d')
|
||||||
|
.drawImage(
|
||||||
|
img,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
destX,
|
||||||
|
destY,
|
||||||
|
destWidth,
|
||||||
|
destHeight
|
||||||
|
)
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the target image should be a canvas element:
|
||||||
|
loadImage.hasCanvasOption = function(options) {
|
||||||
|
return options.canvas || options.crop || !!options.aspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scales and/or crops the given image (img or canvas HTML element)
|
||||||
|
// using the given options.
|
||||||
|
// Returns a canvas object if the browser supports canvas
|
||||||
|
// and the hasCanvasOption method returns true or a canvas
|
||||||
|
// object is passed as image, else the scaled image:
|
||||||
|
loadImage.scale = function(img, options, data) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
options = options || {}
|
||||||
|
var canvas = document.createElement('canvas')
|
||||||
|
var useCanvas =
|
||||||
|
img.getContext ||
|
||||||
|
(loadImage.hasCanvasOption(options) && canvas.getContext)
|
||||||
|
var width = img.naturalWidth || img.width
|
||||||
|
var height = img.naturalHeight || img.height
|
||||||
|
var destWidth = width
|
||||||
|
var destHeight = height
|
||||||
|
var maxWidth
|
||||||
|
var maxHeight
|
||||||
|
var minWidth
|
||||||
|
var minHeight
|
||||||
|
var sourceWidth
|
||||||
|
var sourceHeight
|
||||||
|
var sourceX
|
||||||
|
var sourceY
|
||||||
|
var pixelRatio
|
||||||
|
var downsamplingRatio
|
||||||
|
var tmp
|
||||||
|
/**
|
||||||
|
* Scales up image dimensions
|
||||||
|
*/
|
||||||
|
function scaleUp() {
|
||||||
|
var scale = Math.max(
|
||||||
|
(minWidth || destWidth) / destWidth,
|
||||||
|
(minHeight || destHeight) / destHeight
|
||||||
|
)
|
||||||
|
if (scale > 1) {
|
||||||
|
destWidth *= scale
|
||||||
|
destHeight *= scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Scales down image dimensions
|
||||||
|
*/
|
||||||
|
function scaleDown() {
|
||||||
|
var scale = Math.min(
|
||||||
|
(maxWidth || destWidth) / destWidth,
|
||||||
|
(maxHeight || destHeight) / destHeight
|
||||||
|
)
|
||||||
|
if (scale < 1) {
|
||||||
|
destWidth *= scale
|
||||||
|
destHeight *= scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useCanvas) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
options = loadImage.getTransformedOptions(img, options, data)
|
||||||
|
sourceX = options.left || 0
|
||||||
|
sourceY = options.top || 0
|
||||||
|
if (options.sourceWidth) {
|
||||||
|
sourceWidth = options.sourceWidth
|
||||||
|
if (options.right !== undefined && options.left === undefined) {
|
||||||
|
sourceX = width - sourceWidth - options.right
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceWidth = width - sourceX - (options.right || 0)
|
||||||
|
}
|
||||||
|
if (options.sourceHeight) {
|
||||||
|
sourceHeight = options.sourceHeight
|
||||||
|
if (options.bottom !== undefined && options.top === undefined) {
|
||||||
|
sourceY = height - sourceHeight - options.bottom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceHeight = height - sourceY - (options.bottom || 0)
|
||||||
|
}
|
||||||
|
destWidth = sourceWidth
|
||||||
|
destHeight = sourceHeight
|
||||||
|
}
|
||||||
|
maxWidth = options.maxWidth
|
||||||
|
maxHeight = options.maxHeight
|
||||||
|
minWidth = options.minWidth
|
||||||
|
minHeight = options.minHeight
|
||||||
|
if (useCanvas && maxWidth && maxHeight && options.crop) {
|
||||||
|
destWidth = maxWidth
|
||||||
|
destHeight = maxHeight
|
||||||
|
tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
|
||||||
|
if (tmp < 0) {
|
||||||
|
sourceHeight = (maxHeight * sourceWidth) / maxWidth
|
||||||
|
if (options.top === undefined && options.bottom === undefined) {
|
||||||
|
sourceY = (height - sourceHeight) / 2
|
||||||
|
}
|
||||||
|
} else if (tmp > 0) {
|
||||||
|
sourceWidth = (maxWidth * sourceHeight) / maxHeight
|
||||||
|
if (options.left === undefined && options.right === undefined) {
|
||||||
|
sourceX = (width - sourceWidth) / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (options.contain || options.cover) {
|
||||||
|
minWidth = maxWidth = maxWidth || minWidth
|
||||||
|
minHeight = maxHeight = maxHeight || minHeight
|
||||||
|
}
|
||||||
|
if (options.cover) {
|
||||||
|
scaleDown()
|
||||||
|
scaleUp()
|
||||||
|
} else {
|
||||||
|
scaleUp()
|
||||||
|
scaleDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useCanvas) {
|
||||||
|
pixelRatio = options.pixelRatio
|
||||||
|
if (pixelRatio > 1) {
|
||||||
|
canvas.style.width = destWidth + 'px'
|
||||||
|
canvas.style.height = destHeight + 'px'
|
||||||
|
destWidth *= pixelRatio
|
||||||
|
destHeight *= pixelRatio
|
||||||
|
canvas.getContext('2d').scale(pixelRatio, pixelRatio)
|
||||||
|
}
|
||||||
|
downsamplingRatio = options.downsamplingRatio
|
||||||
|
if (
|
||||||
|
downsamplingRatio > 0 &&
|
||||||
|
downsamplingRatio < 1 &&
|
||||||
|
destWidth < sourceWidth &&
|
||||||
|
destHeight < sourceHeight
|
||||||
|
) {
|
||||||
|
while (sourceWidth * downsamplingRatio > destWidth) {
|
||||||
|
canvas.width = sourceWidth * downsamplingRatio
|
||||||
|
canvas.height = sourceHeight * downsamplingRatio
|
||||||
|
loadImage.renderImageToCanvas(
|
||||||
|
canvas,
|
||||||
|
img,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height
|
||||||
|
)
|
||||||
|
sourceX = 0
|
||||||
|
sourceY = 0
|
||||||
|
sourceWidth = canvas.width
|
||||||
|
sourceHeight = canvas.height
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
img = document.createElement('canvas')
|
||||||
|
img.width = sourceWidth
|
||||||
|
img.height = sourceHeight
|
||||||
|
loadImage.renderImageToCanvas(
|
||||||
|
img,
|
||||||
|
canvas,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.width = destWidth
|
||||||
|
canvas.height = destHeight
|
||||||
|
loadImage.transformCoordinates(canvas, options)
|
||||||
|
return loadImage.renderImageToCanvas(
|
||||||
|
canvas,
|
||||||
|
img,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
destWidth,
|
||||||
|
destHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
img.width = destWidth
|
||||||
|
img.height = destHeight
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
})
|
2
frontend/js/load-image.all.min.js
vendored
Normal file
2
frontend/js/load-image.all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/js/load-image.all.min.js.map
Normal file
1
frontend/js/load-image.all.min.js.map
Normal file
File diff suppressed because one or more lines are too long
163
frontend/js/load-image.js
Normal file
163
frontend/js/load-image.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript Load Image
|
||||||
|
* https://github.com/blueimp/JavaScript-Load-Image
|
||||||
|
*
|
||||||
|
* Copyright 2011, Sebastian Tschan
|
||||||
|
* https://blueimp.net
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license:
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global define, webkitURL, module */
|
||||||
|
|
||||||
|
;(function($) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image for a given File object.
|
||||||
|
* Invokes the callback with an img or optional canvas element
|
||||||
|
* (if supported by the browser) as parameter:.
|
||||||
|
*
|
||||||
|
* @param {File|Blob|string} file File or Blob object or image URL
|
||||||
|
* @param {Function} [callback] Image load event callback
|
||||||
|
* @param {object} [options] Options object
|
||||||
|
* @returns {HTMLImageElement|HTMLCanvasElement|FileReader} image object
|
||||||
|
*/
|
||||||
|
function loadImage(file, callback, options) {
|
||||||
|
var img = document.createElement('img')
|
||||||
|
var url
|
||||||
|
img.onerror = function(event) {
|
||||||
|
return loadImage.onerror(img, event, file, callback, options)
|
||||||
|
}
|
||||||
|
img.onload = function(event) {
|
||||||
|
return loadImage.onload(img, event, file, callback, options)
|
||||||
|
}
|
||||||
|
if (typeof file === 'string') {
|
||||||
|
loadImage.fetchBlob(
|
||||||
|
file,
|
||||||
|
function(blob) {
|
||||||
|
if (blob) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
file = blob
|
||||||
|
url = loadImage.createObjectURL(file)
|
||||||
|
} else {
|
||||||
|
url = file
|
||||||
|
if (options && options.crossOrigin) {
|
||||||
|
img.crossOrigin = options.crossOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.src = url
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
return img
|
||||||
|
} else if (
|
||||||
|
loadImage.isInstanceOf('Blob', file) ||
|
||||||
|
// Files are also Blob instances, but some browsers
|
||||||
|
// (Firefox 3.6) support the File API but not Blobs:
|
||||||
|
loadImage.isInstanceOf('File', file)
|
||||||
|
) {
|
||||||
|
url = img._objectURL = loadImage.createObjectURL(file)
|
||||||
|
if (url) {
|
||||||
|
img.src = url
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
return loadImage.readFile(file, function(e) {
|
||||||
|
var target = e.target
|
||||||
|
if (target && target.result) {
|
||||||
|
img.src = target.result
|
||||||
|
} else if (callback) {
|
||||||
|
callback(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The check for URL.revokeObjectURL fixes an issue with Opera 12,
|
||||||
|
// which provides URL.createObjectURL but doesn't properly implement it:
|
||||||
|
var urlAPI =
|
||||||
|
($.createObjectURL && $) ||
|
||||||
|
($.URL && URL.revokeObjectURL && URL) ||
|
||||||
|
($.webkitURL && webkitURL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to revoke an object URL
|
||||||
|
*
|
||||||
|
* @param {HTMLImageElement} img Image element
|
||||||
|
* @param {object} [options] Options object
|
||||||
|
*/
|
||||||
|
function revokeHelper(img, options) {
|
||||||
|
if (img._objectURL && !(options && options.noRevoke)) {
|
||||||
|
loadImage.revokeObjectURL(img._objectURL)
|
||||||
|
delete img._objectURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the callback given to this function returns a blob, it is used as image
|
||||||
|
// source instead of the original url and overrides the file argument used in
|
||||||
|
// the onload and onerror event callbacks:
|
||||||
|
loadImage.fetchBlob = function(url, callback) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.isInstanceOf = function(type, obj) {
|
||||||
|
// Cross-frame instanceof check
|
||||||
|
return Object.prototype.toString.call(obj) === '[object ' + type + ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.transform = function(img, options, callback, file, data) {
|
||||||
|
callback(img, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.onerror = function(img, event, file, callback, options) {
|
||||||
|
revokeHelper(img, options)
|
||||||
|
if (callback) {
|
||||||
|
callback.call(img, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.onload = function(img, event, file, callback, options) {
|
||||||
|
revokeHelper(img, options)
|
||||||
|
if (callback) {
|
||||||
|
loadImage.transform(img, options, callback, file, {
|
||||||
|
originalWidth: img.naturalWidth || img.width,
|
||||||
|
originalHeight: img.naturalHeight || img.height
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.createObjectURL = function(file) {
|
||||||
|
return urlAPI ? urlAPI.createObjectURL(file) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage.revokeObjectURL = function(url) {
|
||||||
|
return urlAPI ? urlAPI.revokeObjectURL(url) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads a given File object via FileReader interface,
|
||||||
|
// invokes the callback with the event object (load or error).
|
||||||
|
// The result can be read via event.target.result:
|
||||||
|
loadImage.readFile = function(file, callback, method) {
|
||||||
|
if ($.FileReader) {
|
||||||
|
var fileReader = new FileReader()
|
||||||
|
fileReader.onload = fileReader.onerror = callback
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
method = method || 'readAsDataURL'
|
||||||
|
if (fileReader[method]) {
|
||||||
|
fileReader[method](file)
|
||||||
|
return fileReader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(function() {
|
||||||
|
return loadImage
|
||||||
|
})
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = loadImage
|
||||||
|
} else {
|
||||||
|
$.loadImage = loadImage
|
||||||
|
}
|
||||||
|
})((typeof window !== 'undefined' && window) || this)
|
173
frontend/mvimg.html
Normal file
173
frontend/mvimg.html
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<html>
|
||||||
|
<script>'<base href="BASEPATH">';</script>
|
||||||
|
<style>
|
||||||
|
.static {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1em;
|
||||||
|
position: relative;
|
||||||
|
width: 320px;
|
||||||
|
height: 240px;
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.static canvas {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.video {
|
||||||
|
border: none;
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.video:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.video.loading:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script src="js/load-image.js"></script>
|
||||||
|
<script src="js/load-image-scale.js"></script>
|
||||||
|
<script src="js/load-image-meta.js"></script>
|
||||||
|
<script src="js/load-image-orientation.js"></script>
|
||||||
|
<script src="js/load-image-exif.js"></script>
|
||||||
|
<script src="js/load-image-exif-map.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function createMvElement(url) {
|
||||||
|
var static = document.createElement("div");
|
||||||
|
static.classList.add("static");
|
||||||
|
var video = document.createElement("video");
|
||||||
|
|
||||||
|
video.classList.add("video");
|
||||||
|
video.classList.add("loading");
|
||||||
|
video.setAttribute("controls", true);
|
||||||
|
video.setAttribute("loop", true);
|
||||||
|
video.setAttribute("autoplay", true);
|
||||||
|
|
||||||
|
var xmlhttp = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xmlhttp.open("GET", url, true);
|
||||||
|
xmlhttp.responseType = 'arraybuffer';
|
||||||
|
xmlhttp.onprogress = function (event) {
|
||||||
|
var alpha = 0;
|
||||||
|
if (event.total) {
|
||||||
|
alpha = event.loaded / event.total;
|
||||||
|
// console.log("Loaded: " + Math.ceil(100 * alpha) + "%");
|
||||||
|
} else {
|
||||||
|
// console.log("Loaded: N/A");
|
||||||
|
alhpa = 0;
|
||||||
|
}
|
||||||
|
static.style.opacity = alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
xmlhttp.onload = function(event) {
|
||||||
|
static.style.opacity = 1;
|
||||||
|
var uInt8Array = new Uint8Array(this.response),
|
||||||
|
i, codes = new Uint8Array(8);
|
||||||
|
|
||||||
|
codes.forEach(function(char, index) {
|
||||||
|
codes[index] = "ftypmp42".charCodeAt(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* MP4 starts 4 bytes prior to the ftypmp42 header */
|
||||||
|
for (i = 4; i < uInt8Array.length - 8; i++) {
|
||||||
|
if (
|
||||||
|
(uInt8Array[i + 0] == codes[0]) &&
|
||||||
|
(uInt8Array[i + 1] == codes[1]) &&
|
||||||
|
(uInt8Array[i + 2] == codes[2]) &&
|
||||||
|
(uInt8Array[i + 3] == codes[3]) &&
|
||||||
|
(uInt8Array[i + 4] == codes[4]) &&
|
||||||
|
(uInt8Array[i + 5] == codes[5]) &&
|
||||||
|
(uInt8Array[i + 6] == codes[6]) &&
|
||||||
|
(uInt8Array[i + 7] == codes[7])
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == uInt8Array.length - 8) {
|
||||||
|
console.log("No MP4 found.");
|
||||||
|
i = this.response.byteLength;
|
||||||
|
} else {
|
||||||
|
console.log("Found MP4 at: " + i);
|
||||||
|
i -= 4; /* Go back to the start of the MP4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://github.com/blueimp/JavaScript-Load-Image */
|
||||||
|
var imageBlob = new Blob([this.response.slice(0, i)], { type: "image/jpeg" }),
|
||||||
|
options = {
|
||||||
|
maxWidth: static.offsetWidth,
|
||||||
|
maxHeight: static.offsetHeight,
|
||||||
|
canvas: true,
|
||||||
|
pixelRatio: window.devicePixelRatio,
|
||||||
|
downsamplingRatio: 0.5,
|
||||||
|
meta: true,
|
||||||
|
contain: true,
|
||||||
|
orientation: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Load the image and center it in our element */
|
||||||
|
loadImage(imageBlob, function (image, data) {
|
||||||
|
/*
|
||||||
|
if (data.exif) {
|
||||||
|
console.log("Orientation: ", data.exif.get('Orientation'));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
image.style.left = Math.floor((static.offsetWidth - image.width) * 0.5) + "px";
|
||||||
|
static.appendChild(image);
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
/* If an MP4 wasn't found, we're done */
|
||||||
|
if (i == this.response.byteLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static.appendChild(video);
|
||||||
|
/* Set the src= *after* the element has been added to the DOM */
|
||||||
|
setTimeout(function(buffer) {
|
||||||
|
/* This can take some time to chunk through... can we spin it into
|
||||||
|
* a WebWorker or do something to prevent it from janking the UI
|
||||||
|
* thread? Maybe split it into chunks and do it 100k at a time or
|
||||||
|
* something? Or queue them up so only one is being processed? */
|
||||||
|
var base64 = window.btoa(new Uint8Array(buffer).reduce(function (data, byte) {
|
||||||
|
return data + String.fromCharCode(byte);
|
||||||
|
}, ''));
|
||||||
|
video.src = 'data:video/mp4;base64,' + base64;
|
||||||
|
video.classList.remove("loading");
|
||||||
|
}.bind(this, this.response.slice(i)), 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
xmlhttp.send();
|
||||||
|
|
||||||
|
return static;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
window.fetch("api/v1/photos/mvimg/")
|
||||||
|
.then(res => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
data.items.forEach((movie) => {
|
||||||
|
document.body.appendChild(createMvElement(movie.filename));
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
@ -743,6 +743,79 @@ router.get("/trash", function(req, res/*, next*/) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/mvimg/*", function(req, res/*, next*/) {
|
||||||
|
let limit = parseInt(req.query.limit) || 50,
|
||||||
|
id, cursor, index;
|
||||||
|
|
||||||
|
if (req.query.next) {
|
||||||
|
let parts = req.query.next.split("_");
|
||||||
|
cursor = parts[0];
|
||||||
|
id = parseInt(parts[1]);
|
||||||
|
} else {
|
||||||
|
cursor = "";
|
||||||
|
id = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == -1) {
|
||||||
|
index = "";
|
||||||
|
} else {
|
||||||
|
index = "AND ((strftime('%Y-%m-%d',taken)=strftime('%Y-%m-%d',:cursor) AND photos.id<:id) OR " +
|
||||||
|
"strftime('%Y-%m-%d',taken)<strftime('%Y-%m-%d',:cursor))";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(req.url);
|
||||||
|
let path = decodeURI(req.url).replace(/\?.*$/, "").replace(/^\//, "").replace(/^mvimg\//, ""),
|
||||||
|
query = "SELECT photos.*,albums.path AS path FROM photos " +
|
||||||
|
"INNER JOIN albums ON (albums.id=photos.albumId AND albums.path LIKE :path) " +
|
||||||
|
"WHERE (photos.name LIKE 'MVIMG%' AND photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL) " + index + " " +
|
||||||
|
"ORDER BY strftime('%Y-%m-%d',taken) DESC,id DESC LIMIT " + (limit + 1),
|
||||||
|
replacements = {
|
||||||
|
id: id,
|
||||||
|
cursor: cursor,
|
||||||
|
path: path + "%"
|
||||||
|
};
|
||||||
|
console.log("Trying path as: " + path);
|
||||||
|
// console.log("Fetching from: " + JSON.stringify(replacements, null, 2) + "\nwith:\n" + query);
|
||||||
|
|
||||||
|
return photoDB.sequelize.query(query, {
|
||||||
|
replacements: replacements,
|
||||||
|
type: photoDB.Sequelize.QueryTypes.SELECT
|
||||||
|
}).then(function(photos) {
|
||||||
|
photos.forEach(function(photo) {
|
||||||
|
for (var key in photo) {
|
||||||
|
if (photo[key] instanceof Date) {
|
||||||
|
photo[key] = moment(photo[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let more = photos.length > limit; /* We queried one extra item to see if there are more than LIMIT available */
|
||||||
|
|
||||||
|
// console.log("Requested " + limit + " and matched " + photos.length);
|
||||||
|
|
||||||
|
let last;
|
||||||
|
if (more) {
|
||||||
|
photos.splice(limit);
|
||||||
|
last = photos[photos.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = {
|
||||||
|
items: photos.sort(function(a, b) {
|
||||||
|
return new Date(b.taken) - new Date(a.taken);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (more) {
|
||||||
|
results.cursor = new Date(last.taken).toISOString().replace(/T.*/, "") + "_" + last.id;
|
||||||
|
results.more = true;
|
||||||
|
}
|
||||||
|
return res.status(200).json(results);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error("Query failed: " + query);
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/*", function(req, res/*, next*/) {
|
router.get("/*", function(req, res/*, next*/) {
|
||||||
let limit = parseInt(req.query.limit) || 50,
|
let limit = parseInt(req.query.limit) || 50,
|
||||||
id, cursor, index;
|
id, cursor, index;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user