Skip to content

Commit 4ab0b2a

Browse files
authored
feat: extend Audio content for data urls and raw data (#250)
1 parent 953b914 commit 4ab0b2a

File tree

5 files changed

+94
-33
lines changed

5 files changed

+94
-33
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ use PhpLlm\LlmChain\Model\Message\MessageBag;
521521
$messages = new MessageBag(
522522
Message::ofUser(
523523
'What is this recording about?',
524-
new Audio(dirname(__DIR__).'/tests/Fixture/audio.mp3'), // Path to an audio file
524+
Audio:fromFile(dirname(__DIR__).'/tests/Fixture/audio.mp3'), // Path to an audio file
525525
),
526526
);
527527
$response = $chain->call($messages);

examples/audio-describer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
$messages = new MessageBag(
2424
Message::ofUser(
2525
'What is this recording about?',
26-
new Audio(dirname(__DIR__).'/tests/Fixture/audio.mp3'),
26+
Audio::fromFile(dirname(__DIR__).'/tests/Fixture/audio.mp3'),
2727
),
2828
);
2929
$response = $chain->call($messages);

src/Model/Message/Content/Audio.php

+29-8
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,50 @@
66

77
use PhpLlm\LlmChain\Exception\InvalidArgumentException;
88

9+
use function Symfony\Component\String\u;
10+
911
final readonly class Audio implements Content
1012
{
1113
public function __construct(
12-
public string $path,
14+
public string $data,
15+
public string $format,
1316
) {
14-
if (!is_readable($path) || false === file_get_contents($path)) {
15-
throw new InvalidArgumentException(sprintf('The file "%s" does not exist or is not readable.', $path));
17+
}
18+
19+
public static function fromDataUrl(string $dataUrl): self
20+
{
21+
if (!str_starts_with($dataUrl, 'data:audio/')) {
22+
throw new InvalidArgumentException('Invalid audio data URL format.');
1623
}
24+
25+
return new self(
26+
u($dataUrl)->after('base64,')->toString(),
27+
u($dataUrl)->after('data:audio/')->before(';base64,')->toString(),
28+
);
29+
}
30+
31+
public static function fromFile(string $filePath): self
32+
{
33+
if (!is_readable($filePath) || false === $audioData = file_get_contents($filePath)) {
34+
throw new InvalidArgumentException(sprintf('The file "%s" does not exist or is not readable.', $filePath));
35+
}
36+
37+
return new self(
38+
base64_encode($audioData),
39+
pathinfo($filePath, PATHINFO_EXTENSION)
40+
);
1741
}
1842

1943
/**
2044
* @return array{type: 'input_audio', input_audio: array{data: string, format: string}}
2145
*/
2246
public function jsonSerialize(): array
2347
{
24-
$data = file_get_contents($this->path);
25-
$format = pathinfo($this->path, PATHINFO_EXTENSION);
26-
2748
return [
2849
'type' => 'input_audio',
2950
'input_audio' => [
30-
'data' => base64_encode($data),
31-
'format' => $format,
51+
'data' => $this->data,
52+
'format' => $this->format,
3253
],
3354
];
3455
}

tests/Model/Message/Content/AudioTest.php

+62-22
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,85 @@
1616
final class AudioTest extends TestCase
1717
{
1818
#[Test]
19-
public function constructWithValidPath(): void
19+
public function constructWithValidData(): void
2020
{
21-
$audio = new Audio(dirname(__DIR__, 3).'/Fixture/audio.mp3');
21+
$audio = new Audio('base64data', 'mp3');
2222

23-
self::assertSame(dirname(__DIR__, 3).'/Fixture/audio.mp3', $audio->path);
23+
self::assertSame('base64data', $audio->data);
24+
self::assertSame('mp3', $audio->format);
2425
}
2526

2627
#[Test]
27-
#[DataProvider('provideValidPaths')]
28-
public function jsonSerializeWithValid(string $path, array $expected): void
28+
public function fromDataUrlWithValidUrl(): void
2929
{
30-
$audio = new Audio($path);
30+
$dataUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAfVREUkMAAAAMAAADMj';
31+
$audio = Audio::fromDataUrl($dataUrl);
3132

32-
$expected = [
33-
'type' => 'input_audio',
34-
'input_audio' => $expected,
35-
];
36-
37-
$actual = $audio->jsonSerialize();
33+
self::assertSame('SUQzBAAAAAAAfVREUkMAAAAMAAADMj', $audio->data);
34+
self::assertSame('mp3', $audio->format);
35+
}
3836

39-
// shortening the base64 data
40-
$actual['input_audio']['data'] = substr($actual['input_audio']['data'], 0, 30);
37+
#[Test]
38+
public function fromDataUrlWithInvalidUrl(): void
39+
{
40+
$this->expectException(\InvalidArgumentException::class);
41+
$this->expectExceptionMessage('Invalid audio data URL format.');
4142

42-
self::assertSame($expected, $actual);
43+
Audio::fromDataUrl('invalid-url');
4344
}
4445

45-
public static function provideValidPaths(): \Generator
46+
#[Test]
47+
public function fromFileWithValidPath(): void
4648
{
47-
yield 'mp3' => [dirname(__DIR__, 3).'/Fixture/audio.mp3', [
48-
'data' => 'SUQzBAAAAAAAfVREUkMAAAAMAAADMj', // shortened
49-
'format' => 'mp3',
50-
]];
49+
$audio = Audio::fromFile(dirname(__DIR__, 3).'/Fixture/audio.mp3');
50+
51+
self::assertSame('mp3', $audio->format);
52+
self::assertNotEmpty($audio->data);
5153
}
5254

5355
#[Test]
54-
public function constructWithInvalidPath(): void
56+
public function fromFileWithInvalidPath(): void
5557
{
58+
$this->expectException(\InvalidArgumentException::class);
5659
$this->expectExceptionMessage('The file "foo.mp3" does not exist or is not readable.');
5760

58-
new Audio('foo.mp3');
61+
Audio::fromFile('foo.mp3');
62+
}
63+
64+
#[Test]
65+
#[DataProvider('provideAudioData')]
66+
public function jsonSerializeReturnsCorrectFormat(string $data, string $format, array $expected): void
67+
{
68+
$audio = new Audio($data, $format);
69+
$actual = $audio->jsonSerialize();
70+
71+
self::assertSame($expected, $actual);
72+
}
73+
74+
public static function provideAudioData(): \Generator
75+
{
76+
yield 'mp3 data' => [
77+
'SUQzBAAAAAAAfVREUkMAAAAMAAADMj',
78+
'mp3',
79+
[
80+
'type' => 'input_audio',
81+
'input_audio' => [
82+
'data' => 'SUQzBAAAAAAAfVREUkMAAAAMAAADMj',
83+
'format' => 'mp3',
84+
],
85+
],
86+
];
87+
88+
yield 'wav data' => [
89+
'UklGRiQAAABXQVZFZm10IBA=',
90+
'wav',
91+
[
92+
'type' => 'input_audio',
93+
'input_audio' => [
94+
'data' => 'UklGRiQAAABXQVZFZm10IBA=',
95+
'format' => 'wav',
96+
],
97+
],
98+
];
5999
}
60100
}

tests/Model/Message/UserMessageTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function hasAudioContentWithoutAudio(): void
5252
#[Test]
5353
public function hasAudioContentWithAudio(): void
5454
{
55-
$message = new UserMessage(new Text('foo'), new Audio(dirname(__DIR__, 2).'/Fixture/audio.mp3'));
55+
$message = new UserMessage(new Text('foo'), Audio::fromFile(dirname(__DIR__, 2).'/Fixture/audio.mp3'));
5656

5757
self::assertTrue($message->hasAudioContent());
5858
}

0 commit comments

Comments
 (0)