1 |
|
2 |
|
3 | 'use strict'
|
4 |
|
5 | const fs = require('fs-extra')
|
6 | const path = require('path')
|
7 | const util = require('util')
|
8 | const url = require('url')
|
9 |
|
10 | const chai = require('chai')
|
11 | const dirtyChai = require('dirty-chai')
|
12 |
|
13 | const genThumbnail = require('../')
|
14 | const looksSame = util.promisify(require('looks-same'))
|
15 | const nock = require('nock')
|
16 |
|
17 | const { expect } = chai
|
18 | const absolutePath = relative => path.join(__dirname, relative)
|
19 |
|
20 | chai.use(dirtyChai)
|
21 |
|
22 | describe('simple-thumbnail creates thumbnails for videos', () => {
|
23 | const tinySize = '50x?'
|
24 |
|
25 | before(async () => {
|
26 | await fs.remove(absolutePath('./out'))
|
27 | await fs.mkdirp(absolutePath('./out/storage'))
|
28 | await fs.mkdirp(absolutePath('./out/input-formats'))
|
29 | await fs.mkdirp(absolutePath('./out/image-formats'))
|
30 | await fs.mkdirp(absolutePath('./out/sizes'))
|
31 | })
|
32 |
|
33 | describe('invalid input', () => {
|
34 | const filePath = absolutePath('./data/bunny.mp4')
|
35 | const outPath = absolutePath('./out/invalid.png')
|
36 |
|
37 | it('throws an error on malformed size string', async () => {
|
38 | try {
|
39 | await genThumbnail(filePath, outPath, 'not a real size')
|
40 | } catch (err) {
|
41 | expect(err.message).to.equal('Invalid size argument')
|
42 | }
|
43 | })
|
44 |
|
45 | it('throws an error given a "?x?" size string', async () => {
|
46 | try {
|
47 | await genThumbnail(filePath, outPath, '?x?')
|
48 | } catch (err) {
|
49 | expect(err.message).to.equal('Invalid size argument')
|
50 | }
|
51 | })
|
52 |
|
53 | it('throws an error given a percentage string with no value (%)', async () => {
|
54 | try {
|
55 | await genThumbnail(filePath, outPath, '%')
|
56 | } catch (err) {
|
57 | expect(err.message).to.equal('Invalid size argument')
|
58 | }
|
59 | })
|
60 |
|
61 | it('throws a ffmpeg stderr dump on non-zero exit', async () => {
|
62 | try {
|
63 | await genThumbnail('not a real path', outPath, '200x200')
|
64 | } catch (err) {
|
65 | const stderrLines = err.message.split('\n')
|
66 |
|
67 | expect(stderrLines[0]).to.equal('ffmpeg exited 1')
|
68 | expect(stderrLines[1]).to.equal('ffmpeg stderr:')
|
69 | }
|
70 | })
|
71 | })
|
72 |
|
73 | describe('thumbnail creation for different storage mediums', () => {
|
74 | const filePath = absolutePath('./data/bunny.webm')
|
75 |
|
76 | it('creates thumbnails for files saved on disk', async () => {
|
77 | try {
|
78 | await genThumbnail(filePath, absolutePath('./out/storage/disk.png'), tinySize)
|
79 | } catch (err) {
|
80 | console.log(err)
|
81 |
|
82 | expect.fail()
|
83 | }
|
84 | })
|
85 |
|
86 |
|
87 | it('creates thumbnails from read streams', async () => {
|
88 | const stream = fs.createReadStream(filePath)
|
89 |
|
90 | try {
|
91 | await genThumbnail(stream, absolutePath('./out/storage/stream.png'), tinySize)
|
92 | } catch (err) {
|
93 | console.log(err)
|
94 |
|
95 | expect.fail()
|
96 | }
|
97 | })
|
98 |
|
99 | describe('creates thumbnails for remote files', () => {
|
100 | const protocols = ['http', 'https']
|
101 |
|
102 | protocols.forEach((protocol) => {
|
103 | const fileUrl = `${protocol}://www.w3schools.com/html/mov_bbb.webm`
|
104 | const parsedUrl = url.parse(fileUrl)
|
105 |
|
106 | nock(`${parsedUrl.protocol}//${parsedUrl.host}`)
|
107 | .get(parsedUrl.path)
|
108 | .replyWithFile(200, filePath, { 'Content-Type': 'video/webm' })
|
109 |
|
110 | it(`${protocol} protocol`, () => {
|
111 | it('successfully generates thumbnails', async () => {
|
112 | try {
|
113 | await genThumbnail(fileUrl, absolutePath(`./out/storage/${protocol}.png`), tinySize)
|
114 | } catch (err) {
|
115 | console.log(err)
|
116 |
|
117 | expect.fail()
|
118 | }
|
119 | })
|
120 | })
|
121 | })
|
122 | })
|
123 | })
|
124 |
|
125 | describe('file formats', () => {
|
126 | const formats = ['webm', 'mp4']
|
127 |
|
128 | formats.forEach((format) => {
|
129 | it(`can create thumbnails for ${format}`, async () => {
|
130 | const filePath = absolutePath(`./data/bunny.${format}`)
|
131 |
|
132 | try {
|
133 | await genThumbnail(filePath, absolutePath(`./out/input-formats/${format}.png`), tinySize)
|
134 | } catch (err) {
|
135 | console.log(err)
|
136 |
|
137 | expect.fail()
|
138 | }
|
139 | })
|
140 | })
|
141 | })
|
142 |
|
143 | describe('thumbnail sizes', () => {
|
144 | const sizes = ['25%', '101%', '50x?', '?x50', '100x50']
|
145 | const filePath = absolutePath('./data/bunny.webm')
|
146 |
|
147 | sizes.forEach((size) => {
|
148 | it(`handles sizes of the form ${size}`, async () => {
|
149 | try {
|
150 | await genThumbnail(
|
151 | filePath,
|
152 | absolutePath(`./out/sizes/size-${size.replace('%', '')}.png`),
|
153 | size
|
154 | )
|
155 | } catch (err) {
|
156 | console.log(err)
|
157 |
|
158 | expect.fail()
|
159 | }
|
160 | })
|
161 | })
|
162 | })
|
163 |
|
164 | describe('thumbnail output image formats', () => {
|
165 | const filePath = absolutePath('./data/bunny.webm')
|
166 | const formats = ['gif', 'jpg', 'png']
|
167 |
|
168 | formats.forEach((format) => {
|
169 | it(`can create ${format} images`, async () => {
|
170 | try {
|
171 | await genThumbnail(
|
172 | filePath,
|
173 | absolutePath(`./out/image-formats/${format}.${format}`),
|
174 | tinySize
|
175 | )
|
176 | } catch (err) {
|
177 | console.log(err)
|
178 |
|
179 | expect.fail()
|
180 | }
|
181 | })
|
182 | })
|
183 | })
|
184 |
|
185 | describe('thumbnail correctness', () => {
|
186 | it('produces thumbnail images that are identical to expected output', async () => {
|
187 | const config = { tolerance: 5 }
|
188 |
|
189 | const storageFiles = await fs.readdir(absolutePath('./out/storage'))
|
190 | const inputFormatFiles = await fs.readdir(absolutePath('./out/input-formats'))
|
191 | const sizeFiles = await fs.readdir(absolutePath('./out/sizes'))
|
192 |
|
193 | const storagePromises = storageFiles
|
194 | .map(file => looksSame(
|
195 | absolutePath('./expected/tiny.png'),
|
196 | absolutePath(`./out/storage/${file}`),
|
197 | config
|
198 | ))
|
199 |
|
200 | const inputFormatPromises = inputFormatFiles
|
201 | .map(file => looksSame(
|
202 | absolutePath('./expected/tiny.png'),
|
203 | absolutePath(`./out/input-formats/${file}`),
|
204 | config
|
205 | ))
|
206 |
|
207 | const sizePromises = sizeFiles
|
208 | .map(file => looksSame(
|
209 | absolutePath(`./expected/${file}`),
|
210 | absolutePath(`./out/sizes/${file}`),
|
211 | config
|
212 | ))
|
213 |
|
214 | try {
|
215 | const results = await Promise.all(
|
216 | storagePromises.concat(inputFormatPromises, sizePromises)
|
217 | )
|
218 |
|
219 | expect(results.every(x => x)).to.be.true()
|
220 | } catch (err) {
|
221 | console.log(err)
|
222 |
|
223 | expect.fail()
|
224 | }
|
225 | })
|
226 | })
|
227 |
|
228 | describe('external error handling', () => {
|
229 | it('throws an error if stderr fires an error event', async () => {
|
230 |
|
231 | })
|
232 | })
|
233 | })
|