1

I'm trying to set up a form that can upload to both YouTube and Vimeo simultaneously. I would prefer to use Posterous.com for something like this, but since they've been acquired by twitter, their help team has dropped off the face of the earth as my emails are now going unanswered (they've removed a bunch of services)...

So anyways here's how the youtube process is supposed to work:

  1. Set a title and category for the video you want to upload via webform
  2. submit form, get an access token back from youtube
  3. another form is generated, allowing you to select the file to upload
  4. submit form, access token and file are sent and youtube uploads the video

What I'm trying to do is turn this into a single step with a drag and drop uploader:

  1. drag and drop file onto page
  2. javascript grabs the file information, sets the filename as the video title and uses a default category
  3. javascript calls php and sends filename & category to youtube, gets access token back and creates form with file input
  4. after getting the token, send a POST (via PHP) request with file upload stored in a session variable (right now I have to select the file again and click submit)

shouldn't I be able to use the file information from the first step, store it in a session variable and programmatically submit the token and file information via php? I don't know how to send this data like it was sent as a form submission and I'm not always getting response codes back from youtube to fix my code.

this may be the answer I need: sending xml and headers via curl but I don't know how to set the $xmlString or $videoData

EDIT::

I think I need to do this via PHP, not javascript because I'm trying to modify the following code:

/**
 * Create upload form by sending the incoming video meta-data to youtube and
 * retrieving a new entry. Prints form HTML to page.
 *
 * @param string $VideoTitle The title for the video entry.
 * @param string $VideoDescription The description for the video entry.
 * @param string $VideoCategory The category for the video entry.
 * @param string $nextUrl (optional) The URL to redirect back to after form upload has completed.
 * @return void
 */

function createUploadForm($videoTitle, $videoCategory, $nextUrl = null) {
$httpClient = getAuthSubHttpClient();
$youTubeService = new Zend_Gdata_YouTube($httpClient);
$newVideoEntry = new Zend_Gdata_YouTube_VideoEntry();

$newVideoEntry->setVideoTitle($videoTitle);

//make sure first character in category is capitalized
$videoCategory = strtoupper(substr($videoCategory, 0, 1))
    . substr($videoCategory, 1);
$newVideoEntry->setVideoCategory($videoCategory);

// convert videoTags from whitespace separated into comma separated

$tokenHandlerUrl = 'https://gdata.youtube.com/action/GetUploadToken';
try {
    $tokenArray = $youTubeService->getFormUploadToken($newVideoEntry, $tokenHandlerUrl);
    if (loggingEnabled()) {
        logMessage($httpClient->getLastRequest(), 'request');
        logMessage($httpClient->getLastResponse()->getBody(), 'response');
    }
} catch (Zend_Gdata_App_HttpException $httpException) {
    print 'ERROR ' . $httpException->getMessage()
        . ' HTTP details<br /><textarea cols="100" rows="20">'
        . $httpException->getRawResponseBody()
        . '</textarea><br />'
        . '<a href="session_details.php">'
        . 'click here to view details of last request</a><br />';
    return;
} catch (Zend_Gdata_App_Exception $e) {
    print 'ERROR - Could not retrieve token for syndicated upload. '
        . $e->getMessage()
        . '<br /><a href="session_details.php">'
        . 'click here to view details of last request</a><br />';
    return;
}

$tokenValue = $tokenArray['token'];
$postUrl = $tokenArray['url'];

// place to redirect user after upload
if (!$nextUrl) {
    $nextUrl = $_SESSION['homeUrl'];
} 

//instead of echoing the form below, send $_FILES from previous form submit

print <<< END
    <br />      

    <form id="uploadToYouTubeForm" action="${postUrl}?nexturl=${nextUrl}" method="post" enctype="multipart/form-data">
    <input id="uploadToYouTube" name="file" type="file" />
    <input name="token" type="hidden" value="${tokenValue}"/>
    <input value="Upload Video File" type="submit" />
    </form>

END;
}
Community
  • 1
  • 1
alyda
  • 559
  • 1
  • 8
  • 17
  • To upload files via javascript you can't use normal ajax post, because it doens't support binary file types. For that you'll need to use an hidden iframe to post to with target="myiddeniframe". You can then poll via ajax what the status is of the upload etc.. – Tschallacka Aug 28 '12 at 07:47
  • @Michael Dibbets: Thats wrong ;) an Ajax Request is a normal http request. You can send Binary Data. http://stackoverflow.com/questions/166221/how-can-i-upload-files-asynchronously-with-jquery – René Höhle Aug 28 '12 at 07:56
  • I edited my post to show the PHP I want to edit, but I'm testing the javascript solutions in the link you gave me, Thanks. – alyda Aug 28 '12 at 09:04
  • @Stony You try uploading file via ajax. I tried everything there was, to get the file form element to submit to php via ajax, the only thing that worked was via hidden iframe to get an result I could use. The plugins that are available also use the iframe technique – Tschallacka Aug 28 '12 at 09:41
  • Can anyone point me in the right direction? I don't understand how to form `POST` requests in PHP (or send the binary file data needed in this case), and I'm trying to learn without any luck... – alyda Aug 28 '12 at 18:49

1 Answers1

0

I think I may have a solution. Below is the form I want to use:

<!--removing the action ensures form will be sent via javascript, then when that request comes back, I can add in the authenticated youtube URL needed-->
<form id="upload" action="" method="POST" enctype="multipart/form-data" class="form-horizontal"><!--upload.php-->
  <fieldset>
    <legend><h1>Video File Upload</h1></legend>
    <input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="1000000000" /> <!--1GB-->
    <p id="filedrag">Drag and drop a video file from your computer here. Or use the 'File upload' button below.</p><!--dragFileHere-->
    <label class="control-label" for="fileselect">Files to upload:</label>
    <input type="file" id="fileselect" name="fileselect[]" /> <!--multiple="multiple"-->
    <button class="btn" id="submitbutton" type="submit">Upload Files</button> <!--hidden via js/css-->
    <div class="progress progress-striped active">
      <div class="bar" style="width: 0%;"></div>
    </div>
    <label class="hide" for="video-title">Title</label>
    <input type="text" id="video-title" class="span4" placeholder="Video Title"/>
    <label class="control-label" for="video-category">Category</label>
    <select id="video-category" name="videoCategory" class="span4">
      <option value="Autos">Autos &amp; Vehicles</option>
      <option value="Music">Music</option>
      .......
      <option value="Entertainment" selected>Entertainment</option>
    </select>
    <input id="token" type="text" placeholder="token"/> <!--will be hidden-->
</div>
</fieldset>
</form>

by leaving the action attribute blank, (and using script I already had in place to respond to user interaction) I can ensure the form is submitted programmatically via javascript:

<script src=http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js></script>
<script src=_js/video_app.js" type="text/javascript></script>
<script>
  /*
  filedrag.js - HTML5 File Drag & Drop demonstration
  Featured on SitePoint.com
  Developed by Craig Buckler (@craigbuckler) of OptimalWorks.net (without jQuery)
  */

  // output information
  function Output(msg) {
    $('#messages').html(msg + $('#messages').html());
  }

  // file drag hover
  function FileDragHover(e) {
    e.stopPropagation();
    e.preventDefault();
    $(this).addClass("hover");
  } 

  function FileDragOut(e) {
    $(this).removeClass("hover");
  }

  // file selection
  function FileSelectHandler(e) {
    // cancel event and hover styling
    FileDragHover(e);

    // fetch FileList object
    var files = e.target.files || e.dataTransfer.files;
    // process all File objects
    for (var i = 0, f; f = files[i]; i++) {
      ParseFile(f); //prints file data to div and optionally displays content of selected file
      UploadFile(f); //uploads file to server
    }
  }

  // output file information
  function ParseFile(file) {
    videoName = file.name;
    videoType = file.type;
    videoURL = "http://localhost/"+videoName;
    videoCategory = $('#video-category').val();

    Output(
      "</strong> type: <strong>" + file.type +
      "</strong> size: <strong>" + file.size +
      "</strong> bytes</p>"
    );

    // sets a default value because a title is needed for youtube to send response  
    if( $('#video-title').val() == $('#video-title').attr('placeholder') ) {
      $('#video-title').val(videoName);
    }

    var reader = new FileReader();
    reader.onload = function(e) {

      var fileContents = e.target.result;

      Output(
        '<img src="'+e.target.result+'"/>'
      );
    }
    reader.readAsDataURL(file);

    //get upload token
    ytVideoApp.prepareSyndicatedUpload(videoName, videoCategory);
  }

  // upload video files
  function UploadFile(file) {       
    var xhr = new XMLHttpRequest();
    if (xhr.upload && file.size <= $('#MAX_FILE_SIZE').val()) { //&& file.type == "video/mp4" or video/*
      xhr.upload.addEventListener("progress", function(e) {
        var pc = Math.ceil(e.loaded / e.total * 100);
      }, false);
      // file received/failed
      xhr.onreadystatechange = function(e) {
        if (xhr.readyState == 4) {
          if(xhr.status == 200) { //success

          } else { //fail  

          }
        }
      };
      // start upload
      xhr.open("POST", 'upload.php', true); //$("#upload").attr('action')
      xhr.setRequestHeader("X_FILENAME", file.name);
      xhr.send(file);
    }
  }

  // initialize
  function Init() {
    // file select
    $('#fileselect').change(FileSelectHandler);
    // is XHR2 available?
    var xhr = new XMLHttpRequest();
    if (xhr.upload) {
      // file drop
      $('#filedrag').bind('dragover', FileDragHover);
      $('#filedrag').bind('dragleave', FileDragOut);

      //I can't get the below line to work, so I've used the ugly fallback
      //$('#filedrag').bind('drop', FileSelectHandler);
      document.getElementById('filedrag').addEventListener("drop", FileSelectHandler, false);           

      filedrag.style.display = "block";
      // remove submit button
      submitbutton.style.display = "none";
    }
  }
  // call initialization file
  if (window.File && window.FileList && window.FileReader) {
    Init();
  }
</script>

_js/video_app.js:

/**
* Zend Framework
* @package    Zend_Gdata
....
/**
* provides namespacing for the YouTube Video Application PHP version (ytVideoApp)
**/
var ytVideoApp = {};
/**
* Sends an AJAX request to the server to retrieve a list of videos or
* the video player/metadata.  Sends the request to the specified filePath
* on the same host, passing the specified params, and filling the specified
* resultDivName with the resutls upon success.
* @param {String} filePath The path to which the request should be sent
* @param {String} params The URL encoded POST params
* @param {String} resultDivName The name of the DIV used to hold the results
*/
ytVideoApp.sendRequest = function(filePath, params, resultDivName) {
  if (window.XMLHttpRequest) {
    var xmlhr = new XMLHttpRequest();
  } else {
    var xmlhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
  }

  xmlhr.open('POST', filePath);
  xmlhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 

  xmlhr.onreadystatechange = function() {
    var resultDiv = document.getElementById(resultDivName);
    if (xmlhr.readyState == 1) {
      resultDiv.innerHTML = '<b>Loading...</b>'; 
    } else if (xmlhr.readyState == 4 && xmlhr.status == 200) {
      if (xmlhr.responseText) {
        resultDiv.innerHTML = xmlhr.responseText;
      }
    } else if (xmlhr.readyState == 4) {
      alert('Invalid response received - Status: ' + xmlhr.status);
    }
  }
  xmlhr.send(params);
}


ytVideoApp.prepareSyndicatedUpload = function(videoTitle, videoCategory, fileContents) {    
  var filePath = '_scripts/operations.php';
  var params = 'operation=create_upload_form' +
    '&videoTitle=' + videoTitle +
    '&videoCategory=' + videoCategory;
  ytVideoApp.sendRequest(filePath, params, ytVideoApp.SYNDICATED_UPLOAD_DIV);
}

_scripts/operations.php:

require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata_YouTube');
Zend_Loader::loadClass('Zend_Gdata_AuthSub');
Zend_Loader::loadClass('Zend_Gdata_App_Exception');
/*
 * The main controller logic.
 *
 * POST used for all authenticated requests
 * otherwise use GET for retrieve and supplementary values
 */
session_start();
setLogging('on');
generateUrlInformation();

if (!isset($_POST['operation'])) {
  // if a GET variable is set then process the token upgrade
  if (isset($_GET['token'])) {
    updateAuthSubToken($_GET['token']);
  } else {
    if (loggingEnabled()) {
      logMessage('reached operations.php without $_POST or $_GET variables set', 'error');
      header('Location: add-content.php');
    }
  }
}

$operation = $_POST['operation'];

switch ($operation) {
  ....
  case 'create_upload_form':
    createUploadForm($_POST['videoTitle'],
                     $_POST['videoCategory'],
                     $_POST['videoContents']);
  break;
  ....
  default:
    unsupportedOperation($_POST);
  break;
}

function createUploadForm($videoTitle, $videoCategory, $nextUrl = null) {
  $httpClient = getAuthSubHttpClient();
  $youTubeService = new Zend_Gdata_YouTube($httpClient);
  $newVideoEntry = new Zend_Gdata_YouTube_VideoEntry();

  $newVideoEntry->setVideoTitle($videoTitle);

  //make sure first character in category is capitalized
  $videoCategory = strtoupper(substr($videoCategory, 0, 1))
                              . substr($videoCategory, 1);
  $newVideoEntry->setVideoCategory($videoCategory);

  // convert videoTags from whitespace separated into comma separated
  $tokenHandlerUrl = 'https://gdata.youtube.com/action/GetUploadToken';
  try {
    $tokenArray = $youTubeService->getFormUploadToken($newVideoEntry, $tokenHandlerUrl);
    if (loggingEnabled()) {
      logMessage($httpClient->getLastRequest(), 'request');
      logMessage($httpClient->getLastResponse()->getBody(), 'response');
    }
  } catch (Zend_Gdata_App_HttpException $httpException) {
    print 'ERROR ' . $httpException->getMessage()
                   . ' HTTP details<br /><textarea cols="100" rows="20">'
                   . $httpException->getRawResponseBody()
                   . '</textarea><br />'
                   . '<a href="session_details.php">'
                   . 'click here to view details of last request</a><br />';
    return;
  } catch (Zend_Gdata_App_Exception $e) {
    print 'ERROR - Could not retrieve token for syndicated upload. '
                 . $e->getMessage()
                 . '<br /><a href="session_details.php">'
                 . 'click here to view details of last request</a><br />';
    return;
  }
  $tokenValue = $tokenArray['token'];
  $postUrl = $tokenArray['url'];
  // place to redirect user after upload
  if (!$nextUrl) {
    $nextUrl = $_SESSION['homeUrl'];
  }

  //instead of outputting the form below, send variables (json???) to be interpreted by xmlhr in _js/video_app.js
  //print <<< END
  //<br />      
  //<p>url: ${postUrl}?nexturl=${nextUrl}</p>
  //<form id="uploadToYouTubeForm" action="temp.php" method="post" enctype="multipart/form-data">
  //<input id="uploadToYouTube" name="file" type="file" onchange="autoUploadToYouTube();" /><br/>
  //token: <input id="token" name="token" type="text" value="${tokenValue}"/><br/>
  //<input value="Manual upload" type="submit" />
  //</form>     
  //END;
  //}

So the first javascript listens for the drag & drop (on the div) or change() event for the input type="file". The form is not actually submitted, but data is gathered and and sent via ajax to the php script which returns the token and url needed for upload. The original PHP script then outputs a form to select the file (and a hidden field containing the token). INSTEAD of that, I want to pass the url and token as variables and use the XMLHttpRequest to put those variables in the action attribute of the form as well set the key to a hidden input field's value. Then the field can be submitted via $('#uploadToYouTubeForm').submit();

The only problem that may arise is sending additional information to the youtube app, I'm hoping it will simply ignore it or i may need to programmatically remove the fields that youtube won't accept (title, category, max file size)....

alyda
  • 559
  • 1
  • 8
  • 17