forked from ttezel/twit
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfile_uploader.js
143 lines (130 loc) · 4.52 KB
/
file_uploader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
var assert = require('assert');
var fs = require('fs');
var mime = require('mime');
var util = require('util');
var MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024;
/**
* FileUploader class used to upload a file to twitter via the /media/upload (chunked) API.
* Usage:
* var fu = new FileUploader({ file_path: '/foo/bar/baz.mp4' }, twit);
* fu.upload(function (err, bodyObj, resp) {
* console.log(err, bodyObj);
* })
*
* @param {Object} params Object of the form { file_path: String }.
* @param {Twit(object)} twit Twit instance.
*/
var FileUploader = function (params, twit) {
assert(params)
assert(params.file_path, 'Must specify `file_path` to upload a file. Got: ' + params.file_path + '.')
var self = this;
self._file_path = params.file_path;
self._twit = twit;
self._isUploading = false;
self._isFileStreamEnded = false;
}
/**
* Upload a file to Twitter via the /media/upload (chunked) API.
*
* @param {Function} cb function (err, data, resp)
*/
FileUploader.prototype.upload = function (cb) {
var self = this;
// Send INIT command with file info and get back a media_id_string we can use to APPEND chunks to it.
self._initMedia(function (err, bodyObj, resp) {
if (err) {
cb(err);
return;
} else {
var mediaTmpId = bodyObj.media_id_string;
var chunkNumber = 0;
var mediaFile = fs.createReadStream(self._file_path, { highWatermark: MAX_FILE_CHUNK_BYTES });
mediaFile.on('data', function (chunk) {
// Pause our file stream from emitting `data` events until the upload of this chunk completes.
// Any data that becomes available will remain in the internal buffer.
mediaFile.pause();
self._isUploading = true;
self._appendMedia(mediaTmpId, chunk.toString('base64'), chunkNumber, function (err, bodyObj, resp) {
self._isUploading = false;
if (err) {
cb(err);
} else {
if (self._isUploadComplete()) {
// We've hit the end of our stream; send FINALIZE command.
self._finalizeMedia(mediaTmpId, cb);
} else {
// Tell our file stream to start emitting `data` events again.
chunkNumber++;
mediaFile.resume();
}
}
});
});
mediaFile.on('end', function () {
// Mark our file streaming complete, and if done, send FINALIZE command.
self._isFileStreamEnded = true;
if (self._isUploadComplete()) {
self._finalizeMedia(mediaTmpId, cb);
}
});
}
})
}
FileUploader.prototype._isUploadComplete = function () {
return !this._isUploading && this._isFileStreamEnded;
}
/**
* Send FINALIZE command for media object with id `media_id`.
*
* @param {String} media_id
* @param {Function} cb
*/
FileUploader.prototype._finalizeMedia = function(media_id, cb) {
var self = this;
self._twit.post('media/upload', {
command: 'FINALIZE',
media_id: media_id
}, cb);
}
/**
* Send APPEND command for media object with id `media_id`.
* Append the chunk to the media object, then resume streaming our mediaFile.
*
* @param {String} media_id media_id_string received from Twitter after sending INIT comand.
* @param {String} chunk_part Base64-encoded String chunk of the media file.
* @param {Number} segment_index Index of the segment.
* @param {Function} cb
*/
FileUploader.prototype._appendMedia = function(media_id_string, chunk_part, segment_index, cb) {
var self = this;
self._twit.post('media/upload', {
command: 'APPEND',
media_id: media_id_string.toString(),
segment_index: segment_index,
media: chunk_part,
}, cb);
}
/**
* Send INIT command for our underlying media object.
*
* @param {Function} cb
*/
FileUploader.prototype._initMedia = function (cb) {
var self = this;
var mediaType = mime.lookup(self._file_path);
var mediaFileSizeBytes = fs.statSync(self._file_path).size;
// Check the file size - it should not go over 15MB for video.
// See https://dev.twitter.com/rest/reference/post/media/upload-chunked
if (mediaFileSizeBytes < MAX_FILE_SIZE_BYTES) {
self._twit.post('media/upload', {
'command': 'INIT',
'media_type': mediaType,
'total_bytes': mediaFileSizeBytes
}, cb);
} else {
var errMsg = util.format('This file is too large. Max size is %dB. Got: %dB.', MAX_FILE_SIZE_BYTES, mediaFileSizeBytes);
cb(new Error(errMsg));
}
}
module.exports = FileUploader