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; }