'use strict' var fs = require("fs"); var os = require("os"); const {encode: encodeQuery} = require('querystring') const {strictEqual} = require('assert') const envPaths = require('env-paths') const FileCache = require('@derhuerst/http-basic/lib/FileCache').default const {extname} = require('path') var ProgressBar = require("progress"); var request = require('@derhuerst/http-basic') const {createGunzip} = require('zlib') const {pipeline} = require('stream') var ffmpegPath = require("."); var pkg = require("./package"); const exitOnError = (err) => { console.error(err) process.exit(1) } const exitOnErrorOrWarnWith = (msg) => (err) => { if (err.statusCode === 404) console.warn(msg) else exitOnError(err) } if (!ffmpegPath) { exitOnError('ffmpeg-static install failed: No binary found for architecture') } try { if (fs.statSync(ffmpegPath).isFile()) { console.info('ffmpeg is installed already.') process.exit(0) } } catch (err) { if (err && err.code !== 'ENOENT') exitOnError(err) } let agent = false // https://github.com/request/request/blob/a9557c9e7de2c57d92d9bab68a416a87d255cd3d/lib/getProxyFromURI.js#L66-L71 const proxyUrl = ( process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy ) if (proxyUrl) { const HttpsProxyAgent = require('https-proxy-agent') const {hostname, port, protocol} = new URL(proxyUrl) agent = new HttpsProxyAgent({hostname, port, protocol}) } // https://advancedweb.hu/how-s3-signed-urls-work/ const normalizeS3Url = (url) => { url = new URL(url) if (url.hostname.slice(-17) !== '.s3.amazonaws.com') return url.href const query = Array.from(url.searchParams.entries()) .filter(([key]) => key.slice(0, 6).toLowerCase() !== 'x-amz-') .reduce((query, [key, val]) => ({...query, [key]: val}), {}) url.search = encodeQuery(query) return url.href } strictEqual( normalizeS3Url('https://example.org/foo?bar'), 'https://example.org/foo?bar' ) strictEqual( normalizeS3Url('https://github-production-release-asset-2e65be.s3.amazonaws.com/29458513/26341680-4231-11ea-8e36-ae454621d74a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20200405%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200405T225358Z&X-Amz-Expires=300&X-Amz-Signature=d6415097af04cf62ea9b69d3c1a421278e96bcb069afa48cf021ec3b6941bae4&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Ddarwin-x64&response-content-type=application%2Foctet-stream'), 'https://github-production-release-asset-2e65be.s3.amazonaws.com/29458513/26341680-4231-11ea-8e36-ae454621d74a?actor_id=0&response-content-disposition=attachment%3B%20filename%3Ddarwin-x64&response-content-type=application%2Foctet-stream' ) const cache = new FileCache(envPaths(pkg.name).cache) cache.getCacheKey = (url) => { return FileCache.prototype.getCacheKey(normalizeS3Url(url)) } const isGzUrl = (url) => { const path = new URL(url).pathname.split('/') const filename = path[path.length - 1] return filename && extname(filename) === '.gz' } const noop = () => {} function downloadFile(url, destinationPath, progressCallback = noop) { let fulfill, reject; let totalBytes = 0; const promise = new Promise((x, y) => { fulfill = x; reject = y; }); request('GET', url, { agent, followRedirects: true, maxRedirects: 3, gzip: true, cache, timeout: 30 * 1000, // 30s retry: true, }, (err, response) => { if (err || response.statusCode !== 200) { err = err || new Error('Download failed.') if (response) { err.url = response.url err.statusCode = response.statusCode } reject(err) return; } const file = fs.createWriteStream(destinationPath); const streams = isGzUrl(url) ? [response.body, createGunzip(), file] : [response.body, file] pipeline( ...streams, (err) => { if (err) { err.url = response.url err.statusCode = response.statusCode reject(err) } else fulfill() } ) if (!response.fromCache && progressCallback) { const cLength = response.headers["content-length"] totalBytes = cLength ? parseInt(cLength, 10) : null response.body.on('data', (chunk) => { progressCallback(chunk.length, totalBytes); }); } }); return promise; } let progressBar = null; function onProgress(deltaBytes, totalBytes) { if (totalBytes === null) return; if (!progressBar) { progressBar = new ProgressBar(`Downloading ffmpeg ${releaseName} [:bar] :percent :etas `, { complete: "|", incomplete: " ", width: 20, total: totalBytes }); } progressBar.tick(deltaBytes); } const release = ( process.env.FFMPEG_BINARY_RELEASE || pkg['ffmpeg-static']['binary-release-tag'] ) const releaseName = ( pkg['ffmpeg-static']['binary-release-name'] || release ) const arch = process.env.npm_config_arch || os.arch() const platform = process.env.npm_config_platform || os.platform() const baseUrl = `https://github.com/eugeneware/ffmpeg-static/releases/download/${release}` const downloadUrl = `${baseUrl}/${platform}-${arch}.gz` const readmeUrl = `${baseUrl}/${platform}-${arch}.README` const licenseUrl = `${baseUrl}/${platform}-${arch}.LICENSE` downloadFile(downloadUrl, ffmpegPath, onProgress) .then(() => { fs.chmodSync(ffmpegPath, 0o755) // make executable }) .catch(exitOnError) .then(() => downloadFile(readmeUrl, `${ffmpegPath}.README`)) .catch(exitOnErrorOrWarnWith('Failed to download the ffmpeg README.')) .then(() => downloadFile(licenseUrl, `${ffmpegPath}.LICENSE`)) .catch(exitOnErrorOrWarnWith('Failed to download the ffmpeg LICENSE.'))