diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index 109e886106..427b54d72b 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -110,7 +110,7 @@ const addFile = async ( } } - const [wh, folder] = await Promise.all([ + const [wh, averageColor, folder] = await Promise.all([ // Width and height (when image) (async () => { // 画像かどうか @@ -125,14 +125,45 @@ const addFile = async ( return null; } + log('calculate image width and height...'); + // Calculate width and height const g = gm(fs.createReadStream(path), name); const size = await prominence(g).size(); - log('image width and height is calculated'); + log(`image width and height is calculated: ${size.width}, ${size.height}`); return [size.width, size.height]; })(), + // average color (when image) + (async () => { + // 画像かどうか + if (!/^image\/.*$/.test(mime)) { + return null; + } + + const imageType = mime.split('/')[1]; + + // 画像でもPNGかJPEGでないならスキップ + if (imageType != 'png' && imageType != 'jpeg') { + return null; + } + + log('calculate average color...'); + + const buffer = await prominence(gm(fs.createReadStream(path), name) + .setFormat('ppm') + .resize(1, 1)) // 1pxのサイズに縮小して平均色を取得するというハック + .toBuffer(); + + const r = buffer.readUInt8(buffer.length - 3); + const g = buffer.readUInt8(buffer.length - 2); + const b = buffer.readUInt8(buffer.length - 1); + + log(`average color is calculated: ${r}, ${g}, ${b}`); + + return [r, g, b]; + })(), // folder (async () => { if (!folderId) { @@ -188,6 +219,10 @@ const addFile = async ( properties['height'] = wh[1]; } + if (averageColor) { + properties['average_color'] = averageColor; + } + return addToGridFS(detectedName, readable, mime, { user_id: user._id, folder_id: folder !== null ? folder._id : null, diff --git a/src/api/endpoints/drive/files/create.ts b/src/api/endpoints/drive/files/create.ts index 7546eca309..437348a1ef 100644 --- a/src/api/endpoints/drive/files/create.ts +++ b/src/api/endpoints/drive/files/create.ts @@ -38,9 +38,15 @@ module.exports = async (file, params, user): Promise<any> => { const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; if (folderIdErr) throw 'invalid folder_id param'; - // Create file - const driveFile = await create(user, file.path, name, null, folderId); + try { + // Create file + const driveFile = await create(user, file.path, name, null, folderId); - // Serialize - return serialize(driveFile); + // Serialize + return serialize(driveFile); + } catch (e) { + console.error(e); + + throw e; + } }; diff --git a/src/web/app/desktop/tags/drive/file.tag b/src/web/app/desktop/tags/drive/file.tag index 0f019d95bf..8b3d36b3f3 100644 --- a/src/web/app/desktop/tags/drive/file.tag +++ b/src/web/app/desktop/tags/drive/file.tag @@ -5,7 +5,9 @@ <div class="label" if={ I.banner_id == file.id }><img src="/assets/label.svg"/> <p>%i18n:desktop.tags.mk-drive-browser-file.banner%</p> </div> - <div class="thumbnail"><img src={ file.url + '?thumbnail&size=128' } alt=""/></div> + <div class="thumbnail" ref="thumbnail" style="background-color:{ file.properties.average_color ? 'rgb(' + file.properties.average_color.join(',') + ')' : 'transparent' }"> + <img src={ file.url + '?thumbnail&size=128' } alt="" onload={ onload }/> + </div> <p class="name"><span>{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }</span><span class="ext" if={ file.name.lastIndexOf('.') != -1 }>{ file.name.substr(file.name.lastIndexOf('.')) }</span></p> <style> :scope @@ -139,6 +141,7 @@ </style> <script> + import anime from 'animejs'; import bytesToSize from '../../../common/scripts/bytes-to-size'; this.mixin('i'); @@ -199,5 +202,16 @@ this.isDragging = false; this.browser.isDragSource = false; }; + + this.onload = () => { + if (this.file.properties.average_color) { + anime({ + targets: this.refs.thumbnail, + backgroundColor: `rgba(${this.file.properties.average_color.join(',')}, 0)`, + duration: 100, + easing: 'linear' + }); + } + }; </script> </mk-drive-browser-file> diff --git a/src/web/app/desktop/tags/images.tag b/src/web/app/desktop/tags/images.tag index ce67d26a9f..5e4be481dc 100644 --- a/src/web/app/desktop/tags/images.tag +++ b/src/web/app/desktop/tags/images.tag @@ -53,7 +53,13 @@ </mk-images> <mk-images-image> - <a ref="view" href={ image.url } onmousemove={ mousemove } onmouseleave={ mouseleave } style={ 'background-image: url(' + image.url + '?thumbnail&size=512' } onclick={ click } title={ image.name }></a> + <a ref="view" + href={ image.url } + onmousemove={ mousemove } + onmouseleave={ mouseleave } + style={ styles } + onclick={ click } + title={ image.name }></a> <style> :scope display block @@ -74,6 +80,11 @@ </style> <script> this.image = this.opts.image; + this.styles = { + 'background-color': `rgb(${this.image.properties.average_color.join(',')})`, + 'background-image': `url(${this.image.url}?thumbnail&size=512)` + }; + console.log(this.styles); this.mousemove = e => { const rect = this.refs.view.getBoundingClientRect(); diff --git a/src/web/app/mobile/tags/drive/file-viewer.tag b/src/web/app/mobile/tags/drive/file-viewer.tag index 48fc83fa67..259873d95c 100644 --- a/src/web/app/mobile/tags/drive/file-viewer.tag +++ b/src/web/app/mobile/tags/drive/file-viewer.tag @@ -1,6 +1,11 @@ <mk-drive-file-viewer> <div class="preview"> - <img if={ kind == 'image' } src={ file.url } alt={ file.name } title={ file.name } onload={ onImageLoaded } ref="img"> + <img if={ kind == 'image' } ref="img" + src={ file.url } + alt={ file.name } + title={ file.name } + onload={ onImageLoaded } + style="background-color:rgb({ file.properties.average_color.join(',') })"> <virtual if={ kind != 'image' }>%fa:file%</virtual> <footer if={ kind == 'image' && file.properties && file.properties.width && file.properties.height }> <span class="size"> diff --git a/src/web/app/mobile/tags/drive/file.tag b/src/web/app/mobile/tags/drive/file.tag index 196dd1141e..684df7dd08 100644 --- a/src/web/app/mobile/tags/drive/file.tag +++ b/src/web/app/mobile/tags/drive/file.tag @@ -1,7 +1,7 @@ <mk-drive-file data-is-selected={ isSelected }> <a onclick={ onclick } href="/i/drive/file/{ file.id }"> <div class="container"> - <div class="thumbnail" style={ 'background-image: url(' + file.url + '?thumbnail&size=128)' }></div> + <div class="thumbnail" style={ thumbnail }></div> <div class="body"> <p class="name"><span>{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }</span><span class="ext" if={ file.name.lastIndexOf('.') != -1 }>{ file.name.substr(file.name.lastIndexOf('.')) }</span></p> <!-- @@ -132,6 +132,10 @@ this.browser = this.parent; this.file = this.opts.file; + this.thumbnail = { + 'background-color': this.file.properties.average_color ? `rgb(${this.file.properties.average_color.join(',')})` : 'transparent', + 'background-image': `url(${this.file.url}?thumbnail&size=128)` + }; this.isSelected = this.browser.selectedFiles.some(f => f.id == this.file.id); this.browser.on('change-selection', selections => { diff --git a/src/web/app/mobile/tags/images.tag b/src/web/app/mobile/tags/images.tag index aaa80e4fd1..b200eefe7e 100644 --- a/src/web/app/mobile/tags/images.tag +++ b/src/web/app/mobile/tags/images.tag @@ -56,7 +56,7 @@ </mk-images> <mk-images-image> - <a ref="view" href={ image.url } target="_blank" style={ 'background-image: url(' + image.url + '?thumbnail&size=512' } title={ image.name }></a> + <a ref="view" href={ image.url } target="_blank" style={ styles } title={ image.name }></a> <style> :scope display block @@ -74,5 +74,9 @@ </style> <script> this.image = this.opts.image; + this.styles = { + 'background-color': `rgb(${this.image.properties.average_color.join(',')})`, + 'background-image': `url(${this.image.url}?thumbnail&size=512)` + }; </script> </mk-images-image> diff --git a/tools/migration/node.2017-12-11.js b/tools/migration/node.2017-12-11.js new file mode 100644 index 0000000000..3a3fef0518 --- /dev/null +++ b/tools/migration/node.2017-12-11.js @@ -0,0 +1,67 @@ +// for Node.js interpret + +const { default: DriveFile, getGridFSBucket } = require('../../built/api/models/drive-file') +const { default: zip } = require('@prezzemolo/zip') + +const _gm = require('gm'); +const gm = _gm.subClass({ + imageMagick: true +}); + +const migrate = doc => new Promise(async (res, rej) => { + const bucket = await getGridFSBucket(); + + const readable = bucket.openDownloadStream(doc._id); + + gm(readable) + .setFormat('ppm') + .resize(1, 1) + .toBuffer(async (err, buffer) => { + if (err) rej(err); + const r = buffer.readUInt8(buffer.length - 3); + const g = buffer.readUInt8(buffer.length - 2); + const b = buffer.readUInt8(buffer.length - 1); + + const result = await DriveFile.update(doc._id, { + $set: { + 'metadata.properties.average_color': [r, g, b] + } + }) + + res(result.ok === 1); + }); +}); + +async function main() { + const query = { + contentType: { + $in: [ + 'image/png', + 'image/jpeg' + ] + } + } + + const count = await DriveFile.count(query); + + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const doc = await DriveFile.find(query, { + limit: dop, skip: time * dop + }) + return Promise.all(doc.map(migrate)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) +} + +main().then(console.dir).catch(console.error)