Appearance
Uploading from the End-User's Browser
Direct Uploads
You can integrate our /files/uploads/ API into your own website in such a way that it enables your users to upload videos directly to our service, e.g., to transcode videos right after upload. This can be very useful if you like to create a website that relies heavily on user-generated content. Then you don't need to deal with the hassles and the inherent scalability problems with receiving all the uploads via your own server.
Note: For security reasons, it is important that you implement a small backend API on your server that proxies all the calls to our /files/uploads/ API that require authorization (= auth token). This is required to prevent leaking your auth token to the frontend javascript code. You need to keep your auth token always safe and must never share it!
Here are two examples of how a direct user upload can be implemented. The first example uses Dropzone.js for the frontend upload, the second Plupload.js. Both examples come in the form of a .php file that renders out a simple test HTML page with the necessary JavaScript and provides a minimal PHP backend that just proxies through the responses of our API. To run the example, you'll first need to add some values to the constants $client_id, $client_secret, $pipeline_id. You can create a fresh application and a fresh pipeline in your MediaHub account for testing.
Warning: In a real application, do not 1:1 proxy the full API responses received from our server like in the simplified example below. Perform error checking server-side first and if successful, pass on just the information that you really need at the frontend. Nothing more.
Dropzone Example - dropzone.php
php
<?php
// config
$client_id = ''; // <-- Enter your client id here
$client_secret = ''; // <-- Enter your client secret here
$pipeline_id = ''; // <-- Enter your pipeline id here
$frontend_html = <<<EOD
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Dropzone Example</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="//rawgit.com/enyo/dropzone/master/dist/dropzone.css">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="//rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>
</head>
<body style="font: 13px Verdana; background: #eee; color: #333">
<h1>Dropzone Example</h1>
<button id="submit-all" class="btn btn-secondary">Submit all files</button>
<form id="dropzoneForm" method="post" class="dropzone"></form>
<script type="text/javascript">
var Uploads = {};
// Initialize the widget when the DOM is ready
$(function() {
Dropzone.options.dropzoneForm = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 500, // MB
uploadMultiple: false,
chunking: true,
chunkSize: 8*1024*1024, /* 8 MB */
retryChunks: true,
forceChunking: true,
clickable: true,
autoProcessQueue: false,
acceptedFiles: '.jpg,.mov,.mp4,.avi,.mkv',
url: function(files) {
console.log("url function called!");
return Uploads.form_post_url;
},
accept: function(file, done) {
console.log("accept called!");
done();
},
transformFile: function(file, done) {
console.log("transformFile called!");
$.ajax({
type : "post",
url: './dropzone.php?filename=' + file.name, // TODO: Add some auth. E.g. only logged-in users should be able to upload files, etc.
cache : false,
dataType : "json",
success: function (result) {
console.log(result);
Uploads[file.upload.uuid] = {'credentials': result.upload, 'file_id': result.upload.file.id};
Uploads.form_post_url = result.upload.form_post_url;
console.log("Form post target " + Uploads.form_post_url);
done(file);
},
error: function(result, error, exception) { alert("Error occurred getting upload credentials!"); }
});
},
params: function(files, xhr, chunk) {
console.log("params called! " + JSON.stringify(xhr));
var form_params = jQuery.extend({}, Uploads[chunk.file.upload.uuid].credentials.form_post_params);
if (chunk.file.upload.totalChunkCount > 1) { // Only suffix object key with chunk index if we have more than one chunk
form_params.key = Uploads[chunk.file.upload.uuid].credentials.form_post_params.key + "." + chunk.index;
form_params.filename = "." + chunk.index;
form_params.chunk = chunk.index;
form_params.chunks = chunk.file.upload.totalChunkCount;
}
return form_params;
},
chunksUploaded: function(file, done) {
console.log("chunksUploaded called!");
var doPollForSuccess = function() {
$.ajax({
type: "get",
url: './dropzone.php?id=' + window.Uploads[file.upload.uuid].file_id + '&_=' + new Date(),
cache: false,
success: function(data) {
console.log(data); // remove
if (data && data.upload && data.upload.status && data.upload.status === 'SUCCESS') {
done();
alert("Success. File ID " + window.Uploads[file.upload.uuid].file_id);
} else if (data && data.upload && data.upload.status && data.upload.status === 'FAILURE') {
alert("ERROR: Upload could not be completed!");
} else {
setTimeout(doPollForSuccess, 2000);
}
},
error: function(result, error, exception) { alert("Error occurred completing upload");}
});
}
$.ajax({
type : "put",
url: './dropzone.php?id=' + window.Uploads[file.upload.uuid].file_id + '&nb_chunks=' + file.upload.totalChunkCount,
cache : false,
dataType : "json",
success: function (result) { console.log(result); doPollForSuccess(); },
error: function(result, error, exception) { alert("Error occurred completing upload"); }
});
},
init: function() {
console.log("init called!");
var submitButton = document.querySelector("#submit-all")
myDropzone = this; // closure
submitButton.addEventListener("click", function() {
myDropzone.processQueue(); // Tell Dropzone to process all queued files.
});
this.on('sending', function(file, xhr, formData) {
console.log("Sending event triggered!");
});
this.on('uploadprogress', function(file, progress, bytesSent) {
console.log("Progress event. Progress " + progress + "% with " + bytesSent + " bytes transferred");
});
}
};
});
</script>
</body>
</html>
EOD;
// Backend code
$object_key_prefix = 'myuploads%2F' . time() . '_' . substr(md5(mt_rand()), 0, 7) . '%2F';
$params = NULL;
if (isset($_SERVER['QUERY_STRING'])) {
parse_str($_SERVER['QUERY_STRING'], $params);
}
// Just return frontend_html by default
if (($_SERVER['REQUEST_METHOD'] === 'GET') && (count($params) == 0)) {
echo $frontend_html;
exit();
}
if (($_SERVER['REQUEST_METHOD'] === 'POST') && isset($params['done'])) {
echo "SUCCESS";
exit();
}
// curl helper function
function make_curl_request($url, $method = 'GET', $data = null) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
));
}
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
// Auth function
function get_oauth_token($clientid, $clientsecret, $expiry) {
$api_url = "https://api.xvid.com/v1/oauth2/token/";
$params = array(
"scope" => 'mediahub,autograph',
"grant_type" => 'client_credential',
"client_id" => $clientid,
"client_secret" => urlencode($clientsecret),
);
//url-ify the data for the POST
$params_string = '';
foreach($params as $key=>$value) { $params_string .= $key.'='.$value.'&'; }
rtrim($params_string, '&');
$url = $api_url . '?' . 'human=true&expires_in=' . $expiry;
return json_decode(make_curl_request($url, 'POST', $params_string))->{'access_token'};
}
// Don't do it like this in production! Auth token should be fetched just once and then cached and reused for further API calls!
$auth_token = get_oauth_token($client_id, $client_secret, 7200);
// Initiate new upload session
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$request_url = 'https://api.xvid.com/v1/files/uploads/?object_key=' . $object_key_prefix . '' . urlencode($params['filename']) . '&pipeline_id=' . $pipeline_id . '&access_token=' . $auth_token;
$result = make_curl_request($request_url, 'POST');
header('Content-Type: application/json');
echo $result;
exit();
}
// Finish up the upload session
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
$data = json_encode(array("upload" => array("nb_chunks" => $params['nb_chunks'])));
$request_url = 'https://api.xvid.com/v1/files/uploads/' . $params['id'] . '?access_token=' . $auth_token;
$result = make_curl_request($request_url, 'PUT', $data);
header('Content-Type: application/json');
echo $result;
exit();
}
if (($_SERVER['REQUEST_METHOD'] === 'GET') && isset($params['id'])) {
$request_url = 'https://api.xvid.com/v1/files/uploads/' . $params['id'] . '?access_token=' . $auth_token;
$result = make_curl_request($request_url);
header('Content-Type: application/json');
echo $result;
exit();
}
?>Dropzone Example - plupload.php
php
<?php
// config
$client_id = ''; // <-- Enter your client id here
$client_secret = ''; // <- Enter your client secret here
$pipeline_id = ''; // <-- Enter your pipeline id here
$frontend_html = <<<EOD
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Plupload - jQuery UI Widget with Bootstrap</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css" type="text/css" />
<link rel="stylesheet" href="https://rawcdn.githack.com/moxiecode/plupload/2.3.6/js/jquery.ui.plupload/css/jquery.ui.plupload.css" type="text/css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript" src="https://rawcdn.githack.com/moxiecode/plupload/2.3.6/js/plupload.full.min.js"></script>
<script type="text/javascript" src="https://rawcdn.githack.com/moxiecode/plupload/2.3.6/js/jquery.ui.plupload/jquery.ui.plupload.min.js"></script>
</head>
<body style="font: 13px Verdana; background: #eee; color: #333">
<h1>jQuery UI Widget with Bootstrap</h1>
<form id="form" method="post" action="./plupload.php?done=true">
<div id="uploader">
<p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
</div>
<br />
<input type="submit" value="Submit" />
</form>
<script type="text/javascript">
// Initialize the widget when the DOM is ready
window.Uploader = {};
window.Uploader.chunkSize = 8*1024*1024; // 8 MB
window.Uploader.done = false;
$(function() {
$("#uploader").plupload({
// General settings
runtimes : 'html5,flash',
url : 'provided at runtime',
// User can upload no more then 1 file in one go (sets multiple_queues to false)
max_file_count: 1,
// Resize images on clientside if we can
resize : {
width : 200,
height : 200,
quality : 90,
crop: true // crop to exact dimensions
},
filters : {
// Maximum file size
max_file_size : '2000mb',
// Specify what files to browse for
mime_types: [
{title : "Image files", extensions : "jpg,gif,png"},
{title : "Video files", extensions : "mp4,mov,avi,mkv"}
]
},
// Rename files by clicking on their titles
rename: true,
// Sort files
sortable: true,
// Enable ability to drag'n'drop files onto the widget (currently only HTML5 supports that)
dragdrop: true,
// Views to activate
views: {
list: true,
thumbs: true, // Show thumbs
active: 'thumbs'
},
// Flash settings
flash_swf_url : 'https://rawcdn.githack.com/moxiecode/plupload/2.3.6/js/Moxie.swf',
preinit: {
Init: function (up, info) {
//console.log('[Init]', 'Info:', info, 'Features:', up.features);
},
BeforeUpload: function (up, file) {
console.log('[BeforeUpload]', file);
$.ajax({
type : "post",
url: './plupload.php?filename=' + file.name, // TODO: Add some auth. E.g. only logged-in users should be able to upload files, etc.
cache : false,
dataType : "json",
success: function (result) {
console.log(result);
window.Uploader.credentials = result.upload;
window.Uploader.file_id = result.upload.file.id;
up.settings.url = window.Uploader.credentials.form_post_url;
// Check whether file is so large it should be chunked?
file.isChunked = (file.size > window.Uploader.chunkSize);
up.settings.multipart_params = $.extend({}, window.Uploader.credentials.form_post_params);
if (file.isChunked) {
console.log("Chunked upload.");
file.chunkIndex = 0;
up.settings.multipart_params.key = (window.Uploader.credentials.form_post_params.key + "." + file.chunkIndex);
up.settings.multipart_params.filename = ('${filename}' + "." + file.chunkIndex);
up.settings.chunk_size = window.Uploader.chunkSize;
// plupload will populate it automatically
if(up.settings.multipart_params.hasOwnProperty('chunks')) {
delete up.settings.multipart_params.chunks;
}
if(up.settings.multipart_params.hasOwnProperty('chunk')) {
delete up.settings.multipart_params.chunk;
}
} else {
console.log("Single file upload.");
up.settings.chunk_size = 0;
up.settings.multipart_params.chunks = 0;
up.settings.multipart_params.chunk = 0;
}
file.status = plupload.UPLOADING;
up.trigger("UploadFile", file);
},
error: function(result, error, exception) { console.log("Error occurred getting upload credentials!"); }
});
return false;
},
ChunkUploaded: function (up, file, info) {
console.log("Chunk uploaded.", info.offset, "of", info.total, "bytes.");
file.chunkIndex++;
up.settings.multipart_params.key = (window.Uploader.credentials.form_post_params.key + "." + file.chunkIndex);
up.settings.multipart_params.filename = ('${filename}' + "." + file.chunkIndex);
},
FileUploaded: function (up, file) {
console.log('[FileUploaded]', file);
var doPollForSuccess = function() {
$.ajax({
type: "get",
url: './plupload.php?id=' + window.Uploader.file_id + '&_=' + new Date(),
cache: false,
success: function(data) {
console.log(data); // remove
if (data && data.upload && data.upload.status && data.upload.status === 'SUCCESS') {
window.Uploader.done = true;
up.trigger("FileUploaded", file);
} else if (data && data.upload && data.upload.status && data.upload.status === 'FAILURE') {
console.log("ERROR: Upload could not be completed!");
} else {
setTimeout(doPollForSuccess, 2000);
}
},
error: function(result, error, exception) { console.log("Error occurred completing upload"); }
});
}
if (!window.Uploader.done) {
$.ajax({
type : "put",
url: './plupload.php?id=' + window.Uploader.file_id + '&nb_chunks=' + Math.ceil(file.size / window.Uploader.chunkSize),
cache : false,
dataType : "json",
success: function (result) { console.log(result); doPollForSuccess(); },
error: function(result, error, exception) { console.log("Error occurred completing upload"); }
});
return false;
}
return true;
}
}
});
// Handle the case when form was submitted before uploading has finished
$('#form').submit(function(e) {
// Files in queue upload them first
if ($('#uploader').plupload('getFiles').length > 0) {
// When all files are uploaded submit form
$('#uploader').on('complete', function() {
$('#form')[0].submit();
});
$('#uploader').plupload('start');
} else {
alert("You must have at least one file in the queue.");
}
return false; // Keep the form from submitting
});
});
</script>
</body>
</html>
EOD;
// Backend code
$object_key_prefix = 'myuploads%2F' . time() . '_' . substr(md5(mt_rand()), 0, 7) . '%2F'; // use some randomized prefix to avoid collisions when multiple users upload a file with same name
$params = NULL;
if (isset($_SERVER['QUERY_STRING'])) {
parse_str($_SERVER['QUERY_STRING'], $params);
}
// Just return frontend_html by default
if (($_SERVER['REQUEST_METHOD'] === 'GET') && (count($params) == 0)) {
echo $frontend_html;
exit();
}
if (($_SERVER['REQUEST_METHOD'] === 'POST') && isset($params['done'])) {
echo "SUCCESS";
exit();
}
// curl helper function
function make_curl_request($url, $method = 'GET', $data = null) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
} elseif ($method === 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
}
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data))
);
}
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
// Auth function
function get_oauth_token($clientid, $clientsecret, $expiry) {
$api_url = "https://api.xvid.com/v1/oauth2/token/";
$params = array(
"scope" => 'mediahub,autograph',
"grant_type" => 'client_credential',
"client_id" => $clientid,
"client_secret" => urlencode($clientsecret),
);
//url-ify the data for the POST
$params_string = '';
foreach($params as $key=>$value) { $params_string .= $key.'='.$value.'&'; }
rtrim($params_string, '&');
$url = $api_url . '?' . 'human=true&expires_in=' . $expiry;
return json_decode(make_curl_request($url, 'POST', $params_string), true)['access_token'];
}
// Don't do it like this in production! Auth token should be fetched just once and then cached and reused for further API calls!
$auth_token = get_oauth_token($client_id, $client_secret, 7200);
// Initiate new upload session
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$request_url = 'https://api.xvid.com/v1/files/uploads/?object_key=' . $object_key_prefix . '' . urlencode($params['filename']) . '&pipeline_id=' . $pipeline_id . '&access_token=' . $auth_token;
$result = make_curl_request($request_url, 'POST');
header('Content-Type: application/json');
echo $result;
exit();
}
// Finish up the upload session
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
$data = array("nb_chunks" => $params['nb_chunks']);
$data_nested = array("upload" => $data);
$data_string = json_encode($data_nested);
$request_url = 'https://api.xvid.com/v1/files/uploads/' . $params['id'] . '?access_token=' . $auth_token;
$result = make_curl_request($request_url, 'PUT', $data_string);
header('Content-Type: application/json');
echo $result;
exit();
}
if (($_SERVER['REQUEST_METHOD'] === 'GET') && isset($params['id'])) {
$request_url = 'https://api.xvid.com/v1/files/uploads/' . $params['id'] . '?access_token=' . $auth_token;
$result = make_curl_request($request_url);
header('Content-Type: application/json');
echo $result;
// You could parse and check the result here also server side
// If "status" is "SUCCESS", the upload is done and you can add the "object_key", "pipeline.id" and/or "file.id" into your database of uploaded files
// and then also post a video conversion job at the /v1/jobs/ API specifying the corresponding "pipeline.id" and "object_key" of the uploaded file as input_name.
exit();
}
?>