feat(core): add support for static and animated GIFs

GraphicsMagick can’t process animated GIFs well unless all frames are coalesced, which creates very large files.
This means there is a new dependency on Gifsicle which is very good at working with animated GIFs.

- Thumbnails are still processed through GraphicsMagick (more options, e.g. centre-crop)
- Large previews of animated GIFs go through Gifsicle

The startup checks are updated to check for the Gifsicle binary.

Fixes #96.
pull/107/head
Romain 6 years ago
parent e1517d2fbd
commit 0adef54dc8

@ -43,12 +43,19 @@ See the website for the full documentation: https://thumbsup.github.io.
## Requirements
Thumbsup requires the following dependencies:
- [Node.js](http://nodejs.org/): `brew install Node`
- [Node.js](http://nodejs.org/): `brew install node`
- [exiftool](http://www.sno.phy.queensu.ca/~phil/exiftool/): `brew install exiftool`
- [GraphicsMagick](http://www.graphicsmagick.org/): `brew install graphicsmagick`
- [FFmpeg](http://www.ffmpeg.org/): `brew install ffmpeg`
You can run thumbsup as a Docker container ([thumbsupgallery/thumbsup](https://hub.docker.com/r/thumbsupgallery/thumbsup/)) which pre-packages all the dependencies above.
And optionally:
- [FFmpeg](http://www.ffmpeg.org/) to process videos: `brew install ffmpeg`
- [Gifsicle](http://www.lcdf.org/gifsicle/) to process animated GIFs: `brew install gifsicle`
You can run thumbsup as a Docker container ([thumbsupgallery/thumbsup](https://hub.docker.com/r/thumbsupgallery/thumbsup/)) which pre-packages all the dependencies above. Read the [thumbsup on Docker](https://thumbsup.github.io/docs/2-installation/docker/) documentation for more detail.
```bash
docker run -v `pwd`:/work thumbsupgallery/thumbsup [...]
```
## Sample gallery

@ -1,31 +0,0 @@
const chalk = require('chalk')
const commandExists = require('command-exists')
const COMMANDS = {
'gm': 'http://www.graphicsmagick.org',
'exiftool': 'https://www.sno.phy.queensu.ca/~phil/exiftool',
'ffmpeg': 'https://www.ffmpeg.org'
}
exports.verify = function () {
const missing = Object.keys(COMMANDS).reduce(addToArrayIfMissing, [])
if (missing.length > 0) {
const list = missing.map(nameAndURL).join('\n')
return `The following programs are required to run thumbsup:\n
${list}\n
Please make sure they are installed and available in the system path.`
}
return null
}
function addToArrayIfMissing (acc, cmd) {
if (!commandExists.sync(cmd)) {
acc.push(cmd)
}
return acc
}
function nameAndURL (cmd) {
const url = chalk.green(COMMANDS[cmd])
return `- ${cmd} (${url})`
}

@ -0,0 +1,60 @@
const chalk = require('chalk')
const commandExists = require('command-exists')
const warn = require('debug')('thumbsup:warn')
const messages = require('./messages')
const BINARIES = [
{
// required to build the database
mandatory: true,
cmd: 'exiftool',
url: 'https://www.sno.phy.queensu.ca/~phil/exiftool',
msg: ''
},
{
// required to build thumbnails, even if we're only processing videos
mandatory: true,
cmd: 'gm',
url: 'http://www.graphicsmagick.org',
msg: ''
},
{
// optional to process videos
mandatory: false,
cmd: 'ffmpeg',
url: 'https://www.ffmpeg.org',
msg: 'You will not be able to process videos.'
},
{
// optional to process animated GIFs
mandatory: false,
cmd: 'gifsicle2',
url: 'http://www.lcdf.org/gifsicle',
msg: 'You will not be able to process animated GIFs.'
}
]
exports.checkRequired = () => {
const missing = BINARIES.filter(bin => bin.mandatory).reduce(addToArrayIfMissing, [])
if (missing.length > 0) {
const list = missing.map(bin => `- ${bin.cmd} (${chalk.green(bin.url)})`)
return messages.BINARIES_REQUIRED(list)
}
return null
}
exports.checkOptional = () => {
const missing = BINARIES.filter(bin => !bin.mandatory).reduce(addToArrayIfMissing, [])
if (missing.length > 0) {
missing.forEach(bin => {
warn(`${bin.cmd} (${bin.url}) is not installed. ${bin.msg}`)
})
}
}
function addToArrayIfMissing (acc, binary) {
if (!commandExists.sync(binary.cmd)) {
acc.push(binary)
}
return acc
}

@ -21,6 +21,12 @@ per argument, not including the leading "--". For example:
{ "sort-albums-by": "start-date" }
`
exports.BINARIES_REQUIRED = (list) => `
Error: the following programs are required to run thumbsup.
Please make sure they are installed and available in the system path.\n
${list.join('\n')}
`
exports.SUCCESS = (stats) => box(`
Gallery generated successfully!
${stats.albums} albums, ${stats.photos} photos, ${stats.videos} videos

@ -1,12 +1,12 @@
#!/usr/bin/env node
const Analytics = require('./analytics')
const fs = require('fs')
const messages = require('./messages')
const path = require('path')
const options = require('./options')
const checks = require('./checks')
const tty = require('tty')
const Analytics = require('./analytics')
const dependencies = require('./dependencies')
const messages = require('./messages')
const options = require('./options')
console.log('')
@ -39,9 +39,10 @@ analytics.start()
process.on('uncaughtException', handleError)
// Check that all binary dependencies are present
const missingErrors = checks.verify()
dependencies.checkOptional()
const missingErrors = dependencies.checkRequired()
if (missingErrors) {
console.log(`${missingErrors}\n`)
console.log(`${missingErrors}`)
exit(1)
}

10944
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -49,7 +49,7 @@
"moment": "^2.22.1",
"readdir-enhanced": "^2.2.1",
"through2": "^2.0.3",
"thumbsup-downsize": "^2.0.0",
"thumbsup-downsize": "^2.1.0",
"url-join": "^4.0.0",
"yargs": "^11.0.0",
"zen-observable": "^0.8.8"

@ -2,7 +2,7 @@ const warn = require('debug')('thumbsup:warn')
const path = require('path')
const urljoin = require('url-join')
const BROWSER_SUPPORTED_EXT = /\.(jpg|jpeg|png)$/i
const BROWSER_SUPPORTED_EXT = /\.(jpg|jpeg|png|gif)$/i
exports.paths = function (filepath, mediaType, opts) {
if (mediaType === 'image') {

@ -105,7 +105,8 @@ function getActionMap (opts) {
})
const large = Object.assign({}, defaultOptions, {
height: largeSize,
watermark: watermark
watermark: watermark,
animated: true
})
return {
'fs:copy': (task, done) => fs.copy(task.src, task.dest, done),

@ -61,7 +61,7 @@ describe('Output paths', function () {
})
it('keeps the original image format if the browser supports it', function () {
['jpg', 'JPG', 'jpeg', 'JPEG', 'png', 'PNG'].forEach(ext => {
['jpg', 'JPG', 'jpeg', 'JPEG', 'png', 'PNG', 'gif', 'GIF'].forEach(ext => {
var o = output.paths(`holidays/beach.${ext}`, 'image', {})
should(o.thumbnail.path).eql(`media/thumbs/holidays/beach.${ext}`)
})

Loading…
Cancel
Save