function cache_file_frames($vid_file_path, $fps, $start = 0, $duration = -1, $options = array(), $switches = array(), $ffmpeg = '') { $result = FALSE; if ($vid_file_path && $fps && $duration && file_exists($vid_file_path)) { $media_dimensions = get_file_info('dimensions', $vid_file_path, $ffmpeg); // target frame size, if different from video if (empty($options['dimensions'])) { $options['dimensions'] = $media_dimensions; } // video duration, for convenience if (empty($options['duration'])) { $options['duration'] = get_file_info('duration', $vid_file_path, $ffmpeg); } $dimensions = scale_even($options['dimensions']); $size = explode('x', $dimensions); if (empty($options['dimensions_dir'])) { $options['dimensions_dir'] = $dimensions; } $build_dir = frame_file_path($vid_file_path, $options['dimensions_dir'], $fps); $media_duration = $options['duration']; if ($build_dir && $media_duration && $media_dimensions && safe_path($build_dir)) { $orig_size = explode('x', $media_dimensions); if ($duration == -1) { $duration = $media_duration; if ($duration && $start) { $duration -= $start; } } $fps = floatval($fps); $start = floatval($start); $start_frame = ceil($fps * $start); $duration = floatval($duration); $media_duration = floatval($media_duration); $media_frames = floor($media_duration * $fps); $digits = strlen($media_frames); $frames = 2 + ceil($fps * $duration); $files = array(); $need_files = FALSE; $stop_frame = $start_frame + $frames; // print 'cacheVideoFileFrames: ' . $start_frame . ' to ' . $stop_frame . "\n"; for ($i = $start_frame; $i <= $stop_frame; $i++) { $seq_file = $build_dir . str_pad(1 + $i, $digits, '0', STR_PAD_LEFT) . '.jpg'; $files[] = $seq_file; if (!$need_files && !file_exists($seq_file)) { $need_files = TRUE; } } $result = array(); $result['files'] = $files; $err = FALSE; if ($need_files) { $cmd = ''; $cmd .= $ffmpeg; if ($start) { $cmd .= ' -ss ' . $start; } $cmd .= ' -s ' . $orig_size[0] . 'x' . $orig_size[1]; $cmd .= ' -i ' . $vid_file_path; $cmd .= ' -s ' . join('x', scale_proud($orig_size, $size)); if ($switches) { $cmd .= ' ' . stringFromSwitches($switches); } if (empty($switches['b']) && !empty($options['bitrate'])) { $cmd .= ' -b ' . $options['bitrate'] . 'K'; } $cmd .= ' -an -r ' . $fps . ' -vframes ' . $frames . ' ' . $build_dir . 'build%d.jpg'; $result['command'] = $cmd; //print $cmd; $response = shell_command($cmd); $result['result'] = $response; if ($response) { $last_file = ''; for ($i = 0; $i <= $frames; $i++) { $frame_file = $build_dir . 'build' . (1 + $i) . '.jpg'; if (!file_exists($frame_file)) { // ffmpeg doesn't always write every last frame if ($last_file) { // so we copy the last one repeatedly if (!@copy($last_file, $frame_file)) { $err = TRUE; } } else { $err = TRUE; } } if (!$err) { $seq_file = $files[$i]; $last_file = $seq_file; if (!@rename($frame_file, $seq_file)) { $err = TRUE; } } if ($err) { break; } } } } /* if (! $err) { $result['duration'] = $frames / $fps; $result['orig_dimensions'] = join('x', $orig_size); $result['orig_extension'] = file_extension($vid_file_path); $result['dimensions'] = join('x', $size); $result['type'] = 'video'; $result['zeropadding'] = $digits; $result['fps'] = $fps; $result['orig_fps'] = get_file_info('fps', $vid_file_path, $ffmpeg); $result['pattern'] = '%.jpg'; $result['icon'] = str_pad(1 + $start_frame + floor($frames / 2), $digits, '0', STR_PAD_LEFT) . '.jpg'; } */ if ($err) { $result['files'] = ''; } } } return $result; }
function __cacheMedia() { $fps = floatval($this->_options['DecoderFPS']); // cache frames for all video files used in flash $z = sizeof($this->__mashData['timespans']); for ($i = 0; $i < $z; $i++) { $span = $this->__mashData['timespans'][$i]; $span_start = $span[0]; $span_end = $span[0] + $span[1]; $clips = MovieMasher_Coder_Decoder::videoBetween($this->__mashData['clips'], $span_start, $span_end); $this->_progressStep('CacheFiles', 1 + round(30 * ($i / $z)), 'Caching Frames'); foreach ($clips as $clip) { if (empty($clip['encode'])) { continue; } $clip_trim = $this->__clipTrim($clip, $span_start, $span_end, $fps); if (!$clip_trim) { throw new UnexpectedValueException('Could not get clip info: ' . $clip['id']); } $media_tag = $this->__mashData['media'][$clip['id']]; $url = (string) $media_tag['source']; $url = absolute_url(end_with_slash($this->_options['CoderBaseURL']), $url); $ext = file_extension($url); if (!$ext) { throw new UnexpectedValueException('Media source attributes must have a file extension: ' . $url); } $vid_file_path = cache_url($url, $this->_options['DirCache']); if (!$vid_file_path) { throw new UnexpectedValueException('Could not cache URL: ' . $url); } $options = array(); $orig_dimensions = get_file_info('dimensions', $vid_file_path, $this->_options['PathFFmpeg']); if (!$orig_dimensions) { throw new RuntimeException('Could not read dimensions: ' . $vid_file_path); } $target_dimensions = scale_proud($orig_dimensions, $this->_options['DecoderDimensions']); if (!$target_dimensions) { $target_dimensions = $this->_options['DecoderDimensions']; } $options['dimensions'] = $target_dimensions; $options['bitrate'] = 8000; // max? $options['dimensions_dir'] = $this->_options['DecoderDimensions']; $options['duration'] = $clip['duration']; $switches = array(); $fps_secs = floatval(1) / $this->__mashData['quantize']; // we need an extra frame in mash's fps if ($clip_trim['trimstart']) { $clip_trim['trimstart'] -= $fps_secs; $clip_trim['trimlength'] += $fps_secs; } $clip_trim['trimlength'] += $fps_secs; if ($clip['speed'] != 1 || !empty($this->_options['DecoderCacheAllVideoFrames'])) { // TODO: don't cache all the frames when speed isn't normal $clip_trim['trimstart'] = 0; $clip_trim['trimlength'] = -1; } //$this->log('Caching ' . ((string) $media_tag['label']) . ' @ ' . $this->_options['DecoderFPS'] . ' ' . $clip_trim['trimstart'] . ' -> ' . $clip_trim['trimlength']); $cached_frames = cache_file_frames($vid_file_path, $this->_options['DecoderFPS'], $clip_trim['trimstart'], $clip_trim['trimlength'], $options, $switches, $this->_options['PathFFmpeg']); if (!$cached_frames) { throw new RuntimeException('Could not prepare to cache image sequence: ' . $url); } if (!empty($this->_options['Verbose']) && !empty($cached_frames['command'])) { $this->log($cached_frames['command'] . "\n" . $cached_frames['result']); } if (!$cached_frames['files']) { throw new RuntimeException('Could not cache image sequence: ' . $url); } $media_tag['url'] = 'http://' . $this->getOption('HostLocal') . '/mm.' . $this->getVersion() . '/cache/' . frame_file_path(url_file_path($url), $this->_options['DecoderDimensions'], $this->_options['DecoderFPS']); } } // cache the non-flash video content $zero = floatval(0); $cur_time = $zero; $z = sizeof($this->__mashData['timespans']); for ($i = 0; $i < $z; $i++) { if (floatgtr($this->__mashData['timespans'][$i][0], $cur_time)) { // first timespan doesn't start where the last left off, so there is something between $this->__cacheVisuals($cur_time, $this->__mashData['timespans'][$i][0]); } $cur_time = $this->__mashData['timespans'][$i][1]; $this->_progressStep('CacheFiles', 30 + (1 + round(30 * ($i / $z))), 'Caching Visuals'); } if (!floatcmp($cur_time, $this->__mashData['duration'])) { // last timespan doesn't end the mash, so there is stuff after it $this->__cacheVisuals($cur_time, $this->__mashData['duration']); } // cache audio and video with audio files $i = 0; $z = sizeof(array_keys($this->__mashData['cache_urls'])); foreach ($this->__mashData['cache_urls'] as $url => $true) { $this->_progressStep('CacheFiles', 60 + (1 + round(30 * ($i++ / $z))), 'Caching Audio'); $result = cache_url($url, $this->_options['DirCache']); if (!$result) { throw new RuntimeException('Could not cache audio: ' . $url); } } $this->_progressStep('CacheFiles', 100, 'Cached Media'); }
function __buildSequence($file_path) { $this->_progressStep('EncodeVideo', 5, 'Encoding Video'); if (isset($this->_options['EncoderExtension'])) { $options = array(); $orig_dimensions = get_file_info('dimensions', $file_path); if (!$orig_dimensions) { throw new RuntimeException('Could not read dimensions: ' . $file_path); } $target_dimensions = scale_proud($orig_dimensions, $this->_options['EncoderDimensions']); if (!$target_dimensions) { $target_dimensions = $this->_options['EncoderDimensions']; } $target_size = explode('x', $target_dimensions); $options['bitrate'] = round($this->_options['EncoderImageQuality'] / 100 * ($target_size[0] * $target_size[1] * 3 * $this->_options['EncoderFPS'] / 1024)); $options['dimensions'] = $target_dimensions; $options['dimensions_dir'] = $this->_options['EncoderDimensions']; $switches = switchesFromString($this->_options['EncoderSwitches']); $cached_frames = cache_file_frames($file_path, $this->_options['EncoderFPS'], 0, -1, $options, $switches, $this->_options['PathFFmpeg']); if (!$cached_frames) { throw new RuntimeException('Could not prepare to cache image sequence: ' . $file_path); } if (!empty($this->_options['Verbose']) && !empty($cached_frames['command'])) { $this->log($cached_frames['command'] . "\n" . $cached_frames['result']); } if (!$cached_frames['files']) { throw new RuntimeException('Could not cache image sequence: ' . $file_path); } $this->_progressStep('EncodeVideo', 100, 'Encoded Video'); } }