1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-26 19:24:42 +01:00

Fixes to conversion

This commit is contained in:
Daniil Gentili 2024-04-18 23:20:23 +02:00
parent 9bd1113b5b
commit 3f31a3e22a
4 changed files with 87 additions and 48 deletions

2
docs

@ -1 +1 @@
Subproject commit 821000468d14f943496b8bbc7183360c83630d6b
Subproject commit 741e0ceadb21214cd44991fce2dd2a5f16b2881d

View File

@ -354,6 +354,13 @@ abstract class InternalDoc
{
return \danog\MadelineProto\Tools::canConvertOgg();
}
/**
* Whether we can convert any audio/video file using ffmpeg.
*/
final public static function canUseFFmpeg(?\Amp\Cancellation $cancellation = null): bool
{
return \danog\MadelineProto\Tools::canUseFFmpeg($cancellation);
}
/**
* Cancel a running broadcast.
*

View File

@ -23,6 +23,9 @@ namespace danog\MadelineProto\MTProtoTools;
use Amp\ByteStream\ReadableBuffer;
use Amp\ByteStream\ReadableStream;
use Amp\Cancellation;
use Amp\CompositeCancellation;
use Amp\DeferredCancellation;
use Amp\NullCancellation;
use Amp\Process\Process;
use AssertionError;
use danog\MadelineProto\BotApiFileId;
@ -43,11 +46,14 @@ use danog\MadelineProto\ParseMode;
use danog\MadelineProto\RemoteUrl;
use danog\MadelineProto\Settings;
use danog\MadelineProto\TL\Types\Bytes;
use danog\MadelineProto\Tools;
use Webmozart\Assert\Assert;
use function Amp\async;
use function Amp\ByteStream\buffer;
use function Amp\ByteStream\pipe;
use function Amp\ByteStream\splitLines;
use function Amp\Future\await;
/**
* Manages upload and download of files.
@ -692,7 +698,7 @@ trait FilesAbstraction
}
$attributes = match ($type) {
Video::class => [
Video::class, Gif::class => [
[
'_' => 'documentAttributeVideo',
'round_message' => $file instanceof RoundVideo
@ -741,9 +747,11 @@ trait FilesAbstraction
: $attributes['waveform'],
],
],
Gif::class => [['_' => 'documentAttributeAnimated']],
default => [],
};
if ($type === Gif::class) {
$attributes []= ['_' => 'documentAttributeAnimated'];
}
$attributes[] = ['_' => 'documentAttributeFilename', 'file_name' => $fileName];
if (DialogId::isSecretChat($peer)) {
@ -866,17 +874,27 @@ trait FilesAbstraction
if ($reuseId) {
// Reuse
} elseif ($type === Video::class) {
if (Process::start('ffmpeg -version')->join() !== 0) {
} elseif ($type === Video::class || $type === Gif::class) {
if (!Tools::canUseFFmpeg($cancellation)) {
$this->logger->logger('Install ffmpeg for video info extraction!');
} elseif ($thumb === null || $attributes[0]['duration'] === null || $attributes[0]['w'] === null || $attributes[0]['h'] === null) {
$file = $this->getStream($file, $cancellation);
$dl = new DeferredCancellation;
$copy = $this->getStream($file, new CompositeCancellation($dl->getCancellation(), $cancellation ?? new NullCancellation));
$ffmpeg = 'ffmpeg -i pipe: -ss 00:00:01.000 -frames:v 1 -f image2pipe -vcodec mjpeg pipe:1';
$process = Process::start($ffmpeg);
async(static fn () => pipe($file, $process->getStdin()))->finally(static fn () => $process->getStdin()->close());
$thumb ??= new ReadableBuffer(buffer($process->getStdout()));
$output = buffer($process->getStderr());
if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2}),.*? (\d{3,4})x(\d{3,4})~s', $output, $matches)) {
$process = Process::start($ffmpeg, cancellation: $cancellation);
$stdin = $process->getStdin();
async(pipe(...), $copy, $stdin, $cancellation)->finally(static function () use ($stdin, $dl): void {
$stdin->close();
$dl->cancel();
})->ignore();
[$stdout, $stderr] = await([
async(buffer(...), $process->getStdout(), $cancellation),
async(buffer(...), $process->getStderr(), $cancellation),
]);
$thumb ??= new ReadableBuffer($stdout);
$process->join($cancellation);
if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2}),.*? (\d{3,4})x(\d{3,4})~s', $stderr, $matches)) {
$time = explode(':', $matches[1]);
$hours = (int) $time[0];
$minutes = (int) $time[1];
@ -889,44 +907,37 @@ trait FilesAbstraction
$attributes[0]['duration'] ??= $duration;
}
}
} elseif ($type === Sticker::class) {
if (!\extension_loaded('gd')) {
throw Exception::extension('gd');
}
$file = buffer($this->getStream($file, $cancellation), $cancellation);
$img = imagecreatefromstring($file);
$width = imagesx($img);
$height = imagesy($img);
if ($width > $height) {
$newWidth = 512;
$newHeight = (int) (512 * $height / $width);
} elseif ($width < $height) {
$newWidth = (int) (512 * $width / $height);
$newHeight = 512;
} else {
$newWidth = 512;
$newHeight = 512;
}
$temp = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresized($temp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
$stream = fopen('php://memory', 'r+');
imagewebp($temp, $stream);
rewind($stream);
$file = stream_get_contents($stream);
fclose($stream);
unset($stream, $temp);
$file = new ReadableBuffer($file);
} elseif ($type === Audio::class or $type === Voice::class) {
if (Process::start('ffmpeg -version')->join() !== 0) {
} elseif ($type === Audio::class || $type === Voice::class) {
if (!Tools::canUseFFmpeg($cancellation)) {
$this->logger->logger('Install ffmpeg for audio info extraction!');
} elseif ($attributes[0]['duration'] === null) {
$file = $this->getStream($file, $cancellation);
$ffmpeg = 'ffmpeg -i pipe: 2>&1';
$process = Process::start($ffmpeg);
async(static fn () => pipe($file, $process->getStdin()));
$output = buffer($process->getStdout());
if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2})~', $output, $matches)) {
} elseif ($attributes[0]['duration'] === null || $attributes[0]['title'] === null || $attributes[0]['performer'] === null) {
$dl = new DeferredCancellation;
$copy = $this->getStream($file, new CompositeCancellation($dl->getCancellation(), $cancellation ?? new NullCancellation));
// Todo: cover
$ffmpeg = 'ffmpeg -i pipe: -f ffmetadata -';
$process = Process::start($ffmpeg, cancellation: $cancellation);
$stdin = $process->getStdin();
$stdout = $process->getStdout();
async(pipe(...), $copy, $stdin, $cancellation)->finally(static function () use ($stdin, $dl): void {
$stdin->close();
$dl->cancel();
})->ignore();
[$result, $stderr] = await([
async(static function () use ($stdout, $cancellation): array {
$result = [];
foreach (splitLines($stdout, $cancellation) as $line) {
if (!str_contains($line, '=')) {
continue;
}
[$k, $v] = explode("=", $line, 2);
$result[strtolower($k)] = $v;
}
return $result;
}),
async(buffer(...), $process->getStderr(), $cancellation),
]);
$process->join($cancellation);
if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2})~', $stderr, $matches)) {
$time = explode(':', $matches[1]);
$hours = (int) $time[0];
$minutes = (int) $time[1];
@ -934,6 +945,8 @@ trait FilesAbstraction
$duration = $hours * 3600 + $minutes * 60 + $seconds;
$attributes[0]['duration'] = $duration;
}
$attributes[0]['title'] ??= $result['title'] ?? null;
$attributes[0]['performer'] ??= $result['artist'] ?? null;
}
}

View File

@ -29,6 +29,7 @@ use Amp\File\File;
use Amp\Http\Client\HttpClient;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use Amp\Process\Process;
use ArrayAccess;
use Closure;
use Countable;
@ -908,4 +909,22 @@ abstract class Tools extends AsyncTools
}
return self::$canConvert;
}
private static ?bool $canFFmpeg = null;
/**
* Whether we can convert any audio/video file using ffmpeg.
*/
public static function canUseFFmpeg(?Cancellation $cancellation = null): bool
{
if (self::$canFFmpeg !== null) {
return self::$canFFmpeg;
}
try {
self::$canFFmpeg = Process::start('ffmpeg -version', cancellation: $cancellation)->join($cancellation) === 0;
} catch (\Throwable $e) {
Logger::log("An error occurred while attempting conversion: $e");
self::$canFFmpeg = false;
}
return self::$canFFmpeg;
}
}