<?php
|
|
|
|
|
|
namespace App\Services;
|
|
use App\Utils\FileUtils;
|
|
use FFMpeg\Coordinate\AspectRatio;
|
|
use FFMpeg\Coordinate\Dimension;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Redis;
|
|
use Mhor\MediaInfo\MediaInfo;
|
|
use FFMpeg\FFProbe;
|
|
use Throwable;
|
|
|
|
|
|
class FfmpegService
|
|
{
|
|
|
|
private $mediainfo;
|
|
|
|
private $ffprobe;
|
|
|
|
private $needRemoveAfterEncode = false;
|
|
|
|
private $needRemoveExistFiles = false;
|
|
|
|
const DEFAULT_EXTENSION = "mp4";
|
|
|
|
public function __construct()
|
|
{
|
|
$this->mediainfo = new MediaInfo();
|
|
$this->mediainfo->setConfig('use_oldxml_mediainfo_output_format', true);
|
|
|
|
$config = array(
|
|
'ffmpeg.binarie' => '/Users/shixuesen/Downloads/ffmpeg',
|
|
'ffprobe.binaries' => '/opt/homebrew/bin/ffprobe',
|
|
'timeout' => 3600,
|
|
'ffmpeg.threads' => 16,
|
|
);
|
|
$this->ffprobe = FFProbe::create($config);
|
|
|
|
}
|
|
|
|
public function handleVideos($dir = "/Users/shixuesen/Documents/tmp/柚木/2017/泡泡条纹袜/")
|
|
{
|
|
$files = scandir($dir);
|
|
foreach ($files as $file) {
|
|
if ($file == "." || $file == "..") {
|
|
continue;
|
|
}
|
|
$subDir = implode("/", [$dir, $file]);
|
|
$isDir = is_dir($subDir);
|
|
if ($isDir) {
|
|
$subFiles = scandir($subDir);
|
|
foreach ($subFiles as $subFile) {
|
|
$subPathFile = implode("/", [$subDir, $subFile]);
|
|
if (is_dir($subPathFile) || $subFile == ".DS_Store") {
|
|
continue;
|
|
}
|
|
$mime = mime_content_type($subPathFile);
|
|
// dump("file type", [$mime, $subPathFile]);
|
|
// continue;
|
|
if (strstr($mime, "video/")) {
|
|
if (is_file($subPathFile)) {
|
|
$fileInfo = pathinfo($subPathFile);
|
|
dump("fileInfo", $fileInfo);
|
|
if (ends_with($fileInfo["filename"], "-1")) {
|
|
continue;
|
|
}
|
|
if (is_file($fileInfo["dirname"] . '/' .$fileInfo["filename"] . '-1'. '.' . $fileInfo["extension"])) {
|
|
unlink($subPathFile);
|
|
continue;
|
|
}
|
|
$targetFile = $fileInfo["dirname"] . '/' .$fileInfo["filename"] . '-1'. '.' . $fileInfo["extension"];
|
|
dump("targetFile", [$targetFile]);
|
|
// $result = shell_exec("handBrakeCli -Z 'Very Fast 720p30' -i '". $subPathFile ."' -o '". $targetFile . " && echo 'success'");
|
|
$result = shell_exec("handBrakeCli -Z 'Very Fast 720p30' -i '". $subPathFile ."' -o '". $targetFile . "'");
|
|
dump($result);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
print_r($files);
|
|
}
|
|
|
|
// public function processDir($baseDir = "/Volumes/WD/Video/HuaVid/")
|
|
public function processDir($baseDir = "/Volumes/Backup/HuaVid/大忽悠")
|
|
{
|
|
$files = scandir($baseDir);
|
|
foreach ($files as $file) {
|
|
if ($file == "." || $file == ".." || $file == ".DS_Store" || $file == "1762") {
|
|
continue;
|
|
}
|
|
$subDir = implode("/", [$baseDir, $file]);
|
|
$isDir = is_dir($subDir);
|
|
if ($isDir) {
|
|
$this->processDir($subDir);
|
|
} else {
|
|
$this->processVideo($subDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function processDirWithQueue($baseDir = "/Volumes/Backup/HuaVid/大忽悠", $queue)
|
|
{
|
|
$files = scandir($baseDir);
|
|
foreach ($files as $file) {
|
|
if ($file == "." || $file == ".." || $file == ".DS_Store" || $file == "1762") {
|
|
continue;
|
|
}
|
|
$subDir = implode("/", [$baseDir, $file]);
|
|
$isDir = is_dir($subDir);
|
|
if ($isDir) {
|
|
$this->processDir($subDir);
|
|
} else {
|
|
$item = Redis::connection()->rpop($queue);
|
|
while ($item != null) {
|
|
$this->processVideo($item);
|
|
echo $item . "\n";
|
|
$item = Redis::connection()->rpop($queue);
|
|
}
|
|
$this->processVideo($subDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function processVideo($pathFile)
|
|
{
|
|
while (date("H") >= 22 || date("H") < 8) {
|
|
Log::info("now is " . date("Y-m-d H:i:s") . " sleep 5 minutes");
|
|
sleep(5 * 60);
|
|
}
|
|
Log::info("current process pathFile " . $pathFile);
|
|
try {
|
|
$mime = mime_content_type($pathFile);
|
|
} catch (Throwable $e) {
|
|
Log::error("mime_content_type has exception " . $e->getMessage());
|
|
}
|
|
$mediaInfo = new MediaInfo();
|
|
$mediaInfo->setConfig('use_oldxml_mediainfo_output_format', true);
|
|
if (strstr($mime, "video/") || strstr($mime, "application/octet-stream")) {
|
|
if (is_file($pathFile)) {
|
|
if (Redis::get("stopFlag") != null) {
|
|
Log::info("stopFlag is set");
|
|
dump("stopFlag is set");
|
|
exit;
|
|
}
|
|
$fileInfo = pathinfo($pathFile);
|
|
if (Redis::get("encode:lock:" . $fileInfo["filename"]) == 1) {
|
|
Log::info("file is encoding filename: " . $fileInfo["filename"]);
|
|
return;
|
|
}
|
|
if (!Redis::set("encode:lock:" . $fileInfo["filename"] , 1, "nx", "ex", 36000)) {
|
|
Log::info("lock failed filename: " . $fileInfo["filename"]);
|
|
return;
|
|
}
|
|
if (Redis::sismember("unneed", $fileInfo["filename"])) {
|
|
Log::info("in uneed: " . $fileInfo["filename"]);
|
|
return;
|
|
}
|
|
if (Redis::sismember("sizeSmall", $fileInfo["filename"]) || !$this->checkFileSize($pathFile)) {
|
|
Redis::sadd("sizeSmall", $fileInfo["filename"]);
|
|
Log::info("filesize: " . $fileInfo["filename"]);
|
|
return;
|
|
}
|
|
if (Redis::sismember("hasEncode", $fileInfo["filename"]) || $this->checkFileEncodeType($pathFile)) {
|
|
Redis::sadd("hasEncode", $fileInfo["filename"]);
|
|
Log::info("$pathFile has already encode by h265 return");
|
|
return;
|
|
}
|
|
if (filemtime($pathFile) > strtotime("2021-07-26 00:00:00")) {
|
|
$mtime = date("Y-m-d H:i:s", filemtime($pathFile));
|
|
// dump("$pathFile modify at $mtime is after 2021-07-26 00:00:00 skip");
|
|
// return;
|
|
} else {
|
|
$mtime = date("Y-m-d H:i:s", filemtime($pathFile));
|
|
// dump("$pathFile modify at $mtime is before 2021-07-19 00:00:00");
|
|
}
|
|
if (ends_with($fileInfo["filename"], "-x265")) {
|
|
return;
|
|
}
|
|
$targetFile = $fileInfo["dirname"] . DIRECTORY_SEPARATOR .$fileInfo["filename"] . '-x265'. '.' . self::DEFAULT_EXTENSION;
|
|
if (is_file($targetFile) && $this->isNeedRemoveExistFiles()) {
|
|
Log::info("$targetFile is exists");
|
|
unlink($pathFile);
|
|
rename($targetFile, $pathFile);
|
|
return;
|
|
}
|
|
dump("targetFile", [$targetFile]);
|
|
Log::info("process target file : $targetFile");
|
|
if (filesize($pathFile) > 10 * 1024 * 1024 * 1024 || $this->getDimension($pathFile)->getWidth() > 1920) {
|
|
// $result = shell_exec("/Users/shixuesen/Downloads/ffmpeg -threads 16 -i ". escapeshellarg($pathFile) ." -preset ultrafast -crf 25 -c:v libx265 -x265-params pools=8 -vtag hvc1 " . escapeshellarg($targetFile) . " && echo 'ok'");
|
|
$result = shell_exec("/Users/shixuesen/Downloads/ffmpeg -i ". escapeshellarg($pathFile) ." -c:v libx265 -x265-params pools=8 -vtag hvc1 -vf \"scale=4096:-1\" " . escapeshellarg($targetFile) . " && echo 'ok'");
|
|
} else {
|
|
$result = shell_exec("/Users/shixuesen/Downloads/ffmpeg -threads 16 -i ". escapeshellarg($pathFile) ." -preset fast -c:v libx265 -x265-params pools=8 -vtag hvc1 " . escapeshellarg($targetFile) . " && echo 'ok'");
|
|
}
|
|
// echo $result;
|
|
// return;
|
|
if (trim($result) == "ok" && $this->isNeedRemoveAfterEncode()) {
|
|
echo "compress work done remove the file \n";
|
|
Log::info("compress work done remove the file");
|
|
$oldFileSize = filesize($pathFile);
|
|
$newFileSize = filesize($targetFile);
|
|
if ($newFileSize >= $oldFileSize) {
|
|
Redis::sadd("unneed", $fileInfo["filename"]);
|
|
echo "old file size is smaller than new one, old is " . file_size($oldFileSize) . " and new is " . file_size($newFileSize) . ", now remove new one";
|
|
Log::info("old file size is smaller than new one, old is " . file_size($oldFileSize) . " and new is " . file_size($newFileSize) . ", now remove new one");
|
|
unlink($targetFile);
|
|
} else {
|
|
Redis::sadd("unneed", $fileInfo["filename"]);
|
|
echo "new file size is smaller than old one, new is " . file_size($newFileSize) . " and old is " . file_size($oldFileSize) . ", now remove old one";
|
|
Log::info("new file size is smaller than old one, new is " . file_size($newFileSize) . " and old is " . file_size($oldFileSize) . ", now remove old one");
|
|
unlink($pathFile);
|
|
rename($targetFile, $pathFile);
|
|
}
|
|
}
|
|
Redis::del("encode:lock:" . $fileInfo["filename"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function processUnCompleteDir($baseDir = "/Volumes/WD/tmp/探花系列【AI高清2K修复】大合集")
|
|
// public function processDir($baseDir = "/Volumes/Backup/iPhone nPlayer/")
|
|
{
|
|
$files = scandir($baseDir);
|
|
foreach ($files as $file) {
|
|
if ($file == "." || $file == "..") {
|
|
continue;
|
|
}
|
|
$subDir = implode("/", [$baseDir, $file]);
|
|
$isDir = is_dir($subDir);
|
|
if ($isDir) {
|
|
$this->processUnCompleteDir($subDir);
|
|
} else {
|
|
$this->processUnCompleteVideo($subDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function processUnCompleteVideo($pathFile)
|
|
{
|
|
//...
|
|
$mediaInfo = new MediaInfo();
|
|
$mediaInfo->setConfig('use_oldxml_mediainfo_output_format', true);
|
|
|
|
$mime = mime_content_type($pathFile);
|
|
// dump("file type", [$mime, $subPathFile]);
|
|
// continue;
|
|
if (strstr($mime, "video/")) {
|
|
if (is_file($pathFile)) {
|
|
$fileInfo = pathinfo($pathFile);
|
|
// dump("fileInfo", $fileInfo);
|
|
if (ends_with($fileInfo["filename"], "-1")) {
|
|
return;
|
|
}
|
|
if (is_file($fileInfo["dirname"] . '/' .$fileInfo["filename"] . '-1'. '.' . $fileInfo["extension"])) {
|
|
$mediaInfoContainer1 = $mediaInfo->getInfo($fileInfo["dirname"] . '/' .$fileInfo["filename"] . '-1'. '.' . $fileInfo["extension"]);
|
|
$millSecond1 = $mediaInfoContainer1->getGeneral()->get("duration")->getMilliseconds();
|
|
echo gettype($millSecond1) . "\n";
|
|
// ["duration"] . "\n";
|
|
$mediaInfoContainer = $mediaInfo->getInfo($pathFile);
|
|
$millSecond = $mediaInfoContainer->getGeneral()->get("duration")->getMilliseconds();
|
|
echo gettype($millSecond) . "\n";
|
|
if (abs(intval($millSecond) - intval($millSecond1)) > 100) {
|
|
echo $pathFile . "\n";
|
|
echo abs(intval($millSecond) - intval($millSecond1)) . "\n";
|
|
}
|
|
|
|
// unlink($pathFile);
|
|
return;
|
|
}
|
|
// $targetFile = $fileInfo["dirname"] . '/' .$fileInfo["filename"] . '-1'. '.' . $fileInfo["extension"];
|
|
// dump("targetFile", [$targetFile]);
|
|
//// $result = shell_exec("handBrakeCli -Z 'Very Fast 720p30' -i '". $subPathFile ."' -o '". $targetFile . " && echo 'success'");
|
|
// $result = shell_exec("handBrakeCli -Z 'Very Fast 720p30' -i '". $pathFile ."' -o '". $targetFile . "'");
|
|
// dump($result);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public function checkFileDimension($file) : bool
|
|
{
|
|
$mediaContainer = $this->mediainfo->getInfo($file);
|
|
foreach ($mediaContainer->getVideos() as $video) {
|
|
$height = $video->get('height')->getAbsoluteValue();
|
|
$width = $video->get('width')->getAbsoluteValue();
|
|
if ($height > $width && $width <= 720) {
|
|
echo "$file 分辨率小于 720p 跳过\n";
|
|
return false;
|
|
}
|
|
if ($height <= $width && $height <= 720) {
|
|
echo "$file 分辨率小于 720p 跳过\n";
|
|
return false;
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function checkFileSize($file, $size = 1): bool
|
|
{
|
|
|
|
if (is_file($file) && filesize($file) > 1 * 1024 * 1024) {
|
|
return true;
|
|
}
|
|
$fileSize = FileUtils::humanFilesize(filesize($file));
|
|
echo "$file size < 1Mb filesize is $fileSize skip \n";
|
|
return true;
|
|
}
|
|
|
|
public function checkFileEncodeType($file): bool
|
|
{
|
|
|
|
try {
|
|
$codecName = $this->ffprobe
|
|
->streams($file) // extracts streams informations
|
|
->videos() // filters video streams
|
|
->first() // returns the first video stream
|
|
->get('codec_name');
|
|
} catch (Throwable $e) {
|
|
echo "error $file \n";
|
|
Log::error("ffprobe has error just return false for test, exception: ". $e->getMessage());
|
|
return false;
|
|
}
|
|
return false;
|
|
// return trim($codecName) == "hevc";
|
|
}
|
|
|
|
public function getDimension($file): Dimension
|
|
{
|
|
$dimensions = new Dimension(1920, 1080);
|
|
|
|
try {
|
|
$dimensions = $this->ffprobe
|
|
->streams($file)
|
|
->videos()
|
|
->first()
|
|
->getDimensions();
|
|
} catch (Throwable $e) {
|
|
Log::error("ffprobe getDimension has error just return default dimension, exception: ". $e->getMessage(), ["file" => $file]);
|
|
return $dimensions;
|
|
}
|
|
return $dimensions;
|
|
}
|
|
|
|
/**
|
|
* @param bool $needRemoveAfterEncode
|
|
*/
|
|
public function setNeedRemoveAfterEncode(bool $needRemoveAfterEncode): void
|
|
{
|
|
$this->needRemoveAfterEncode = $needRemoveAfterEncode;
|
|
}
|
|
|
|
/**
|
|
* @param bool $needRemoveExistFiles
|
|
*/
|
|
public function setNeedRemoveExistFiles(bool $needRemoveExistFiles): void
|
|
{
|
|
$this->needRemoveExistFiles = $needRemoveExistFiles;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isNeedRemoveAfterEncode(): bool
|
|
{
|
|
return $this->needRemoveAfterEncode;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isNeedRemoveExistFiles(): bool
|
|
{
|
|
return $this->needRemoveExistFiles;
|
|
}
|
|
|
|
|
|
}
|
|
|