<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xml:base="http://echodittolabs.org" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
 <title>EchoDitto Labs - Tieing Hey!Watch Into CCK - Comments</title>
 <link>http://echodittolabs.org/heywatch-and-cck</link>
 <description>Comments for &quot;Tieing Hey!Watch Into CCK&quot;</description>
 <language>en</language>
<item>
 <title>Tieing Hey!Watch Into CCK</title>
 <link>http://echodittolabs.org/heywatch-and-cck</link>
 <description>&lt;p&gt;Here&#039;s something I recently presented at the DC Drupal Meetup:&lt;/p&gt;

&lt;p&gt;You&#039;ve already heard us prattle on about &lt;a href=&quot;http://www.heywatch.com&quot;&gt;Hey!Watch&lt;/a&gt; &amp;mdash; it&#039;s a great video transcoding service that we&#039;ve gotten in the habit of using for several of our clients.  As much fun as wrestling with ffmpeg is, sometimes it&#039;s more appealing to just bit then bullet, pay a dime per transcode and not have to worry about keeping your codecs up to date.&lt;/p&gt;

&lt;p&gt;They offer some neat add-on features, too, like direct uploading of transcoded videos to your Amazon S3 account.  Even more tantalizingly, there&#039;s &lt;a href=&quot;http://wiki.heywatch.com/Upload_videos_from_your_service&quot;&gt;this&lt;/a&gt;: a method for allowing your users to upload directly to Hey!Watch&#039;s servers, complete with fancy-pants AJAX progress indicator.  There&#039;s no need to spend your server&#039;s bandwidth and CPU on videos at all &amp;mdash; you can stick to running Drupal on your system, outsourcing all the heavy lifting.  You can send along your own arbitrary variables with the video submission, then receive them back via a ping that Hey!Watch sends once transcoding is complete (allowing you to keep track of who uploaded what video).  It&#039;s pretty slick.&lt;/p&gt;

&lt;p&gt;But interested Drupal developers will click through to the Hey!Watch code and despair.  First, the AJAX is written with Prototype, which won&#039;t play nicely with Drupal&#039;s preferred jQuery library.  Sure, it&#039;d be possible to rewrite it in jQuery &amp;mdash; but I&#039;d really rather not.&lt;/p&gt;

&lt;p&gt;Second and more damningly (and unsurprisingly), the form that uploads to Hey!Watch needs to stand on its own.  You won&#039;t be able to nest it in a Drupal form &amp;mdash; Drupal understandably doesn&#039;t like nested forms.&lt;/p&gt;

&lt;p&gt;A clever hacker will realize that there are a lot of ways around this problem.  You could probably work something out with PageRoute, or make a popup window, or who knows what else.  But we&#039;d like to keep things as Drupaly as possible.  It&#039;d be great if it looked as if Hey!Watch was just another CCK widget.&lt;/p&gt;

&lt;p&gt;Well, thanks to the miracle of iframes and Javascript, this is possible.  Here&#039;s how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new CCK type for your videos.  Add whatever fields you want, but also specify two text fields for the video&#039;s ID and status.  I named these &quot;heywatch_video_id&quot; and &quot;heywatch_video_status&quot; and placed them in a group called &quot;group_heywatch&quot;.&lt;/li&gt;

&lt;li&gt;Create a standalone PHP script containing the AJAX uploader code provided by Hey!Watch at the aforementioned link.  It&#039;ll need to grab the video ID and Hey!Watch upload key from the querystring.  You may also want it to handle various status messages, to reduce clutter.  Here&#039;s mine:

&lt;blockquote&gt;&lt;pre&gt;&amp;lt;?php
define(&#039;VIDEO_ID&#039;,$_GET[&#039;video_id&#039;]);
define(&#039;UPLOAD_KEY&#039;,$_GET[&#039;heywatch_key&#039;]);

if(isset($_GET[&#039;success&#039;])):

   define(&#039;SUCCESS_VAL&#039;,intval($_GET[&#039;success&#039;]));

?&amp;gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
   &amp;lt;title&amp;gt;HeyWatch Video Upload - Success!&amp;lt;/title&amp;gt;
   
   &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
   &amp;lt;!--
   function ResetVideoFile()
   {
      if(confirm(&#039;Are you sure you want to discard \&#039;&amp;lt;?php print urldecode($_GET[&#039;original_filename&#039;]);?&amp;gt;\&#039; and start over?&#039;))
      {
         window.parent.document.getElementById(&#039;edit-field-heywatch-video-status-0-value&#039;).value = &#039;&#039;;
         return true;
      }
      else
      {
         return false;
      }
   }
   --&amp;gt;
   &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

   &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
   &amp;lt;!--
   // set the parent window&#039;s video upload status field
   window.parent.document.getElementById(&#039;edit-field-heywatch-video-status-0-value&#039;).value = &#039;&amp;lt;?php print (SUCCESS_VAL&amp;gt;0) ? &quot;success&quot; : &quot;error&quot;;?&amp;gt;&#039;;
   --&amp;gt;
   &amp;lt;/script&amp;gt;

&amp;lt;?php if(SUCCESS_VAL==1):?&amp;gt;

   &amp;lt;div id=&quot;success&quot;&amp;gt;Video uploaded successfully.  There will be a delay while we convert it to a usable format.&amp;lt;/div&amp;gt;
   
&amp;lt;?php elseif(SUCCESS_VAL==2): ?&amp;gt;

   &amp;lt;div id=&quot;exists&quot;&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The video file &amp;lt;em&amp;gt;&amp;lt;?php print urldecode($_GET[&#039;original_filename&#039;]);?&amp;gt;&amp;lt;/em&amp;gt; has been successfully uploaded.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

      &amp;lt;p&amp;gt;To erase this file and upload a new one, click &amp;lt;a href=&quot;http://&amp;lt;?php print $_SERVER[&#039;SERVER_NAME&#039;] . $_SERVER[&#039;PHP_SELF&#039;];?&amp;gt;?video_id=&amp;lt;?php print VIDEO_ID;?&amp;gt;&amp;amp;heywatch_key=&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&quot; onclick=&quot;return ResetVideoFile()&quot;&amp;gt;here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
   &amp;lt;/div&amp;gt;

&amp;lt;?php else:?&amp;gt;

   &amp;lt;div id=&quot;error&quot;&amp;gt;There was a problem uploading your video.  Please click &amp;lt;a href=&quot;http://&amp;lt;?php print $_SERVER[&#039;SERVER_NAME&#039;] . $_SERVER[&#039;PHP_SELF&#039;];?&amp;gt;?video_id=&amp;lt;?php print VIDEO_ID;?&amp;gt;&amp;amp;video_key=&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&quot;&amp;gt;here&amp;lt;/a&amp;gt; to try again.&amp;lt;/div&amp;gt;

&amp;lt;?php endif;?&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&amp;lt;?php
else:
?&amp;gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
   &amp;lt;title&amp;gt;HeyWatch Video Upload&amp;lt;/title&amp;gt;
   &amp;lt;script type=&quot;text/javascript&quot; src=&quot;prototype.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
   &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
   &amp;lt;!--
   /* don&#039;t forget to change your upload_key */
   var UploadProgress = {
     uploading: null,
     monitor: function(upid) {
      if(!this.periodicExecuter) {
        this.periodicExecuter = new PeriodicalExecuter(function() {
         if(!UploadProgress.uploading) return;
         new Ajax.Request(&#039;ajaxproxy.php?path=/upload/&#039; + upid+&#039;?key=&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&#039;, {method:&#039;get&#039;});
        }, 3);
      }
      this.uploading = true;
      this.StatusBar.create();
     },
     /* change this function if you want to change the progression */
     update: function(total, current) {
      if(!this.uploading) return;
      var status     = current / total;
      var statusHTML = status.toPercentage();
      $(&#039;results&#039;).innerHTML = &quot;&amp;lt;span&amp;gt;&quot; + statusHTML + &quot;&amp;lt;/span&amp;gt; - &quot; + current.toHumanSize() + &#039; of &#039; + total.toHumanSize() + &quot; uploaded.&quot;;
      this.StatusBar.update(status, statusHTML);
     },    
     finish: function(video_id) {
      this.uploading = false;
      this.StatusBar.finish();
      $(&#039;results&#039;).innerHTML = &quot;finished&quot;;
      $(&#039;progress-bar&#039;).hide();
     },    
     error: function(msg) {
      this.uploading = false;
      this.StatusBar.finish();
      $(&#039;results&#039;).innerHTML = &quot;&amp;lt;img src=&#039;/images/log_error.gif&#039; alt=&#039;error&#039; /&amp;gt; Error with the file: &quot;+msg+&quot;.&quot;;
      $(&#039;progress-bar&#039;).hide();
      setTimeout(&quot;window.location = &#039;/transfer/new&#039;&quot;, 3000);
     },    
     cancel: function(msg) {
      if(!this.uploading) return;
      this.uploading = false;
      if(this.StatusBar.statusText) this.StatusBar.statusText.innerHTML = msg || &#039;canceled&#039;;
     },    
     StatusBar: {
      statusBar: null,
      statusText: null,
      statusBarWidth: 500,    
      create: function() {
        this.statusBar  = this._createStatus(&#039;status-bar&#039;);
        this.statusBar.style.width = &#039;0&#039;;
      },
      update: function(status, statusHTML) {
        this.statusBar.style.width = status*100 + &#039;%&#039;;
      },
      finish: function() {
        this.statusBar.style.width = &#039;100%&#039;;
      },      
      _createStatus: function(id) {
        el = $(id);
        if(!el) {
         el = document.createElement(&#039;span&#039;);
         el.setAttribute(&#039;id&#039;, id);
         $(&#039;progress-bar&#039;).appendChild(el);
        }
        return el;
      }
     },    
     FileField: {
      add: function() {
        new Insertion.Bottom(&#039;file-fields&#039;, &#039;&amp;lt;p style=&quot;display:none&quot;&amp;gt;&amp;lt;input id=&quot;data&quot; name=&quot;data&quot; type=&quot;file&quot; /&amp;gt; &amp;lt;a href=&quot;#&quot; onclick=&quot;UploadProgress.FileField.remove(this);return false;&quot;&amp;gt;x&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&#039;)
        $$(&#039;#file-fields p&#039;).last().visualEffect(&#039;blind_down&#039;, {duration:0.3});
      },      
      remove: function(anchor) {
        anchor.parentNode.visualEffect(&#039;drop_out&#039;, {duration:0.25});
      }
     }
   }
   Number.prototype.bytes     = function() { return this; };
   Number.prototype.kilobytes = function() { return this *  1024; };
   Number.prototype.megabytes = function() { return this * (1024).kilobytes(); };
   Number.prototype.gigabytes = function() { return this * (1024).megabytes(); };
   Number.prototype.terabytes = function() { return this * (1024).gigabytes(); };
   Number.prototype.petabytes = function() { return this * (1024).terabytes(); };
   Number.prototype.exabytes =  function() { return this * (1024).petabytes(); };
   [&#039;byte&#039;, &#039;kilobyte&#039;, &#039;megabyte&#039;, &#039;gigabyte&#039;, &#039;terabyte&#039;, &#039;petabyte&#039;, &#039;exabyte&#039;].each(function(meth) {
     Number.prototype[meth] = Number.prototype[meth+&#039;s&#039;];
   });
   Number.prototype.toPrecision = function() {
     var precision = arguments[0] || 2;
     var s         = Math.round(this * Math.pow(10, precision)).toString();
     var pos       = s.length - precision;
     var last      = s.substr(pos, precision);
     return s.substr(0, pos) + (last.match(&quot;^0{&quot; + precision + &quot;}$&quot;) ? &#039;&#039; : &#039;.&#039; + last);
   }
   // (1/10).toPercentage()
   // # =&amp;gt; &#039;10%&#039;
   Number.prototype.toPercentage = function() {
     return (this * 100).toPrecision() + &#039;%&#039;;
   }
   Number.prototype.toHumanSize = function() {
     if(this &amp;lt; (1).kilobyte())  return this + &quot; Bytes&quot;;
     if(this &amp;lt; (1).megabyte())  return (this / (1).kilobyte()).toPrecision()  + &#039; KB&#039;;
     if(this &amp;lt; (1).gigabytes()) return (this / (1).megabyte()).toPrecision()  + &#039; MB&#039;;
     if(this &amp;lt; (1).terabytes()) return (this / (1).gigabytes()).toPrecision() + &#039; GB&#039;;
     if(this &amp;lt; (1).petabytes()) return (this / (1).terabytes()).toPrecision() + &#039; TB&#039;;
     if(this &amp;lt; (1).exabytes())  return (this / (1).petabytes()).toPrecision() + &#039; PB&#039;;
                     	  return (this / (1).exabytes()).toPrecision()  + &#039; EB&#039;;
   }
   --&amp;gt;
   &amp;lt;/script&amp;gt;
&amp;lt;?php endif;?&amp;gt;
   
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;p id=&quot;instructions&quot;&amp;gt;Please choose a file and click the &quot;Upload&quot; button.&amp;lt;/p&amp;gt;

&amp;lt;!-- javascript enabled AJAX upload --&amp;gt;
&amp;lt;div id=&quot;form-stuff&quot;&amp;gt;
&amp;lt;form action=&quot;http://heywatch.com/upload?upload_id=&amp;lt;?php print VIDEO_ID;?&amp;gt;&quot; enctype=&quot;multipart/form-data&quot; id=&quot;form-upload-tag&quot; method=&quot;post&quot; onsubmit=&quot;window.parent.document.getElementById(&#039;edit-field-heywatch-video-original-f-0-value&#039;).value = $(&#039;data&#039;).value; $(&#039;form-stuff&#039;).toggle(); $(&#039;progress-bar&#039;).toggle(); UploadProgress.monitor(&#039;&amp;lt;?php print VIDEO_ID;?&amp;gt;&#039;);&quot;&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;input id=&quot;data&quot; name=&quot;data&quot; type=&quot;file&quot; /&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; value=&quot;&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&quot; name=&quot;key&quot; /&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; value=&quot;http://&amp;lt;?php print $_SERVER[&#039;SERVER_NAME&#039;] . $_SERVER[&#039;PHP_SELF&#039;];?&amp;gt;?success=1&amp;amp;video_id=&amp;lt;?php print VIDEO_ID;?&amp;gt;&amp;amp;key=&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&quot; name=&quot;redirect_if_success&quot; /&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; value=&quot;http://&amp;lt;?php print $_SERVER[&#039;SERVER_NAME&#039;] . $_SERVER[&#039;PHP_SELF&#039;];?&amp;gt;?success=0&amp;amp;video_id=&amp;lt;?php print VIDEO_ID;?&amp;gt;&amp;amp;key=&amp;lt;?php print UPLOAD_KEY;?&amp;gt;&quot; name=&quot;redirect_if_error&quot; /&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; value=&quot;&amp;lt;?php print VIDEO_ID;?&amp;gt;&quot; name=&quot;title&quot; /&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; value=&quot;&amp;lt;?php print VIDEO_ID;?&amp;gt;&quot; name=&quot;video_id&quot; /&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;input name=&quot;commit&quot; id=&quot;submit&quot; type=&quot;submit&quot; value=&quot;Upload&quot; /&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&quot;progress-bar&quot; style=&quot;display:none&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div id=&quot;results&quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&amp;lt;?php
endif;
?&amp;gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;/li&gt;

&lt;li&gt;Put Hey!Watch&#039;s ajaxproxy.php in the same directory as your upload script.&lt;/li&gt;

&lt;li&gt;Time for some real Drupal fun.  Let&#039;s crack open an amenable module and edit our hook_form_alter.

&lt;blockquote&gt;&lt;pre&gt;
if($form_id==&#039;heywatchccktype_node_form&#039;)
{
   $heywatch_upload_key = variable_get(&#039;heywatch_upload_key&#039;,&#039;0&#039;);		   

   // retrieve the video_id, or generate a new one if necessary
   $video_id = -1;
   if(strlen(trim($form[&#039;#post&#039;][&#039;field_heywatch_video_status&#039;][0][&#039;value&#039;]))&amp;gt;0)
   {
      $video_id = $form[&#039;#post&#039;][&#039;field_heywatch_video_id&#039;][0][&#039;value&#039;];
   }   elseif(strlen(trim($form[&#039;group_heywatch&#039;][&#039;field_heywatch_video_status&#039;][0][&#039;value&#039;][&#039;#default_value&#039;]))&amp;gt;0)
   {
      $video_id = $form[&#039;group_heywatch&#039;][&#039;field_heywatch_video_id&#039;][0][&#039;value&#039;][&#039;#default_value&#039;];
   }
   else
   {
      $video_id = db_next_id(&#039;heywatch_video_id&#039;);      $form[&#039;group_heywatch&#039;][&#039;field_heywatch_video_id&#039;][0][&#039;value&#039;][&#039;#default_value&#039;] = $video_id;
   }
   
   // set the iframe up
   $iframe_src = &#039;/path/to/your/heywatch/AJAX/script.php?video_id=&#039; . $video_id . &#039;&amp;heywatch_key=&#039; . $heywatch_upload_key;		
   if(trim($form[&#039;#post&#039;][&#039;field_heywatch_video_status&#039;][0][&#039;value&#039;])==&#039;success&#039;)
      $iframe_src .= &quot;&amp;success=1&quot;;
   else if($form[&#039;group_heywatch&#039;][&#039;field_heywatch_video_status&#039;][0][&#039;value&#039;][&#039;#default_value&#039;]==&#039;success&#039;)
      $iframe_src .= &quot;&amp;success=2&amp;original_filename=&quot; . urlencode(basename(str_replace(&quot;\\&quot;,&quot;/&quot;,$form[&#039;group_heywatch&#039;][&#039;field_heywatch_video_original_f&#039;][0][&#039;value&#039;][&#039;#default_value&#039;])));

   $form[&#039;heywatch_iframe_container&#039;] = array
   (
      &#039;#type&#039; =&amp;gt; &#039;fieldset&#039;,
      &#039;#title&#039; =&amp;gt; t(&#039;Video File&#039;),
      &#039;#collapsible&#039; =&amp;gt; FALSE,
      &#039;#collapsed&#039; =&amp;gt; FALSE,
   );

   $form[&#039;heywatch_iframe_container&#039;][&#039;heywatch_iframe&#039;] = array
   (
      &#039;#type&#039; =&amp;gt; &#039;markup&#039;,
      &#039;#weight&#039; =&amp;gt; 9,
      &#039;#value&#039; =&amp;gt; &#039;&amp;lt;iframe src=&quot;&#039; . $iframe_src . &#039;&quot; id=&quot;heywatch&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&#039;,
   );		
}
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/li&gt;

&lt;li&gt;In your Hey!Watch account settings, under the &quot;Developer&quot; tab, create a URL for heywatch to ping.  Let&#039;s say &quot;http://www.yourserver.com/heywatch-ping&quot;.  Provide some module code to handle the incoming Hey!Watch ping.  This should locate the node on the basis of its video ID, then update its status to contain the filename (included in the ping) and the fact that the video has successfully been transcoded.  I would provide code, but I&#039;m glossing over this for a reason that will be discussed below.&lt;/li&gt;

&lt;li&gt;Finally, edit your stylesheet and add:

&lt;blockquote&gt;&lt;pre&gt;fieldset.group-heywatch { 
   display: none; 
}

iframe#heywatch {
   width: 100%;
   border: 0;
   overflow: visible;
}&lt;/pre&gt;&lt;/blockquote&gt;

You may want to specify a height value for the iframe, too.  It&#039;ll likely depend on how you decided to customize the AJAX uploader and your success or failure status displays.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;So how the heck does this all work?  Well, in a nutshell:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The form_alter hook finds the video_id if it already exists (i.e. if the node is being edited rather than created).  It passes the ID and the status to an iframe that it sticks into the form.&lt;/li&gt;

&lt;li&gt;The iframe serves the Prototype-powered video upload form.  It uploads directly to Hey!Watch, which will then redirect to a URL passed as a hidden field.&lt;/li&gt;

&lt;li&gt;That URL says &quot;success!&quot; and also uses Javascript to crawl up the DOM, into the iframe&#039;s parent window.  It then alters the heywatch_status text field&#039;s value to &#039;success&#039;.  The text field is hidden by CSS &amp;mdash; but CCK doesn&#039;t know that.  You can use the contents of that hidden field during form validation as a stand-in for what&#039;s been going on in the iframe.  This lets you ensure that the user successfully uploaded their video.&lt;/li&gt;

&lt;li&gt;When Hey!Watch is done transcoding it sends Drupal a ping.  The ping-handling hook updates the video node once more, this time with the filename of the transcoded file.  The node&#039;s template simply has to check the status value to determine whether to say &quot;you didn&#039;t upload a file!&quot; or &quot;video is being transcoded&quot; &amp;mdash; or, if the ping has arrived and the filename is available, to embed a video player pointing at the transcoded file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are a few things I&#039;ve left out. First, Hey!Watch is quite fast &amp;mdash; fast enough that a slow-typing user may find their video transcoded (and the Hey!Watch ping received) before they&#039;ve saved the node they&#039;re trying to attach their video to.  There&#039;s also the question of how the ping-handling code should find the node with just a video ID to go on &amp;mdash; laboriously cycling through nodes is a bad, bad idea.&lt;/p&gt;

&lt;p&gt;The answer is a video_id &amp;raquo; nid lookup table, with an extra field to temporarily hold incoming pings&#039; filename values for nodes that haven&#039;t yet been saved.  While you&#039;re at it you should probably also add a uid column to keep users from stealing one another&#039;s video IDs.  You&#039;ll then want to change that db_next_id() in hook_form_alter() to a routine that calls db_next_id() and inserts the returned value and the user&#039;s uid into that lookup table.  You&#039;ll also want to add code to hook_nodeapi&#039;s insert handler to check to see whether an early-arriving ping has queued up information that ought to be associated with the newly-available nid.&lt;/p&gt;

&lt;p&gt;All of this gets a little complicated, I&#039;ll admit, so I&#039;ve omitted it &amp;mdash; I really didn&#039;t want this post to have to get bogged down at the SQL level when it&#039;s really intended as an idea starter.  Besides, there are still other questions you&#039;ll have to answer about how to keep users from borrowing your Hey!Watch upload key or overwriting existing videos.  But that&#039;s a bit much for this conversation &amp;mdash; I&#039;m already worried that the code listed above has some errors in it, as I&#039;ve just done a quick pass at scraping project-specific information out of it.  But as complicated as it may seem, I still think this setup is vastly preferable to maintaining your own transcoding service.&lt;/p&gt;

&lt;p&gt;And hopefully this demonstrates a general technique for getting around some limitations imposed by CCK.  Iframes, hidden fields and Javascript can let you create interactions between Drupal and entirely distinct web development worlds, and do so in a way that&#039;s transparent to your users.&lt;/p&gt;
</description>
 <comments>http://echodittolabs.org/heywatch-and-cck#comments</comments>
 <category domain="http://echodittolabs.org/taxonomy/term/13">api</category>
 <category domain="http://echodittolabs.org/taxonomy/term/67">cck</category>
 <category domain="http://echodittolabs.org/taxonomy/term/46">drupal</category>
 <category domain="http://echodittolabs.org/taxonomy/term/75">heywatch</category>
 <category domain="http://echodittolabs.org/taxonomy/term/60">php</category>
 <category domain="http://echodittolabs.org/taxonomy/term/76">transcoding</category>
 <category domain="http://echodittolabs.org/taxonomy/term/11">video</category>
 <pubDate>Fri, 20 Jul 2007 15:02:03 -0700</pubDate>
 <dc:creator>Tom</dc:creator>
 <guid isPermaLink="false">36 at http://echodittolabs.org</guid>
</item>
</channel>
</rss>
