Skip to content

Commit fc1a6d9

Browse files
committed
Add support for ConvertVideo
1 parent 7bf646e commit fc1a6d9

File tree

10 files changed

+417
-21
lines changed

10 files changed

+417
-21
lines changed

AiServer.ServiceInterface/Generation/ComfyProvider.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public async Task<TransformResult> TransformMediaAsync(MediaProvider provider, M
217217
var inputKwargs = new Dictionary<string, object>();
218218
if (args.CutStart.HasValue)
219219
inputKwargs["ss"] = args.CutStart.Value.ToString(CultureInfo.InvariantCulture);
220-
if (args.CutEnd.HasValue && args.CutEnd.Value > 0)
220+
if (args.CutEnd is > 0)
221221
inputKwargs["t"] = (args.CutEnd.Value - (args.CutStart ?? 0)).ToString(CultureInfo.InvariantCulture);
222222

223223
// Prepare output_kwargs
@@ -270,7 +270,11 @@ public async Task<TransformResult> TransformMediaAsync(MediaProvider provider, M
270270

271271
var httpClient = GetHttpClient(provider);
272272

273-
var response = await httpClient.PostAsync($"{provider.ApiBaseUrl}/transform", content, token);
273+
var url = $"{provider.ApiBaseUrl}/transform";
274+
logger.LogDebug("POST {Url}", url);
275+
await content.LogContentAsync(logger);
276+
277+
var response = await httpClient.PostAsync(url, content, token);
274278
// Log the response
275279
var contentString = await response.Content.ReadAsStringAsync(token);
276280
logger.LogInformation($"Response: {response.StatusCode} - {contentString}");

AiServer.ServiceInterface/MediaTransform/CreateMediaTransformCommand.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ protected override async Task<TransformResult> RunAsync(CreateMediaTransform req
3838

3939
// Short circuit if is an image operation and process locally.
4040
// If the request task type is image crop, image scale or image convert, we can process locally
41-
if (request.Request.TaskType == MediaTransformTaskType.ImageCrop ||
42-
request.Request.TaskType == MediaTransformTaskType.ImageScale ||
43-
request.Request.TaskType == MediaTransformTaskType.ImageConvert ||
44-
request.Request.TaskType == MediaTransformTaskType.WatermarkImage)
41+
if (request.Request.TaskType is MediaTransformTaskType.ImageCrop
42+
or MediaTransformTaskType.ImageScale
43+
or MediaTransformTaskType.ImageConvert
44+
or MediaTransformTaskType.WatermarkImage)
4545
{
4646
switch (request.Request.TaskType)
4747
{
@@ -61,7 +61,7 @@ protected override async Task<TransformResult> RunAsync(CreateMediaTransform req
6161
var (response, durationMs) = await transformProvider.RunAsync(apiProviderInstance, request.Request, token);
6262
log.LogInformation("Finished {TaskType} generation request {RefId} with {Provider} in {DurationMs}ms",
6363
request.Request.TaskType, request.RefId, apiProviderInstance.Name, durationMs);
64-
if (response.Outputs != null && response.Outputs.Count > 0)
64+
if (response.Outputs is { Count: > 0 })
6565
await DownloadOutputsAsync(transformProvider, apiProviderInstance, response.Outputs, keyId, token);
6666

6767
ResponseStatus? error = null;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Text;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace AiServer.ServiceInterface;
5+
6+
// Extension method version
7+
public static class MultipartFormDataExtensions
8+
{
9+
public static async Task<string> ToDebugStringAsync(this MultipartFormDataContent content)
10+
{
11+
var sb = new StringBuilder();
12+
13+
sb.AppendLine("<MultipartFormDataContent");
14+
sb.AppendLine($"Boundary: {content.Headers.ContentType?.Parameters.FirstOrDefault(p => p.Name == "boundary")?.Value}");
15+
16+
int index = 0;
17+
foreach (var part in content)
18+
{
19+
sb.AppendLine($"\nPart {index++}:");
20+
21+
// Headers
22+
foreach (var header in part.Headers)
23+
{
24+
sb.AppendLine($" {header.Key}: {string.Join("; ", header.Value)}");
25+
}
26+
27+
// Content
28+
if (part is StringContent stringContent)
29+
{
30+
sb.AppendLine($" Content: {await stringContent.ReadAsStringAsync()}");
31+
}
32+
else if (part is ByteArrayContent)
33+
{
34+
sb.AppendLine(" ByteArrayContent");
35+
}
36+
else
37+
{
38+
sb.AppendLine($" Content-Type: {part.GetType().Name}");
39+
}
40+
}
41+
sb.AppendLine("MultipartFormDataContent>");
42+
43+
return sb.ToString();
44+
}
45+
public static async Task LogContentAsync(this MultipartFormDataContent content,
46+
ILogger logger, LogLevel logLevel = LogLevel.Debug)
47+
{
48+
if (!logger.IsEnabled(logLevel))
49+
return;
50+
51+
logger.Log(logLevel, await content.ToDebugStringAsync());
52+
}
53+
}

AiServer.ServiceInterface/VideoServices.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ public static async Task<ArtifactGenerationResponse> ProcessSyncTransformAsync(t
375375
var completedResponse = new ArtifactGenerationResponse();
376376

377377
// Wait for the job to complete max 1 minute
378-
var timeout = DateTime.UtcNow.AddMinutes(1);
378+
var timeout = DateTime.UtcNow.AddSeconds(job.Job?.TimeoutSecs ?? 20 * 60);
379379
while (queuedJob?.Job?.State is not (
380380
BackgroundJobState.Completed or BackgroundJobState.Cancelled or BackgroundJobState.Failed)
381381
&& DateTime.UtcNow < timeout)

AiServer.ServiceModel/QueueMediaTransforms.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,20 @@ public enum ConvertVideoOutputFormat
309309
{
310310
[EnumMember(Value = "mp4")]
311311
MP4,
312+
[EnumMember(Value = "mov")]
313+
MOV,
314+
[EnumMember(Value = "webm")]
315+
WebM,
316+
[EnumMember(Value = "mkv")]
317+
MVK,
312318
[EnumMember(Value = "avi")]
313319
AVI,
314-
[EnumMember(Value = "mov")]
315-
MOV
320+
[EnumMember(Value = "wmv")]
321+
WMV,
322+
[EnumMember(Value = "mpeg")]
323+
MPEG,
324+
[EnumMember(Value = "ogg")]
325+
Ogg,
316326
}
317327

318328
[DataContract]
@@ -322,8 +332,14 @@ public enum AudioFormat
322332
MP3,
323333
[EnumMember(Value = "wav")]
324334
WAV,
335+
[EnumMember(Value = "aac")]
336+
AAC,
325337
[EnumMember(Value = "flac")]
326338
FLAC,
327339
[EnumMember(Value = "ogg")]
328340
OGG,
341+
[EnumMember(Value = "m4a")]
342+
M4A,
343+
[EnumMember(Value = "wma")]
344+
WMA,
329345
}

AiServer/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
using AiServer.ServiceInterface;
2+
using Microsoft.AspNetCore.Server.Kestrel.Core;
23

34
Licensing.RegisterLicense("OSS GPL-3.0 2025 https://github.com/ServiceStack/ai-server B2fSVlQ1mYLSxRYTSvsS1aORN0Og++8DTDsxY0+2lBt8Wj7VwLZYbHJY4/UnJFpaagxoQepeXXHMPfZcmP9eUjyhRaqWe3OJI4+3ct/2Wr+rfR5roBrUer8mzJhrQDj1t3L3x42dy/pZiOQKMccAShk4psGLS/TG86MTzuPk2XE=");
45

56
var builder = WebApplication.CreateBuilder(args);
67
var services = builder.Services;
78

9+
services.Configure<KestrelServerOptions>(options => {
10+
options.Limits.MaxRequestBodySize = int.MaxValue; // if don't set default value is: 30 MB
11+
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; //50MB
12+
});
13+
814
services.AddServiceStack(typeof(MyServices).Assembly);
915

1016
var app = builder.Build();

AiServer/wwwroot/mjs/components/ConvertImage.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default {
2727
<div class="grid grid-cols-6 gap-4">
2828
<div class="col-span-6">
2929
<FileUpload ref="refImage" id="image" v-model="request.image" required
30-
accept=".webp,.jpg,.jpeg,.png,.gif" :acceptLabel="acceptedImages" @change="renderKey++">
30+
accept=".webp,.jpg,.jpeg,.png,.gif,.bmp,.tiff" :acceptLabel="acceptedImages" @change="renderKey++">
3131
<template #title>
3232
<span class="font-semibold text-green-600">Click to upload</span> or drag and drop
3333
</template>
@@ -50,7 +50,7 @@ export default {
5050
<div class="mt-4 mb-8 flex justify-center">
5151
<PrimaryButton :key="renderKey" type="submit" :disabled="!validPrompt()">
5252
<svg class="-ml-0.5 h-6 w-6 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M11 16V7.85l-2.6 2.6L7 9l5-5l5 5l-1.4 1.45l-2.6-2.6V16zm-5 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"/></svg>
53-
<span class="text-base font-semibold">Upload</span>
53+
<span class="text-base font-semibold">{{ request.outputFormat ? ('Convert to .' + ImageOutputFormat[request.outputFormat]) : 'Upload' }}</span>
5454
</PrimaryButton>
5555
</div>
5656
</div>
@@ -140,7 +140,7 @@ export default {
140140
}
141141

142142
async function send() {
143-
console.log('send', validPrompt(), client.loading.value)
143+
console.debug('send', validPrompt(), client.loading.value)
144144
if (!validPrompt() || client.loading.value) return
145145

146146
savePrefs()
@@ -215,14 +215,14 @@ export default {
215215
}
216216

217217
function onRouteChange() {
218-
console.log('onRouteChange', routes.id)
218+
// console.log('onRouteChange', routes.id)
219219
refImage.value?.clear()
220220
loadHistory()
221221
if (routes.id) {
222222
const id = parseInt(routes.id)
223223
thread.value = storage.getThread(storage.getThreadId(id))
224224
threadRef.value = history.value.find(x => x.id === parseInt(routes.id))
225-
console.debug('thread', thread.value, threadRef.value)
225+
// console.debug('thread', thread.value, threadRef.value)
226226
if (thread.value) {
227227
Object.keys(storage.defaults).forEach(k =>
228228
request.value[k] = thread.value[k] ?? storage.defaults[k])

0 commit comments

Comments
 (0)