├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── configs.js ├── errors.js ├── ffmpeg.js ├── presets.js ├── utils.js └── video.js ├── node_modules └── when │ ├── .editorconfig │ ├── .jshintrc │ ├── LICENSE.txt │ ├── README.md │ ├── apply.js │ ├── cancelable.js │ ├── debug.js │ ├── delay.js │ ├── package.json │ ├── parallel.js │ ├── pipeline.js │ ├── sequence.js │ ├── timed.js │ ├── timeout.js │ └── when.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ 2 | /nbproject/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Damiano Ciarla, Ross and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-ffmpeg 2 | =========== 3 | 4 | [FFmpeg](http://ffmpeg.org/) module for [Node](http://nodejs.org/). This library provides a set of functions and utilities to abstract commands-line usage of ffmpeg. To use this library requires that ffmpeg is already installed (including all necessary encoding libraries like libmp3lame or libx264) 5 | 6 | You can install this module using [npm](http://github.com/isaacs/npm): 7 | 8 | npm install ffmpeg 9 | 10 | ## Usage 11 | 12 | To start using this library, you must include it in your project and then you can either use the callback function or through the [promise](https://github.com/cujojs/when) library: 13 | 14 | var ffmpeg = require('ffmpeg'); 15 | 16 | Use the callback function 17 | ```js 18 | try { 19 | new ffmpeg('/path/to/your_movie.avi', function (err, video) { 20 | if (!err) { 21 | console.log('The video is ready to be processed'); 22 | } else { 23 | console.log('Error: ' + err); 24 | } 25 | }); 26 | } catch (e) { 27 | console.log(e.code); 28 | console.log(e.msg); 29 | } 30 | ``` 31 | Use the approach with the library promise 32 | ```js 33 | try { 34 | var process = new ffmpeg('/path/to/your_movie.avi'); 35 | process.then(function (video) { 36 | console.log('The video is ready to be processed'); 37 | }, function (err) { 38 | console.log('Error: ' + err); 39 | }); 40 | } catch (e) { 41 | console.log(e.code); 42 | console.log(e.msg); 43 | } 44 | ``` 45 | ## The video object 46 | 47 | Each time you create a new instance, this library provides a new object to retrieve the information of the video, the ffmpeg configuration and all methods to make the necessary conversions: 48 | ```js 49 | try { 50 | var process = new ffmpeg('/path/to/your_movie.avi'); 51 | process.then(function (video) { 52 | // Video metadata 53 | console.log(video.metadata); 54 | // FFmpeg configuration 55 | console.log(video.info_configuration); 56 | }, function (err) { 57 | console.log('Error: ' + err); 58 | }); 59 | } catch (e) { 60 | console.log(e.code); 61 | console.log(e.msg); 62 | } 63 | ``` 64 | ## Preset functions 65 | 66 | The video object contains a set of functions that allow you to perform specific operations independent of the settings for the conversion. In all the functions you can use the approach with the callback function or with the promise object 67 | 68 | ### *video.fnExtractSoundToMP3 (destionationFileName, callback)* 69 | 70 | This function extracts the audio stream of a video into an mp3 file 71 | 72 | Params: 73 | 74 | * __destionationFileName__: Full path of the new file: 75 | > /path/to/your_audio_file.mp3 76 | 77 | * __callback__: *(optional)* If specified at the end of the process it will return the path of the new audio file: 78 | > function (error, file) 79 | 80 | Example: 81 | ```js 82 | try { 83 | var process = new ffmpeg('/path/to/your_movie.avi'); 84 | process.then(function (video) { 85 | // Callback mode 86 | video.fnExtractSoundToMP3('/path/to/your_audio_file.mp3', function (error, file) { 87 | if (!error) 88 | console.log('Audio file: ' + file); 89 | }); 90 | }, function (err) { 91 | console.log('Error: ' + err); 92 | }); 93 | } catch (e) { 94 | console.log(e.code); 95 | console.log(e.msg); 96 | } 97 | ``` 98 | ### *video.fnExtractFrameToJPG(destinationFolder, settings, callback)* 99 | 100 | This function takes care of extracting one or more frames from the video that is being developed. At the end of the operation will return an array containing the list of extracted images 101 | 102 | Params: 103 | 104 | * __destinationFolder__: Destination folder for the frames generated: 105 | > /path/to/save_your_frames 106 | 107 | * __settings__: *(optional)* Settings to change the default settings: 108 | ```js 109 | { 110 | start_time : null // Start time to recording 111 | , duration_time : null // Duration of recording 112 | , frame_rate : null // Number of the frames to capture in one second 113 | , size : null // Dimension each frame 114 | , number : null // Total frame to capture 115 | , every_n_frames : null // Frame to capture every N frames 116 | , every_n_seconds : null // Frame to capture every N seconds 117 | , every_n_percentage : null // Frame to capture every N percentage range 118 | , keep_pixel_aspect_ratio : true // Mantain the original pixel video aspect ratio 119 | , keep_aspect_ratio : true // Mantain the original aspect ratio 120 | , padding_color : 'black' // Padding color 121 | , file_name : null // File name 122 | } 123 | ``` 124 | * __callback__: *(optional)* If specified at the end of the process will be returned list of paths of frames created: 125 | > function (error, files) 126 | 127 | Example: 128 | ```js 129 | try { 130 | var process = new ffmpeg('/path/to/your_movie.avi'); 131 | process.then(function (video) { 132 | // Callback mode 133 | video.fnExtractFrameToJPG('/path/to/save_your_frames', { 134 | frame_rate : 1, 135 | number : 5, 136 | file_name : 'my_frame_%t_%s' 137 | }, function (error, files) { 138 | if (!error) 139 | console.log('Frames: ' + files); 140 | }); 141 | }, function (err) { 142 | console.log('Error: ' + err); 143 | }); 144 | } catch (e) { 145 | console.log(e.code); 146 | console.log(e.msg); 147 | } 148 | ``` 149 | ### *video.fnAddWatermark(watermarkPath, newFilepath, settings, callback)* 150 | 151 | This function takes care of adding a watermark to the video that is being developed. You can specify the exact position in which position the image 152 | 153 | Params: 154 | 155 | * __watermarkPath__: The full path where the image is stored to add as watermark: 156 | > /path/to/retrieve/watermark_file.png 157 | 158 | * __newFilepath__: *(optional)* Name of the new video. If not specified will be created by the function: 159 | > /path/to/save/your_file_video.mp4 160 | 161 | * __settings__: *(optional)* Settings to change the default settings: 162 | ```js 163 | { 164 | position : "SW" // Position: NE NC NW SE SC SW C CE CW 165 | , margin_nord : null // Margin nord 166 | , margin_sud : null // Margin sud 167 | , margin_east : null // Margin east 168 | , margin_west : null // Margin west 169 | }; 170 | ``` 171 | * __callback__: *(optional)* If specified at the end of the process it will return the path of the new video containing the watermark: 172 | > function (error, files) 173 | 174 | Example: 175 | ```js 176 | try { 177 | var process = new ffmpeg('/path/to/your_movie.avi'); 178 | process.then(function (video) { 179 | // Callback mode 180 | video.fnAddWatermark('/path/to/retrieve/watermark_file.png', '/path/to/save/your_file_video.mp4', { 181 | position : 'SE' 182 | }, function (error, file) { 183 | if (!error) 184 | console.log('New video file: ' + file); 185 | }); 186 | }, function (err) { 187 | console.log('Error: ' + err); 188 | }); 189 | } catch (e) { 190 | console.log(e.code); 191 | console.log(e.msg); 192 | } 193 | ``` 194 | ## Custom settings 195 | 196 | In addition to the possibility of using the preset, this library provides a variety of settings with which you can modify to your liking settings for converting video 197 | 198 | * __video.setDisableAudio()__: Disables audio encoding 199 | 200 | * __video.setDisableVideo()__: Disables video encoding 201 | 202 | * __video.setVideoFormat(format)__: Sets the new video format. Example: 203 | 204 | video.setVideoFormat('avi') 205 | 206 | * __video.setVideoCodec(codec)__: Sets the new audio codec. Example: 207 | 208 | video.setVideoCodec('mpeg4') 209 | 210 | * __video.setVideoBitRate(bitrate)__: Sets the video bitrate in kb. Example: 211 | 212 | video.setVideoBitRate(1024) 213 | 214 | * __video.setVideoFrameRate(framerate)__: Sets the framerate of the video. Example: 215 | 216 | video.setVideoFrameRate(25) 217 | 218 | * __video.setVideoStartTime(time)__: Sets the start time. You can specify the value in seconds or in date time format. Example: 219 | ```js 220 | // Seconds 221 | video.setVideoStartTime(13) 222 | 223 | // Date time format 224 | video.setVideoStartTime('00:00:13') 225 | ``` 226 | * __video.setVideoDuration(duration)__: Sets the duration. You can specify the value in seconds or in date time format. Example: 227 | ```js 228 | // Seconds 229 | video.setVideoDuration(100) 230 | 231 | // Date time format 232 | video.setVideoDuration('00:01:40') 233 | ``` 234 | * __video.setVideoAspectRatio(aspect)__: Sets the new aspetc ratio. You can specify the value with a number or with a string in the format 'xx:xx'. Example: 235 | ```js 236 | // Value 237 | video.setVideoAspectRatio(1.77) 238 | 239 | // Format xx:xx 240 | video.setVideoAspectRatio('16:9') 241 | ``` 242 | * __video.setVideoSize(size, keepPixelAspectRatio, keepAspectRatio, paddingColor)__: Set the size of the video. This library can handle automatic resizing of the video. You can also apply a padding automatically keeping the original aspect ratio 243 | 244 | The following size formats are allowed to be passed to _size_: 245 | 246 | > 640x480 _Fixed size (plain ffmpeg way)_ 247 | 248 | > 50% _Percental resizing_ 249 | 250 | > ?x480 _Fixed height, calculate width_ 251 | 252 | > 640x? _Fixed width, calculate height_ 253 | 254 | Example: 255 | ```js 256 | // In this example, the video will be automatically resized to 640 pixels wide and will apply a padding white 257 | video.setVideoSize('640x?', true, true, '#fff') 258 | 259 | // In this example, the video will be resized to 640x480 pixel, and if the aspect ratio is different the video will be stretched 260 | video.setVideoSize('640x480', true, false) 261 | ``` 262 | * __video.setAudioCodec(codec)__: Sets the new audio codec. Example: 263 | 264 | video.setAudioCodec('libfaac') 265 | 266 | * __video.setAudioFrequency(frequency)__: Sets the audio sample frequency for audio outputs in kb. Example: 267 | 268 | video.setAudioFrequency(48) 269 | 270 | * __video.setAudioChannels(channel)__: Sets the number of audio channels. Example: 271 | 272 | video.setAudioChannels(2) 273 | 274 | * __video.setAudioBitRate(bitrate)__: Sets the audio bitrate in kb. Example: 275 | 276 | video.setAudioBitRate(128) 277 | 278 | * __video.setAudioQuality(quality)__: Sets the audio quality. Example: 279 | 280 | video.setAudioQuality(128) 281 | 282 | * __video.setWatermark(watermarkPath, settings)__: Sets the watermark. You must specify the path where the image is stored to be inserted as watermark 283 | 284 | The possible settings (the values ​​shown are the default): 285 | 286 | * **position : "SW"** 287 | 288 | Position: NE NC NW SE SC SW C CE CW 289 | 290 | * **margin_nord : null** 291 | 292 | Margin nord (specify in pixel) 293 | 294 | * **margin_sud : null** 295 | 296 | Margin sud (specify in pixel) 297 | 298 | * **margin_east : null** 299 | 300 | Margin east (specify in pixel) 301 | 302 | * **margin_west : null** 303 | 304 | Margin west (specify in pixel) 305 | 306 | Example: 307 | 308 | // In this example will be added the watermark at the bottom right of the video 309 | video.setWatermark('/path/to/retrieve/watermark_file.png') 310 | 311 | ## Add custom options 312 | 313 | If the ffmpeg parameters are not present in the list of available function you can add it manually through the following function 314 | 315 | **video.addCommand(command, argument)** 316 | 317 | Example: 318 | ```js 319 | // In this example will be changed the output to avi format 320 | video.addCommand('-f', 'avi'); 321 | ``` 322 | ## Save the file 323 | 324 | After setting the desired parameters have to start the conversion process. To do this you must call the function 'save'. This method takes as input the final destination of the file and optionally a callback function. If the function callback is not specified it's possible use the promise object. 325 | 326 | **video.save(destionationFileName, callback)** 327 | 328 | Example: 329 | ```js 330 | try { 331 | var process = new ffmpeg('/path/to/your_movie.avi'); 332 | process.then(function (video) { 333 | 334 | video 335 | .setVideoSize('640x?', true, true, '#fff') 336 | .setAudioCodec('libfaac') 337 | .setAudioChannels(2) 338 | .save('/path/to/save/your_movie.avi', function (error, file) { 339 | if (!error) 340 | console.log('Video file: ' + file); 341 | }); 342 | 343 | }, function (err) { 344 | console.log('Error: ' + err); 345 | }); 346 | } catch (e) { 347 | console.log(e.code); 348 | console.log(e.msg); 349 | } 350 | ``` 351 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/ffmpeg'); -------------------------------------------------------------------------------- /lib/configs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic configuration 3 | */ 4 | module.exports = function () { 5 | this.encoding = 'utf8'; 6 | this.timeout = 0; 7 | this.maxBuffer = 200 * 1024 8 | } -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | // Error list with code and message 4 | var list = { 5 | 'empty_input_filepath' : { 'code' : 100, 'msg' : 'The input file path can not be empty' } 6 | , 'input_filepath_must_be_string' : { 'code' : 101, 'msg' : 'The input file path must be a string' } 7 | , 'invalid_option_name' : { 'code' : 102, 'msg' : 'The option "%s" is invalid. Check the list of available options' } 8 | , 'fileinput_not_exist' : { 'code' : 103, 'msg' : 'The input file does not exist' } 9 | , 'format_not_supported' : { 'code' : 104, 'msg' : 'The format "$s" is not supported by the version of ffmpeg' } 10 | , 'audio_channel_is_invalid' : { 'code' : 105, 'msg' : 'The audio channel "$s" is not valid' } 11 | , 'mkdir' : { 'code' : 106, 'msg' : 'Error occurred during creation folder: $s' } 12 | , 'extract_frame_invalid_everyN_options' : { 'code' : 107, 'msg' : 'You can specify only one option between everyNFrames and everyNSeconds' } 13 | , 'invalid_watermark' : { 'code' : 108, 'msg' : 'The watermark "%s" does not exists' } 14 | , 'invalid_watermark_position' : { 'code' : 109, 'msg' : 'Invalid watermark position "%s"' } 15 | , 'size_format' : { 'code' : 110, 'msg' : 'The format "%s" not supported by the function "setSize"' } 16 | , 'resolution_square_not_defined' : { 'code' : 111, 'msg' : 'The resolution for pixel aspect ratio is not defined' } 17 | , 'command_already_exists' : { 'code' : 112, 'msg' : 'The command "%s" already exists' } 18 | , 'codec_not_supported' : { 'code' : 113, 'msg' : 'The codec "$s" is not supported by the version of ffmpeg' } 19 | } 20 | 21 | /** 22 | * Return the error by the codename 23 | */ 24 | var renderError = function (codeName) { 25 | // Get the error object by the codename 26 | var params = [list[codeName].msg]; 27 | // Get the possible arguments 28 | if (arguments.length > 1) 29 | params = params.concat(Array.prototype.slice.call(arguments, 1)); 30 | // Call the function for replace the letter '%s' with the found arguments 31 | return { 'code' : list[codeName].code, 'msg' : util.format.apply(this, params) }; 32 | } 33 | 34 | module.exports.list = list; 35 | module.exports.renderError = renderError; -------------------------------------------------------------------------------- /lib/ffmpeg.js: -------------------------------------------------------------------------------- 1 | var when = require('when') 2 | , fs = require('fs'); 3 | 4 | var errors = require('./errors') 5 | , utils = require('./utils') 6 | , configs = require('./configs') 7 | , video = require('./video'); 8 | 9 | var ffmpeg = function (/* inputFilepath, settings, callback */) { 10 | 11 | /** 12 | * Retrieve the list of the codec supported by the ffmpeg software 13 | */ 14 | var _ffmpegInfoConfiguration = function (settings) { 15 | // New 'promise' instance 16 | var deferred = when.defer(); 17 | // Instance the new arrays for the format 18 | var format = { modules : new Array(), encode : new Array(), decode : new Array() }; 19 | // Make the call to retrieve information about the ffmpeg 20 | utils.exec([ffmpeg.bin,'-formats','2>&1'], settings, function (error, stdout, stderr) { 21 | // Get the list of modules 22 | var configuration = /configuration:(.*)/.exec(stdout); 23 | // Check if exists the configuration 24 | if (configuration) { 25 | // Get the list of modules 26 | var modules = configuration[1].match(/--enable-([a-zA-Z0-9\-]+)/g); 27 | // Scan all modules 28 | for (var indexModule in modules) { 29 | // Add module to the list 30 | format.modules.push(/--enable-([a-zA-Z0-9\-]+)/.exec(modules[indexModule])[1]); 31 | } 32 | } 33 | // Get the codec list 34 | var codecList = stdout.match(/ (DE|D|E) (.*) {1,} (.*)/g); 35 | // Scan all codec 36 | for (var i in codecList) { 37 | // Get the match value 38 | var match = / (DE|D|E) (.*) {1,} (.*)/.exec(codecList[i]); 39 | // Check if match is valid 40 | if (match) { 41 | // Get the value from the match 42 | var scope = match[1].replace(/\s/g,'') 43 | , extension = match[2].replace(/\s/g,''); 44 | // Check which scope is best suited 45 | if (scope == 'D' || scope == 'DE') 46 | format.decode.push(extension); 47 | if (scope == 'E' || scope == 'DE') 48 | format.encode.push(extension); 49 | } 50 | } 51 | // Returns the list of supported formats 52 | deferred.resolve(format); 53 | }); 54 | // Return 'promise' instance 55 | return deferred.promise; 56 | } 57 | 58 | /** 59 | * Get the video info 60 | */ 61 | var _videoInfo = function (fileInput, settings) { 62 | // New 'promise' instance 63 | var deferred = when.defer(); 64 | // Make the call to retrieve information about the ffmpeg 65 | utils.exec([ffmpeg.bin,'-i', utils.addQuotes(fileInput),'2>&1'], settings, function (error, stdout, stderr) { 66 | // Perse output for retrieve the file info 67 | var filename = /from \'(.*)\'/.exec(stdout) || [] 68 | , title = /(INAM|title)\s+:\s(.+)/.exec(stdout) || [] 69 | , artist = /artist\s+:\s(.+)/.exec(stdout) || [] 70 | , album = /album\s+:\s(.+)/.exec(stdout) || [] 71 | , track = /track\s+:\s(.+)/.exec(stdout) || [] 72 | , date = /date\s+:\s(.+)/.exec(stdout) || [] 73 | , is_synched = (/start: 0.000000/.exec(stdout) !== null) 74 | , duration = /Duration: (([0-9]+):([0-9]{2}):([0-9]{2}).([0-9]+))/.exec(stdout) || [] 75 | 76 | , container = /Input #0, ([a-zA-Z0-9]+),/.exec(stdout) || [] 77 | , video_bitrate = /bitrate: ([0-9]+) kb\/s/.exec(stdout) || [] 78 | , video_stream = /Stream #([0-9\.]+)([a-z0-9\(\)\[\]]*)[:] Video/.exec(stdout) || [] 79 | , video_codec = /Video: ([\w]+)/.exec(stdout) || [] 80 | , resolution = /(([0-9]{2,5})x([0-9]{2,5}))/.exec(stdout) || [] 81 | , pixel = /[SP]AR ([0-9\:]+)/.exec(stdout) || [] 82 | , aspect = /DAR ([0-9\:]+)/.exec(stdout) || [] 83 | , fps = /([0-9\.]+) (fps|tb\(r\))/.exec(stdout) || [] 84 | 85 | , audio_stream = /Stream #([0-9\.]+)([a-z0-9\(\)\[\]]*)[:] Audio/.exec(stdout) || [] 86 | , audio_codec = /Audio: ([\w]+)/.exec(stdout) || [] 87 | , sample_rate = /([0-9]+) Hz/i.exec(stdout) || [] 88 | , channels = /Audio:.* (stereo|mono)/.exec(stdout) || [] 89 | , audio_bitrate = /Audio:.* ([0-9]+) kb\/s/.exec(stdout) || [] 90 | , rotate = /rotate[\s]+:[\s]([\d]{2,3})/.exec(stdout) || []; 91 | // Build return object 92 | var ret = { 93 | filename : filename[1] || '' 94 | , title : title[2] || '' 95 | , artist : artist[1] || '' 96 | , album : album[1] || '' 97 | , track : track[1] || '' 98 | , date : date[1] || '' 99 | , synched : is_synched 100 | , duration : { 101 | raw : duration[1] || '' 102 | , seconds : duration[1] ? utils.durationToSeconds(duration[1]) : 0 103 | } 104 | , video : { 105 | container : container[1] || '' 106 | , bitrate : (video_bitrate.length > 1) ? parseInt(video_bitrate[1], 10) : 0 107 | , stream : video_stream.length > 1 ? parseFloat(video_stream[1]) : 0.0 108 | , codec : video_codec[1] || '' 109 | , resolution : { 110 | w : resolution.length > 2 ? parseInt(resolution[2], 10) : 0 111 | , h : resolution.length > 3 ? parseInt(resolution[3], 10) : 0 112 | } 113 | , resolutionSquare : {} 114 | , aspect : {} 115 | , rotate : rotate.length > 1 ? parseInt(rotate[1], 10) : 0 116 | , fps : fps.length > 1 ? parseFloat(fps[1]) : 0.0 117 | } 118 | , audio : { 119 | codec : audio_codec[1] || '' 120 | , bitrate : audio_bitrate[1] || '' 121 | , sample_rate : sample_rate.length > 1 ? parseInt(sample_rate[1], 10) : 0 122 | , stream : audio_stream.length > 1 ? parseFloat(audio_stream[1]) : 0.0 123 | , channels : { 124 | raw : channels[1] || '' 125 | , value : (channels.length > 0) ? ({ stereo : 2, mono : 1 }[channels[1]] || 0) : '' 126 | } 127 | } 128 | }; 129 | // Check if exist aspect ratio 130 | if (aspect.length > 0) { 131 | var aspectValue = aspect[1].split(":"); 132 | ret.video.aspect.x = parseInt(aspectValue[0], 10); 133 | ret.video.aspect.y = parseInt(aspectValue[1], 10); 134 | ret.video.aspect.string = aspect[1]; 135 | ret.video.aspect.value = parseFloat((ret.video.aspect.x / ret.video.aspect.y)); 136 | } else { 137 | // If exists horizontal resolution then calculate aspect ratio 138 | if(ret.video.resolution.w > 0) { 139 | var gcdValue = utils.gcd(ret.video.resolution.w, ret.video.resolution.h); 140 | // Calculate aspect ratio 141 | ret.video.aspect.x = ret.video.resolution.w / gcdValue; 142 | ret.video.aspect.y = ret.video.resolution.h / gcdValue; 143 | ret.video.aspect.string = ret.video.aspect.x + ':' + ret.video.aspect.y; 144 | ret.video.aspect.value = parseFloat((ret.video.aspect.x / ret.video.aspect.y)); 145 | } 146 | } 147 | // Save pixel ratio for output size calculation 148 | if (pixel.length > 0) { 149 | ret.video.pixelString = pixel[1]; 150 | var pixelValue = pixel[1].split(":"); 151 | ret.video.pixel = parseFloat((parseInt(pixelValue[0], 10) / parseInt(pixelValue[1], 10))); 152 | } else { 153 | if (ret.video.resolution.w !== 0) { 154 | ret.video.pixelString = '1:1'; 155 | ret.video.pixel = 1; 156 | } else { 157 | ret.video.pixelString = ''; 158 | ret.video.pixel = 0.0; 159 | } 160 | } 161 | // Correct video.resolution when pixel aspectratio is not 1 162 | if (ret.video.pixel !== 1 || ret.video.pixel !== 0) { 163 | if( ret.video.pixel > 1 ) { 164 | ret.video.resolutionSquare.w = parseInt(ret.video.resolution.w * ret.video.pixel, 10); 165 | ret.video.resolutionSquare.h = ret.video.resolution.h; 166 | } else { 167 | ret.video.resolutionSquare.w = ret.video.resolution.w; 168 | ret.video.resolutionSquare.h = parseInt(ret.video.resolution.h / ret.video.pixel, 10); 169 | } 170 | } 171 | // Returns the list of supported formats 172 | deferred.resolve(ret); 173 | }); 174 | // Return 'promise' instance 175 | return deferred.promise; 176 | } 177 | 178 | /** 179 | * Get the info about ffmpeg's codec and about file 180 | */ 181 | var _getInformation = function (fileInput, settings) { 182 | var deferreds = []; 183 | // Add promise 184 | deferreds.push(_ffmpegInfoConfiguration(settings)); 185 | deferreds.push(_videoInfo(fileInput, settings)); 186 | // Return defer 187 | return when.all(deferreds); 188 | } 189 | 190 | var __constructor = function (args) { 191 | // Check if exist at least one option 192 | if (args.length == 0 || args[0] == undefined) 193 | throw errors.renderError('empty_input_filepath'); 194 | // Check if first argument is a string 195 | if (typeof args[0] != 'string') 196 | throw errors.renderError('input_filepath_must_be_string'); 197 | // Get the input filepath 198 | var inputFilepath = args[0]; 199 | // Check if file exist 200 | if (!fs.existsSync(inputFilepath)) 201 | throw errors.renderError('fileinput_not_exist'); 202 | 203 | // New instance of the base configuration 204 | var settings = new configs(); 205 | // Callback to call 206 | var callback = null; 207 | 208 | // Scan all arguments 209 | for (var i = 1; i < args.length; i++) { 210 | // Check the type of variable 211 | switch (typeof args[i]) { 212 | case 'object' : 213 | utils.mergeObject(settings, args[i]); 214 | break; 215 | case 'function' : 216 | callback = args[i]; 217 | break; 218 | } 219 | } 220 | 221 | // Building the value for return value. Check if the callback is not a function. In this case will created a new instance of the deferred class 222 | var deferred = typeof callback != 'function' ? when.defer() : { promise : null }; 223 | 224 | when(_getInformation(inputFilepath, settings), function (data) { 225 | // Check if the callback is a function 226 | if (typeof callback == 'function') { 227 | // Call the callback function e return the new instance of 'video' class 228 | callback(null, new video(inputFilepath, settings, data[0], data[1])); 229 | } else { 230 | // Positive response 231 | deferred.resolve(new video(inputFilepath, settings, data[0], data[1])); 232 | } 233 | }, function (error) { 234 | // Check if the callback is a function 235 | if (typeof callback == 'function') { 236 | // Call the callback function e return the error found 237 | callback(error, null); 238 | } else { 239 | // Negative response 240 | deferred.reject(error); 241 | } 242 | }); 243 | 244 | // Return a possible promise instance 245 | return deferred.promise; 246 | } 247 | 248 | return __constructor.call(this, arguments); 249 | }; 250 | 251 | // configurable path to ffmpeg binaries. Default uses ffmpeg on PATH 252 | ffmpeg.bin = 'ffmpeg'; 253 | 254 | module.exports = ffmpeg; -------------------------------------------------------------------------------- /lib/presets.js: -------------------------------------------------------------------------------- 1 | module.exports.size = { 2 | 'SQCIF' : '128x96' 3 | , 'QCIF' : '176x144' 4 | , 'CIF' : '352x288' 5 | , '4CIF' : '704x576' 6 | , 'QQVGA' : '160x120' 7 | , 'QVGA' : '320x240' 8 | , 'VGA' : '640x480' 9 | , 'SVGA' : '800x600' 10 | , 'XGA' : '1024x768' 11 | , 'UXGA' : '1600x1200' 12 | , 'QXGA' : '2048x1536' 13 | , 'SXGA' : '1280x1024' 14 | , 'QSXGA' : '2560x2048' 15 | , 'HSXGA' : '5120x4096' 16 | , 'WVGA' : '852x480' 17 | , 'WXGA' : '1366x768' 18 | , 'WSXGA' : '1600x1024' 19 | , 'WUXGA' : '1920x1200' 20 | , 'WOXGA' : '2560x1600' 21 | , 'WQSXGA' : '3200x2048' 22 | , 'WQUXGA' : '3840x2400' 23 | , 'WHSXGA' : '6400x4096' 24 | , 'WHUXGA' : '7680x4800' 25 | , 'CGA' : '320x200' 26 | , 'EGA' : '640x350' 27 | , 'HD480' : '852x480' 28 | , 'HD720' : '1280x720' 29 | , 'HD1080' : '1920x1080' 30 | } 31 | 32 | module.exports.ratio = { 33 | '4:3' : 1.33 34 | , '3:2' : 1.5 35 | , '14:9' : 1.56 36 | , '16:9' : 1.78 37 | , '21:9' : 2.33 38 | } 39 | 40 | module.exports.audio_channel = { 41 | 'mono' : 1 42 | , 'stereo' : 2 43 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | , fs = require('fs') 3 | , path = require('path'); 4 | 5 | var errors = require('./errors'); 6 | 7 | /** 8 | * Exec the list of commands and call the callback function at the end of the process 9 | */ 10 | module.exports.exec = function (commands, settings, callback) { 11 | // Create final command line 12 | var finalCommand = commands.join(" "); 13 | // Create the timeoutId for stop the timeout at the end the process 14 | var timeoutID = null; 15 | // Exec the command 16 | var process = exec(finalCommand, settings, function (error, stdout, stderr) { 17 | // Clear timeout if 'timeoutID' are setted 18 | if (timeoutID !== null) clearTimeout(timeoutID); 19 | // Call the callback function 20 | callback(error, stdout, stderr); 21 | }); 22 | // Verify if the timeout are setting 23 | if (settings.timeout > 0) { 24 | // Set the timeout 25 | timeoutID = setTimeout(function () { 26 | process.kill(); 27 | }, 100); 28 | } 29 | } 30 | 31 | /** 32 | * Check if object is empty 33 | */ 34 | module.exports.isEmptyObj = function (obj) { 35 | // Scan all properties 36 | for(var prop in obj) 37 | // Check if obj has a property 38 | if(obj.hasOwnProperty(prop)) 39 | // The object is not empty 40 | return false; 41 | // The object is empty 42 | return true; 43 | } 44 | 45 | /** 46 | * Merge obj1 into obj 47 | */ 48 | module.exports.mergeObject = function (obj, obj1) { 49 | // Check if there are options set 50 | if (!module.exports.isEmptyObj(obj1)) { 51 | // Scan all settings 52 | for (var key in obj1) { 53 | // Check if the option is valid 54 | if (!obj.hasOwnProperty(key)) 55 | throw errors.renderError('invalid_option_name', key); 56 | // Set new option value 57 | obj[key] = obj1[key]; 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * Calculate the duration in seconds from the string retrieved by the ffmpeg info 64 | */ 65 | module.exports.durationToSeconds = function(duration) { 66 | var parts = duration.substr(0,8).split(':'); 67 | return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseInt(parts[2], 10); 68 | }; 69 | 70 | /** 71 | * Calculate the greatest common divisor 72 | */ 73 | module.exports.gcd = function (a, b) { 74 | if (b === 0) return a; 75 | return module.exports.gcd(b, a % b); 76 | } 77 | 78 | /** 79 | * Offers functionality similar to mkdir -p 80 | */ 81 | module.exports.mkdir = function (dirpath, mode, callback, position) { 82 | // Split all directories 83 | var parts = path.normalize(dirpath).split('/'); 84 | // If the first part is empty then remove this part 85 | if (parts[0] == "") 86 | parts = parts.slice(1); 87 | 88 | // Set the initial configuration 89 | mode = mode || 0x0777; 90 | position = position || 0; 91 | 92 | // Check se current position is greater than the list of folders 93 | if (position > parts.length) { 94 | // If isset the callback then it will be invoked 95 | if (callback) 96 | callback(); 97 | // Exit and return a positive value 98 | return true; 99 | } 100 | 101 | // Build the directory path 102 | var directory = (dirpath.charAt(0) == '/' ? '/' : '') + parts.slice(0, position + 1).join('/'); 103 | 104 | // Check if directory exists 105 | if (fs.existsSync(directory)) { 106 | module.exports.mkdir(dirpath, mode, callback, position + 1); 107 | } else { 108 | if (fs.mkdirSync(directory, mode)) { 109 | // If isset the callback then it will be invoked 110 | if (callback) 111 | callback(errors.renderError('mkdir', directory)); 112 | // Send the new exception 113 | throw errors.renderError('mkdir', directory); 114 | } else { 115 | module.exports.mkdir(dirpath, mode, callback, position + 1); 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Check if a value is present inside an array 122 | */ 123 | module.exports.in_array = function (value, array) { 124 | // Scan all element 125 | for (var i in array) 126 | // Check if value exists 127 | if (array[i] == value) 128 | // Return the position of value 129 | return i; 130 | // The value not exists 131 | return false; 132 | } 133 | 134 | /** 135 | * Ensure command line parameters containing spaces don't break 136 | * e.g. (input file) 137 | */ 138 | module.exports.addQuotes = function (filename) { 139 | // Add quotes 140 | return JSON.stringify(filename); 141 | } -------------------------------------------------------------------------------- /lib/video.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , when = require('when'); 4 | 5 | var errors = require('./errors') 6 | , presets = require('./presets') 7 | , utils = require('./utils'); 8 | 9 | module.exports = function (filePath, settings, infoConfiguration, infoFile) { 10 | 11 | // Public info about file and ffmpeg configuration 12 | this.file_path = filePath; 13 | this.info_configuration = infoConfiguration; 14 | this.metadata = infoFile; 15 | 16 | // Commands for building the ffmpeg string conversion 17 | var commands = new Array() 18 | , inputs = new Array() 19 | , filtersComlpex = new Array() 20 | , output = null; 21 | 22 | // List of options generated from setting functions 23 | var options = new Object(); 24 | 25 | /*****************************************/ 26 | /* FUNCTION FOR FILL THE COMMANDS OBJECT */ 27 | /*****************************************/ 28 | 29 | /** 30 | * Add a command to be bundled into the ffmpeg command call 31 | */ 32 | this.addCommand = function (command, argument) { 33 | // Check if exists the current command 34 | if (utils.in_array(command, commands) === false) { 35 | // Add the new command 36 | commands.push(command); 37 | // Add the argument to new command 38 | if (argument != undefined) 39 | commands.push(argument); 40 | } else 41 | throw errors.renderError('command_already_exists', command); 42 | } 43 | 44 | /** 45 | * Add an input stream 46 | */ 47 | this.addInput = function (argument) { 48 | inputs.push(argument); 49 | } 50 | 51 | /** 52 | * Add a filter complex 53 | */ 54 | this.addFilterComplex = function (argument) { 55 | filtersComlpex.push(argument); 56 | } 57 | 58 | /** 59 | * Set the output path 60 | */ 61 | var setOutput = function (path) { 62 | output = path; 63 | } 64 | 65 | /*********************/ 66 | /* SETTING FUNCTIONS */ 67 | /*********************/ 68 | 69 | /** 70 | * Disables audio encoding 71 | */ 72 | this.setDisableAudio = function () { 73 | if (options.audio == undefined) 74 | options.audio = new Object(); 75 | // Set the new option 76 | options.audio.disabled = true; 77 | return this; 78 | } 79 | 80 | /** 81 | * Disables video encoding 82 | */ 83 | this.setDisableVideo = function () { 84 | if (options.video == undefined) 85 | options.video = new Object(); 86 | // Set the new option 87 | options.video.disabled = true; 88 | return this; 89 | } 90 | 91 | /** 92 | * Sets the new video format 93 | */ 94 | this.setVideoFormat = function (format) { 95 | // Check if the format is supported by ffmpeg version 96 | if (this.info_configuration.encode.indexOf(format) != -1) { 97 | if (options.video == undefined) 98 | options.video = new Object(); 99 | // Set the new option 100 | options.video.format = format; 101 | return this; 102 | } else 103 | throw errors.renderError('format_not_supported', format); 104 | } 105 | 106 | /** 107 | * Sets the new audio codec 108 | */ 109 | this.setVideoCodec = function (codec) { 110 | // Check if the codec is supported by ffmpeg version 111 | if (this.info_configuration.encode.indexOf(codec) != -1) { 112 | if (options.video == undefined) 113 | options.video = new Object(); 114 | // Set the new option 115 | options.video.codec = codec; 116 | return this; 117 | } else 118 | throw errors.renderError('codec_not_supported', codec); 119 | } 120 | 121 | /** 122 | * Sets the video bitrate 123 | */ 124 | this.setVideoBitRate = function (bitrate) { 125 | if (options.video == undefined) 126 | options.video = new Object(); 127 | // Set the new option 128 | options.video.bitrate = bitrate; 129 | return this; 130 | } 131 | 132 | /** 133 | * Sets the framerate of the video 134 | */ 135 | this.setVideoFrameRate = function (framerate) { 136 | if (options.video == undefined) 137 | options.video = new Object(); 138 | // Set the new option 139 | options.video.framerate = framerate; 140 | return this; 141 | } 142 | 143 | /** 144 | * Sets the start time 145 | */ 146 | this.setVideoStartTime = function (time) { 147 | if (options.video == undefined) 148 | options.video = new Object(); 149 | 150 | // Check if time is a string that contain: hours, minutes and seconds 151 | if (isNaN(time) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(time)) { 152 | time = utils.durationToSeconds(time); 153 | } else if (!isNaN(time) && parseInt(time) == time) { 154 | time = parseInt(time, 10); 155 | } else { 156 | time = 0; 157 | } 158 | 159 | // Set the new option 160 | options.video.startTime = time; 161 | return this; 162 | } 163 | 164 | /** 165 | * Sets the duration 166 | */ 167 | this.setVideoDuration = function (duration) { 168 | if (options.video == undefined) 169 | options.video = new Object(); 170 | 171 | // Check if duration is a string that contain: hours, minutes and seconds 172 | if (isNaN(duration) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(duration)) { 173 | duration = utils.durationToSeconds(duration); 174 | } else if (!isNaN(duration) && parseInt(duration) == duration) { 175 | duration = parseInt(duration, 10); 176 | } else { 177 | duration = 0; 178 | } 179 | 180 | // Set the new option 181 | options.video.duration = duration; 182 | return this; 183 | } 184 | 185 | /** 186 | * Sets the new aspetc ratio 187 | */ 188 | this.setVideoAspectRatio = function (aspect) { 189 | // Check if aspect is a string 190 | if (isNaN(aspect)) { 191 | // Check if aspet is string xx:xx 192 | if (/([0-9]+):([0-9]+)/.exec(aspect)) { 193 | var check = /([0-9]+):([0-9]+)/.exec(aspect); 194 | aspect = parseFloat((check[1] / check[2])); 195 | } else { 196 | aspect = this.metadata.video.aspect.value; 197 | } 198 | } 199 | 200 | if (options.video == undefined) 201 | options.video = new Object(); 202 | // Set the new option 203 | options.video.aspect = aspect; 204 | return this; 205 | } 206 | 207 | /** 208 | * Set the size of the video 209 | */ 210 | this.setVideoSize = function (size, keepPixelAspectRatio, keepAspectRatio, paddingColor) { 211 | if (options.video == undefined) 212 | options.video = new Object(); 213 | // Set the new option 214 | options.video.size = size; 215 | options.video.keepPixelAspectRatio = keepPixelAspectRatio; 216 | options.video.keepAspectRatio = keepAspectRatio; 217 | options.video.paddingColor = paddingColor; 218 | return this; 219 | } 220 | 221 | /** 222 | * Sets the new audio codec 223 | */ 224 | this.setAudioCodec = function (codec) { 225 | // Check if the codec is supported by ffmpeg version 226 | if (this.info_configuration.encode.indexOf(codec) != -1) { 227 | // Check if codec is equal 'MP3' and check if the version of ffmpeg support the libmp3lame function 228 | if (codec == 'mp3' && this.info_configuration.modules.indexOf('libmp3lame') != -1) 229 | codec = 'libmp3lame'; 230 | 231 | if (options.audio == undefined) 232 | options.audio = new Object(); 233 | // Set the new option 234 | options.audio.codec = codec; 235 | return this; 236 | } else 237 | throw errors.renderError('codec_not_supported', codec); 238 | } 239 | 240 | /** 241 | * Sets the audio sample frequency for audio outputs 242 | */ 243 | this.setAudioFrequency = function (frequency) { 244 | if (options.audio == undefined) 245 | options.audio = new Object(); 246 | // Set the new option 247 | options.audio.frequency = frequency; 248 | return this; 249 | } 250 | 251 | /** 252 | * Sets the number of audio channels 253 | */ 254 | this.setAudioChannels = function (channel) { 255 | // Check if the channel value is valid 256 | if (presets.audio_channel.stereo == channel || presets.audio_channel.mono == channel) { 257 | if (options.audio == undefined) 258 | options.audio = new Object(); 259 | // Set the new option 260 | options.audio.channel = channel; 261 | return this; 262 | } else 263 | throw errors.renderError('audio_channel_is_invalid', channel); 264 | } 265 | 266 | /** 267 | * Sets the audio bitrate 268 | */ 269 | this.setAudioBitRate = function (bitrate) { 270 | if (options.audio == undefined) 271 | options.audio = new Object(); 272 | // Set the new option 273 | options.audio.bitrate = bitrate; 274 | return this; 275 | } 276 | 277 | /** 278 | * Sets the audio quality 279 | */ 280 | this.setAudioQuality = function (quality) { 281 | if (options.audio == undefined) 282 | options.audio = new Object(); 283 | // Set the new option 284 | options.audio.quality = quality; 285 | return this; 286 | } 287 | 288 | /** 289 | * Sets the watermark 290 | */ 291 | this.setWatermark = function (watermarkPath, settings) { 292 | // Base settings 293 | var baseSettings = { 294 | position : "SW" // Position: NE NC NW SE SC SW C CE CW 295 | , margin_nord : null // Margin nord 296 | , margin_sud : null // Margin sud 297 | , margin_east : null // Margin east 298 | , margin_west : null // Margin west 299 | }; 300 | 301 | // Check if watermark exists 302 | if (!fs.existsSync(watermarkPath)) 303 | throw errors.renderError('invalid_watermark', watermarkPath); 304 | 305 | // Check if the settings are specified 306 | if (settings != null) 307 | utils.mergeObject(baseSettings, settings); 308 | 309 | // Check if position is valid 310 | if (baseSettings.position == null || utils.in_array(baseSettings.position, ['NE','NC','NW','SE','SC','SW','C','CE','CW']) === false) 311 | throw errors.renderError('invalid_watermark_position', baseSettings.position); 312 | 313 | // Check if margins are valid 314 | 315 | if (baseSettings.margin_nord == null || isNaN(baseSettings.margin_nord)) 316 | baseSettings.margin_nord = 0; 317 | if (baseSettings.margin_sud == null || isNaN(baseSettings.margin_sud)) 318 | baseSettings.margin_sud = 0; 319 | if (baseSettings.margin_east == null || isNaN(baseSettings.margin_east)) 320 | baseSettings.margin_east = 0; 321 | if (baseSettings.margin_west == null || isNaN(baseSettings.margin_west)) 322 | baseSettings.margin_west = 0; 323 | 324 | var overlay = ''; 325 | 326 | var getSing = function (val, inverse) { 327 | return (val > 0 ? (inverse ? '-' : '+') : (inverse ? '+' : '-')).toString() + Math.abs(val).toString(); 328 | } 329 | 330 | var getHorizontalMargins = function (east, west) { 331 | return getSing(east, false).toString() + getSing(west, true).toString(); 332 | } 333 | 334 | var getVerticalMargins = function (nord, sud) { 335 | return getSing(nord, false).toString() + getSing(sud, true).toString(); 336 | } 337 | 338 | // Calculate formula 339 | switch (baseSettings.position) { 340 | case 'NE': 341 | overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 342 | break; 343 | case 'NC': 344 | overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 345 | break; 346 | case 'NW': 347 | overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 348 | break; 349 | case 'SE': 350 | overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 351 | break; 352 | case 'SC': 353 | overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 354 | break; 355 | case 'SW': 356 | overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 357 | break; 358 | case 'CE': 359 | overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 360 | break; 361 | case 'C': 362 | overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 363 | break; 364 | case 'CW': 365 | overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud); 366 | break; 367 | } 368 | 369 | // Check if the call comes from internal function 370 | if (arguments[2] == undefined || arguments[2] == null) { 371 | if (options.video == undefined) 372 | options.video = new Object(); 373 | // Set the new option 374 | options.video.watermark = { path : watermarkPath, overlay : overlay }; 375 | return this; 376 | } else if (arguments[2] != undefined && arguments[2] === true) { 377 | this.addInput(watermarkPath); 378 | this.addFilterComplex('overlay=' + overlay); 379 | } 380 | } 381 | 382 | /** 383 | * Save all set commands 384 | */ 385 | this.save = function (destionationFileName, callback) { 386 | // Check if the 'video' is present in the options 387 | if (options.hasOwnProperty('video')) { 388 | // Check if video is disabled 389 | if (options.video.hasOwnProperty('disabled')) { 390 | this.addCommand('-vn'); 391 | } else { 392 | // Check all video property 393 | if (options.video.hasOwnProperty('format')) 394 | this.addCommand('-f', options.video.format); 395 | if (options.video.hasOwnProperty('codec')) 396 | this.addCommand('-vcodec', options.video.codec); 397 | if (options.video.hasOwnProperty('bitrate')) 398 | this.addCommand('-b', parseInt(options.video.bitrate, 10) + 'kb'); 399 | if (options.video.hasOwnProperty('framerate')) 400 | this.addCommand('-r', parseInt(options.video.framerate, 10)); 401 | if (options.video.hasOwnProperty('startTime')) 402 | this.addCommand('-ss', parseInt(options.video.startTime, 10)); 403 | if (options.video.hasOwnProperty('duration')) 404 | this.addCommand('-t', parseInt(options.video.duration, 10)); 405 | 406 | if (options.video.hasOwnProperty('watermark')) { 407 | this.addInput(options.video.watermark.path); 408 | this.addFilterComplex('overlay=' + options.video.watermark.overlay); 409 | } 410 | 411 | // Check if the video should be scaled 412 | if (options.video.hasOwnProperty('size')) { 413 | var newDimension = _calculateNewDimension.call(this); 414 | 415 | if (newDimension.aspect != null) { 416 | this.addFilterComplex('scale=iw*sar:ih, pad=max(iw\\,ih*(' + newDimension.aspect.x + '/' + newDimension.aspect.y + ')):ow/(' + newDimension.aspect.x + '/' + newDimension.aspect.y + '):(ow-iw)/2:(oh-ih)/2' + (options.video.paddingColor != null ? ':' + options.video.paddingColor : '')); 417 | this.addCommand('-aspect', newDimension.aspect.string); 418 | } 419 | 420 | this.addCommand('-s', newDimension.width + 'x' + newDimension.height); 421 | } 422 | } 423 | } 424 | // Check if the 'audio' is present in the options 425 | if (options.hasOwnProperty('audio')) { 426 | // Check if audio is disabled 427 | if (options.audio.hasOwnProperty('disabled')) { 428 | this.addCommand('-an'); 429 | } else { 430 | // Check all audio property 431 | if (options.audio.hasOwnProperty('codec')) 432 | this.addCommand('-acodec', options.audio.codec); 433 | if (options.audio.hasOwnProperty('frequency')) 434 | this.addCommand('-ar', parseInt(options.audio.frequency)); 435 | if (options.audio.hasOwnProperty('channel')) 436 | this.addCommand('-ac', options.audio.channel); 437 | if (options.audio.hasOwnProperty('quality')) 438 | this.addCommand('-aq', options.audio.quality); 439 | if (options.audio.hasOwnProperty('bitrate')) 440 | this.addCommand('-ab', parseInt(options.audio.bitrate, 10) + 'k'); 441 | } 442 | } 443 | 444 | setOutput(destionationFileName); 445 | 446 | return execCommand.call(this, callback); 447 | } 448 | 449 | /*********************/ 450 | /* INTERNAL FUNCTION */ 451 | /*********************/ 452 | 453 | /** 454 | * Reset the list of commands 455 | */ 456 | var resetCommands = function (self) { 457 | commands = new Array() 458 | inputs = [self.file_path]; 459 | filtersComlpex = new Array(); 460 | output = null; 461 | options = new Object(); 462 | } 463 | 464 | /** 465 | * Calculate width, height and aspect ratio by the new dimension data 466 | */ 467 | var _calculateNewDimension = function () { 468 | // Check if keepPixelAspectRatio is undefined 469 | var keepPixelAspectRatio = typeof options.video.keepPixelAspectRatio != 'boolean' ? false : options.video.keepPixelAspectRatio; 470 | // Check if keepAspectRatio is undefined 471 | var keepAspectRatio = typeof options.video.keepAspectRatio != 'boolean' ? false : options.video.keepAspectRatio; 472 | 473 | // Resolution to be taken as a reference 474 | var referrerResolution = this.metadata.video.resolution; 475 | // Check if is need keep pixel aspect ratio 476 | if (keepPixelAspectRatio) { 477 | // Check if exists resolution for pixel aspect ratio 478 | if (utils.isEmptyObj(this.metadata.video.resolutionSquare)) 479 | throw errors.renderError('resolution_square_not_defined'); 480 | 481 | // Apply the resolutionSquare 482 | referrerResolution = this.metadata.video.resolutionSquare; 483 | } 484 | 485 | // Final data 486 | var width = null 487 | , height = null 488 | , aspect = null; 489 | 490 | // Regex to check which type of dimension was specified 491 | var fixedWidth = /([0-9]+)x\?/.exec(options.video.size) 492 | , fixedHeight = /\?x([0-9]+)/.exec(options.video.size) 493 | , percentage = /([0-9]{1,2})%/.exec(options.video.size) 494 | , classicSize = /([0-9]+)x([0-9]+)/.exec(options.video.size); 495 | 496 | if (fixedWidth) { 497 | // Set the width dimension 498 | width = parseInt(fixedWidth[1], 10); 499 | // Check if the video has the aspect ratio setted 500 | if (!utils.isEmptyObj(this.metadata.video.aspect)) { 501 | height = Math.round((width / this.metadata.video.aspect.x) * this.metadata.video.aspect.y); 502 | } else { 503 | // Calculte the new height 504 | height = Math.round(referrerResolution.h / (referrerResolution.w / parseInt(fixedWidth[1], 10))); 505 | } 506 | } else if (fixedHeight) { 507 | // Set the width dimension 508 | height = parseInt(fixedHeight[1], 10); 509 | // Check if the video has the aspect ratio setted 510 | if (!utils.isEmptyObj(this.metadata.video.aspect)) { 511 | width = Math.round((height / this.metadata.video.aspect.y) * this.metadata.video.aspect.x); 512 | } else { 513 | // Calculte the new width 514 | width = Math.round(referrerResolution.w / (referrerResolution.h / parseInt(fixedHeight[1], 10))); 515 | } 516 | } else if (percentage) { 517 | // Calculte the ratio from percentage 518 | var ratio = parseInt(percentage[1], 10) / 100; 519 | // Calculate the new dimensions 520 | width = Math.round(referrerResolution.w * ratio); 521 | height = Math.round(referrerResolution.h * ratio); 522 | } else if (classicSize) { 523 | width = parseInt(classicSize[1], 10); 524 | height = parseInt(classicSize[2], 10); 525 | } else 526 | throw errors.renderError('size_format', options.video.size); 527 | 528 | // If the width or height are not multiples of 2 will be decremented by one unit 529 | if (width % 2 != 0) width -= 1; 530 | if (height % 2 != 0) height -= 1; 531 | 532 | if (keepAspectRatio) { 533 | // Calculate the new aspect ratio 534 | var gcdValue = utils.gcd(width, height); 535 | 536 | aspect = new Object(); 537 | aspect.x = width / gcdValue; 538 | aspect.y = height / gcdValue; 539 | aspect.string = aspect.x + ':' + aspect.y; 540 | } 541 | 542 | return { width : width, height : height, aspect : aspect }; 543 | } 544 | 545 | /** 546 | * Executing the commands list 547 | */ 548 | var execCommand = function (callback, folder) { 549 | var i; 550 | // Checking if folder is defined 551 | var onlyDestinationFile = folder != undefined ? false : true; 552 | // Building the value for return value. Check if the callback is not a function. In this case will created a new instance of the deferred class 553 | var deferred = typeof callback != 'function' ? when.defer() : { promise : null }; 554 | // Deal with input paths that have spaces in them, by quoting them 555 | for (i=0; i 0 ? ['-filter_complex "'].concat(filtersComlpex.join(', ')).join('') + '"' : []) 563 | .concat([output]); 564 | // Reset commands 565 | resetCommands(this); 566 | // Execute the commands from the list 567 | utils.exec(finalCommands, settings, function (error, stdout, stderr) { 568 | // Building the result 569 | var result = null; 570 | if (!error) { 571 | // Check if show only destination filename or the complete file list 572 | if (onlyDestinationFile) { 573 | result = finalCommands[finalCommands.length-1]; 574 | } else { 575 | // Clean possible "/" at the end of the string 576 | if (folder.charAt(folder.length-1) == "/") 577 | folder = folder.substr(0, folder.length-1); 578 | // Read file list inside the folder 579 | result = fs.readdirSync(folder); 580 | // Scan all file and prepend the folder path 581 | for (var i in result) 582 | result[i] = [folder, result[i]].join('/') 583 | } 584 | } 585 | // Check if the callback is a function 586 | if (typeof callback == 'function') { 587 | // Call the callback to return the info 588 | callback(error, result); 589 | } else { 590 | if (error) { 591 | // Negative response 592 | deferred.reject(error); 593 | } else { 594 | // Positive response 595 | deferred.resolve(result); 596 | } 597 | } 598 | }); 599 | // Return a possible promise instance 600 | return deferred.promise; 601 | } 602 | 603 | /*******************/ 604 | /* PRESET FUNCTION */ 605 | /*******************/ 606 | 607 | /** 608 | * Extracting sound from a video, and save it as Mp3 609 | */ 610 | this.fnExtractSoundToMP3 = function (destionationFileName, callback) { 611 | // Check if file already exists. In this case will remove it 612 | if (fs.existsSync(destionationFileName)) 613 | fs.unlinkSync(destionationFileName); 614 | 615 | // Building the final path 616 | var destinationDirName = path.dirname(destionationFileName) 617 | , destinationFileNameWE = path.basename(destionationFileName, path.extname(destionationFileName)) + '.mp3' 618 | , finalPath = path.join(destinationDirName, destinationFileNameWE); 619 | 620 | resetCommands(this); 621 | 622 | // Adding commands to the list 623 | this.addCommand('-vn'); 624 | this.addCommand('-ar', 44100); 625 | this.addCommand('-ac', 2); 626 | this.addCommand('-ab', 192); 627 | this.addCommand('-f', 'mp3'); 628 | 629 | // Add destination file path to the command list 630 | setOutput(finalPath); 631 | 632 | // Executing the commands list 633 | return execCommand.call(this, callback); 634 | } 635 | 636 | /** 637 | * Extract frame from video file 638 | */ 639 | this.fnExtractFrameToJPG = function (/* destinationFolder, settings, callback */) { 640 | 641 | var destinationFolder = null 642 | , newSettings = null 643 | , callback = null; 644 | 645 | var settings = { 646 | start_time : null // Start time to recording 647 | , duration_time : null // Duration of recording 648 | , frame_rate : null // Number of the frames to capture in one second 649 | , size : null // Dimension each frame 650 | , number : null // Total frame to capture 651 | , every_n_frames : null // Frame to capture every N frames 652 | , every_n_seconds : null // Frame to capture every N seconds 653 | , every_n_percentage : null // Frame to capture every N percentage range 654 | , keep_pixel_aspect_ratio : true // Mantain the original pixel video aspect ratio 655 | , keep_aspect_ratio : true // Mantain the original aspect ratio 656 | , padding_color : 'black' // Padding color 657 | , file_name : null // File name 658 | }; 659 | 660 | // Scan all arguments 661 | for (var i in arguments) { 662 | // Check the type of the argument 663 | switch (typeof arguments[i]) { 664 | case 'string': 665 | destinationFolder = arguments[i]; 666 | break; 667 | case 'object': 668 | newSettings = arguments[i]; 669 | break; 670 | case 'function': 671 | callback = arguments[i]; 672 | break; 673 | } 674 | } 675 | 676 | // Check if the settings are specified 677 | if (newSettings !== null) 678 | utils.mergeObject(settings, newSettings); 679 | 680 | // Check if 'start_time' is in the format hours:minutes:seconds 681 | if (settings.start_time != null) { 682 | if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.start_time)) 683 | settings.start_time = utils.durationToSeconds(settings.start_time); 684 | else if (!isNaN(settings.start_time)) 685 | settings.start_time = parseInt(settings.start_time, 10); 686 | else 687 | settings.start_time = null; 688 | } 689 | 690 | // Check if 'duration_time' is in the format hours:minutes:seconds 691 | if (settings.duration_time != null) { 692 | if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.duration_time)) 693 | settings.duration_time = utils.durationToSeconds(settings.duration_time); 694 | else if (!isNaN(settings.duration_time)) 695 | settings.duration_time = parseInt(settings.duration_time, 10); 696 | else 697 | settings.duration_time = null; 698 | } 699 | 700 | // Check if the value of the framerate is number type 701 | if (settings.frame_rate != null && isNaN(settings.frame_rate)) 702 | settings.frame_rate = null; 703 | 704 | // If the size is not settings then the size of the screenshots is equal to video size 705 | if (settings.size == null) 706 | settings.size = this.metadata.video.resolution.w + 'x' + this.metadata.video.resolution.h; 707 | 708 | // Check if the value of the 'number frame to capture' is number type 709 | if (settings.number != null && isNaN(settings.number)) 710 | settings.number = null; 711 | 712 | var every_n_check = 0; 713 | 714 | // Check if the value of the 'every_n_frames' is number type 715 | if (settings.every_n_frames != null && isNaN(settings.every_n_frames)) { 716 | settings.every_n_frames = null; 717 | every_n_check++; 718 | } 719 | 720 | // Check if the value of the 'every_n_seconds' is number type 721 | if (settings.every_n_seconds != null && isNaN(settings.every_n_seconds)) { 722 | settings.every_n_seconds = null; 723 | every_n_check++; 724 | } 725 | 726 | // Check if the value of the 'every_n_percentage' is number type 727 | if (settings.every_n_percentage != null && (isNaN(settings.every_n_percentage) || settings.every_n_percentage > 100)) { 728 | settings.every_n_percentage = null; 729 | every_n_check++; 730 | } 731 | 732 | if (every_n_check >= 2) { 733 | if (callback) { 734 | callback(errors.renderError('extract_frame_invalid_everyN_options')); 735 | } else { 736 | throw errors.renderError('extract_frame_invalid_everyN_options'); 737 | } 738 | } 739 | 740 | // If filename is null then his value is equal to original filename 741 | if (settings.file_name == null) { 742 | settings.file_name = path.basename(this.file_path, path.extname(this.file_path)); 743 | } else { 744 | // Retrieve all possible replacements 745 | var replacements = settings.file_name.match(/(\%[a-zA-Z]{1})/g); 746 | // Check if exists replacements. The scan all replacements and build the final filename 747 | if (replacements) { 748 | for (var i in replacements) { 749 | switch (replacements[i]) { 750 | case '%t': 751 | settings.file_name = settings.file_name.replace('%t', new Date().getTime()); 752 | break; 753 | case '%s': 754 | settings.file_name = settings.file_name.replace('%s', settings.size); 755 | break; 756 | case '%x': 757 | settings.file_name = settings.file_name.replace('%x', settings.size.split(':')[0]); 758 | break; 759 | case '%y': 760 | settings.file_name = settings.file_name.replace('%y', settings.size.split(':')[1]); 761 | break; 762 | default: 763 | settings.file_name = settings.file_name.replace(replacements[i], ''); 764 | break; 765 | } 766 | } 767 | } 768 | } 769 | // At the filename will added the number of the frame 770 | settings.file_name = path.basename(settings.file_name, path.extname(settings.file_name)) + '_%d.jpg'; 771 | 772 | // Create the directory to save the extracted frames 773 | utils.mkdir(destinationFolder, 0x0777); 774 | 775 | resetCommands(this); 776 | 777 | // Adding commands to the list 778 | if (settings.start_time) 779 | this.addCommand('-ss', settings.start_time); 780 | if (settings.duration_time) 781 | this.addCommand('-t', settings.duration_time); 782 | if (settings.frame_rate) 783 | this.addCommand('-r', settings.frame_rate); 784 | 785 | // Setting the size and padding settings 786 | this.setVideoSize(settings.size, settings.keep_pixel_aspect_ratio, settings.keep_aspect_ratio, settings.padding_color); 787 | // Get the dimensions 788 | var newDimension = _calculateNewDimension.call(this); 789 | // Apply the size and padding commands 790 | this.addCommand('-s', newDimension.width + 'x' + newDimension.height); 791 | // CHeck if isset aspect ratio options 792 | if (newDimension.aspect != null) { 793 | this.addFilterComplex('scale=iw*sar:ih, pad=max(iw\\,ih*(' + newDimension.aspect.x + '/' + newDimension.aspect.y + ')):ow/(' + newDimension.aspect.x + '/' + newDimension.aspect.y + '):(ow-iw)/2:(oh-ih)/2' + (settings.padding_color != null ? ':' + settings.padding_color : '')); 794 | this.addCommand('-aspect', newDimension.aspect.string); 795 | } 796 | 797 | if (settings.number) 798 | this.addCommand('-vframes', settings.number); 799 | if (settings.every_n_frames) { 800 | this.addCommand('-vsync', 0); 801 | this.addFilterComplex('select=not(mod(n\\,' + settings.every_n_frames + '))'); 802 | } 803 | if (settings.every_n_seconds) { 804 | this.addCommand('-vsync', 0); 805 | this.addFilterComplex('select=not(mod(t\\,' + settings.every_n_seconds + '))'); 806 | } 807 | if (settings.every_n_percentage) { 808 | this.addCommand('-vsync', 0); 809 | this.addFilterComplex('select=not(mod(t\\,' + parseInt((this.metadata.duration.seconds / 100) * settings.every_n_percentage) + '))'); 810 | } 811 | 812 | // Add destination file path to the command list 813 | setOutput([destinationFolder,settings.file_name].join('/')); 814 | 815 | // Executing the commands list 816 | return execCommand.call(this, callback, destinationFolder); 817 | } 818 | 819 | /** 820 | * Add a watermark to the video and save it 821 | */ 822 | this.fnAddWatermark = function (watermarkPath /* newFilepath , settings, callback */) { 823 | 824 | var newFilepath = null 825 | , newSettings = null 826 | , callback = null; 827 | 828 | // Scan all arguments 829 | for (var i = 1; i < arguments.length; i++) { 830 | // Check the type of the argument 831 | switch (typeof arguments[i]) { 832 | case 'string': 833 | newFilepath = arguments[i]; 834 | break; 835 | case 'object': 836 | newSettings = arguments[i]; 837 | break; 838 | case 'function': 839 | callback = arguments[i]; 840 | break; 841 | } 842 | } 843 | 844 | resetCommands(this); 845 | 846 | // Call the function to add the watermark options 847 | this.setWatermark(watermarkPath, newSettings, true); 848 | 849 | if (newFilepath == null) 850 | newFilepath = path.dirname(this.file_path) + '/' + 851 | path.basename(this.file_path, path.extname(this.file_path)) + '_watermark_' + 852 | path.basename(watermarkPath, path.extname(watermarkPath)) + 853 | path.extname(this.file_path); 854 | 855 | // Add destination file path to the command list 856 | setOutput(newFilepath); 857 | 858 | commands.push('-strict'); 859 | commands.push('-2') 860 | 861 | // Executing the commands list 862 | return execCommand.call(this, callback); 863 | } 864 | 865 | /** 866 | * Constructor 867 | */ 868 | var __constructor = function (self) { 869 | resetCommands(self); 870 | }(this); 871 | } 872 | -------------------------------------------------------------------------------- /node_modules/when/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = LF -------------------------------------------------------------------------------- /node_modules/when/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "node": true, 4 | "es5": true, 5 | 6 | "predef": [ 7 | "define", 8 | "module", 9 | "system" 10 | ], 11 | 12 | "boss": true, 13 | "curly": true, 14 | "eqnull": true, 15 | "expr": true, 16 | "globalstrict": false, 17 | "laxbreak": true, 18 | "newcap": true, 19 | "noarg": true, 20 | "noempty": true, 21 | "nonew": true, 22 | "quotmark": "single", 23 | "strict": false, 24 | "trailing": true, 25 | "undef": true, 26 | "unused": true, 27 | 28 | "maxdepth": 3, 29 | "maxcomplexity": 4 30 | } -------------------------------------------------------------------------------- /node_modules/when/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Open Source Initiative OSI - The MIT License 2 | 3 | http://www.opensource.org/licenses/mit-license.php 4 | 5 | Copyright (c) 2011 Brian Cavalier 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /node_modules/when/README.md: -------------------------------------------------------------------------------- 1 | # when.js [![Build Status](https://secure.travis-ci.org/cujojs/when.png)](http://travis-ci.org/cujojs/when) 2 | 3 | When.js is cujojs's lightweight [CommonJS](http://wiki.commonjs.org/wiki/Promises) [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) and `when()` implementation, derived from the async core of [wire.js](https://github.com/cujojs/wire), cujojs's IOC Container. It also provides several other useful Promise-related concepts, such as joining multiple promises, mapping and reducing collections of promises, timed promises, and has a robust [unit test suite](#running-the-unit-tests). 4 | 5 | It passes the [Promises/A Test Suite](https://github.com/domenic/promise-tests), is [frighteningly fast](https://github.com/cujojs/promise-perf-tests#test-results), and is **under 1.3k** when compiled with Google Closure (w/advanced optimizations) and gzipped, and has no dependencies. 6 | 7 | # What's New? 8 | 9 | ### 1.6.1 10 | 11 | * Fix for accidental coercion of non-promises. See [#62](https://github.com/cujojs/when/issues/60). 12 | 13 | ### 1.6.0 14 | 15 | * New [when.join](when/blob/master/docs/api.md#whenjoin) - Joins 2 or more promises together into a single promise. 16 | * [when.some](when/blob/master/docs/api.md#whensome) and [when.any](when/blob/master/docs/api.md#whenany) now act like competitive races, and have generally more useful behavior. [Read the discussion in #60](https://github.com/cujojs/when/issues/60). 17 | * *Experimental* progress event propagation. Progress events will propagate through promise chains. [Read the details here](when/blob/master/docs/api.md#progress-events). 18 | * *Temporarily* removed calls to `Object.freeze`. Promises are no longer frozen due to a horrendous v8 performance penalty. [Read discussion here](https://groups.google.com/d/topic/cujojs/w_olYqorbsY/discussion). 19 | * **IMPORTANT:** Continue to treat promises as if they are frozen, since `freeze()` will be reintroduced once v8 performance improves. 20 | * [when/debug](https://github.com/cujojs/when/wiki/when-debug) now allows setting global a debugging callback for rejected promises. 21 | 22 | ### 1.5.2 23 | 24 | * Integrate @domenic's [Promises/A Test Suite](https://github.com/domenic/promise-tests). Runs via `npm test`. 25 | * No functional change 26 | 27 | ### 1.5.1 28 | 29 | * Performance optimization for [when.defer](when/blob/master/docs/api.md#whendefer), up to 1.5x in some cases. 30 | * [when/debug](https://github.com/cujojs/when/wiki/when-debug) can now log exceptions and rejections in deeper promise chains, in some cases, even when the promises involved aren't when.js promises. 31 | 32 | ### 1.5.0 33 | 34 | * New task execution and concurrency management: [when/sequence](when/blob/master/docs/api.md#whensequence), [when/pipeline](when/blob/master/docs/api.md#whenpipeline), and [when/parallel](when/blob/master/docs/api.md#whenparallel). 35 | * Performance optimizations for [when.all](when/blob/master/docs/api.md#whenall) and [when.map](when/blob/master/docs/api.md#whenmap), up to 2x in some cases. 36 | * Options for disabling [paranoid mode](when/blob/master/docs/api.md#paranoid-mode) that provides a significant performance gain in v8 (e.g. Node and Chrome). See this [v8 performance problem with Object.freeze](http://stackoverflow.com/questions/8435080/any-performance-benefit-to-locking-down-javascript-objects) for more info. 37 | * **Important:** `deferred` and `deferred.resolver` no longer throw when resolved/rejected multiple times. They will return silently as if the they had succeeded. This prevents parties to whom *only* the `resolver` has been given from using `try/catch` to determine the state of the associated promise. 38 | * For debugging, you can use the [when/debug](https://github.com/cujojs/when/wiki/when-debug) module, which will still throw when a deferred is resolved/rejected multiple times. 39 | 40 | [Full Changelog](https://github.com/cujojs/when/wiki/Changelog) 41 | 42 | # Docs & Examples 43 | 44 | [API docs](when/blob/master/docs/api.md#api) 45 | 46 | [More info on the wiki](https://github.com/cujojs/when/wiki) 47 | 48 | [Examples](https://github.com/cujojs/when/wiki/Examples) 49 | 50 | Quick Start 51 | =========== 52 | 53 | ### AMD 54 | 55 | 1. `git clone https://github.com/cujojs/when` or `git submodule add https://github.com/cujojs/when` 56 | 1. Configure your loader with a package: 57 | 58 | ```javascript 59 | packages: [ 60 | { name: 'when', location: 'path/to/when/', main: 'when' }, 61 | // ... other packages ... 62 | ] 63 | ``` 64 | 65 | 1. `define(['when', ...], function(when, ...) { ... });` or `require(['when', ...], function(when, ...) { ... });` 66 | 67 | ### Script Tag 68 | 69 | 1. `git clone https://github.com/cujojs/when` or `git submodule add https://github.com/cujojs/when` 70 | 1. `` 71 | 1. `when` will be available as `window.when` 72 | 73 | ### Node 74 | 75 | 1. `npm install when` 76 | 1. `var when = require('when');` 77 | 78 | ### RingoJS 79 | 80 | 1. `ringo-admin install cujojs/when` 81 | 1. `var when = require('when');` 82 | 83 | # Running the Unit Tests 84 | 85 | ## Node 86 | 87 | Note that when.js includes @domenic's [Promises/A Test Suite](https://github.com/domenic/promise-tests). Running unit tests in Node will run both when.js's own test suite, and the Promises/A Test Suite. 88 | 89 | 1. `npm install` 90 | 1. `npm test` 91 | 92 | ## Browsers 93 | 94 | 1. `npm install` 95 | 1. `npm start` - starts buster server & prints a url 96 | 1. Point browsers at /capture, e.g. `localhost:1111/capture` 97 | 1. `npm run-script test-browser` 98 | 99 | References 100 | ---------- 101 | 102 | Much of this code was inspired by @[unscriptable](https://github.com/unscriptable)'s [tiny promises](https://github.com/unscriptable/promises), the async innards of [wire.js](https://github.com/cujojs/wire), and some gists [here](https://gist.github.com/870729), [here](https://gist.github.com/892345), [here](https://gist.github.com/894356), and [here](https://gist.github.com/894360) 103 | 104 | Some of the code has been influenced by the great work in [Q](https://github.com/kriskowal/q), [Dojo's Deferred](https://github.com/dojo/dojo), and [uber.js](https://github.com/phiggins42/uber.js). 105 | -------------------------------------------------------------------------------- /node_modules/when/apply.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * apply.js 5 | * Helper for using arguments-based and variadic callbacks with any 6 | * {@link Promise} that resolves to an array. 7 | * 8 | * @author brian@hovercraftstudios.com 9 | */ 10 | 11 | (function(define) { 12 | define(function() { 13 | 14 | var toString = Object.prototype.toString; 15 | 16 | /** 17 | * Creates a function that accepts a function that takes individual 18 | * arguments (it can be variadic, too), and returns a new function that 19 | * takes a single array as its only param: 20 | * 21 | * function argBased(a, b, c) { 22 | * return a + b + c; 23 | * } 24 | * 25 | * argBased(1, 2, 3); // 6 26 | * 27 | * // Create an array-based version of argBased 28 | * var arrayBased = apply(argBased); 29 | * var inputs = [1, 2, 3]; 30 | * 31 | * arrayBased(inputs); // 6 32 | * 33 | * With promises: 34 | * 35 | * var d = when.defer(); 36 | * d.promise.then(arrayBased); 37 | * 38 | * d.resolve([1, 2, 3]); // arrayBased called with args 1, 2, 3 -> 6 39 | * 40 | * @param f {Function} arguments-based function 41 | * 42 | * @returns {Function} a new function that accepts an array 43 | */ 44 | return function(f) { 45 | /** 46 | * @param array {Array} must be an array of arguments to use to apply the original function 47 | * 48 | * @returns the result of applying f with the arguments in array. 49 | */ 50 | return function(array) { 51 | // It better be an array 52 | if(toString.call(array) != '[object Array]') { 53 | throw new Error('apply called with non-array arg'); 54 | } 55 | 56 | return f.apply(null, array); 57 | }; 58 | }; 59 | 60 | }); 61 | })(typeof define == 'function' 62 | ? define 63 | : function (factory) { typeof module != 'undefined' 64 | ? (module.exports = factory()) 65 | : (this.when_apply = factory()); 66 | } 67 | // Boilerplate for AMD, Node, and browser global 68 | ); 69 | 70 | 71 | -------------------------------------------------------------------------------- /node_modules/when/cancelable.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * cancelable.js 5 | * 6 | * Decorator that makes a deferred "cancelable". It adds a cancel() method that 7 | * will call a special cancel handler function and then reject the deferred. The 8 | * cancel handler can be used to do resource cleanup, or anything else that should 9 | * be done before any other rejection handlers are executed. 10 | * 11 | * Usage: 12 | * 13 | * var cancelableDeferred = cancelable(when.defer(), myCancelHandler); 14 | * 15 | * @author brian@hovercraftstudios.com 16 | */ 17 | 18 | (function(define) { 19 | define(['./when'], function(when) { 20 | 21 | /** 22 | * Makes deferred cancelable, adding a cancel() method. 23 | * 24 | * @param deferred {Deferred} the {@link Deferred} to make cancelable 25 | * @param canceler {Function} cancel handler function to execute when this deferred is canceled. This 26 | * is guaranteed to run before all other rejection handlers. The canceler will NOT be executed if the 27 | * deferred is rejected in the standard way, i.e. deferred.reject(). It ONLY executes if the deferred 28 | * is canceled, i.e. deferred.cancel() 29 | * 30 | * @returns deferred, with an added cancel() method. 31 | */ 32 | return function(deferred, canceler) { 33 | 34 | var delegate = when.defer(); 35 | 36 | // Add a cancel method to the deferred to reject the delegate 37 | // with the special canceled indicator. 38 | deferred.cancel = function() { 39 | return delegate.reject(canceler(deferred)); 40 | }; 41 | 42 | // Ensure that the original resolve, reject, and progress all forward 43 | // to the delegate 44 | deferred.then(delegate.resolve, delegate.reject, delegate.progress); 45 | 46 | // Replace deferred's promise with the delegate promise 47 | deferred.promise = delegate.promise; 48 | 49 | // Also replace deferred.then to allow it to be called safely and 50 | // observe the cancellation 51 | deferred.then = delegate.promise.then; 52 | 53 | return deferred; 54 | }; 55 | 56 | }); 57 | })(typeof define == 'function' 58 | ? define 59 | : function (deps, factory) { typeof module != 'undefined' 60 | ? (module.exports = factory(require('./when'))) 61 | : (this.when_cancelable = factory(this.when)); 62 | } 63 | // Boilerplate for AMD, Node, and browser global 64 | ); 65 | 66 | 67 | -------------------------------------------------------------------------------- /node_modules/when/debug.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /*jshint devel: true*/ 4 | /*global console:true, setTimeout:true*/ 5 | 6 | /** 7 | * This is a drop-in replacement for the when module that sets up automatic 8 | * debug output for promises created or consumed by when.js. Use this 9 | * instead of when to help with debugging. 10 | * 11 | * WARNING: This module **should never** be use this in a production environment. 12 | * It exposes details of the promise 13 | * 14 | * In an AMD environment, you can simply change your path or package mappings: 15 | * 16 | * paths: { 17 | * // 'when': 'path/to/when/when' 18 | * 'when': 'path/to/when/debug' 19 | * } 20 | * 21 | * or 22 | * 23 | * packages: [ 24 | * // { name: 'when', location: 'path/to/when', main: 'when' } 25 | * { name: 'when', location: 'path/to/when', main: 'debug' } 26 | * ] 27 | * 28 | * In a CommonJS environment, you can directly require this module where 29 | * you would normally require 'when': 30 | * 31 | * // var when = require('when'); 32 | * var when = require('when/debug'); 33 | * 34 | * Or you can temporarily modify the package.js to point main at debug. 35 | * For example, when/package.json: 36 | * 37 | * ... 38 | * "main": "./debug" 39 | * ... 40 | * 41 | * @author brian@hovercraftstudios.com 42 | */ 43 | (function(define) { 44 | define(['./when'], function(when) { 45 | 46 | var promiseId, pending, exceptionsToRethrow, own, undef; 47 | 48 | promiseId = 0; 49 | pending = {}; 50 | own = Object.prototype.hasOwnProperty; 51 | 52 | exceptionsToRethrow = { 53 | RangeError: 1, 54 | ReferenceError: 1, 55 | SyntaxError: 1, 56 | TypeError: 1 57 | }; 58 | 59 | /** 60 | * Replacement for when() that sets up debug logging on the 61 | * returned promise. 62 | */ 63 | function whenDebug(promise, cb, eb, pb) { 64 | var args = [promise].concat(wrapCallbacks(promise, [cb, eb, pb])); 65 | return debugPromise(when.apply(null, args), promise); 66 | } 67 | 68 | /** 69 | * Setup debug output handlers for the supplied promise. 70 | * @param p {Promise} A trusted (when.js) promise 71 | * @param parent {Promise} promise from which p was created (e.g. via then()) 72 | * @return {Promise} a new promise that outputs debug info and 73 | * has a useful toString 74 | */ 75 | function debugPromise(p, parent) { 76 | var id, origThen, newPromise, logReject; 77 | 78 | if(own.call(p, 'parent')) { 79 | return p; 80 | } 81 | 82 | promiseId++; 83 | id = (parent && 'id' in parent) ? (parent.id + '.' + promiseId) : promiseId; 84 | 85 | origThen = p.then; 86 | newPromise = beget(p); 87 | newPromise.id = id; 88 | newPromise.parent = parent; 89 | 90 | newPromise.toString = function() { 91 | return toString('Promise', id); 92 | }; 93 | 94 | newPromise.then = function(cb, eb) { 95 | if(typeof eb === 'function') { 96 | var promise = newPromise; 97 | do { 98 | promise.handled = true; 99 | } while((promise = promise.parent) && !promise.handled); 100 | } 101 | 102 | return debugPromise(origThen.apply(p, wrapCallbacks(newPromise, arguments)), newPromise); 103 | }; 104 | 105 | logReject = function() { 106 | console.error(newPromise.toString()); 107 | }; 108 | 109 | p.then( 110 | function(val) { 111 | newPromise.toString = function() { 112 | return toString('Promise', id, 'resolved', val); 113 | }; 114 | return val; 115 | }, 116 | wrapCallback(newPromise, function(err) { 117 | newPromise.toString = function() { 118 | return toString('Promise', id, 'REJECTED', err); 119 | }; 120 | 121 | callGlobalHandler('reject', newPromise, err); 122 | 123 | if(!newPromise.handled) { 124 | logReject(); 125 | } 126 | 127 | throw err; 128 | }) 129 | ); 130 | 131 | return newPromise; 132 | } 133 | 134 | /** 135 | * Replacement for when.defer() that sets up debug logging 136 | * on the created Deferred, its resolver, and its promise. 137 | * @param [id] anything optional identifier for this Deferred that will show 138 | * up in debug output 139 | * @return {Deferred} a Deferred with debug logging 140 | */ 141 | function deferDebug() { 142 | var d, status, value, origResolve, origReject, origProgress, origThen, id; 143 | 144 | // Delegate to create a Deferred; 145 | d = when.defer(); 146 | 147 | status = 'pending'; 148 | value = pending; 149 | 150 | // if no id provided, generate one. Not sure if this is 151 | // useful or not. 152 | id = arguments[arguments.length - 1]; 153 | if(id === undef) { 154 | id = ++promiseId; 155 | } 156 | 157 | // Promise and resolver are frozen, so have to delegate 158 | // in order to setup toString() on promise, resolver, 159 | // and deferred 160 | origThen = d.promise.then; 161 | d.id = id; 162 | d.promise = debugPromise(d.promise, d); 163 | 164 | d.resolver = beget(d.resolver); 165 | d.resolver.toString = function() { 166 | return toString('Resolver', id, status, value); 167 | }; 168 | 169 | origProgress = d.resolver.progress; 170 | d.progress = d.resolver.progress = function(update) { 171 | // Notify global debug handler, if set 172 | callGlobalHandler('progress', d, update); 173 | 174 | return origProgress(update); 175 | }; 176 | 177 | origResolve = d.resolver.resolve; 178 | d.resolve = d.resolver.resolve = function(val) { 179 | value = val; 180 | status = 'resolving'; 181 | 182 | // Notify global debug handler, if set 183 | callGlobalHandler('resolve', d, val); 184 | 185 | return origResolve.apply(undef, arguments); 186 | }; 187 | 188 | origReject = d.resolver.reject; 189 | d.reject = d.resolver.reject = function(err) { 190 | value = err; 191 | status = 'REJECTING'; 192 | return origReject.apply(undef, arguments); 193 | }; 194 | 195 | d.toString = function() { 196 | return toString('Deferred', id, status, value); 197 | }; 198 | 199 | // Setup final state change handlers 200 | d.then( 201 | function(v) { status = 'resolved'; return v; }, 202 | function(e) { status = 'REJECTED'; return when.reject(e); } 203 | ); 204 | 205 | d.then = d.promise.then; 206 | 207 | // Add an id to all directly created promises. It'd be great 208 | // to find a way to propagate this id to promise created by .then() 209 | d.resolver.id = id; 210 | 211 | return d; 212 | } 213 | 214 | whenDebug.defer = deferDebug; 215 | whenDebug.isPromise = when.isPromise; 216 | 217 | // For each method we haven't already replaced, replace it with 218 | // one that sets up debug logging on the returned promise 219 | for(var p in when) { 220 | if(when.hasOwnProperty(p) && !(p in whenDebug)) { 221 | makeDebug(p, when[p]); 222 | } 223 | } 224 | 225 | return whenDebug; 226 | 227 | // Wrap result of when[name] in a debug promise 228 | function makeDebug(name, func) { 229 | whenDebug[name] = function() { 230 | return debugPromise(func.apply(when, arguments)); 231 | }; 232 | } 233 | 234 | // Wrap a promise callback to catch exceptions and log or 235 | // rethrow as uncatchable 236 | function wrapCallback(promise, cb) { 237 | return function(v) { 238 | try { 239 | return cb(v); 240 | } catch(err) { 241 | if(err) { 242 | if (err.name in exceptionsToRethrow) { 243 | throwUncatchable(err); 244 | } 245 | 246 | callGlobalHandler('reject', promise, err); 247 | } 248 | 249 | throw err; 250 | } 251 | }; 252 | } 253 | 254 | // Wrap a callback, errback, progressback tuple 255 | function wrapCallbacks(promise, callbacks) { 256 | var cb, args, len, i; 257 | 258 | args = []; 259 | 260 | for(i = 0, len = callbacks.length; i < len; i++) { 261 | args[i] = typeof (cb = callbacks[i]) == 'function' 262 | ? wrapCallback(promise, cb) 263 | : cb; 264 | } 265 | 266 | return args; 267 | } 268 | 269 | function callGlobalHandler(handler, promise, triggeringValue, auxValue) { 270 | var globalHandlers = whenDebug.debug; 271 | 272 | if(!(globalHandlers && typeof globalHandlers[handler] === 'function')) { 273 | return; 274 | } 275 | 276 | if(arguments.length < 4 && handler == 'reject') { 277 | try { 278 | throw new Error(promise.toString()); 279 | } catch(e) { 280 | auxValue = e; 281 | } 282 | } 283 | 284 | try { 285 | globalHandlers[handler](promise, triggeringValue, auxValue); 286 | } catch(handlerError) { 287 | throwUncatchable(new Error('when.js global debug handler threw: ' + String(handlerError))); 288 | } 289 | } 290 | 291 | // Stringify a promise, deferred, or resolver 292 | function toString(name, id, status, value) { 293 | var s = '[object ' + name + ' ' + id + ']'; 294 | 295 | if(arguments.length > 2) { 296 | s += ' ' + status; 297 | if(value !== pending) { 298 | s += ': ' + value; 299 | } 300 | } 301 | 302 | return s; 303 | } 304 | 305 | function throwUncatchable(err) { 306 | setTimeout(function() { 307 | throw err; 308 | }, 0); 309 | } 310 | 311 | // The usual Crockford 312 | function F() {} 313 | function beget(o) { 314 | F.prototype = o; 315 | o = new F(); 316 | F.prototype = undef; 317 | 318 | return o; 319 | } 320 | 321 | }); 322 | })(typeof define == 'function' 323 | ? define 324 | : function (deps, factory) { typeof module != 'undefined' 325 | ? (module.exports = factory(require('./when'))) 326 | : (this.when = factory(this.when)); 327 | } 328 | // Boilerplate for AMD, Node, and browser global 329 | ); 330 | -------------------------------------------------------------------------------- /node_modules/when/delay.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /*global setTimeout:true*/ 4 | 5 | /** 6 | * delay.js 7 | * 8 | * Helper that returns a promise that resolves after a delay. 9 | * 10 | * @author brian@hovercraftstudios.com 11 | */ 12 | 13 | (function(define) { 14 | define(['./when'], function(when) { 15 | 16 | var undef; 17 | 18 | /** 19 | * Creates a new promise that will resolve after a msec delay. If promise 20 | * is supplied, the delay will start *after* the supplied promise is resolved. 21 | * 22 | * Usage: 23 | * // Do something after 1 second, similar to using setTimeout 24 | * delay(1000).then(doSomething); 25 | * // or 26 | * when(delay(1000), doSomething); 27 | * 28 | * // Do something 1 second after triggeringPromise resolves 29 | * delay(triggeringPromise, 1000).then(doSomething, handleRejection); 30 | * // or 31 | * when(delay(triggeringPromise, 1000), doSomething, handleRejection); 32 | * 33 | * @param [promise] anything - any promise or value after which the delay will start 34 | * @param msec {Number} delay in milliseconds 35 | */ 36 | return function delay(promise, msec) { 37 | if(arguments.length < 2) { 38 | msec = promise >>> 0; 39 | promise = undef; 40 | } 41 | 42 | var deferred = when.defer(); 43 | 44 | setTimeout(function() { 45 | deferred.resolve(promise); 46 | }, msec); 47 | 48 | return deferred.promise; 49 | }; 50 | 51 | }); 52 | })(typeof define == 'function' 53 | ? define 54 | : function (deps, factory) { typeof module != 'undefined' 55 | ? (module.exports = factory(require('./when'))) 56 | : (this.when_delay = factory(this.when)); 57 | } 58 | // Boilerplate for AMD, Node, and browser global 59 | ); 60 | 61 | 62 | -------------------------------------------------------------------------------- /node_modules/when/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "when", 3 | "version": "1.6.1", 4 | "description": "A lightweight Promise and when() implementation, plus other async goodies.", 5 | "keywords": [ 6 | "promise", 7 | "promises", 8 | "deferred", 9 | "deferreds", 10 | "when", 11 | "async", 12 | "asynchronous", 13 | "cujo" 14 | ], 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "http://www.opensource.org/licenses/mit-license.php" 19 | } 20 | ], 21 | "repositories": [ 22 | { 23 | "type": "git", 24 | "url": "https://github.com/cujojs/when" 25 | } 26 | ], 27 | "bugs": "https://github.com/cujojs/when/issues", 28 | "maintainers": [ 29 | { 30 | "name": "Brian Cavalier", 31 | "url": "http://hovercraftstudios.com" 32 | }, 33 | { 34 | "name": "John Hann", 35 | "url": "http://unscriptable.com" 36 | } 37 | ], 38 | "devDependencies": { 39 | "buster": "~0.6", 40 | "promise-tests": "*" 41 | }, 42 | "main": "when", 43 | "directories": { 44 | "test": "test" 45 | }, 46 | "scripts": { 47 | "test": "buster test -e node && promise-tests promises-a test/when-adapter.js", 48 | "test-all": "buster test -e node && promise-tests all test/when-adapter.js", 49 | "start": "buster server", 50 | "test-browser": "buster test -e browser" 51 | }, 52 | "readme": "# when.js [![Build Status](https://secure.travis-ci.org/cujojs/when.png)](http://travis-ci.org/cujojs/when)\n\nWhen.js is cujojs's lightweight [CommonJS](http://wiki.commonjs.org/wiki/Promises) [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) and `when()` implementation, derived from the async core of [wire.js](https://github.com/cujojs/wire), cujojs's IOC Container. It also provides several other useful Promise-related concepts, such as joining multiple promises, mapping and reducing collections of promises, timed promises, and has a robust [unit test suite](#running-the-unit-tests).\n\nIt passes the [Promises/A Test Suite](https://github.com/domenic/promise-tests), is [frighteningly fast](https://github.com/cujojs/promise-perf-tests#test-results), and is **under 1.3k** when compiled with Google Closure (w/advanced optimizations) and gzipped, and has no dependencies.\n\n# What's New?\n\n### 1.6.1\n\n* Fix for accidental coercion of non-promises. See [#62](https://github.com/cujojs/when/issues/60).\n\n### 1.6.0\n\n* New [when.join](when/blob/master/docs/api.md#whenjoin) - Joins 2 or more promises together into a single promise.\n* [when.some](when/blob/master/docs/api.md#whensome) and [when.any](when/blob/master/docs/api.md#whenany) now act like competitive races, and have generally more useful behavior. [Read the discussion in #60](https://github.com/cujojs/when/issues/60).\n* *Experimental* progress event propagation. Progress events will propagate through promise chains. [Read the details here](when/blob/master/docs/api.md#progress-events).\n* *Temporarily* removed calls to `Object.freeze`. Promises are no longer frozen due to a horrendous v8 performance penalty. [Read discussion here](https://groups.google.com/d/topic/cujojs/w_olYqorbsY/discussion).\n\t* **IMPORTANT:** Continue to treat promises as if they are frozen, since `freeze()` will be reintroduced once v8 performance improves. \n* [when/debug](https://github.com/cujojs/when/wiki/when-debug) now allows setting global a debugging callback for rejected promises.\n\n### 1.5.2\n\n* Integrate @domenic's [Promises/A Test Suite](https://github.com/domenic/promise-tests). Runs via `npm test`.\n* No functional change\n\n### 1.5.1\n\n* Performance optimization for [when.defer](when/blob/master/docs/api.md#whendefer), up to 1.5x in some cases.\n* [when/debug](https://github.com/cujojs/when/wiki/when-debug) can now log exceptions and rejections in deeper promise chains, in some cases, even when the promises involved aren't when.js promises.\n\n### 1.5.0\n\n* New task execution and concurrency management: [when/sequence](when/blob/master/docs/api.md#whensequence), [when/pipeline](when/blob/master/docs/api.md#whenpipeline), and [when/parallel](when/blob/master/docs/api.md#whenparallel).\n* Performance optimizations for [when.all](when/blob/master/docs/api.md#whenall) and [when.map](when/blob/master/docs/api.md#whenmap), up to 2x in some cases.\n* Options for disabling [paranoid mode](when/blob/master/docs/api.md#paranoid-mode) that provides a significant performance gain in v8 (e.g. Node and Chrome). See this [v8 performance problem with Object.freeze](http://stackoverflow.com/questions/8435080/any-performance-benefit-to-locking-down-javascript-objects) for more info.\n* **Important:** `deferred` and `deferred.resolver` no longer throw when resolved/rejected multiple times. They will return silently as if the they had succeeded. This prevents parties to whom *only* the `resolver` has been given from using `try/catch` to determine the state of the associated promise.\n\t* For debugging, you can use the [when/debug](https://github.com/cujojs/when/wiki/when-debug) module, which will still throw when a deferred is resolved/rejected multiple times.\n\n[Full Changelog](https://github.com/cujojs/when/wiki/Changelog)\n\n# Docs & Examples\n\n[API docs](when/blob/master/docs/api.md#api)\n\n[More info on the wiki](https://github.com/cujojs/when/wiki)\n\n[Examples](https://github.com/cujojs/when/wiki/Examples)\n\nQuick Start\n===========\n\n### AMD\n\n1. `git clone https://github.com/cujojs/when` or `git submodule add https://github.com/cujojs/when`\n1. Configure your loader with a package:\n\n\t```javascript\n\tpackages: [\n\t\t{ name: 'when', location: 'path/to/when/', main: 'when' },\n\t\t// ... other packages ...\n\t]\n\t```\n\n1. `define(['when', ...], function(when, ...) { ... });` or `require(['when', ...], function(when, ...) { ... });`\n\n### Script Tag\n\n1. `git clone https://github.com/cujojs/when` or `git submodule add https://github.com/cujojs/when`\n1. ``\n1. `when` will be available as `window.when`\n\n### Node\n\n1. `npm install when`\n1. `var when = require('when');`\n\n### RingoJS\n\n1. `ringo-admin install cujojs/when`\n1. `var when = require('when');`\n\n# Running the Unit Tests\n\n## Node\n\nNote that when.js includes @domenic's [Promises/A Test Suite](https://github.com/domenic/promise-tests). Running unit tests in Node will run both when.js's own test suite, and the Promises/A Test Suite.\n\n1. `npm install`\n1. `npm test`\n\n## Browsers\n\n1. `npm install`\n1. `npm start` - starts buster server & prints a url\n1. Point browsers at /capture, e.g. `localhost:1111/capture`\n1. `npm run-script test-browser`\n\nReferences\n----------\n\nMuch of this code was inspired by @[unscriptable](https://github.com/unscriptable)'s [tiny promises](https://github.com/unscriptable/promises), the async innards of [wire.js](https://github.com/cujojs/wire), and some gists [here](https://gist.github.com/870729), [here](https://gist.github.com/892345), [here](https://gist.github.com/894356), and [here](https://gist.github.com/894360)\n\nSome of the code has been influenced by the great work in [Q](https://github.com/kriskowal/q), [Dojo's Deferred](https://github.com/dojo/dojo), and [uber.js](https://github.com/phiggins42/uber.js).\n", 53 | "_id": "when@1.6.1", 54 | "_from": "when@>= 0.0.1" 55 | } 56 | -------------------------------------------------------------------------------- /node_modules/when/parallel.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * parallel.js 5 | * 6 | * Run a set of task functions in parallel. All tasks will 7 | * receive the same args 8 | * 9 | * @author brian@hovercraftstudios.com 10 | */ 11 | 12 | (function(define) { 13 | define(['./when'], function(when) { 14 | 15 | /** 16 | * Run array of tasks in parallel 17 | * @param tasks {Array|Promise} array or promiseForArray of task functions 18 | * @param [args] {*} arguments to be passed to all tasks 19 | * @return {Promise} promise for array containing the 20 | * result of each task in the array position corresponding 21 | * to position of the task in the tasks array 22 | */ 23 | return function parallel(tasks /*, args... */) { 24 | var args = Array.prototype.slice.call(arguments, 1); 25 | return when.map(tasks, function(task) { 26 | return task.apply(null, args); 27 | }); 28 | }; 29 | 30 | }); 31 | })(typeof define == 'function' && define.amd 32 | ? define 33 | : function (deps, factory) { typeof exports == 'object' 34 | ? (module.exports = factory(require('./when'))) 35 | : (this.when_parallel = factory(this.when)); 36 | } 37 | // Boilerplate for AMD, Node, and browser global 38 | ); 39 | 40 | 41 | -------------------------------------------------------------------------------- /node_modules/when/pipeline.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * pipeline.js 5 | * 6 | * Run a set of task functions in sequence, passing the result 7 | * of the previous as an argument to the next. Like a shell 8 | * pipeline, e.g. `cat file.txt | grep 'foo' | sed -e 's/foo/bar/g' 9 | * 10 | * @author brian@hovercraftstudios.com 11 | */ 12 | 13 | (function(define) { 14 | define(['./when'], function(when) { 15 | 16 | /** 17 | * Run array of tasks in a pipeline where the next 18 | * tasks receives the result of the previous. The first task 19 | * will receive the initialArgs as its argument list. 20 | * @param tasks {Array|Promise} array or promise for array of task functions 21 | * @param [initialArgs...] {*} arguments to be passed to the first task 22 | * @return {Promise} promise for return value of the final task 23 | */ 24 | return function pipeline(tasks /* initialArgs... */) { 25 | var initialArgs, runTask; 26 | 27 | initialArgs = Array.prototype.slice.call(arguments, 1); 28 | 29 | // Self-optimizing function to run first task with multiple 30 | // args using apply, but subsequence tasks via direct invocation 31 | runTask = function(task, args) { 32 | runTask = function(task, arg) { 33 | return task(arg); 34 | }; 35 | 36 | return task.apply(null, args); 37 | }; 38 | 39 | return when.reduce(tasks, 40 | function(args, task) { 41 | return runTask(task, args); 42 | }, 43 | initialArgs 44 | ); 45 | }; 46 | 47 | }); 48 | })(typeof define == 'function' && define.amd 49 | ? define 50 | : function (deps, factory) { typeof exports == 'object' 51 | ? (module.exports = factory(require('./when'))) 52 | : (this.when_pipeline = factory(this.when)); 53 | } 54 | // Boilerplate for AMD, Node, and browser global 55 | ); 56 | 57 | 58 | -------------------------------------------------------------------------------- /node_modules/when/sequence.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * sequence.js 5 | * 6 | * Run a set of task functions in sequence. All tasks will 7 | * receive the same args. 8 | * 9 | * @author brian@hovercraftstudios.com 10 | */ 11 | 12 | (function(define) { 13 | define(['./when'], function(when) { 14 | 15 | /** 16 | * Run array of tasks in sequence with no overlap 17 | * @param tasks {Array|Promise} array or promiseForArray of task functions 18 | * @param [args] {*} arguments to be passed to all tasks 19 | * @return {Promise} promise for an array containing 20 | * the result of each task in the array position corresponding 21 | * to position of the task in the tasks array 22 | */ 23 | return function sequence(tasks /*, args... */) { 24 | var args = Array.prototype.slice.call(arguments, 1); 25 | return when.reduce(tasks, function(results, task) { 26 | return when(task.apply(null, args), function(result) { 27 | results.push(result); 28 | return results; 29 | }); 30 | }, []); 31 | }; 32 | 33 | }); 34 | })(typeof define == 'function' && define.amd 35 | ? define 36 | : function (deps, factory) { typeof exports == 'object' 37 | ? (module.exports = factory(require('./when'))) 38 | : (this.when_sequence = factory(this.when)); 39 | } 40 | // Boilerplate for AMD, Node, and browser global 41 | ); 42 | 43 | 44 | -------------------------------------------------------------------------------- /node_modules/when/timed.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * timed.js 5 | * 6 | * Helper group that aggregates all time & delay related helpers. If you 7 | * use several of these helpers it can be more convenient to use this module 8 | * instead of the individual helpers 9 | * 10 | * @author brian@hovercraftstudios.com 11 | */ 12 | 13 | (function(define) { 14 | define(['./timeout', './delay'], function(timeout, delay) { 15 | 16 | return { 17 | timeout: timeout, 18 | delay: delay 19 | }; 20 | 21 | }); 22 | })(typeof define == 'function' 23 | ? define 24 | : function (deps, factory) { typeof module != 'undefined' 25 | ? (module.exports = factory.apply(this, deps.map(require))) 26 | : (this.when_timed = factory(this.when_timeout, this.when_delay)); 27 | } 28 | // Boilerplate for AMD, Node, and browser global 29 | ); 30 | 31 | 32 | -------------------------------------------------------------------------------- /node_modules/when/timeout.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /*global setTimeout:true, clearTimeout:true*/ 4 | 5 | /** 6 | * timeout.js 7 | * 8 | * Helper that returns a promise that rejects after a specified timeout, 9 | * if not explicitly resolved or rejected before that. 10 | * 11 | * @author brian@hovercraftstudios.com 12 | */ 13 | 14 | (function(define) { 15 | define(['./when'], function(when) { 16 | 17 | var undef; 18 | 19 | /** 20 | * Returns a new promise that will automatically reject after msec if 21 | * the supplied promise doesn't resolve or reject before that. 22 | * 23 | * Usage: 24 | * 25 | * var d = when.defer(); 26 | * // Setup d however you need 27 | * 28 | * // return a new promise that will timeout if d doesn't resolve/reject first 29 | * return timeout(d.promise, 1000); 30 | * 31 | * @param promise anything - any promise or value that should trigger 32 | * the returned promise to resolve or reject before the msec timeout 33 | * @param msec {Number} timeout in milliseconds 34 | * 35 | * @returns {Promise} 36 | */ 37 | return function timeout(promise, msec) { 38 | var deferred, timeoutRef; 39 | 40 | deferred = when.defer(); 41 | 42 | timeoutRef = setTimeout(function onTimeout() { 43 | timeoutRef && deferred.reject(new Error('timed out')); 44 | }, msec); 45 | 46 | function cancelTimeout() { 47 | clearTimeout(timeoutRef); 48 | timeoutRef = undef; 49 | } 50 | 51 | when(promise, 52 | function(value) { 53 | cancelTimeout(); 54 | deferred.resolve(value); 55 | }, 56 | function(reason) { 57 | cancelTimeout(); 58 | deferred.reject(reason); 59 | } 60 | ); 61 | 62 | return deferred.promise; 63 | }; 64 | 65 | }); 66 | })(typeof define == 'function' 67 | ? define 68 | : function (deps, factory) { typeof module != 'undefined' 69 | ? (module.exports = factory(require('./when'))) 70 | : (this.when_timeout = factory(this.when)); 71 | } 72 | // Boilerplate for AMD, Node, and browser global 73 | ); 74 | 75 | 76 | -------------------------------------------------------------------------------- /node_modules/when/when.js: -------------------------------------------------------------------------------- 1 | /** @license MIT License (c) copyright B Cavalier & J Hann */ 2 | 3 | /** 4 | * A lightweight CommonJS Promises/A and when() implementation 5 | * when is part of the cujo.js family of libraries (http://cujojs.com/) 6 | * 7 | * Licensed under the MIT License at: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * @version 1.6.1 11 | */ 12 | 13 | (function(define) { 'use strict'; 14 | define(['module'], function () { 15 | var reduceArray, slice, undef; 16 | 17 | // 18 | // Public API 19 | // 20 | 21 | when.defer = defer; // Create a deferred 22 | when.resolve = resolve; // Create a resolved promise 23 | when.reject = reject; // Create a rejected promise 24 | 25 | when.join = join; // Join 2 or more promises 26 | 27 | when.all = all; // Resolve a list of promises 28 | when.some = some; // Resolve a sub-set of promises 29 | when.any = any; // Resolve one promise in a list 30 | 31 | when.map = map; // Array.map() for promises 32 | when.reduce = reduce; // Array.reduce() for promises 33 | 34 | when.chain = chain; // Make a promise trigger another resolver 35 | 36 | when.isPromise = isPromise; // Determine if a thing is a promise 37 | 38 | /** 39 | * Register an observer for a promise or immediate value. 40 | * @function 41 | * @name when 42 | * @namespace 43 | * 44 | * @param promiseOrValue {*} 45 | * @param {Function} [callback] callback to be called when promiseOrValue is 46 | * successfully fulfilled. If promiseOrValue is an immediate value, callback 47 | * will be invoked immediately. 48 | * @param {Function} [errback] callback to be called when promiseOrValue is 49 | * rejected. 50 | * @param {Function} [progressHandler] callback to be called when progress updates 51 | * are issued for promiseOrValue. 52 | * @returns {Promise} a new {@link Promise} that will complete with the return 53 | * value of callback or errback or the completion value of promiseOrValue if 54 | * callback and/or errback is not supplied. 55 | */ 56 | function when(promiseOrValue, callback, errback, progressHandler) { 57 | // Get a trusted promise for the input promiseOrValue, and then 58 | // register promise handlers 59 | return resolve(promiseOrValue).then(callback, errback, progressHandler); 60 | } 61 | 62 | /** 63 | * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if 64 | * promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise} 65 | * whose value is promiseOrValue if promiseOrValue is an immediate value. 66 | * @memberOf when 67 | * 68 | * @param promiseOrValue {*} 69 | * @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} 70 | * returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} 71 | * whose resolution value is: 72 | * * the resolution value of promiseOrValue if it's a foreign promise, or 73 | * * promiseOrValue if it's a value 74 | */ 75 | function resolve(promiseOrValue) { 76 | var promise, deferred; 77 | 78 | if(promiseOrValue instanceof Promise) { 79 | // It's a when.js promise, so we trust it 80 | promise = promiseOrValue; 81 | 82 | } else { 83 | // It's not a when.js promise. See if it's a foreign promise or a value. 84 | 85 | // Some promises, particularly Q promises, provide a valueOf method that 86 | // attempts to synchronously return the fulfilled value of the promise, or 87 | // returns the unresolved promise itself. Attempting to break a fulfillment 88 | // value out of a promise appears to be necessary to break cycles between 89 | // Q and When attempting to coerce each-other's promises in an infinite loop. 90 | // For promises that do not implement "valueOf", the Object#valueOf is harmless. 91 | // See: https://github.com/kriskowal/q/issues/106 92 | // IMPORTANT: Must check for a promise here, since valueOf breaks other things 93 | // like Date. 94 | if (isPromise(promiseOrValue) && typeof promiseOrValue.valueOf === 'function') { 95 | promiseOrValue = promiseOrValue.valueOf(); 96 | } 97 | 98 | if(isPromise(promiseOrValue)) { 99 | // It looks like a thenable, but we don't know where it came from, 100 | // so we don't trust its implementation entirely. Introduce a trusted 101 | // middleman when.js promise 102 | deferred = defer(); 103 | 104 | // IMPORTANT: This is the only place when.js should ever call .then() on 105 | // an untrusted promise. 106 | promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress); 107 | promise = deferred.promise; 108 | 109 | } else { 110 | // It's a value, not a promise. Create a resolved promise for it. 111 | promise = fulfilled(promiseOrValue); 112 | } 113 | } 114 | 115 | return promise; 116 | } 117 | 118 | /** 119 | * Returns a rejected promise for the supplied promiseOrValue. If 120 | * promiseOrValue is a value, it will be the rejection value of the 121 | * returned promise. If promiseOrValue is a promise, its 122 | * completion value will be the rejected value of the returned promise 123 | * @memberOf when 124 | * 125 | * @param promiseOrValue {*} the rejected value of the returned {@link Promise} 126 | * @return {Promise} rejected {@link Promise} 127 | */ 128 | function reject(promiseOrValue) { 129 | return when(promiseOrValue, function(value) { 130 | return rejected(value); 131 | }); 132 | } 133 | 134 | /** 135 | * Trusted Promise constructor. A Promise created from this constructor is 136 | * a trusted when.js promise. Any other duck-typed promise is considered 137 | * untrusted. 138 | * @constructor 139 | * @name Promise 140 | */ 141 | function Promise(then) { 142 | this.then = then; 143 | } 144 | 145 | Promise.prototype = { 146 | /** 147 | * Register a callback that will be called when a promise is 148 | * resolved or rejected. Optionally also register a progress handler. 149 | * Shortcut for .then(alwaysback, alwaysback, progback) 150 | * @memberOf Promise 151 | * @param alwaysback {Function} 152 | * @param progback {Function} 153 | * @return {Promise} 154 | */ 155 | always: function(alwaysback, progback) { 156 | return this.then(alwaysback, alwaysback, progback); 157 | }, 158 | 159 | /** 160 | * Register a rejection handler. Shortcut for .then(null, errback) 161 | * @memberOf Promise 162 | * @param errback {Function} 163 | * @return {Promise} 164 | */ 165 | otherwise: function(errback) { 166 | return this.then(undef, errback); 167 | } 168 | }; 169 | 170 | /** 171 | * Create an already-resolved promise for the supplied value 172 | * @private 173 | * 174 | * @param value anything 175 | * @return {Promise} 176 | */ 177 | function fulfilled(value) { 178 | var p = new Promise(function(callback) { 179 | try { 180 | return resolve(callback ? callback(value) : value); 181 | } catch(e) { 182 | return rejected(e); 183 | } 184 | }); 185 | 186 | return p; 187 | } 188 | 189 | /** 190 | * Create an already-rejected {@link Promise} with the supplied 191 | * rejection reason. 192 | * @private 193 | * 194 | * @param reason rejection reason 195 | * @return {Promise} 196 | */ 197 | function rejected(reason) { 198 | var p = new Promise(function(callback, errback) { 199 | try { 200 | return errback ? resolve(errback(reason)) : rejected(reason); 201 | } catch(e) { 202 | return rejected(e); 203 | } 204 | }); 205 | 206 | return p; 207 | } 208 | 209 | /** 210 | * Creates a new, Deferred with fully isolated resolver and promise parts, 211 | * either or both of which may be given out safely to consumers. 212 | * The Deferred itself has the full API: resolve, reject, progress, and 213 | * then. The resolver has resolve, reject, and progress. The promise 214 | * only has then. 215 | * @memberOf when 216 | * @function 217 | * 218 | * @return {Deferred} 219 | */ 220 | function defer() { 221 | var deferred, promise, handlers, progressHandlers, 222 | _then, _progress, _resolve; 223 | 224 | /** 225 | * The promise for the new deferred 226 | * @type {Promise} 227 | */ 228 | promise = new Promise(then); 229 | 230 | /** 231 | * The full Deferred object, with {@link Promise} and {@link Resolver} parts 232 | * @class Deferred 233 | * @name Deferred 234 | */ 235 | deferred = { 236 | then: then, 237 | resolve: promiseResolve, 238 | reject: promiseReject, 239 | // TODO: Consider renaming progress() to notify() 240 | progress: promiseProgress, 241 | 242 | promise: promise, 243 | 244 | resolver: { 245 | resolve: promiseResolve, 246 | reject: promiseReject, 247 | progress: promiseProgress 248 | } 249 | }; 250 | 251 | handlers = []; 252 | progressHandlers = []; 253 | 254 | /** 255 | * Pre-resolution then() that adds the supplied callback, errback, and progback 256 | * functions to the registered listeners 257 | * @private 258 | * 259 | * @param [callback] {Function} resolution handler 260 | * @param [errback] {Function} rejection handler 261 | * @param [progback] {Function} progress handler 262 | * @throws {Error} if any argument is not null, undefined, or a Function 263 | */ 264 | _then = function(callback, errback, progback) { 265 | var deferred, progressHandler; 266 | 267 | deferred = defer(); 268 | progressHandler = progback 269 | ? function(update) { 270 | try { 271 | // Allow progress handler to transform progress event 272 | deferred.progress(progback(update)); 273 | } catch(e) { 274 | // Use caught value as progress 275 | deferred.progress(e); 276 | } 277 | } 278 | : deferred.progress; 279 | 280 | handlers.push(function(promise) { 281 | promise.then(callback, errback) 282 | .then(deferred.resolve, deferred.reject, progressHandler); 283 | }); 284 | 285 | progressHandlers.push(progressHandler); 286 | 287 | return deferred.promise; 288 | }; 289 | 290 | /** 291 | * Issue a progress event, notifying all progress listeners 292 | * @private 293 | * @param update {*} progress event payload to pass to all listeners 294 | */ 295 | _progress = function(update) { 296 | processQueue(progressHandlers, update); 297 | return update; 298 | }; 299 | 300 | /** 301 | * Transition from pre-resolution state to post-resolution state, notifying 302 | * all listeners of the resolution or rejection 303 | * @private 304 | * @param completed {Promise} the completed value of this deferred 305 | */ 306 | _resolve = function(completed) { 307 | completed = resolve(completed); 308 | 309 | // Replace _then with one that directly notifies with the result. 310 | _then = completed.then; 311 | // Replace _resolve so that this Deferred can only be completed once 312 | _resolve = resolve; 313 | // Make _progress a noop, to disallow progress for the resolved promise. 314 | _progress = noop; 315 | 316 | // Notify handlers 317 | processQueue(handlers, completed); 318 | 319 | // Free progressHandlers array since we'll never issue progress events 320 | progressHandlers = handlers = undef; 321 | 322 | return completed; 323 | }; 324 | 325 | return deferred; 326 | 327 | /** 328 | * Wrapper to allow _then to be replaced safely 329 | * @param [callback] {Function} resolution handler 330 | * @param [errback] {Function} rejection handler 331 | * @param [progback] {Function} progress handler 332 | * @return {Promise} new Promise 333 | * @throws {Error} if any argument is not null, undefined, or a Function 334 | */ 335 | function then(callback, errback, progback) { 336 | return _then(callback, errback, progback); 337 | } 338 | 339 | /** 340 | * Wrapper to allow _resolve to be replaced 341 | */ 342 | function promiseResolve(val) { 343 | return _resolve(val); 344 | } 345 | 346 | /** 347 | * Wrapper to allow _resolve to be replaced 348 | */ 349 | function promiseReject(err) { 350 | return _resolve(rejected(err)); 351 | } 352 | 353 | /** 354 | * Wrapper to allow _progress to be replaced 355 | * @param {*} update progress update 356 | */ 357 | function promiseProgress(update) { 358 | return _progress(update); 359 | } 360 | } 361 | 362 | /** 363 | * Determines if promiseOrValue is a promise or not. Uses the feature 364 | * test from http://wiki.commonjs.org/wiki/Promises/A to determine if 365 | * promiseOrValue is a promise. 366 | * 367 | * @param {*} promiseOrValue anything 368 | * @returns {Boolean} true if promiseOrValue is a {@link Promise} 369 | */ 370 | function isPromise(promiseOrValue) { 371 | return promiseOrValue && typeof promiseOrValue.then === 'function'; 372 | } 373 | 374 | /** 375 | * Initiates a competitive race, returning a promise that will resolve when 376 | * howMany of the supplied promisesOrValues have resolved, or will reject when 377 | * it becomes impossible for howMany to resolve, for example, when 378 | * (promisesOrValues.length - howMany) + 1 input promises reject. 379 | * @memberOf when 380 | * 381 | * @param promisesOrValues {Array} array of anything, may contain a mix 382 | * of {@link Promise}s and values 383 | * @param howMany {Number} number of promisesOrValues to resolve 384 | * @param [callback] {Function} resolution handler 385 | * @param [errback] {Function} rejection handler 386 | * @param [progback] {Function} progress handler 387 | * @returns {Promise} promise that will resolve to an array of howMany values that 388 | * resolved first, or will reject with an array of (promisesOrValues.length - howMany) + 1 389 | * rejection reasons. 390 | */ 391 | function some(promisesOrValues, howMany, callback, errback, progback) { 392 | 393 | checkCallbacks(2, arguments); 394 | 395 | return when(promisesOrValues, function(promisesOrValues) { 396 | 397 | var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, progress, len, i; 398 | 399 | len = promisesOrValues.length >>> 0; 400 | 401 | toResolve = Math.max(0, Math.min(howMany, len)); 402 | values = []; 403 | 404 | toReject = (len - toResolve) + 1; 405 | reasons = []; 406 | 407 | deferred = defer(); 408 | 409 | // No items in the input, resolve immediately 410 | if (!toResolve) { 411 | deferred.resolve(values); 412 | 413 | } else { 414 | progress = deferred.progress; 415 | 416 | rejectOne = function(reason) { 417 | reasons.push(reason); 418 | if(!--toReject) { 419 | fulfillOne = rejectOne = noop; 420 | deferred.reject(reasons); 421 | } 422 | }; 423 | 424 | fulfillOne = function(val) { 425 | // This orders the values based on promise resolution order 426 | // Another strategy would be to use the original position of 427 | // the corresponding promise. 428 | values.push(val); 429 | 430 | if (!--toResolve) { 431 | fulfillOne = rejectOne = noop; 432 | deferred.resolve(values); 433 | } 434 | }; 435 | 436 | for(i = 0; i < len; ++i) { 437 | if(i in promisesOrValues) { 438 | when(promisesOrValues[i], fulfiller, rejecter, progress); 439 | } 440 | } 441 | } 442 | 443 | return deferred.then(callback, errback, progback); 444 | 445 | function rejecter(reason) { 446 | rejectOne(reason); 447 | } 448 | 449 | function fulfiller(val) { 450 | fulfillOne(val); 451 | } 452 | 453 | }); 454 | } 455 | 456 | /** 457 | * Initiates a competitive race, returning a promise that will resolve when 458 | * any one of the supplied promisesOrValues has resolved or will reject when 459 | * *all* promisesOrValues have rejected. 460 | * @memberOf when 461 | * 462 | * @param promisesOrValues {Array|Promise} array of anything, may contain a mix 463 | * of {@link Promise}s and values 464 | * @param [callback] {Function} resolution handler 465 | * @param [errback] {Function} rejection handler 466 | * @param [progback] {Function} progress handler 467 | * @returns {Promise} promise that will resolve to the value that resolved first, or 468 | * will reject with an array of all rejected inputs. 469 | */ 470 | function any(promisesOrValues, callback, errback, progback) { 471 | 472 | function unwrapSingleResult(val) { 473 | return callback ? callback(val[0]) : val[0]; 474 | } 475 | 476 | return some(promisesOrValues, 1, unwrapSingleResult, errback, progback); 477 | } 478 | 479 | /** 480 | * Return a promise that will resolve only once all the supplied promisesOrValues 481 | * have resolved. The resolution value of the returned promise will be an array 482 | * containing the resolution values of each of the promisesOrValues. 483 | * @memberOf when 484 | * 485 | * @param promisesOrValues {Array|Promise} array of anything, may contain a mix 486 | * of {@link Promise}s and values 487 | * @param [callback] {Function} 488 | * @param [errback] {Function} 489 | * @param [progressHandler] {Function} 490 | * @returns {Promise} 491 | */ 492 | function all(promisesOrValues, callback, errback, progressHandler) { 493 | checkCallbacks(1, arguments); 494 | return map(promisesOrValues, identity).then(callback, errback, progressHandler); 495 | } 496 | 497 | /** 498 | * Joins multiple promises into a single returned promise. 499 | * @memberOf when 500 | * @param {Promise|*} [...promises] two or more promises to join 501 | * @return {Promise} a promise that will fulfill when *all* the input promises 502 | * have fulfilled, or will reject when *any one* of the input promises rejects. 503 | */ 504 | function join(/* ...promises */) { 505 | return map(arguments, identity); 506 | } 507 | 508 | /** 509 | * Traditional map function, similar to `Array.prototype.map()`, but allows 510 | * input to contain {@link Promise}s and/or values, and mapFunc may return 511 | * either a value or a {@link Promise} 512 | * 513 | * @memberOf when 514 | * 515 | * @param promise {Array|Promise} array of anything, may contain a mix 516 | * of {@link Promise}s and values 517 | * @param mapFunc {Function} mapping function mapFunc(value) which may return 518 | * either a {@link Promise} or value 519 | * @returns {Promise} a {@link Promise} that will resolve to an array containing 520 | * the mapped output values. 521 | */ 522 | function map(promise, mapFunc) { 523 | return when(promise, function(array) { 524 | var results, len, toResolve, resolve, reject, i, d; 525 | 526 | // Since we know the resulting length, we can preallocate the results 527 | // array to avoid array expansions. 528 | toResolve = len = array.length >>> 0; 529 | results = []; 530 | d = defer(); 531 | 532 | if(!toResolve) { 533 | d.resolve(results); 534 | } else { 535 | 536 | reject = d.reject; 537 | resolve = function resolveOne(item, i) { 538 | when(item, mapFunc).then(function(mapped) { 539 | results[i] = mapped; 540 | 541 | if(!--toResolve) { 542 | d.resolve(results); 543 | } 544 | }, reject); 545 | }; 546 | 547 | // Since mapFunc may be async, get all invocations of it into flight 548 | for(i = 0; i < len; i++) { 549 | if(i in array) { 550 | resolve(array[i], i); 551 | } else { 552 | --toResolve; 553 | } 554 | } 555 | 556 | } 557 | 558 | return d.promise; 559 | 560 | }); 561 | } 562 | 563 | /** 564 | * Traditional reduce function, similar to `Array.prototype.reduce()`, but 565 | * input may contain {@link Promise}s and/or values, and reduceFunc 566 | * may return either a value or a {@link Promise}, *and* initialValue may 567 | * be a {@link Promise} for the starting value. 568 | * @memberOf when 569 | * 570 | * @param promise {Array|Promise} array of anything, may contain a mix 571 | * of {@link Promise}s and values. May also be a {@link Promise} for 572 | * an array. 573 | * @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total), 574 | * where total is the total number of items being reduced, and will be the same 575 | * in each call to reduceFunc. 576 | * @param [initialValue] {*} starting value, or a {@link Promise} for the starting value 577 | * @returns {Promise} that will resolve to the final reduced value 578 | */ 579 | function reduce(promise, reduceFunc /*, initialValue */) { 580 | var args = slice.call(arguments, 1); 581 | 582 | return when(promise, function(array) { 583 | var total; 584 | 585 | total = array.length; 586 | 587 | // Wrap the supplied reduceFunc with one that handles promises and then 588 | // delegates to the supplied. 589 | args[0] = function (current, val, i) { 590 | return when(current, function (c) { 591 | return when(val, function (value) { 592 | return reduceFunc(c, value, i, total); 593 | }); 594 | }); 595 | }; 596 | 597 | return reduceArray.apply(array, args); 598 | }); 599 | } 600 | 601 | /** 602 | * Ensure that resolution of promiseOrValue will complete resolver with the completion 603 | * value of promiseOrValue, or instead with resolveValue if it is provided. 604 | * @memberOf when 605 | * 606 | * @param promiseOrValue 607 | * @param resolver {Resolver} 608 | * @param [resolveValue] anything 609 | * @returns {Promise} 610 | */ 611 | function chain(promiseOrValue, resolver, resolveValue) { 612 | var useResolveValue = arguments.length > 2; 613 | 614 | return when(promiseOrValue, 615 | function(val) { 616 | return resolver.resolve(useResolveValue ? resolveValue : val); 617 | }, 618 | resolver.reject, 619 | resolver.progress 620 | ); 621 | } 622 | 623 | // 624 | // Utility functions 625 | // 626 | 627 | function processQueue(queue, value) { 628 | var handler, i = 0; 629 | 630 | while (handler = queue[i++]) { 631 | handler(value); 632 | } 633 | } 634 | 635 | /** 636 | * Helper that checks arrayOfCallbacks to ensure that each element is either 637 | * a function, or null or undefined. 638 | * @private 639 | * 640 | * @param arrayOfCallbacks {Array} array to check 641 | * @throws {Error} if any element of arrayOfCallbacks is something other than 642 | * a Functions, null, or undefined. 643 | */ 644 | function checkCallbacks(start, arrayOfCallbacks) { 645 | var arg, i = arrayOfCallbacks.length; 646 | 647 | while(i > start) { 648 | arg = arrayOfCallbacks[--i]; 649 | 650 | if (arg != null && typeof arg != 'function') { 651 | throw new Error('arg '+i+' must be a function'); 652 | } 653 | } 654 | } 655 | 656 | /** 657 | * No-Op function used in method replacement 658 | * @private 659 | */ 660 | function noop() {} 661 | 662 | slice = [].slice; 663 | 664 | // ES5 reduce implementation if native not available 665 | // See: http://es5.github.com/#x15.4.4.21 as there are many 666 | // specifics and edge cases. 667 | reduceArray = [].reduce || 668 | function(reduceFunc /*, initialValue */) { 669 | /*jshint maxcomplexity: 7*/ 670 | 671 | // ES5 dictates that reduce.length === 1 672 | 673 | // This implementation deviates from ES5 spec in the following ways: 674 | // 1. It does not check if reduceFunc is a Callable 675 | 676 | var arr, args, reduced, len, i; 677 | 678 | i = 0; 679 | // This generates a jshint warning, despite being valid 680 | // "Missing 'new' prefix when invoking a constructor." 681 | // See https://github.com/jshint/jshint/issues/392 682 | arr = Object(this); 683 | len = arr.length >>> 0; 684 | args = arguments; 685 | 686 | // If no initialValue, use first item of array (we know length !== 0 here) 687 | // and adjust i to start at second item 688 | if(args.length <= 1) { 689 | // Skip to the first real element in the array 690 | for(;;) { 691 | if(i in arr) { 692 | reduced = arr[i++]; 693 | break; 694 | } 695 | 696 | // If we reached the end of the array without finding any real 697 | // elements, it's a TypeError 698 | if(++i >= len) { 699 | throw new TypeError(); 700 | } 701 | } 702 | } else { 703 | // If initialValue provided, use it 704 | reduced = args[1]; 705 | } 706 | 707 | // Do the actual reduce 708 | for(;i < len; ++i) { 709 | // Skip holes 710 | if(i in arr) { 711 | reduced = reduceFunc(reduced, arr[i], i, arr); 712 | } 713 | } 714 | 715 | return reduced; 716 | }; 717 | 718 | function identity(x) { 719 | return x; 720 | } 721 | 722 | return when; 723 | }); 724 | })(typeof define == 'function' && define.amd 725 | ? define 726 | : function (deps, factory) { typeof exports === 'object' 727 | ? (module.exports = factory()) 728 | : (this.when = factory()); 729 | } 730 | // Boilerplate for AMD, Node, and browser global 731 | ); 732 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ffmpeg", 3 | "description": "Utility for managing video streams using ffmpeg", 4 | "version": "0.0.4", 5 | "author": { 6 | "name": "Damiano Ciarla", 7 | "email": "damiano.ciarl@gmail.com" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/damianociarla/node-ffmpeg.git" 12 | }, 13 | "main": "./index.js", 14 | "license": "MIT", 15 | "dependencies": { 16 | "when" : ">= 0.0.1" 17 | } 18 | } 19 | --------------------------------------------------------------------------------