-
Notifications
You must be signed in to change notification settings - Fork 0
/
auphonic.js
executable file
·237 lines (202 loc) · 8.99 KB
/
auphonic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#!/usr/bin/env node
const path = require('path')
const program = require('commander')
const request = require('request')
const config = require('./config')
const fs = require('fs')
const process = require('process')
const commands = require('./commands')
//set up the command line interface with the commander module
program
.version(config.version)
.usage('[options] <file ...>')
.description('Processes each file with the auphonic API. Works with video files, but for faster upload it is better to split videos and only run this command on the audio channel.')
.option('--output-suffix <string>', 'Suffix for output filenames [auphonic]', 'auphonic')
.option('--output-folder <string>', 'Folder for output filenames [auphonic]', 'auphonic')
.option('--output-extension [string]', 'Extension for output filenames [.m4a]', '.m4a')
.option('-u, --username <username>', 'Username for Auphonic API account (required)')
.option('-p, --password <password>', 'Password for Auphonic API account (required)')
.option('-n, --noise-reduction', 'Apply noise reduction [false]', false)
.option('-l, --loudness-target <amount>', "Loudness target in dB [-18]", -18, parseInt)
.option('--batch-size <amount>', "Simultaneous number of concurrent files to process with auphonic", 8, parseInt)
.option('-v, --verbose', 'Logs information about execution')
.option('--debug', 'Dump debugging info to the console')
.parse(process.argv)
//expand globs in file arguments
let filenames = commands.expandGlobsSync(program.args)
//typecast arguments
if (typeof program.loudnessTarget == "string")
program.loudnessTarget = parseInt(program.loudnessTarget)
//command-line username and password override the config file
if (program.username)
config.auphonicUsername = program.username
if (program.password)
config.auphonicPassword = program.password
//assert that an Auphonic user has been configured
config.requireAuphonicUser()
//ensure that the output folder exists
commands.ensureOutputFolder(program)
//run the Auphonic command on all files
commands.runCommandAllConcurrentBatches(program, filenames, auphonicCommand, parseInt(program.batchSize))
//sequence the creation of production, uploading of file, etc. with the Auphonic API
function auphonicCommand (options, filename, metadata) {
//first the production is created, file is uploaded and the production is started
//these all resolve to the production uuid
let pUuid = createProductionAsync(options, filename)
.then( uuid => uploadFileToProductionAsync(options,filename, uuid))
.then( uuid => startProductionAsync(options, filename, uuid))
//now wait until the production is finished processing
//this resolves to the auphonic file data that has a url
let pAuphonicFileData = pUuid.then (uuid => awaitProductionAsync(options, filename, uuid))
//now download the file
//this resolves to information about the download
let pDownloadResult = pAuphonicFileData.then(auphonicFileData => getFileAsync(options, filename, auphonicFileData))
//resolve all of these promises and pass the results to the delete production method
return Promise.all([pUuid, pAuphonicFileData, pDownloadResult])
.then(([uuid, auphonicFileData, downloadResult]) => deleteProductionAsync(options, filename, uuid, auphonicFileData, downloadResult))
.then(() => {})
.catch(err => console.log("Error running command: ", err))
}
//create a new production in Auphonic
//resolves to the uuid of the production
function createProductionAsync(options, filename) {
return new Promise( (resolve, reject) => {
if (options.verbose)
console.log("Creating Auphonic production for", filename)
request.post({
url: `https://${config.auphonicUsername}:${config.auphonicPassword}@auphonic.com/api/productions.json`,
json: true,
body: {
output_files: [{format:'aac', ending:'m4a'}],
algorithms: {leveler:true, normloudness: true, loudnesstarget: options.loudnessTarget, denoise: options.denoise},
title: `TVT - ${filename}`,
tags: ['tvt', 'tuts video tools']
}
}, (err, response, body) => {
if (err)
reject(err)
else if (body.status_code != 200)
reject(`Auphonic API request failed with ${body.status_code}: ${body.error_message}`)
else
resolve(body.data.uuid)
}
)
})
}
//upload the file to the auphonic production with the given uuid
//resolves to the uuid of the production
function uploadFileToProductionAsync(options, filename, uuid) {
//returns a promise that resolves or rejects according to the results of the filters
return new Promise( (resolve, reject) => {
if (options.verbose)
console.log("Uploading file to auphonic", filename)
//create the form data
let formData = {
input_file: fs.createReadStream(filename)
}
//create a request for uploading the file
request.post({
url: `https://${config.auphonicUsername}:${config.auphonicPassword}@auphonic.com/api/production/${uuid}/upload.json`,
json: true,
formData: formData
}, (err, response, body) => {
if (err)
reject(err)
else if (body.status_code != 200)
reject(`Auphonic API request failed with ${body.status_code}: ${body.error_message}`)
else
resolve(uuid)
}
)
})
}
//start the production
function startProductionAsync(options, filename, uuid) {
return new Promise( (resolve, reject) => {
if (options.verbose)
console.log("Starting Auphonic production for ", filename)
request.post({
url: `https://${config.auphonicUsername}:${config.auphonicPassword}@auphonic.com/api/production/${uuid}/start.json`,
json: true
}, (err, response, body) => {
if (err)
reject(err)
else if (body.status_code != 200)
reject(`Auphonic API request failed with ${body.status_code}: ${body.error_message}`)
else
resolve(uuid)
}
)
})
}
//poll the production until the status is "Done"
//resolves to the url link to the downloadable result file
function awaitProductionAsync(options, filename, uuid) {
return new Promise ( (resolve, reject) => {
request.get({
url: `https://${config.auphonicUsername}:${config.auphonicPassword}@auphonic.com/api/production/${uuid}.json`,
json: true
}, (err, response, body) => {
if (err)
reject(err)
else if (body.status_code != 200)
reject(`Auphonic API request failed with ${body.status_code}: ${body.error_message}`)
else {
const data = body.data
if (options.verbose)
console.log(`Received production status for ${filename}: ${data.status_string}`)
//if status is done, resolve
if (data.status == 3) {
resolve(data.output_files[0].download_url)
}
//if the status is an error, reject
else if (data.status == 2 || data.status == 9 || data.status == 13 || data.status == 11)
reject(`Product returned error status (${data.status}): ${data.status_string}`)
//otherwise pause for a second and retry
else {
setTimeout(() => resolve(awaitProductionAsync(options, filename, uuid)), 2000)
}
}
}
)
})
}
//delete the production now that it is completed and the file has been downloaded
//!!! should not delete if the download failed?
function deleteProductionAsync(options, filename, uuid, auphonicFileData, downloadResult) {
return new Promise( (resolve, reject) => {
if (options.verbose)
console.log("Cleaning up Auphonic production for ", filename)
request.delete({
url: `https://${config.auphonicUsername}:${config.auphonicPassword}@auphonic.com/api/production/${uuid}.json`,
json: true
}, (err, response, body) => {
if (err)
reject(err)
else if (body.status_code != 200)
reject(`Auphonic API request failed with ${body.status_code}: ${body.error_message}`)
else
resolve(uuid)
}
)
})
}
//download the file from the given download url
//and save it to the appropriate location
function getFileAsync(options, filename, downloadUrl) {
//get the extension of the download file
const downloadExt = '.'+downloadUrl.split('.').pop()
//and the path part of the download url
const downloadUrlPath = downloadUrl.split("//")[1]
//create an output filename by combining the input file name with the extension of the file provided by auphonic
const outputFilename = commands.changeExtension(commands.getOutputFilename(options, filename), downloadExt)
return new Promise ((resolve, reject) => {
if (options.verbose)
console.log(`Downloading file from auphonic at URL ${downloadUrlPath} to ${outputFilename}`)
//download the file
request.get(`https://${config.auphonicUsername}:${config.auphonicPassword}@${downloadUrlPath}`)
.on('error', (err) => reject(err))
.pipe(fs.createWriteStream(outputFilename))
.on('finish', () => resolve(outputFilename))
})
}