summaryrefslogtreecommitdiff
path: root/.temp/youtube-dl.js
blob: ae2ad1510e3a894ef5a5af64174ae857071f1a9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
var spawn        = require('child_process').spawn;
var execFile     = require('child_process').execFile;
var EventEmitter = require('events').EventEmitter;
var fs           = require('fs');
var path         = require('path');
var url          = require('url');
var split        = require('event-stream').split;
var util         = require('./util');


// Check that youtube-dl file exists.
var file = path.join(__dirname, '..', 'bin', 'youtube-dl');
fs.exists(file, function(exists) {
  if (exists) return;

  require(__dirname + '/../scripts/download');
  fs.exists(file, function(exists) {
    if (!exists) {
      throw new Error('youtube-dl file does not exist. tried to download it but failed.');
    }
  });
});

var progressRegex = /(\d+(?:\.\d)?)% of (\d+\.\d+\w+) at\s+([^\s]+) ETA ((\d|-)+:(\d|-)+)/;

var isYouTubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\//;

// check if win
var isWin = /^win/.test(process.platform);

/**
 * Downloads a video.
 *
 * @param {String} urladdr
 * @param {String} dest The directory where the video will be saved.
 * @param {Array.<String>} args
 */
exports.download = function(urladdr, dest, args) {
  // Setup settings.
  dest = dest || process.cwd();
  if (args == null) {
    args = [];
  } else {
    args = util.parseOpts(args);
  }
  args.push(urladdr);

   // Parse url.
  var details = url.parse(urladdr, true);
  var query = details.query;

  // Get possible IDs.
  var id = query.v || '';

  // Check for long and short youtube video url.
  if (!id && isYouTubeRegex.test(urladdr)) {
    // Get possible IDs for youtu.be from urladdr.
    id = details.pathname.slice(1);
  }

  var speed = [];
  var start = Date.now();

  var filename, size, state, youtubedl;
  var emitter = new EventEmitter();
  var line = new split(/\r?\n|\r/);

  // Call youtube-dl.
  if (!isWin) {
    youtubedl = spawn(file, args, { cwd: dest });
  } else {
    youtubedl = spawn(process.env.PYTHON, [file].concat(args), { cwd: dest });
  }

  youtubedl.stdout.setEncoding('utf8');
  youtubedl.stdout.pipe(line);
  line.on('data', function(data) {
    var pos;

    // Check if video is uploading so script can start
    // calling the download progress function.
    if (state === 'download') {
      var result;
      if (result = progressRegex.exec(data)) {
        // If this is the first progress display, grab file size.
        if (!size) {
          emitter.emit(state, {
              filename : filename
            , size     : size = result[2]
          });
        }

        if (result[3] !== '---b/s') {
          speed.push(util.toBytes(result[3].substring(0, result[3].length - 2)));
        }
        emitter.emit('progress', {
            percent : result[1]
          , speed   : result[3]
          , eta     : result[4]
        });
      }

    // About to start downloading video.
    } else if ((pos = data.indexOf('[download] ')) === 0) {
      state = 'download';
      filename = data.slice(24);

    // Check if this is any other state.
    } else if ((pos = data.indexOf(']')) !== -1) {
      state = data.slice(pos + 2);
      emitter.emit(state);
    }
  });

  youtubedl.stderr.on('data', function(data) {
    data = data.toString().trim();
    var err = new Error(data.substring(7, data.length - 1));
    emitter.emit('error', err);
  });

  youtubedl.on('exit', function() {
    var averageSpeed = 0;
    if (speed.length) {
      for (var i = 0, len = speed.length; i < len; i++) {
        averageSpeed += speed[i];
      }
      averageSpeed /= len;
    }

    var timeTaken = Date.now() - start;
    emitter.emit('end', {
        id                : id
      , filename          : filename
      , size              : size
      , timeTakenms       : timeTaken
      , timeTaken         : util.getHumanTime(timeTaken)
      , averageSpeedBytes : util.round(averageSpeed, 2)
      , averageSpeed      : util.getHumanSize(averageSpeed) + '/s'
    });
  });

  return emitter;
};


/**
 * Calls youtube-dl with some arguments and the `callback`
 * gets called with the output.
 *
 * @param {String} url
 * @param {Array.<String>} args
 * @param {Array.<String>} options
 * @param {Function(!Error, String)} callback
 */
function call(url, args, options, callback) {
  args = args.concat(util.parseOpts(options));
  args.push(url);

  var opt = [file, args];

  if (isWin) { opt = [process.env.PYTHON, [file].concat(args)]; }

  // Call youtube-dl.
  execFile(opt[0], opt[1], function(err, stdout, stderr) {
    if (err) return callback(err);
    if (stderr) return callback(new Error(stderr.slice(7)));

    var data = stdout.trim().split(/\r?\n/);
    callback(null, data);
  });

}


/**
 * Gets info from a video.
 *
 * @param {String} url
 * @param {Array.<String>} options
 * @param {Function(!Error, Object)} callback
 */
exports.getInfo = function(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = [];
  }
  var args = [
      '--get-id'
    , '--get-url'
    , '--get-title'
    , '--get-thumbnail'
    , '--get-filename'
    , '--get-format'
    , '--get-description'
  ];

  call(url, args, options, function(err, data) {
    if (err) return callback(err);

    var format = data[data.length - 1].split(' - ');
    var info = {
        title       : data[0]
      , id          : data[1]
      , url         : data[2]
      , thumbnail   : data[3]
      , description : data.slice(4, data.length - 2).join('\n')
      , filename    : data[data.length - 2]
      , itag        : parseInt(format[0], 10)
      , resolution  : format[1]
    };

    callback(null, info);
  });
};


var formatsRegex = /^(\d+)\s+([a-z0-9]+)\s+(\d+x\d+|d+p|audio only)/;

/**
 * @param {String} url
 * @param {Array.<String>} options
 * @param {Function(!Error, Object)} callback
 */
exports.getFormats = function(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = [];
  }
  call(url, ['--list-formats'], options, function(err, data) {
    if (err) return callback(err);

    var formats = [];
    data.map(function(line) {
      var result = formatsRegex.exec(line);
      if (result) {
        formats.push({
          itag       : parseInt(result[1], 10),
          filetype   : result[2],
          resolution : result[3],
        });
      }
    });

    callback(null, formats);
  });
};