Skip to content

Commit b1bb121

Browse files
committed
Support streaming image generation
1 parent 04fc741 commit b1bb121

5 files changed

Lines changed: 906 additions & 132 deletions

File tree

Private/ObjectParser.ps1

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function ParseThreadRunObject {
4343

4444
# Add custom type name and properties to output object.
4545
$InputObject.PSObject.TypeNames.Insert(0, 'PSOpenAI.Thread.Run')
46-
('created_at', 'expires_at', 'started_at', 'cancelled_at', 'failed_at', 'completed_at') | ForEach-Object {
46+
('created_at', 'expires_at', 'started_at', 'cancelled_at', 'failed_at', 'completed_at') | ForEach-Object {
4747
if ($null -ne $InputObject.$_ -and ($unixtime = $InputObject.$_ -as [long])) {
4848
# convert unixtime to [DateTime] for read suitable
4949
$InputObject | Add-Member -MemberType NoteProperty -Name $_ -Value ([System.DateTimeOffset]::FromUnixTimeSeconds($unixtime).LocalDateTime) -Force
@@ -137,7 +137,7 @@ function ParseThreadRunStepObject {
137137

138138
# Add custom type name and properties to output object.
139139
$InputObject.PSObject.TypeNames.Insert(0, 'PSOpenAI.Thread.Run.Step')
140-
('created_at', 'expired_at', 'cancelled_at', 'failed_at', 'completed_at') | ForEach-Object {
140+
('created_at', 'expired_at', 'cancelled_at', 'failed_at', 'completed_at') | ForEach-Object {
141141
if ($null -ne $InputObject.$_ -and ($unixtime = $InputObject.$_ -as [long])) {
142142
# convert unixtime to [DateTime] for read suitable
143143
$InputObject | Add-Member -MemberType NoteProperty -Name $_ -Value ([System.DateTimeOffset]::FromUnixTimeSeconds($unixtime).LocalDateTime) -Force
@@ -503,3 +503,21 @@ function ParseContainerFileObject {
503503
}
504504
Write-Output $InputObject
505505
}
506+
507+
function ParseImageGenerationObject {
508+
[CmdletBinding()]
509+
param (
510+
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
511+
[PSCustomObject]$InputObject
512+
)
513+
514+
# Add custom type name and properties to output object.
515+
$InputObject.PSObject.TypeNames.Insert(0, 'PSOpenAI.Image')
516+
('created', 'created_at') | ForEach-Object {
517+
if ($null -ne $InputObject.$_ -and ($unixtime = $InputObject.$_ -as [long])) {
518+
# convert unixtime to [DateTime] for read suitable
519+
$InputObject | Add-Member -MemberType NoteProperty -Name $_ -Value ([System.DateTimeOffset]::FromUnixTimeSeconds($unixtime).LocalDateTime) -Force
520+
}
521+
}
522+
Write-Output $InputObject
523+
}

Public/Images/Request-ImageEdit.ps1

Lines changed: 195 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,28 @@ function Request-ImageEdit {
3434
[ValidateSet('standard', 'low', 'medium', 'high', 'auto')]
3535
[string][LowerCaseTransformation()]$Quality = 'auto',
3636

37+
[Parameter()]
38+
[ValidateSet('transparent', 'opaque', 'auto')]
39+
[string][LowerCaseTransformation()]$Background = 'auto',
40+
41+
[Parameter()]
42+
[Alias('input_fidelity')]
43+
[ValidateSet('low', 'high')]
44+
[string][LowerCaseTransformation()]$InputFidelity = 'low',
45+
46+
[Parameter()]
47+
[Alias('output_compression')]
48+
[ValidateRange(0, 100)]
49+
[uint16]$OutputCompression = 100,
50+
51+
[Parameter()]
52+
[Alias('output_format')]
53+
[ValidateSet('png', 'jpeg', 'webp')]
54+
[string][LowerCaseTransformation()]$OutputFormat = 'png',
55+
3756
[Parameter(ParameterSetName = 'Format')]
3857
[Alias('response_format')]
39-
[ValidateSet('url', 'base64', 'byte')]
58+
[ValidateSet('url', 'base64', 'byte', 'object')]
4059
[string]$ResponseFormat = 'url',
4160

4261
[Parameter(ParameterSetName = 'Format')]
@@ -46,6 +65,16 @@ function Request-ImageEdit {
4665
[ValidateNotNullOrEmpty()]
4766
[string]$OutFile,
4867

68+
#region Stream
69+
[Parameter()]
70+
[switch]$Stream = $false,
71+
72+
[Parameter()]
73+
[Alias('partial_images')]
74+
[ValidateRange(0, 3)]
75+
[uint16]$PartialImages = 0,
76+
#endregion Stream
77+
4978
[Parameter()]
5079
[string]$User,
5180

@@ -142,17 +171,37 @@ function Request-ImageEdit {
142171
if ($PSBoundParameters.ContainsKey('Quality')) {
143172
$PostBody.quality = $Quality
144173
}
174+
if ($PSBoundParameters.ContainsKey('InputFidelity')) {
175+
$PostBody.input_fidelity = $InputFidelity
176+
}
177+
if ($PSBoundParameters.ContainsKey('Background')) {
178+
$PostBody.background = $Background
179+
}
180+
if ($PSBoundParameters.ContainsKey('OutputCompression')) {
181+
$PostBody.output_compression = $OutputCompression
182+
}
145183
if ($PSBoundParameters.ContainsKey('User')) {
146184
$PostBody.user = $User
147185
}
186+
if ($PartialImages -gt 0) {
187+
$Stream = $true
188+
$PostBody.partial_images = $PartialImages
189+
}
190+
if ($Stream) {
191+
$PostBody.stream = [bool]$Stream
192+
}
148193

149-
switch ($ResponseFormat) {
150-
{ $Model -like 'gpt-image-*' } {
151-
# GPT-Image model does not support response_format parameter
152-
break
194+
# GPT-Image model does not support response_format parameter
195+
if ($Model -like 'gpt-image-*') {
196+
if ($PSBoundParameters.ContainsKey('ResponseFormat') -and $ResponseFormat -eq 'url') {
197+
Write-Warning 'Your specified model does not support response_format=url. Defaulting to object.'
198+
$ResponseFormat = 'object'
153199
}
200+
}
201+
202+
switch ($ResponseFormat) {
154203
{ $PSCmdlet.ParameterSetName -eq 'OutFile' } {
155-
$PostBody.response_format = 'b64_json'
204+
$ResponseFormat = 'base64'
156205
break
157206
}
158207
'url' {
@@ -168,9 +217,30 @@ function Request-ImageEdit {
168217
break
169218
}
170219
}
220+
221+
if ($Model -like 'gpt-image-*') {
222+
# The output_format parameter is only supported for gpt-image-1.
223+
if ($PSBoundParameters.ContainsKey('OutputFormat')) {
224+
$PostBody.output_format = $OutputFormat
225+
}
226+
elseif ($PSCmdlet.ParameterSetName -eq 'OutFile') {
227+
$ext = [System.IO.Path]::GetExtension($OutFile).ToLower()
228+
if ($ext -eq '.png') {
229+
$PostBody.output_format = 'png'
230+
}
231+
elseif ($ext -eq '.jpeg' -or $ext -eq '.jpg') {
232+
$PostBody.output_format = 'jpeg'
233+
}
234+
elseif ($ext -eq '.webp') {
235+
$PostBody.output_format = 'webp'
236+
}
237+
else {
238+
$PostBody.output_format = 'png'
239+
}
240+
}
241+
}
171242
#endregion
172243

173-
#region Send API Request
174244
$splat = @{
175245
Method = $OpenAIParameter.Method
176246
Uri = $OpenAIParameter.Uri
@@ -184,80 +254,140 @@ function Request-ImageEdit {
184254
AdditionalHeaders = $AdditionalHeaders
185255
AdditionalBody = $AdditionalBody
186256
}
187-
$Response = Invoke-OpenAIAPIRequest @splat
188257

189-
# error check
190-
if ($null -eq $Response) {
258+
#region Send API Request (Stream)
259+
if ($Stream) {
260+
# Stream output
261+
Invoke-OpenAIAPIRequestSSE @splat |
262+
Where-Object {
263+
-not [string]::IsNullOrEmpty($_)
264+
} | ForEach-Object -Process {
265+
if ($OutputRawResponse) {
266+
Write-Output $_
267+
}
268+
else {
269+
# Parse response object
270+
try {
271+
$Response = $_ | ConvertFrom-Json -ErrorAction Stop
272+
}
273+
catch {
274+
Write-Error -Exception $_.Exception
275+
}
276+
277+
if ($Response.type -in ('image_edit.partial_image', 'image_edit.completed') -and $Response.b64_json) {
278+
279+
if ($null -ne $Response.partial_image_index) {
280+
$pidx = $Response.partial_image_index
281+
Write-Verbose ('Partial image generated. Index:{0}' -f $pidx)
282+
}
283+
else {
284+
$pidx = $null
285+
Write-Verbose 'Final image generated.'
286+
}
287+
288+
#region Output
289+
if ($PSCmdlet.ParameterSetName -eq 'OutFile') {
290+
# Save image
291+
$AbsoluteFilePath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutFile)
292+
$Ext = [System.IO.Path]::GetExtension($AbsoluteFilePath)
293+
$BaseName = [System.IO.Path]::GetFileNameWithoutExtension($AbsoluteFilePath)
294+
if ($null -ne $pidx) {
295+
$FileName = '{0}-{1}{2}' -f $BaseName, $pidx, $Ext
296+
}
297+
else {
298+
$FileName = '{0}{1}' -f $BaseName, $Ext
299+
}
300+
$SaveToPath = Join-Path (Split-Path $AbsoluteFilePath -Parent) $FileName
301+
302+
Write-Verbose ('Save image to {0}' -f $SaveToPath)
303+
Write-ByteContent -OutFile $SaveToPath -Bytes ([Convert]::FromBase64String($Response.b64_json))
304+
}
305+
elseif ($ResponseFormat -eq 'object') {
306+
ParseImageGenerationObject $Response
307+
}
308+
elseif ($ResponseFormat -eq 'base64') {
309+
Write-Output ($Response.b64_json)
310+
}
311+
elseif ($ResponseFormat -eq 'byte') {
312+
Write-Output (, ([Convert]::FromBase64String(($Response.b64_json))))
313+
}
314+
#endregion
315+
}
316+
else {
317+
continue
318+
}
319+
}
320+
}
191321
return
192322
}
193323
#endregion
194324

195-
#region Parse response object
196-
if ($OutputRawResponse) {
197-
Write-Output $Response
198-
return
199-
}
200-
try {
201-
$Response = $Response | ConvertFrom-Json -ErrorAction Stop
202-
if ($null -ne $Response.error.message) {
203-
Write-Error -Message ('API returned error: ({0}) {1}' -f $Response.error.code, $Response.error.message)
325+
#region Send API Request
326+
else {
327+
$Response = Invoke-OpenAIAPIRequest @splat
328+
329+
# error check
330+
if ($null -eq $Response) {
204331
return
205332
}
206-
}
207-
catch {
208-
Write-Error -Exception $_.Exception
209-
}
210-
if ($null -ne $Response.data) {
211-
$ResponseContent = $Response.data
212-
}
213-
#endregion
333+
#endregion
214334

215-
#region Output
216-
if ($PSCmdlet.ParameterSetName -eq 'OutFile') {
217-
$AbsoluteFilePath = New-ParentFolder -File $OutFile
218-
219-
# Save image
220-
$ResponseContent | Select-Object -ExpandProperty 'b64_json' | ForEach-Object -Begin { $Suffix = 0 } -Process {
221-
if ( $Suffix -gt 0) {
222-
$Ext = [System.IO.Path]::GetExtension($AbsoluteFilePath)
223-
$BaseName = [System.IO.Path]::GetFileNameWithoutExtension($AbsoluteFilePath)
224-
$FileName = '{0}-{1}{2}' -f $BaseName, $Suffix, $Ext
225-
$SaveToPath = Join-Path (Split-Path $AbsoluteFilePath -Parent) $FileName
226-
}
227-
else {
228-
$SaveToPath = $AbsoluteFilePath
229-
}
230-
231-
Write-Verbose ('Save image to {0}' -f $SaveToPath)
232-
try {
233-
[System.IO.File]::WriteAllBytes($SaveToPath, [Convert]::FromBase64String($_))
234-
}
235-
catch {
236-
Write-Error -Exception $_.Exception
335+
#region Parse response object
336+
if ($OutputRawResponse) {
337+
Write-Output $Response
338+
return
339+
}
340+
try {
341+
$Response = $Response | ConvertFrom-Json -ErrorAction Stop
342+
if ($null -ne $Response.error.message) {
343+
Write-Error -Message ('API returned error: ({0}) {1}' -f $Response.error.code, $Response.error.message)
344+
return
237345
}
238-
$Suffix++
239346
}
240-
}
241-
elseif ($ResponseFormat -eq 'url') {
242-
Write-Output ($ResponseContent | Select-Object -ExpandProperty 'url')
243-
}
244-
elseif ($ResponseFormat -eq 'base64') {
245-
Write-Output ($ResponseContent | Select-Object -ExpandProperty 'b64_json')
246-
}
247-
elseif ($ResponseFormat -eq 'byte') {
248-
$ByteArrayList = [System.Collections.Generic.List[byte[]]]::new()
249-
$ResponseContent | Select-Object -ExpandProperty 'b64_json' | ForEach-Object {
250-
$ByteArrayList.Add([Convert]::FromBase64String($_))
347+
catch {
348+
Write-Error -Exception $_.Exception
251349
}
350+
#endregion
252351

253-
if ( $ByteArrayList.Count -eq 1) {
254-
Write-Output ($ByteArrayList[0])
352+
#region Output
353+
if ($ResponseFormat -eq 'object') {
354+
ParseImageGenerationObject $Response
355+
return
255356
}
256357
else {
257-
Write-Output ($ByteArrayList.ToArray())
358+
$Suffix = 0
359+
foreach ($content in $Response.data) {
360+
if ($PSCmdlet.ParameterSetName -eq 'OutFile') {
361+
$AbsoluteFilePath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutFile)
362+
363+
# Save image
364+
if ($Suffix -gt 0) {
365+
$Ext = [System.IO.Path]::GetExtension($AbsoluteFilePath)
366+
$BaseName = [System.IO.Path]::GetFileNameWithoutExtension($AbsoluteFilePath)
367+
$FileName = '{0}-{1}{2}' -f $BaseName, $Suffix, $Ext
368+
$SaveToPath = Join-Path (Split-Path $AbsoluteFilePath -Parent) $FileName
369+
}
370+
else {
371+
$SaveToPath = $AbsoluteFilePath
372+
}
373+
374+
Write-Verbose ('Save image to {0}' -f $SaveToPath)
375+
Write-ByteContent -OutFile $SaveToPath -Bytes ([Convert]::FromBase64String($content.b64_json))
376+
$Suffix++
377+
}
378+
elseif ($ResponseFormat -eq 'url') {
379+
Write-Output ($content | Select-Object -ExpandProperty 'url')
380+
}
381+
elseif ($ResponseFormat -eq 'base64') {
382+
Write-Output ($content | Select-Object -ExpandProperty 'b64_json')
383+
}
384+
elseif ($ResponseFormat -eq 'byte') {
385+
Write-Output (, ([Convert]::FromBase64String(($content.b64_json))))
386+
}
387+
}
258388
}
389+
#endregion
259390
}
260-
#endregion
261391
}
262392

263393
end {

0 commit comments

Comments
 (0)