getPhotoMetadata() before and after
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Original version using callbacks and the entire setup in a single giant function | |
getPhotoMetadata: (options, callback) => { | |
async.parallel({ | |
exif: cb => { | |
jsonService.getJsonFromUrl(baseRestUrlService.getUrl('flickr') | |
.addQueryParam('method', 'flickr.photos.getExif') | |
.addQueryParam('photo_id', options.photoId), | |
(err, data) => { | |
if (err) { return cb(err, null); } | |
if (data.stat === 'fail') { return cb(data.message, null); } | |
return cb(null, data); | |
}); | |
}, | |
details: cb => { | |
jsonService.getJsonFromUrl(baseRestUrlService.getUrl('flickr') | |
.addQueryParam('method', 'flickr.photos.getInfo') | |
.addQueryParam('photo_id', options.photoId), | |
(err, data) => { | |
if (err) { return cb(err, null); } | |
if (data.stat === 'fail') { return cb(data.message, null); } | |
return cb(null, data); | |
}); | |
}, | |
favourites: cb => { | |
jsonService.getJsonFromUrl(baseRestUrlService.getUrl('flickr') | |
.addQueryParam('method', 'flickr.photos.getFavorites') | |
.addQueryParam('photo_id', options.photoId), | |
(err, data) => { | |
if (err) { return cb(err); } | |
if (data.stat === 'fail') { return cb(data.message); } | |
return cb(null, data); | |
}); | |
}, | |
photoset: cb => { | |
if (options.type === 'photosets') { | |
jsonService.getJsonFromUrl(baseRestUrlService.getUrl('flickr') | |
.addQueryParam('method', 'flickr.photosets.getPhotos') | |
.addQueryParam('photoset_id', options.photosetId) | |
.addQueryParam('per_page', '500'), | |
(err, data) => { | |
if (err) { return cb(err, null); } | |
if (data.stat === 'fail') { return cb(data.message, null); } | |
return cb(null, data); | |
}); | |
} else { | |
return cb(); | |
} | |
}, | |
}, | |
(err, results) => { | |
if (err) { return callback(err, null); } | |
return callback(null, { | |
exif: { | |
camera: results.exif.photo.exif.filter( | |
item => { | |
return item.label.match(/^Model$/); | |
}) | |
.reduce((previous, current) => { return current.raw._content; }, null), | |
focalLength: results.exif.photo.exif.filter( | |
item => { | |
return item.label.match(/^Focal Length$/); | |
}) | |
.reduce((previous, current) => { return current.clean ? current.clean._content : null; }, null), | |
aperture: results.exif.photo.exif.filter(item => { | |
return item.label.match(/^Aperture$/); | |
}) | |
.reduce((previous, current) => { return current.clean ? current.clean._content : null; }, null), | |
shutterSpeed: results.exif.photo.exif.filter( | |
item => { | |
return item.label.match(/^Exposure$/); | |
}) | |
.reduce((previous, current) => { | |
// Exposures of 1 second or higher have only the raw number of seconds, there's no "clean" version | |
return current.clean | |
? current.clean._content | |
: `${current.raw._content} sec`; | |
}, null), | |
iso: results.exif.photo.exif.filter( | |
item => { | |
return item.label.match(/^ISO Speed$/); | |
}) | |
.reduce((previous, current) => { return current.raw._content; }, null), | |
lens: results.details.photo.tags.tag.filter( | |
tag => { | |
return tag.raw.match(/(\d+)mm(.*)/g); | |
}) | |
.map(tag => { | |
return tag.raw.replace(/\sUSM/, ''); | |
}) | |
.reduce((previous, current) => { return current; }, null), | |
dateTaken: results.details.photo.dates.taken | |
? timestampFormatService.generateTimestamp({value: results.details.photo.dates.taken, type: 'photos'}) | |
: null, | |
}, | |
title: results.details.photo.title._content | |
? results.details.photo.title._content | |
: 'Untitled', | |
description: results.details.photo.description._content | |
? results.details.photo.description._content.replace(/\n/, '<br />') | |
: null, | |
imageUrl: photosService.generateImageUrl({ | |
farm: results.exif.photo.farm, | |
server: results.exif.photo.server, | |
photo_id: results.exif.photo.id, | |
secret: results.exif.photo.secret, | |
dateupload: results.details.photo.dates.posted, | |
}), | |
lightboxUrl: `https://www.flickr.com/photos/virtualwolf/${results.exif.photo.id}/lightbox`, | |
numComments: results.details.photo.comments._content, | |
numViews: results.details.photo.views, | |
numFavourites: results.favourites.photo.total, | |
photosetInfo: options.type === 'photosets' | |
? (function(photosetData, photoId) { | |
const photoIds = photosetData.photoset.photo.map(photo => { return photo.id; }); | |
const currentImageIndex = photoIds.findIndex(i => i == photoId); | |
return { | |
title: photosetData.photoset.title, | |
photosInSet: { | |
currentPhoto: photoIds[currentImageIndex], | |
nextPhoto: photoIds[currentImageIndex + 1], | |
previousPhoto: photoIds[currentImageIndex - 1], | |
lastPhoto: photoIds[photoIds.length - 1], | |
}, | |
}; | |
})(results.photoset, results.exif.photo.id) | |
: null, | |
}); | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Updated version using Promises, it's almost identical in terms of number of lines of code to above but | |
// the readability is significantly better thanks to being split into several smaller functions | |
getPhotoMetadata: options => { | |
const exif = jsonService.getJsonFromUrl( | |
photosService.buildFlickrApiUrl({ | |
type: 'exifData', | |
photoId: options.photoId, | |
}) | |
); | |
const details = jsonService.getJsonFromUrl( | |
photosService.buildFlickrApiUrl({ | |
type: 'photoInfo', | |
photoId: options.photoId, | |
}) | |
); | |
const favourites = jsonService.getJsonFromUrl( | |
photosService.buildFlickrApiUrl({ | |
type: 'favouritesInfo', | |
photoId: options.photoId, | |
}) | |
); | |
const photoset = options.type === 'photosets' | |
? jsonService.getJsonFromUrl( | |
photosService.buildFlickrApiUrl({ | |
type: 'photosetPhotos', | |
photosetId: options.photosetId, | |
}) | |
) | |
: null; | |
return Promise.all([ | |
exif, details, favourites, photoset, | |
]).then(results => { | |
results.map(result => { | |
if (result && result.stat === 'fail') { throw new Error(result.message); } | |
}); | |
const exif = results[0]; | |
const details = results[1]; | |
const favourites = results[2]; | |
const photoset = results[3]; | |
return { | |
exif: photosService.generateExifData({exif: exif, details: details}), | |
title: details.photo.title._content | |
? details.photo.title._content | |
: 'Untitled', | |
description: details.photo.description._content | |
? details.photo.description._content.replace(/\n/, '<br />') | |
: null, | |
imageUrl: photosService.generateImageUrl({ | |
farm: details.photo.farm, | |
server: details.photo.server, | |
photo_id: details.photo.id, | |
secret: details.photo.secret, | |
dateupload: details.photo.dates.posted, | |
}), | |
lightboxUrl: `https://www.flickr.com/photos/virtualwolf/${details.photo.id}/lightbox`, | |
numComments: details.photo.comments._content, | |
numViews: details.photo.views, | |
numFavourites: favourites.photo.total, | |
photosetInfo: options.type === 'photosets' | |
? photosService.generatePhotosetInfo({photosetDetails: photoset.photoset, photoId: details.photo.id}) | |
: null, | |
}; | |
}).catch(err => { | |
throw err; | |
}); | |
}; | |
generateExifData: data => { | |
const camera = photosService.getRawExifName({exifData: data.exif.photo, exifType: 'Model'}); | |
const focalLength = photosService.getCleanExifName({exifData: data.exif.photo, exifType: 'Focal Length'}); | |
const aperture = photosService.getCleanExifName({exifData: data.exif.photo, exifType: 'Aperture'}); | |
const iso = photosService.getRawExifName({exifData: data.exif.photo, exifType: 'ISO Speed'}); | |
// Exposures of 1 second or higher have only the raw number of seconds, there's no "clean" version, so | |
// the shutter speed might be clean _or_ raw. | |
const shutterSpeed = data.exif.photo.exif.filter(item => { | |
return item.label.match(/^Exposure$/); | |
}).reduce((previous, current) => { | |
return current.clean | |
? current.clean._content | |
: `${current.raw._content} sec`; | |
}, null); | |
const lens = data.details.photo.tags.tag.filter(tag => { | |
return tag.raw.match(/(\d+)mm(.*)/g); | |
}).map(tag => { | |
return tag.raw.replace(/\sUSM/, ''); | |
}).reduce((previous, current) => { return current; }, null); | |
const dateTaken = data.details.photo.dates.taken | |
? timestampFormatService.generateTimestamp({value: data.details.photo.dates.taken, type: 'photos'}) | |
: null; | |
return { | |
camera: camera, | |
focalLength: focalLength, | |
aperture: aperture, | |
shutterSpeed: shutterSpeed, | |
iso: iso, | |
lens: lens, | |
dateTaken: dateTaken, | |
}; | |
}; | |
getCleanExifName: options => { | |
const regex = new RegExp(`^${options.exifType}$`); | |
return options.exifData.exif.filter(item => { | |
return item.label.match(regex); | |
}).filter(item => { | |
return item.clean; | |
}).reduce((previous, current) => { | |
return current.clean._content; | |
}, null); | |
}; | |
getRawExifName: options => { | |
const regex = new RegExp(`^${options.exifType}$`); | |
return options.exifData.exif.filter(item => { | |
return item.label.match(regex); | |
}).filter(item => { | |
return item.raw; | |
}).reduce((previous, current) => { | |
return current.raw._content; | |
}, null); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Further simplifying and tidying, with more code being split out into separate functions | |
getPhotoMetadata: ({type, page, photoId, photosetId}) => { | |
const exif = jsonService.getJsonFromUrl(apiUrlService.flickrExifDataUrl(photoId)); | |
const details = jsonService.getJsonFromUrl(apiUrlService.flickrPhotoInfoUrl(photoId)); | |
const favourites = jsonService.getJsonFromUrl(apiUrlService.flickrFavouritesUrl(photoId)); | |
const photoset = type === 'photosets' | |
? jsonService.getJsonFromUrl(apiUrlService.flickrPhotosetPhotosUrl(photosetId)) | |
: null; | |
return Promise.all([exif, details, favourites, photoset]) | |
.then(results => results.map(result => checkForError(result))) | |
.then(results => { | |
const [exif, details, favourites, photoset] = results; | |
const exifDetails = generateExifData({exif, details}); | |
const title = details.photo.title._content | |
? details.photo.title._content | |
: 'Untitled'; | |
const description = details.photo.description._content | |
? details.photo.description._content.replace(/\n/, '<br />') | |
: null; | |
const imageUrl = generateImageUrl({ | |
farm: details.photo.farm, | |
server: details.photo.server, | |
photo_id: details.photo.id, | |
secret: details.photo.secret, | |
dateupload: details.photo.dates.posted, }); | |
const lightboxUrl = `https://www.flickr.com/photos/virtualwolf/${details.photo.id}/lightbox`; | |
const numComments = details.photo.comments._content; | |
const numViews = details.photo.views; | |
const numFavourites = favourites.photo.total; | |
const photosetInfo = type === 'photosets' | |
? generatePhotosetInfo({photosetDetails: photoset.photoset, photoId: details.photo.id}) | |
: null; | |
return { | |
exif: exifDetails, | |
title, | |
description, | |
imageUrl, | |
lightboxUrl, | |
numComments, | |
numViews, | |
numFavourites, | |
photosetInfo, | |
}; | |
}) | |
.catch(err => { | |
throw err; | |
}); | |
} | |
function generateExifData({exif, details}) { | |
const camera = getRawExifName({data: exif.photo.exif, type: 'Model'}); | |
const iso = getRawExifName({data: exif.photo.exif, type: 'ISO Speed'}); | |
const focalLength = getCleanExifName({data: exif.photo.exif, type: 'Focal Length'}); | |
const aperture = getCleanExifName({data: exif.photo.exif, type: 'Aperture'}); | |
const shutterSpeed = getShutterSpeed(exif.photo.exif); | |
const lens = getLensName(details.photo.tags.tag); | |
const dateTaken = details.photo.dates.taken | |
? timestampFormatService.flickrDatetime(details.photo.dates.taken) | |
: null; | |
return { | |
camera, | |
focalLength, | |
aperture, | |
shutterSpeed, | |
iso, | |
lens, | |
dateTaken, | |
}; | |
} | |
function getCleanExifName({data, type}) { | |
return data | |
.filter(item => item.label.match(`^${type}$`)) | |
.filter(item => item.clean) | |
.reduce((previous, current) => current.clean._content, null); | |
} | |
function getRawExifName({data, type}) { | |
return data | |
.filter(item => item.label.match(`^${type}$`)) | |
.filter(item => item.raw) | |
.reduce((previous, current) => current.raw._content, null); | |
} | |
function getShutterSpeed(exif) { | |
// Exposures of 1 second or higher have only the raw number of seconds, there's no "clean" version, so | |
// the shutter speed might be clean _or_ raw. | |
return exif | |
.filter(item => item.label.match(/^Exposure$/)) | |
.reduce((previous, current) => current.clean | |
? current.clean._content | |
: `${current.raw._content} sec`, null); | |
} | |
function getLensName(tags) { | |
return tags | |
.filter(tag => tag.raw.match(/(\d+)mm(.*)/g)) | |
.map(tag => tag.raw.replace(/\sUSM/, '')) | |
.reduce((previous, current) => current, null); | |
} | |
function generatePhotosetInfo({photosetDetails, photoId}) { | |
const photoIds = photosetDetails.photo.map(photo => { return photo.id; }); | |
const currentImageIndex = photoIds.findIndex(i => i === photoId); | |
return { | |
title: photosetDetails.title, | |
photosInSet: { | |
currentPhoto: photoIds[currentImageIndex], | |
nextPhoto: photoIds[currentImageIndex + 1], | |
previousPhoto: photoIds[currentImageIndex - 1], | |
lastPhoto: photoIds[photoIds.length - 1], | |
}, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment