{"id":328779,"date":"2022-01-29T21:00:16","date_gmt":"2022-01-29T21:00:16","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=328779"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=328779","title":{"rendered":"<span>S3 + Lambda + ffmpeg (supports heic)<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0447\u0430\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u0435\u0431 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 &#8212; \u043d\u0430\u0440\u0435\u0437\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438. \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0432\u0430\u0448\u0435\u043c\u0443 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044e \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Serverless framework + Lambda + S3.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/df2\/0b0\/156\/df20b0156cfcb2251fa09631a27de994.jpg\" width=\"498\" height=\"115\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/df2\/0b0\/156\/df20b0156cfcb2251fa09631a27de994.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043b\u0443\u0448\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432 S3 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f. \u041d\u0430 \u0432\u0445\u043e\u0434\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u0438\u043c\u044f S3 \u0438 \u043a\u043b\u044e\u0447 \u0444\u0430\u0439\u043b\u0430.\u00a0\u0414\u0430\u043b\u0435\u0435 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0442\u0430\u043a\u043e\u0439: \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u043b\u044f\u043c\u0431\u0434\u0443, \u043d\u0430\u0440\u0435\u0437\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438\u0445 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 S3. \u041e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0432 \u0434\u0440\u0443\u0433\u043e\u0439, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0432 \u0442\u043e\u0442 \u0436\u0435 S3 \u0432\u044b\u0437\u043e\u0432\u0435\u0442\u0441\u044f \u043b\u044f\u043c\u0431\u0434\u0430 \u043e\u043f\u044f\u0442\u044c \u0438 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0435\u0437\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u0430\u0440\u0435\u0437\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 (\u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u0430\u044f \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u044f, \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c). \u042d\u0442\u043e \u0432\u0441\u0435 \u0432 \u0442\u0435\u043e\u0440\u0438\u0438. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.\u00a0<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0440\u0435\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/www.ffmpeg.org\/\" rel=\"noopener noreferrer nofollow\">ffmpeg<\/a>. \u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0435 \u0438 \u043e\u0431\u0440\u0435\u0437\u0430\u043d\u043d\u043e\u0435 \u0434\u043e 300\u0445300px:\u00a0<\/p>\n<pre><code class=\"bash\">ffmpeg -i source.jpg -filter:v \"scale=300:-1,crop=300:300:0:0\" -y crop300x300.jpg<\/code><\/pre>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"788\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/843\/3aa\/b28\/8433aab28792dec1d9d5867ee42eb2f1.gif\" data-width=\"1280\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 thumb:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"714\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/186\/3d0\/4be\/1863d04be9dbb6224d1bd11fb5d6ec9f.gif\" data-width=\"1164\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0441\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u043d\u043e \u0432 \u043b\u044f\u043c\u0431\u0434\u0435 \u043d\u0435\u0442 ffmpeg. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/ffmpeg\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a<\/a> \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0432 \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0439.\u00a0\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0438\u0437 JavaScript \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f child_process:<\/p>\n<pre><code class=\"javascript\">const childProcess = require('child_process') spawnPromise(command, argsarray, envOptions) {     return new Promise((resolve, reject) => {       const childProc = childProcess.spawn(command, argsarray, envOptions || { env: process.env, cwd: process.cwd() }),         resultBuffers = []       childProc.stdout.on('data', buffer => {         resultBuffers.push(buffer)       })       childProc.stderr.on('data', buffer => console.log(buffer.toString()))       childProc.on('exit', (code, signal) => {         if (code || signal) {           reject(`${command} failed with ${code || signal}`)         } else {           resolve(Buffer.concat(resultBuffers).toString().trim())         }       })     })   }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u043c\u0435\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0438\u0437 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438: \u0448\u0438\u0440\u0438\u043d\u0443, \u0432\u044b\u0441\u043e\u0442\u0443, \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 (\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c, \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0435\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043d\u0438\u043c\u043e\u043a \u043d\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d). \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f <a href=\"https:\/\/ffmpeg.org\/ffprobe.html\" rel=\"noopener noreferrer nofollow\">ffprobe<\/a>. \u0422\u0430\u043a\u0436\u0435, \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/ffprobe\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a<\/a> \u0432 \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0435.\u00a0\u0418 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435:<\/p>\n<pre><code class=\"javascript\">async getResolution(inputFile) {     try {       let rotate = await this.spawnPromise(         config.FFPROBE,         [           '-v', 'error',           '-select_streams', 'v:0',           '-show_entries',           'stream_tags=rotate',           '-of', 'default=nw=1:nk=1',           inputFile],       )       rotate = rotate || 0       const str = await this.spawnPromise(         config.FFPROBE,         [           '-v', 'error',           '-select_streams', 'v:0',           '-show_entries',           'stream=width,height',           '-of', 'csv=s=x:p=0',           inputFile],       )        let [width, height] = String(str).split('x')        if (String(rotate) === '90' || String(rotate) === '-90' || String(rotate) === '270' || String(rotate) ===         '-270') {         [width, height] = [height, width]       }       return { width, height, rotate }     } catch (e) {       console.log('ffprobe error', e)       return { width: 1080, height: 1920 }     }   }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u0437\u043d\u0430\u044f \u043c\u0435\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0444\u0430\u0439\u043b\u0435, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"java\">async thumb(inputFile, outputFile, filterData, imageData = {}) {     try {       const isVertical = Number(imageData.width) &lt; Number(imageData.height)       let filter       if (filterData.crop) {         const newImageWidth = isVertical ? filterData.height : filterData.width         const newImageHeight = isVertical ? filterData.width : filterData.height         const resizeK = newImageWidth \/ imageData.width          const x = parseInt(isVertical ? 0 : (resizeK * imageData.width \/ 2 - newImageWidth \/ 2))         const y = parseInt(isVertical ? (resizeK * imageData.height \/ 2 - newImageHeight \/ 2) : 0)          if (newImageWidth === newImageHeight) {           \/\/ square           filter = `scale=${isVertical             ? `${newImageWidth}:-1`             : `-1:${newImageWidth}`},crop=${newImageWidth}:${newImageHeight}:${x}:${y}`         } else {           filter = `scale=${isVertical             ? `${newImageWidth}:-1`             : `-1:${newImageWidth}`},crop=${newImageWidth}:${newImageHeight}:${x}:${y}`         }       } else {         const newImageWidth = isVertical ? filterData.height : filterData.width         filter = `scale=${newImageWidth}:-1`       }        if (Number(imageData.rotate) !== 0) {         const times = Number(imageData.rotate) \/ 90         for (let i = 0; i &lt; times; i++) {           filter += `,transpose=1`         }       }       const callData = [         '-v',         'error',         '-noautorotate',         '-i',         inputFile,         '-filter:v',         filter,         '-y',       ]       return await this.spawnPromise(         config.FFMPEG,         [           ...callData,           outputFile],       )     } catch (e) {       console.log('ffmpeg error', e)     }<\/code><\/pre>\n<p>\u042f \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u043b \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438 \u043f\u0435\u0440\u0432\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u044d\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438\u0437 iPhone. \u0422\u0430\u043a \u043a\u0430\u043a Apple \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0441\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0435\u0449\u0435 \u043d\u0435 \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442. \u041f\u043e\u043a\u0430 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u043d\u0430\u0448\u0435\u043b \u044d\u0442\u043e \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/tifig\" rel=\"noopener noreferrer nofollow\">tifig<\/a> \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0432\u0441\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 \u044f \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u043b \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b \u0434\u0430\u0432\u043d\u043e \u0438 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c tifig \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043d\u043e \u0437\u0430\u0442\u043e \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u043c\u0438 <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/tree\/main\/lib\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0430\u043c\u0438<\/a>.<\/p>\n<p>\u0418 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 heic \u0432 jpg:<\/p>\n<pre><code class=\"javascript\">heicToJpg(filePath, newFilePath) {     return new Promise((resolve, reject) => {       childProcess.exec(se([config.TIFIG, filePath, newFilePath]), { encoding: 'utf8' },         function (error, stdout, stderr) {           if (error) {             console.error(stderr)             return reject(error)           }           fs.unlinkSync(filePath)           return resolve(true)         })     })   }<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"javascript\"> module.exports.handler = async (event) => {   const s3Record = event.Records[0].s3   const sourceKey = s3Record.object.key   if (!file.isPhoto(sourceKey)) {     return true   }   await file.download(s3Record.bucket.name, sourceKey,file.getDownloadName(sourceKey))   const ext = file.getExt(sourceKey)   const isHeic = file.isHeic(sourceKey)    \/\/ convert iphone photo format to jpg   if (isHeic) {     await convert.heicToJpg(file.getDownloadName(sourceKey), file.getDownloadNameIfHeic(sourceKey, ext))   }    \/\/ create thumbs by config.RESOLUTIONS   const { width, height, rotate } = await convert.getResolution(file.getDownloadNameIfHeic(sourceKey, ext))   await Promise.all(config.RESOLUTIONS.map(r => createThumbAndUpload(s3Record, r, { width, height, rotate, ext })))    \/\/ remove unnecessary files   await file.remove(file.getDownloadName(sourceKey))   if (isHeic) {     await file.remove(file.getDownloadNameIfHeic(sourceKey, ext))   }   return true }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u0441\u0435\u043c config.RESOLUTIONS \u044d\u0442\u043e \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0442\u044c \u0432 \u043a\u0430\u043a\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432:<\/p>\n<pre><code class=\"javascript\">RESOLUTIONS: [   { name: '1920x1080', width: 1920, height: 1080 },   { name: '1280x720', width: 1280, height: 720 },   { name: 'crop600x400', crop: true, width: 600, height: 400 },   { name: 'crop300x300', crop: true, width: 300, height: 300 }, ]<\/code><\/pre>\n<p>\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u0432\u0441\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u0435.\u00a0\u0418\u0442\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u043a\u043e\u043d\u0444\u0438\u0433 \u0434\u0435\u043f\u043b\u043e\u044f Serverless Framework \u0441 \u043f\u043e\u044f\u0441\u043d\u044f\u044e\u0449\u0438\u043c\u0438 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"yaml\">service: service-lambda-thumb-tifig frameworkVersion: '2 || 3' app: lambda-thumb-tifig  package:   # \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043b\u0438\u0448\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430 \u043b\u044f\u043c\u0431\u0434\u044b, \u0447\u0442\u043e\u0431 \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u0435\u0435 \u0440\u0430\u0437\u043c\u0435\u0440   patterns:     - '!node_modules\/aws-sdk'     - '!node_modules\/@aws-cdk'     - '!node_modules\/serverless'     - '!node_modules\/serverless-lift'     - '!.idea'     - '!yarn.lock'     - '!yarn.error.log'     - '!README.md'     - '!.gitignore'     - '!package.json'     - '!lib'     - '!.git'  custom:   name: 'lambda-thumb-tifig'   environment: 'prod'   region: 'us-east-1'   lambda_prefix: ${self:custom.environment}-${self:custom.name}  # \u041e\u0442\u043c\u0435\u0447\u0443 \u0447\u0442\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0430\u0432\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 S3 \u0434\u043b\u044f \u0434\u0435\u043c\u043e.  # \u041d\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0443\u0436\u0435 \u0440\u0430\u043d\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 S3. # \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u0444\u0438\u0433 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0435 S3 constructs:   sourceBucket:     type: storage   destinationBucket:     type: storage   provider:   name: aws   lambdaHashingVersion: '20201221'   environment:     DESTINATION_BUCKET: ${construct:destinationBucket.bucketName}     REGION: ${self:custom.region}   region: ${self:custom.region}   runtime: nodejs14.x   iamRoleStatements:     - Effect: \"Allow\"       Action:         - \"s3:PutBucketNotification\"         - \"s3:GetObject\"         - \"s3:PutObject\"       Resource:         Fn::Join:           - \"\"           - - \"arn:aws:s3:::*\"     - Effect: \"Allow\"       Action:         - \"rds:*\"       Resource: \"*\"  plugins:   - serverless-lift  functions:   # \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043b\u044f\u043c\u0431\u0434\u0430-\u0444\u0443\u043d\u043a\u0446\u0438\u044e   ffmpeg-tifig:     # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u043f\u0430\u043c\u044f\u0442\u044c \u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440 \u043b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438      # \u0434\u043b\u044f \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f     # \u0442\u0430\u043a\u0438\u0435 \u043b\u044f\u043c\u0431\u0434\u044b \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0440\u043e\u0436\u0435, \u043d\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435      # \u044f \u043d\u0435 \u0432\u044b\u0445\u043e\u0436\u0443 \u0437\u0430 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043b\u0438\u043c\u0438\u0442\u044b \u0432 \u043c\u0435\u0441\u044f\u0446 - \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c      # \u0441\u0430\u043c\u044b\u0439 \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442     memorySize: 10240     name: ${self:custom.lambda_prefix}     handler: src\/index.handler     # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438     timeout: 300     # \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a \u0441\u043e\u0431\u0438\u0442\u0438\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432 S3 Source     events:       - s3:           bucket: ${construct:sourceBucket.bucketName}           existing: true     # \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0441\u043b\u043e\u0439 Lib \u043a \u043b\u044f\u043c\u0431\u0434\u0430-\u0444\u0443\u043d\u043a\u0446\u0438\u0438     layers:       - { Ref: LibLambdaLayer }  # \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0439 \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0432 \u043d\u0435\u0433\u043e \u0432\u0441\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043f\u043a\u0438 lib. layers:   lib:     path: lib <\/code><\/pre>\n<p>\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u044d\u0442\u043e \u0432\u0441\u0435 \u0432 AWS:<\/p>\n<pre><code class=\"bash\">sls deploy<\/code><\/pre>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0443\u044e \u043a S3:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"750\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2de\/28b\/134\/2de28b1340bf8da4da105f708717a006.png\" data-width=\"2000\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u044d\u0442\u043e \u0432\u0441\u0435 \u0432 AWS Console, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 <strong>Source S3<\/strong>. \u0418 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432 <strong>Destination S3<\/strong>:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"652\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/200\/005\/1af\/2000051aff8edda26c7d1cdc64566d86.gif\" data-width=\"1280\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043a\u043e\u0434 &#8212; \u043f\u0440\u043e\u0448\u0443 \u0441\u0434\u0435\u043b\u0430\u0442\u044c PR \u0432 <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0442<\/a> \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043c\u0438\u0440 \u043b\u0443\u0447\u0448\u0435 \u0432\u043c\u0435\u0441\u0442\u0435.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/648519\/\"> https:\/\/habr.com\/ru\/post\/648519\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0447\u0430\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u0435\u0431 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 &#8212; \u043d\u0430\u0440\u0435\u0437\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438. \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0432\u0430\u0448\u0435\u043c\u0443 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044e \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Serverless framework + Lambda + S3.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>\u041b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043b\u0443\u0448\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432 S3 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f. \u041d\u0430 \u0432\u0445\u043e\u0434\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u0438\u043c\u044f S3 \u0438 \u043a\u043b\u044e\u0447 \u0444\u0430\u0439\u043b\u0430.\u00a0\u0414\u0430\u043b\u0435\u0435 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0442\u0430\u043a\u043e\u0439: \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u043b\u044f\u043c\u0431\u0434\u0443, \u043d\u0430\u0440\u0435\u0437\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438\u0445 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 S3. \u041e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0432 \u0434\u0440\u0443\u0433\u043e\u0439, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0432 \u0442\u043e\u0442 \u0436\u0435 S3 \u0432\u044b\u0437\u043e\u0432\u0435\u0442\u0441\u044f \u043b\u044f\u043c\u0431\u0434\u0430 \u043e\u043f\u044f\u0442\u044c \u0438 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0435\u0437\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u0430\u0440\u0435\u0437\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 (\u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u0430\u044f \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u044f, \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c). \u042d\u0442\u043e \u0432\u0441\u0435 \u0432 \u0442\u0435\u043e\u0440\u0438\u0438. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.\u00a0<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0440\u0435\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/www.ffmpeg.org\/\" rel=\"noopener noreferrer nofollow\">ffmpeg<\/a>. \u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0435 \u0438 \u043e\u0431\u0440\u0435\u0437\u0430\u043d\u043d\u043e\u0435 \u0434\u043e 300\u0445300px:\u00a0<\/p>\n<pre><code class=\"bash\">ffmpeg -i source.jpg -filter:v \"scale=300:-1,crop=300:300:0:0\" -y crop300x300.jpg<\/code><\/pre>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 thumb:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0441\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u043d\u043e \u0432 \u043b\u044f\u043c\u0431\u0434\u0435 \u043d\u0435\u0442 ffmpeg. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/ffmpeg\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a<\/a> \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0432 \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0439.\u00a0\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0438\u0437 JavaScript \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f child_process:<\/p>\n<pre><code class=\"javascript\">const childProcess = require('child_process') spawnPromise(command, argsarray, envOptions) {     return new Promise((resolve, reject) => {       const childProc = childProcess.spawn(command, argsarray, envOptions || { env: process.env, cwd: process.cwd() }),         resultBuffers = []       childProc.stdout.on('data', buffer => {         resultBuffers.push(buffer)       })       childProc.stderr.on('data', buffer => console.log(buffer.toString()))       childProc.on('exit', (code, signal) => {         if (code || signal) {           reject(`${command} failed with ${code || signal}`)         } else {           resolve(Buffer.concat(resultBuffers).toString().trim())         }       })     })   }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u043c\u0435\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0438\u0437 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438: \u0448\u0438\u0440\u0438\u043d\u0443, \u0432\u044b\u0441\u043e\u0442\u0443, \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 (\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c, \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0435\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043d\u0438\u043c\u043e\u043a \u043d\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d). \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f <a href=\"https:\/\/ffmpeg.org\/ffprobe.html\" rel=\"noopener noreferrer nofollow\">ffprobe<\/a>. \u0422\u0430\u043a\u0436\u0435, \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/ffprobe\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a<\/a> \u0432 \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0435.\u00a0\u0418 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435:<\/p>\n<pre><code class=\"javascript\">async getResolution(inputFile) {     try {       let rotate = await this.spawnPromise(         config.FFPROBE,         [           '-v', 'error',           '-select_streams', 'v:0',           '-show_entries',           'stream_tags=rotate',           '-of', 'default=nw=1:nk=1',           inputFile],       )       rotate = rotate || 0       const str = await this.spawnPromise(         config.FFPROBE,         [           '-v', 'error',           '-select_streams', 'v:0',           '-show_entries',           'stream=width,height',           '-of', 'csv=s=x:p=0',           inputFile],       )        let [width, height] = String(str).split('x')        if (String(rotate) === '90' || String(rotate) === '-90' || String(rotate) === '270' || String(rotate) ===         '-270') {         [width, height] = [height, width]       }       return { width, height, rotate }     } catch (e) {       console.log('ffprobe error', e)       return { width: 1080, height: 1920 }     }   }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u0437\u043d\u0430\u044f \u043c\u0435\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0444\u0430\u0439\u043b\u0435, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"java\">async thumb(inputFile, outputFile, filterData, imageData = {}) {     try {       const isVertical = Number(imageData.width) &lt; Number(imageData.height)       let filter       if (filterData.crop) {         const newImageWidth = isVertical ? filterData.height : filterData.width         const newImageHeight = isVertical ? filterData.width : filterData.height         const resizeK = newImageWidth \/ imageData.width          const x = parseInt(isVertical ? 0 : (resizeK * imageData.width \/ 2 - newImageWidth \/ 2))         const y = parseInt(isVertical ? (resizeK * imageData.height \/ 2 - newImageHeight \/ 2) : 0)          if (newImageWidth === newImageHeight) {           \/\/ square           filter = `scale=${isVertical             ? `${newImageWidth}:-1`             : `-1:${newImageWidth}`},crop=${newImageWidth}:${newImageHeight}:${x}:${y}`         } else {           filter = `scale=${isVertical             ? `${newImageWidth}:-1`             : `-1:${newImageWidth}`},crop=${newImageWidth}:${newImageHeight}:${x}:${y}`         }       } else {         const newImageWidth = isVertical ? filterData.height : filterData.width         filter = `scale=${newImageWidth}:-1`       }        if (Number(imageData.rotate) !== 0) {         const times = Number(imageData.rotate) \/ 90         for (let i = 0; i &lt; times; i++) {           filter += `,transpose=1`         }       }       const callData = [         '-v',         'error',         '-noautorotate',         '-i',         inputFile,         '-filter:v',         filter,         '-y',       ]       return await this.spawnPromise(         config.FFMPEG,         [           ...callData,           outputFile],       )     } catch (e) {       console.log('ffmpeg error', e)     }<\/code><\/pre>\n<p>\u042f \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u043b \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438 \u043f\u0435\u0440\u0432\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u044d\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438\u0437 iPhone. \u0422\u0430\u043a \u043a\u0430\u043a Apple \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0441\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0435\u0449\u0435 \u043d\u0435 \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442. \u041f\u043e\u043a\u0430 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u043d\u0430\u0448\u0435\u043b \u044d\u0442\u043e \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/blob\/main\/lib\/tifig\" rel=\"noopener noreferrer nofollow\">tifig<\/a> \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0432\u0441\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 \u044f \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u043b \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b \u0434\u0430\u0432\u043d\u043e \u0438 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c tifig \u0434\u043b\u044f \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043d\u043e \u0437\u0430\u0442\u043e \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u043c\u0438 <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\/tree\/main\/lib\" rel=\"noopener noreferrer nofollow\">\u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0430\u043c\u0438<\/a>.<\/p>\n<p>\u0418 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 heic \u0432 jpg:<\/p>\n<pre><code class=\"javascript\">heicToJpg(filePath, newFilePath) {     return new Promise((resolve, reject) => {       childProcess.exec(se([config.TIFIG, filePath, newFilePath]), { encoding: 'utf8' },         function (error, stdout, stderr) {           if (error) {             console.error(stderr)             return reject(error)           }           fs.unlinkSync(filePath)           return resolve(true)         })     })   }<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"javascript\"> module.exports.handler = async (event) => {   const s3Record = event.Records[0].s3   const sourceKey = s3Record.object.key   if (!file.isPhoto(sourceKey)) {     return true   }   await file.download(s3Record.bucket.name, sourceKey,file.getDownloadName(sourceKey))   const ext = file.getExt(sourceKey)   const isHeic = file.isHeic(sourceKey)    \/\/ convert iphone photo format to jpg   if (isHeic) {     await convert.heicToJpg(file.getDownloadName(sourceKey), file.getDownloadNameIfHeic(sourceKey, ext))   }    \/\/ create thumbs by config.RESOLUTIONS   const { width, height, rotate } = await convert.getResolution(file.getDownloadNameIfHeic(sourceKey, ext))   await Promise.all(config.RESOLUTIONS.map(r => createThumbAndUpload(s3Record, r, { width, height, rotate, ext })))    \/\/ remove unnecessary files   await file.remove(file.getDownloadName(sourceKey))   if (isHeic) {     await file.remove(file.getDownloadNameIfHeic(sourceKey, ext))   }   return true }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u0441\u0435\u043c config.RESOLUTIONS \u044d\u0442\u043e \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0442\u044c \u0432 \u043a\u0430\u043a\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432:<\/p>\n<pre><code class=\"javascript\">RESOLUTIONS: [   { name: '1920x1080', width: 1920, height: 1080 },   { name: '1280x720', width: 1280, height: 720 },   { name: 'crop600x400', crop: true, width: 600, height: 400 },   { name: 'crop300x300', crop: true, width: 300, height: 300 }, ]<\/code><\/pre>\n<p>\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u0432\u0441\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u0435.\u00a0\u0418\u0442\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u043a\u043e\u043d\u0444\u0438\u0433 \u0434\u0435\u043f\u043b\u043e\u044f Serverless Framework \u0441 \u043f\u043e\u044f\u0441\u043d\u044f\u044e\u0449\u0438\u043c\u0438 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"yaml\">service: service-lambda-thumb-tifig frameworkVersion: '2 || 3' app: lambda-thumb-tifig  package:   # \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043b\u0438\u0448\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430 \u043b\u044f\u043c\u0431\u0434\u044b, \u0447\u0442\u043e\u0431 \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u0435\u0435 \u0440\u0430\u0437\u043c\u0435\u0440   patterns:     - '!node_modules\/aws-sdk'     - '!node_modules\/@aws-cdk'     - '!node_modules\/serverless'     - '!node_modules\/serverless-lift'     - '!.idea'     - '!yarn.lock'     - '!yarn.error.log'     - '!README.md'     - '!.gitignore'     - '!package.json'     - '!lib'     - '!.git'  custom:   name: 'lambda-thumb-tifig'   environment: 'prod'   region: 'us-east-1'   lambda_prefix: ${self:custom.environment}-${self:custom.name}  # \u041e\u0442\u043c\u0435\u0447\u0443 \u0447\u0442\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0430\u0432\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 S3 \u0434\u043b\u044f \u0434\u0435\u043c\u043e.  # \u041d\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0443\u0436\u0435 \u0440\u0430\u043d\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 S3. # \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u0444\u0438\u0433 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0435 S3 constructs:   sourceBucket:     type: storage   destinationBucket:     type: storage   provider:   name: aws   lambdaHashingVersion: '20201221'   environment:     DESTINATION_BUCKET: ${construct:destinationBucket.bucketName}     REGION: ${self:custom.region}   region: ${self:custom.region}   runtime: nodejs14.x   iamRoleStatements:     - Effect: \"Allow\"       Action:         - \"s3:PutBucketNotification\"         - \"s3:GetObject\"         - \"s3:PutObject\"       Resource:         Fn::Join:           - \"\"           - - \"arn:aws:s3:::*\"     - Effect: \"Allow\"       Action:         - \"rds:*\"       Resource: \"*\"  plugins:   - serverless-lift  functions:   # \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043b\u044f\u043c\u0431\u0434\u0430-\u0444\u0443\u043d\u043a\u0446\u0438\u044e   ffmpeg-tifig:     # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u043f\u0430\u043c\u044f\u0442\u044c \u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440 \u043b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438      # \u0434\u043b\u044f \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f     # \u0442\u0430\u043a\u0438\u0435 \u043b\u044f\u043c\u0431\u0434\u044b \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0440\u043e\u0436\u0435, \u043d\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435      # \u044f \u043d\u0435 \u0432\u044b\u0445\u043e\u0436\u0443 \u0437\u0430 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043b\u0438\u043c\u0438\u0442\u044b \u0432 \u043c\u0435\u0441\u044f\u0446 - \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c      # \u0441\u0430\u043c\u044b\u0439 \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442     memorySize: 10240     name: ${self:custom.lambda_prefix}     handler: src\/index.handler     # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438     timeout: 300     # \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a \u0441\u043e\u0431\u0438\u0442\u0438\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432 S3 Source     events:       - s3:           bucket: ${construct:sourceBucket.bucketName}           existing: true     # \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0441\u043b\u043e\u0439 Lib \u043a \u043b\u044f\u043c\u0431\u0434\u0430-\u0444\u0443\u043d\u043a\u0446\u0438\u0438     layers:       - { Ref: LibLambdaLayer }  # \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043b\u044f\u043c\u0431\u0434\u0430 \u0441\u043b\u043e\u0439 \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0432 \u043d\u0435\u0433\u043e \u0432\u0441\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043f\u043a\u0438 lib. layers:   lib:     path: lib <\/code><\/pre>\n<p>\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u044d\u0442\u043e \u0432\u0441\u0435 \u0432 AWS:<\/p>\n<pre><code class=\"bash\">sls deploy<\/code><\/pre>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043b\u044f\u043c\u0431\u0434\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0443\u044e \u043a S3:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u044d\u0442\u043e \u0432\u0441\u0435 \u0432 AWS Console, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 <strong>Source S3<\/strong>. \u0418 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432 <strong>Destination S3<\/strong>:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043a\u043e\u0434 &#8212; \u043f\u0440\u043e\u0448\u0443 \u0441\u0434\u0435\u043b\u0430\u0442\u044c PR \u0432 <a href=\"https:\/\/github.com\/avkos\/lambda-ffmpeg-tifig\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0442<\/a> \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043c\u0438\u0440 \u043b\u0443\u0447\u0448\u0435 \u0432\u043c\u0435\u0441\u0442\u0435.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/648519\/\"> https:\/\/habr.com\/ru\/post\/648519\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-328779","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328779","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=328779"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328779\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=328779"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=328779"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=328779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}