1) { $dur = 0; for ($mul = 1, $i = 0; $i < count($colon); $i++, $mul *= 60) $dur += $colon[count($colon)-1-$i] * $mul; } // Пустой формат видео означает, что файл ещё нужно обработать (минимум qt-faststart и вытащить тамбик) return [ 'format' => 'video', 'width' => $data['size'][1][0], 'height' => $data['size'][2][0], 'props' => [ 'duration' => $dur, 'video_format' => @$data['video_format'][1], 'audio_format' => @$data['audio_format'][1], ], ]; } return NULL; } protected static function extractVideoPreview($file, $redirect_output = '') { $conv = App::$config['video_converter']; $ss = $file['props']['duration'] * $conv['preview_moment']; $fn = FileHandler::getPath(false, $file); $out = FileHandler::getThumbPath(false, $file, 'src'); FileUtils::mkpath(dirname($out), true); $cmd = $conv['extract_frame']; $cmd = str_replace([ '$input', '$output', '$position' ], [ escapeshellarg($fn), escapeshellarg($out), ceil($ss) ], $cmd); system($cmd . ($redirect_output ? ' &> '.$redirect_output : '')); if (!file_exists($out) || !filesize($out)) { throw new Exception('Failed to extract video frame for preview'); } } public static function runConvertJobDaemon($redirect_output = '') { while (1) { $file = App::$db->selectRow(File::$table, '*', [ 'format' => 'video' ], [ 'LIMIT' => 1 ]); if ($file) { self::convertVideoAndUpdate($file, $redirect_output); } App::$db->commitAll(); sleep(5); } } public static function convertVideoAndUpdate($file, $redirect_output = '') { $props = self::convertVideo($file, $redirect_output); if (!$props) { return; } self::extractVideoPreview($props + $file, $redirect_output); $update = $props; if (isset($update['props'])) { $update['props'] = json_encode($update['props']); } App::$db->update(File::$table, $update, [ 'id' => $file['id'] ]); if (isset($props['sha1']) && $props['sha1'] != $file['sha1']) { @unlink(FileHandler::getPath(false, $file)); } elseif (isset($props['format']) && $props['format'] != $file['format']) { @rename(FileHandler::getPath(false, $file), FileHandler::getPath(false, $props + $file)); } return $props + $file; } protected static function convertVideo($file, $redirect_output = '') { // Краткая сводка: // Flash понимает форматы файлов MP4/FLV с видео+аудио FLV+MP3 или H.264+AAC // HTML5 кроссбраузерно понимает форматы MP4 с видео H.264 и аудио MP3/AAC, либо WebM с видео VP8 и аудио Vorbis // Для MP4 надо делать qt-faststart, если MOOV ATOM не находится в начале видеофайла // // Итак, если формат WebM+VP8+Vorbis или FLV+FLV+MP3 или FLV+H.264+AAC, видео можно вообще не конвертировать // Если формат MP4+H.264+MP3/AAC, надо проверить/сделать qt-faststart // Любой другой формат надо перегнать в MP4+H.264+AAC if ($file['format'] != 'video') { return NULL; } if (empty(App::$config['video_converter'])) { throw new Exception('Video converter is not configured on the server'); } $conv = App::$config['video_converter']; $vf = $file['props']['video_format'][0]; $af = $file['props']['audio_format'][0]; if ($file['mimetype'] == 'video/x-flv' && ($vf == 'flv1' && $af == 'mp3' || $vf == 'h264' && $af == 'aac')) { return [ 'format' => 'flv', ]; } elseif ($file['mimetype'] == 'video/webm' && $vf == 'vp8' && $af == 'vorbis') { return [ 'format' => 'webm', ]; } $fn = FileHandler::getPath(false, $file); $out = tempnam(sys_get_temp_dir(), 'ffc'); if ($file['mimetype'] == 'video/mp4' && $vf == 'h264' && ($af == 'mp3' || $af == 'aac')) { $cmd = $conv['qt_faststart'].' '.escapeshellarg($fn).' '.escapeshellarg($out); system($cmd . ($redirect_output ? ' &> '.$redirect_output : '')); if (!file_exists($out)) { // Уже было faststart, всё ок return [ 'format' => 'mp4', ]; } } else { $cmd = $conv['convert']; $cmd = str_replace([ '$input', '$output' ], [ escapeshellarg($fn), escapeshellarg($out) ], $cmd); system($cmd . ($redirect_output ? ' &> '.$redirect_output : '')); if (!file_exists($out)) { // Не сконвертировалось, плохо throw new Exception('Failed to convert video to MP4'); } } // Сконвертированный файл $update = FileUtils::getProps(File::ONLY_VIDEO, $this->config['mime_blacklist'], $out, $out); $update['format'] = 'mp4'; $newfn = FileHandler::getPath(false, $update + $file); FileUtils::mkpath(dirname($newfn), true); if (!@rename($out, $newfn)) { $error = error_get_last(); throw new Exception($error['message']); } // FIXME Теоретически может получиться так, что полученный файл будет дубликатом, и запрос свалится // (Может ли ffmpeg дважды выдать одинаковый файл? Тогда будет проблемка) return $update; } }