static function mashInfo($xml, $path = 'http://www.example.com/', $fps = 0, $exact = FALSE)
 {
     if (!$fps) {
         $fps = MovieMasher_Coder_Decoder::defaultOption('DecoderFPS');
     }
     $render_clips = array();
     $result = array();
     $result['has_audio'] = FALSE;
     $result['has_video'] = FALSE;
     $result['clips'] = array();
     // indexed array() of arrays with keys needed for quick sorting and time based retrieval (start, stop)
     $result['render_all'] = $exact;
     // switch for full flash rendering
     $result['cache_urls'] = array();
     // files needing to be cached
     $result['timespans'] = array();
     // array(startseconds, stopseconds, startframe, framecount, framecounter)
     $result['media'] = array();
     // holds references to all media tags (they may get altered)
     $mash_tags = $xml->xpath('//mash');
     if (!sizeof($mash_tags)) {
         throw new UnexpectedValueException('No mash tag found');
     }
     $mash_tag = $mash_tags[0];
     $quantize = floatval($mash_tag['quantize']);
     if (!$quantize) {
         $quantize = floatval(1);
     }
     $result['quantize'] = $quantize;
     // grab all media tags, even nested ones
     $media_tags = $xml->xpath('//media');
     $media_count = sizeof($media_tags);
     for ($i = 0; $i < $media_count; $i++) {
         $media = $media_tags[$i];
         $result['media'][(string) $media['id']] = $media;
     }
     // grab all clip tags within mash, even nested ones
     $clip_tags = $xml->mash->xpath('//clip');
     $clip_count = sizeof($clip_tags);
     $result['duration'] = floatval(0);
     for ($i = 0; $i < $clip_count; $i++) {
         $clip_tag = $clip_tags[$i];
         $id = (string) $clip_tag['id'];
         if (!isset($result['media'][$id])) {
             throw new UnexpectedValueException('No media found for clip ID ' . $id);
         }
         $media = $result['media'][$id];
         $clip = array();
         $clip['type'] = (string) $media['type'];
         $clip['id'] = (string) $media['id'];
         $clip['track'] = (int) $clip_tag['track'];
         // clips with negative tracks will have zero for start - we want to topmost clip's start
         if ($clip['track'] < 0) {
             $clip['start'] = MovieMasher_Coder_Decoder::nestedStart($clip_tag, $media);
             if (!floatgtre($clip['start'], floatval(0))) {
                 continue;
             }
         } else {
             $clip['start'] = floatval($clip_tag['start']);
         }
         $clip['start'] = $clip['start'] / $quantize;
         $clip['length'] = floatval($clip_tag['length']) / $quantize;
         $clip['stop'] = $clip['start'] + $clip['length'];
         // duration of underlying audio/video clip, or zero
         $clip['duration'] = floatval($media['duration']);
         // speed defaults to one
         $clip['speed'] = floatval($clip_tag['speed']);
         if (!$clip['speed']) {
             $clip['speed'] = floatval(1);
         }
         // loops defaults to one
         $clip['loops'] = floatval($clip_tag['loops']);
         if (!$clip['loops']) {
             $clip['loops'] = floatval(1);
         }
         // will be 0.0 for all but trimmed audio/video
         $clip['trimstart'] = floatval($clip_tag['trimstart']) / $quantize;
         // URLs defaults to empty
         $clip['source'] = '';
         $clip['audio'] = '';
         switch ($clip['type']) {
             case 'image':
                 // so any reference in flash points to hi res source file
                 $url = (string) $media['source'];
                 $url = MovieMasher_Coder::cleanURL($url);
                 if ($url) {
                     $media['url'] = $url;
                 }
                 $clip['source'] = (string) $media['url'];
                 // intentional fallthrough to video
             // intentional fallthrough to video
             case 'video':
                 $clip['fill'] = (string) $clip_tag['fill'];
                 if (!$clip['fill']) {
                     $clip['fill'] = (string) $media['fill'];
                 }
                 if (!$clip['fill']) {
                     $clip['fill'] = 'stretch';
                 }
                 break;
         }
         $clip['audio'] = '';
         switch ($clip['type']) {
             case 'audio':
                 $audio = (string) $media['source'];
                 $audio = MovieMasher_Coder::cleanURL($audio);
                 if (empty($audio)) {
                     $audio = (string) $media['audio'];
                 }
                 if (!empty($audio)) {
                     // media has an audio url
                     $volume = (string) $clip_tag['volume'];
                     if (empty($volume) || $volume != '0,0,100,0') {
                         // clip has volume and is not muted
                         $url = absolute_url(end_with_slash($path), $audio);
                         $url = MovieMasher_Coder::cleanURL($url);
                         $result['cache_urls'][$url] = TRUE;
                         $result['has_audio'] = TRUE;
                         $clip['audio'] = $url;
                         $clip['volume'] = $volume;
                     }
                 }
                 break;
             case 'effect':
                 $result['has_video'] = TRUE;
                 if ($clip['track'] < 0) {
                     // effect clip is attached to another clip or the mash itself
                     $parent_tag = MovieMasher_Coder_Decoder::parentTag($clip_tag);
                     if ($parent_tag == NULL) {
                         throw new UnexpectedValueException('Could not determine parent of effect');
                     }
                     if ($parent_tag->getName() == 'mash') {
                         $result['render_all'] = 1;
                     }
                 }
                 $render_clips[] = $clip;
                 break;
             case 'theme':
                 if ($clip['track'] < 0) {
                     break;
                 }
                 // otherwise we're not composited, so fallthrough to transition
             // otherwise we're not composited, so fallthrough to transition
             case 'transition':
                 $render_clips[] = $clip;
                 // fallthrough to other visuals (image and video)
             default:
                 $url = (string) $media['source'];
                 if (!$url) {
                     $url = (string) $media['url'];
                 }
                 $url = MovieMasher_Coder::cleanURL($url);
                 if ($url) {
                     if (file_extension($url) == 'youtube') {
                         throw new UnexpectedValueException('YouTube video decoding unsupported: ' . substr($url, 0, -8));
                     }
                     $url = absolute_url(end_with_slash($path), $url);
                     $clip['source'] = $url;
                 }
                 $result['has_video'] = TRUE;
                 $shifted = !floatcmp($clip['speed'], floatval(1));
                 // check for audio=0 in clip tag (composited video with no audio)
                 $audio = (string) $clip_tag['audio'];
                 if ($audio !== '0') {
                     $audio = (string) $media['audio'];
                     if (!$shifted && !empty($audio)) {
                         // media has an audio url
                         $volume = (string) $clip_tag['volume'];
                         // make sure this isn't a composited visual
                         if (empty($volume) || $volume != '0,0,100,0') {
                             // clip has volume and is not muted
                             if (!$url) {
                                 // '1' means to use the audio in the video file
                                 if ($audio == '1') {
                                     $url = (string) $media['url'];
                                 } else {
                                     $url = $audio;
                                 }
                             }
                             if ($url) {
                                 $url = absolute_url(end_with_slash($path), $url);
                                 $url = MovieMasher_Coder::cleanURL($url);
                                 $result['cache_urls'][$url] = TRUE;
                                 $result['has_audio'] = TRUE;
                                 $clip['audio'] = $url;
                                 $clip['volume'] = $volume;
                             }
                         }
                     }
                 }
                 // at the moment, timeshifted video clips are handled in flash
                 if ($shifted && $clip['type'] == 'video') {
                     $render_clips[] = $clip;
                 }
         }
         $result['duration'] = floatmax($clip['stop'], $result['duration']);
         $result['clips'][] = $clip;
     }
     usort($result['clips'], array('MovieMasher_Coder_Decoder', '__sortByStartTime'));
     if (!empty($result['render_all'])) {
         $result['timespans'][] = array(floatval(0), $result['duration']);
     } else {
         // determine timespans of all clips requiring flash rendering
         $z = sizeof($render_clips);
         for ($i = 0; $i < $z; $i++) {
             $clip = $render_clips[$i];
             $start = $clip['start'];
             $stop = $clip['stop'];
             $y = sizeof($result['timespans']);
             for ($j = $y - 1; $j > -1; $j--) {
                 $spanstart = $result['timespans'][$j][0];
                 $spanstop = $result['timespans'][$j][1];
                 if (!(floatgtr($start, $spanstop) || floatgtr($spanstart, $stop))) {
                     // they touch or overlap, so remove and expand
                     $start = floatmin($start, $spanstart);
                     $stop = floatmax($stop, $spanstop);
                     array_splice($result['timespans'], $j, 1);
                 }
             }
             $result['timespans'][] = array($start, $stop);
         }
         usort($result['timespans'], 'floatsort');
         if (sizeof($result['timespans']) == 1) {
             if ($result['timespans'][0][0] == floatval(0) && $result['timespans'][0][1] == $result['duration']) {
                 $result['render_all'] = 1;
             }
         }
     }
     $z = sizeof($result['timespans']);
     $result['timespan_frame_count'] = 0;
     $result['timespan_frame_index'] = 0;
     $result['timespan_index'] = 0;
     if ($z) {
         $done = array();
         // initialize timespans and alter media tags
         $float_fps = floatval($fps);
         for ($i = 0; $i < $z; $i++) {
             $span =& $result['timespans'][$i];
             // save frame for times, as convenience
             $span[2] = intval(floor($span[0] * $float_fps));
             $span[3] = intval(floor($span[1] * $float_fps));
             // create a frame counter for span
             $span[4] = $span[2];
             // add to global frame count
             $result['timespan_frame_count'] += $span[3] - $span[2];
             // update media tag of video clips within this range with higher resolution media
             $clips = MovieMasher_Coder_Decoder::videoBetween($result['clips'], $span[0], $span[0] + $span[1]);
             $y = sizeof($clips);
             for ($j = 0; $j < $y; $j++) {
                 $clip =& $clips[$j];
                 if ($clip['type'] == 'video') {
                     $media_tag = $result['media'][$clip['id']];
                     $url = (string) $media_tag['source'];
                     if (!$url) {
                         $url = (string) $media_tag['url'];
                     }
                     $url = MovieMasher_Coder::cleanURL($url);
                     $ext = file_extension($url);
                     if ($ext) {
                         $url = absolute_url(end_with_slash($path), $url);
                         if (empty($done[$clip['id']])) {
                             $done[$clip['id']] = TRUE;
                             $media_tag['zeropadding'] = strlen(floor($float_fps * $clip['duration']));
                             $media_tag['pattern'] = '%.jpg';
                             $media_tag['fps'] = $fps;
                             $media_tag['source'] = $url;
                             $result['cache_urls'][$url] = TRUE;
                         }
                         $clip['encode'] = TRUE;
                     }
                 }
             }
         }
     }
     return $result;
 }