mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-30 04:08:59 +01:00
Refactor
This commit is contained in:
parent
3f31a3e22a
commit
5d30d1cc56
@ -1002,6 +1002,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.getContactSignUpNotification.html" name="account.getContactSignUpNotification">Whether the user will receive notifications when contacts sign up: account.getContactSignUpNotification</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#isAltervista" name="isAltervista">Whether this is altervista: isAltervista</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#canConvertOgg" name="canConvertOgg">Whether we can convert any audio/video file to a VoIP OGG OPUS file, or the files must be preconverted using @libtgvoipbot: canConvertOgg</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#canUseFFmpeg" name="canUseFFmpeg">Whether we can convert any audio/video file using ffmpeg: canUseFFmpeg</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#isIpc" name="isIpc">Whether we're an IPC client instance: isIpc</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#isIpcWorker" name="isIpcWorker">Whether we're an IPC server process (as opposed to an event handler): isIpcWorker</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#isTestMode" name="isTestMode">Whether we're currently connected to the test DCs: isTestMode</a>
|
||||
|
@ -35,6 +35,7 @@ use danog\MadelineProto\EventHandler\Plugin\RestartPlugin;
|
||||
use danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin;
|
||||
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
|
||||
use danog\MadelineProto\EventHandler\SimpleFilter\IsReply;
|
||||
use danog\MadelineProto\LocalFile;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\ParseMode;
|
||||
use danog\MadelineProto\RemoteUrl;
|
||||
@ -103,6 +104,9 @@ class MyEventHandler extends SimpleEventHandler
|
||||
$this->logger($this->getFullInfo('MadelineProto'));
|
||||
|
||||
$this->sendMessageToAdmins("The bot was started!");
|
||||
|
||||
$this->sendAudio(peer: 'danogentili', file: new LocalFile('/home/daniil/Music/01. Madge - 2WORLDS.flac'));
|
||||
$this->sendVideo(peer: 'danogentili', file: new LocalFile('/home/daniil/m.mp4'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1741,9 +1741,9 @@ abstract class InternalDoc
|
||||
* @param ?Cancellation $cancellation Cancellation.
|
||||
*
|
||||
*/
|
||||
final public function sendGif(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, bool $spoiler = false, ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message
|
||||
final public function sendGif(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, bool $spoiler = false, ?int $duration = null, ?int $width = null, ?int $height = null, string $thumbSeek = '00:00:01.000', ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message
|
||||
{
|
||||
return $this->wrapper->getAPI()->sendGif($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $ttl, $spoiler, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation);
|
||||
return $this->wrapper->getAPI()->sendGif($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $ttl, $spoiler, $duration, $width, $height, $thumbSeek, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation);
|
||||
}
|
||||
/**
|
||||
* Sends a message.
|
||||
@ -1873,9 +1873,9 @@ abstract class InternalDoc
|
||||
* @param Cancellation $cancellation Cancellation.
|
||||
*
|
||||
*/
|
||||
final public function sendVideo(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, string $mimeType = 'video/mp4', ?int $ttl = null, bool $spoiler = false, bool $roundMessage = false, bool $supportsStreaming = true, bool $noSound = false, ?int $duration = null, ?int $width = null, ?int $height = null, ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $forceResend = false, bool $updateStickersetsOrder = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message
|
||||
final public function sendVideo(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, string $mimeType = 'video/mp4', ?int $ttl = null, bool $spoiler = false, bool $roundMessage = false, bool $supportsStreaming = true, bool $noSound = false, ?int $duration = null, ?int $width = null, ?int $height = null, string $thumbSeek = '00:00:01.000', ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $forceResend = false, bool $updateStickersetsOrder = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message
|
||||
{
|
||||
return $this->wrapper->getAPI()->sendVideo($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $mimeType, $ttl, $spoiler, $roundMessage, $supportsStreaming, $noSound, $duration, $width, $height, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $updateStickersetsOrder, $cancellation);
|
||||
return $this->wrapper->getAPI()->sendVideo($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $mimeType, $ttl, $spoiler, $roundMessage, $supportsStreaming, $noSound, $duration, $width, $height, $thumbSeek, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $updateStickersetsOrder, $cancellation);
|
||||
}
|
||||
/**
|
||||
* Sends a voice.
|
||||
|
@ -20,12 +20,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use Amp\ByteStream\Pipe;
|
||||
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;
|
||||
@ -45,14 +43,14 @@ use danog\MadelineProto\LocalFile;
|
||||
use danog\MadelineProto\ParseMode;
|
||||
use danog\MadelineProto\RemoteUrl;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\StreamDuplicator;
|
||||
use danog\MadelineProto\TL\Types\Bytes;
|
||||
use danog\MadelineProto\Tools;
|
||||
use finfo;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -117,7 +115,7 @@ trait FilesAbstraction
|
||||
type: Document::class,
|
||||
mimeType: $mimeType,
|
||||
thumb: $thumb,
|
||||
attributes: [],
|
||||
attributesOrig: [],
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
caption: $caption,
|
||||
@ -190,7 +188,7 @@ trait FilesAbstraction
|
||||
type: Photo::class,
|
||||
mimeType: 'image/jpeg',
|
||||
thumb: null,
|
||||
attributes: [],
|
||||
attributesOrig: [],
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
caption: $caption,
|
||||
@ -261,7 +259,7 @@ trait FilesAbstraction
|
||||
type: Sticker::class,
|
||||
mimeType: $mimeType,
|
||||
thumb: null,
|
||||
attributes: [],
|
||||
attributesOrig: [],
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
caption: $emoji,
|
||||
@ -334,6 +332,7 @@ trait FilesAbstraction
|
||||
?int $duration = null,
|
||||
?int $width = null,
|
||||
?int $height = null,
|
||||
string $thumbSeek = '00:00:01.000',
|
||||
?int $replyToMsgId = null,
|
||||
?int $topMsgId = null,
|
||||
?array $replyMarkup = null,
|
||||
@ -351,13 +350,14 @@ trait FilesAbstraction
|
||||
type: Video::class,
|
||||
mimeType: $mimeType,
|
||||
thumb: $thumb,
|
||||
attributes: [
|
||||
attributesOrig: [
|
||||
'round_message' => $roundMessage,
|
||||
'supports_streaming' => $supportsStreaming,
|
||||
'no_sound' => $noSound,
|
||||
'duration' => $duration,
|
||||
'w' => $width,
|
||||
'h' => $height,
|
||||
'thumbSeek' => $thumbSeek,
|
||||
],
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
@ -418,6 +418,10 @@ trait FilesAbstraction
|
||||
?string $fileName = null,
|
||||
?int $ttl = null,
|
||||
bool $spoiler = false,
|
||||
?int $duration = null,
|
||||
?int $width = null,
|
||||
?int $height = null,
|
||||
string $thumbSeek = '00:00:01.000',
|
||||
?int $replyToMsgId = null,
|
||||
?int $topMsgId = null,
|
||||
?array $replyMarkup = null,
|
||||
@ -434,7 +438,15 @@ trait FilesAbstraction
|
||||
type: Gif::class,
|
||||
mimeType: 'video/mp4',
|
||||
thumb: $thumb,
|
||||
attributes: [],
|
||||
attributesOrig: [
|
||||
'round_message' => false,
|
||||
'supports_streaming' => true,
|
||||
'no_sound' => true,
|
||||
'duration' => $duration,
|
||||
'w' => $width,
|
||||
'h' => $height,
|
||||
'thumbSeek' => $thumbSeek,
|
||||
],
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
caption: $caption,
|
||||
@ -514,7 +526,7 @@ trait FilesAbstraction
|
||||
type: Audio::class,
|
||||
mimeType: $mimeType,
|
||||
thumb: $thumb,
|
||||
attributes: [
|
||||
attributesOrig: [
|
||||
'duration' => $duration,
|
||||
'title' => $title,
|
||||
'performer' => $performer,
|
||||
@ -599,7 +611,7 @@ trait FilesAbstraction
|
||||
type: Voice::class,
|
||||
mimeType: 'audio/ogg',
|
||||
thumb: null,
|
||||
attributes: $attributes,
|
||||
attributesOrig: $attributes,
|
||||
peer: $peer,
|
||||
file: $file,
|
||||
caption: $caption,
|
||||
@ -634,7 +646,7 @@ trait FilesAbstraction
|
||||
?string $mimeType,
|
||||
Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file,
|
||||
Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb,
|
||||
array $attributes,
|
||||
array $attributesOrig,
|
||||
string $caption,
|
||||
ParseMode $parseMode,
|
||||
?callable $callback,
|
||||
@ -703,22 +715,22 @@ trait FilesAbstraction
|
||||
'_' => 'documentAttributeVideo',
|
||||
'round_message' => $file instanceof RoundVideo
|
||||
? true
|
||||
: $attributes['round_message'],
|
||||
: $attributesOrig['round_message'],
|
||||
'supports_streaming' => $file instanceof AbstractVideo
|
||||
? $file->supportsStreaming
|
||||
: $attributes['supports_streaming'],
|
||||
: $attributesOrig['supports_streaming'],
|
||||
'no_sound' => $file instanceof Gif
|
||||
? true
|
||||
: $attributes['no_sound'],
|
||||
: $attributesOrig['no_sound'],
|
||||
'duration' => $file instanceof AbstractVideo
|
||||
? $file->duration
|
||||
: $attributes['duration'],
|
||||
: $attributesOrig['duration'],
|
||||
'w' => $file instanceof AbstractVideo
|
||||
? $file->width
|
||||
: $attributes['w'],
|
||||
: $attributesOrig['w'],
|
||||
'h' => $file instanceof AbstractVideo
|
||||
? $file->height
|
||||
: $attributes['h'],
|
||||
: $attributesOrig['h'],
|
||||
],
|
||||
],
|
||||
Audio::class => [
|
||||
@ -726,13 +738,13 @@ trait FilesAbstraction
|
||||
'_' => 'documentAttributeAudio',
|
||||
'duration' => $file instanceof Audio
|
||||
? $file->duration
|
||||
: $attributes['duration'],
|
||||
: $attributesOrig['duration'],
|
||||
'title' => $file instanceof Audio
|
||||
? $file->title
|
||||
: $attributes['title'],
|
||||
: $attributesOrig['title'],
|
||||
'performer' => $file instanceof Audio
|
||||
? $file->performer
|
||||
: $attributes['performer'],
|
||||
: $attributesOrig['performer'],
|
||||
],
|
||||
],
|
||||
Voice::class => [
|
||||
@ -741,10 +753,10 @@ trait FilesAbstraction
|
||||
'voice' => true,
|
||||
'duration' => $file instanceof Voice
|
||||
? $file->duration
|
||||
: $attributes['duration'],
|
||||
: $attributesOrig['duration'],
|
||||
'waveform' => $file instanceof Voice
|
||||
? $file->waveform
|
||||
: $attributes['waveform'],
|
||||
: $attributesOrig['waveform'],
|
||||
],
|
||||
],
|
||||
default => [],
|
||||
@ -875,79 +887,12 @@ trait FilesAbstraction
|
||||
if ($reuseId) {
|
||||
// Reuse
|
||||
} 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) {
|
||||
$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, 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];
|
||||
$seconds = (int) $time[2];
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
$width = $matches[2];
|
||||
$height = $matches[3];
|
||||
$attributes[0]['w'] ??= $width;
|
||||
$attributes[0]['h'] ??= $height;
|
||||
$attributes[0]['duration'] ??= $duration;
|
||||
}
|
||||
}
|
||||
$this->extractVideoInfo($attributesOrig['thumbSeek'], $file, $fileName, $callback, $cancellation, $mimeType, $attributes, $thumb);
|
||||
} 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 || $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];
|
||||
$seconds = (int) $time[2];
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
$attributes[0]['duration'] = $duration;
|
||||
}
|
||||
$attributes[0]['title'] ??= $result['title'] ?? null;
|
||||
$attributes[0]['performer'] ??= $result['artist'] ?? null;
|
||||
}
|
||||
$this->extractAudioInfo($file, $fileName, $callback, $cancellation, $mimeType, $attributes, $thumb);
|
||||
|
||||
} elseif ($mimeType === null) {
|
||||
$mimeType = $this->extractMime($file, $fileName, $callback, $cancellation);
|
||||
}
|
||||
|
||||
$method = 'messages.sendMedia';
|
||||
@ -1014,7 +959,7 @@ trait FilesAbstraction
|
||||
default => 'inputMediaDocument',
|
||||
};
|
||||
$media['id'] = $reuseId;
|
||||
} else {
|
||||
} elseif (!\is_array($media['file'])) {
|
||||
$media['file'] = $this->upload($media['file'], $fileName ?? '', $callback, cancellation: $cancellation);
|
||||
}
|
||||
|
||||
@ -1044,4 +989,153 @@ trait FilesAbstraction
|
||||
\assert($res !== null);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list{array, string}
|
||||
*/
|
||||
private function extractMime(Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation): array
|
||||
{
|
||||
$file = $this->getStream($file, $cancellation);
|
||||
$p = new Pipe(1024*1024);
|
||||
$fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, $p->getSink()), $fileName ?? '', $callback, cancellation: $cancellation));
|
||||
|
||||
$buff = '';
|
||||
while (\strlen($buff) < 1024*1024 && null !== $chunk = $p->getSource()->read($cancellation)) {
|
||||
$buff .= $chunk;
|
||||
}
|
||||
$p->getSink()->close();
|
||||
$p->getSource()->close();
|
||||
unset($p);
|
||||
|
||||
return [$fileFuture->await(), (new finfo())->buffer($buff, FILEINFO_MIME_TYPE)];
|
||||
}
|
||||
private function extractAudioInfo(Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream &$file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation, ?string &$mimeType, array &$attributes, mixed &$thumb): void
|
||||
{
|
||||
if (!Tools::canUseFFmpeg($cancellation)) {
|
||||
$this->logger->logger('Install ffmpeg for audio info extraction!');
|
||||
if ($mimeType === null) {
|
||||
[$file, $mimeType] = $this->extractMime($file, $fileName, $callback, $cancellation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!(
|
||||
$attributes[0]['duration'] === null
|
||||
|| $attributes[0]['title'] === null
|
||||
|| $attributes[0]['performer'] === null
|
||||
|| $thumb === null
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $this->getStream($file, $cancellation);
|
||||
$process = Process::start('ffmpeg -i pipe: -f image2pipe -', cancellation: $cancellation);
|
||||
$stdin = $process->getStdin();
|
||||
$stdout = $process->getStdout();
|
||||
$p = new Pipe(1024*1024);
|
||||
$fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, $stdin, $p->getSink()), $fileName ?? '', $callback, cancellation: $cancellation));
|
||||
|
||||
$f = [
|
||||
async(buffer(...), $process->getStdout(), $cancellation),
|
||||
async(buffer(...), $process->getStderr(), $cancellation),
|
||||
];
|
||||
if ($mimeType === null) {
|
||||
$f []= async(static function () use ($p, $cancellation, &$mimeType): void {
|
||||
$buff = '';
|
||||
while (\strlen($buff) < 1024*1024 && null !== $chunk = $p->getSource()->read($cancellation)) {
|
||||
$buff .= $chunk;
|
||||
}
|
||||
$p->getSink()->close();
|
||||
$p->getSource()->close();
|
||||
unset($p);
|
||||
|
||||
$mimeType ??= (new finfo())->buffer($buff, FILEINFO_MIME_TYPE);
|
||||
});
|
||||
}
|
||||
[$stdout, $stderr] = await($f);
|
||||
|
||||
$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];
|
||||
$seconds = (int) $time[2];
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
$attributes[0]['duration'] = $duration;
|
||||
}
|
||||
if (preg_match('/TITLE\s*:\s*(.+)/', $stderr, $matches)) {
|
||||
$attributes[0]['title'] ??= $matches[1];
|
||||
}
|
||||
if (preg_match('/ARTIST\s*:\s*(.+)/', $stderr, $matches)) {
|
||||
$attributes[0]['performer'] ??= $matches[1];
|
||||
}
|
||||
if ($stdout !== '') {
|
||||
// Todo check if jpg, but should be jpg in most cases anyway
|
||||
$thumb ??= new ReadableBuffer($stdout);
|
||||
}
|
||||
|
||||
$file = $fileFuture->await();
|
||||
}
|
||||
|
||||
private function extractVideoInfo(string $thumbSeek, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream &$file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation, ?string &$mimeType, array &$attributes, mixed &$thumb): void
|
||||
{
|
||||
if (!Tools::canUseFFmpeg($cancellation)) {
|
||||
$this->logger->logger('Install ffmpeg for video info extraction!');
|
||||
if ($mimeType === null) {
|
||||
[$file, $mimeType] = $this->extractMime($file, $fileName, $callback, $cancellation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!(
|
||||
$thumb === null
|
||||
|| $attributes[0]['duration'] === null
|
||||
|| $attributes[0]['w'] === null
|
||||
|| $attributes[0]['h'] === null
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $this->getStream($file, $cancellation);
|
||||
$ffmpeg = 'ffmpeg -i pipe: -ss '.$thumbSeek.' -frames:v 1 -f image2pipe -';
|
||||
$process = Process::start($ffmpeg, cancellation: $cancellation);
|
||||
$stdin = $process->getStdin();
|
||||
$p = new Pipe(1024*1024);
|
||||
$fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, $stdin, $p->getSink()), $fileName ?? '', $callback, cancellation: $cancellation));
|
||||
|
||||
$f = [
|
||||
async(buffer(...), $process->getStdout(), $cancellation),
|
||||
async(buffer(...), $process->getStderr(), $cancellation),
|
||||
];
|
||||
if ($mimeType === null) {
|
||||
$f []= async(static function () use ($p, $cancellation, &$mimeType): void {
|
||||
$buff = '';
|
||||
while (\strlen($buff) < 1024*1024 && null !== $chunk = $p->getSource()->read($cancellation)) {
|
||||
$buff .= $chunk;
|
||||
}
|
||||
$p->getSink()->close();
|
||||
$p->getSource()->close();
|
||||
unset($p);
|
||||
|
||||
$mimeType ??= (new finfo())->buffer($buff, FILEINFO_MIME_TYPE);
|
||||
});
|
||||
}
|
||||
[$stdout, $stderr] = await($f);
|
||||
|
||||
$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];
|
||||
$seconds = (int) $time[2];
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
$width = $matches[2];
|
||||
$height = $matches[3];
|
||||
$attributes[0]['w'] ??= $width;
|
||||
$attributes[0]['h'] ??= $height;
|
||||
$attributes[0]['duration'] ??= $duration;
|
||||
}
|
||||
|
||||
$file = $fileFuture->await();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user