using System.Net.Http.Headers; using Encoder; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.WebUtilities; var builder = WebApplication.CreateBuilder(args); //Services builder.Services.AddOpenApi(); builder.Services.AddLogging(); string uploadsPath = builder.Configuration.GetSection("UploadsPath").Get() ?? "./Uploads"; uploadsPath = uploadsPath.AbsoluteOrProcessPath(); Directory.CreateDirectory(uploadsPath); // Ensure the uploads directory exists builder.Services.Configure(builder.Configuration.GetSection(FFmpegOptions.SectionName)); builder.Services.AddSingleton(); builder.Services.AddHostedService(p => p.GetRequiredService()); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); // Get a video file as multipart form data and schedule it for encoding // Get video encoding settings from query parameters // Returns the ID of the job handling the encoding app.MapPost("encode", async context => { // Disable request size limit context.Features.Get()?.MaxRequestBodySize = null; // Fail fast, if EncoderService is not available don't bother processing the request var encSrv = context.RequestServices.GetService(); if (encSrv == null) { context.Response.StatusCode = 500; await context.Response.WriteAsync("Encoder service not available."); return; } // Parse multipart form data manually to handle large file uploads var request = context.Request; if (!request.HasFormContentType) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Invalid content type. Expected multipart/form-data."); return; } Guid jobGuid = Guid.NewGuid(); EncoderType encoderType = EncoderType.H264; string tempFilePath = Path.Combine(uploadsPath, jobGuid.ToString("D")); var boundary = Utils.GetBoundary(request.ContentType!); var reader = new MultipartReader(boundary!, context.Request.Body); MultipartSection? section; while ((section = await reader.ReadNextSectionAsync()) != null) { if(section.ContentDisposition is null) continue; var contentDisposition = ContentDispositionHeaderValue.Parse(section.ContentDisposition); switch (contentDisposition.Name) { case "encoder": { string encoderTypeStr = await section.ReadAsStringAsync(); if (!Enum.TryParse(encoderTypeStr, true, out encoderType)) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Invalid or missing encoderType parameter."); return; } continue; } case "video" when string.IsNullOrWhiteSpace(contentDisposition.FileName): { context.Response.StatusCode = 400; await context.Response.WriteAsync("No video file provided."); return; } case "video": { tempFilePath += Path.GetExtension(contentDisposition.FileName); await using var fstream = File.Create(tempFilePath); await section.Body.CopyToAsync(fstream); continue; } } } var job = new EncodingJob(jobGuid, tempFilePath, encoderType); encSrv.EnqueueJob(job); context.Response.StatusCode = 200; await context.Response.WriteAsJsonAsync(new { JobId = jobGuid }); }).WithFormOptions(multipartBodyLengthLimit: 1_073_741_824L*64L); app.MapGet("status", (context) => { var encSrv = context.RequestServices.GetService(); if (encSrv == null) { context.Response.StatusCode = 500; return context.Response.WriteAsync("Encoder service not available."); } var jobs = encSrv.GetJobs().ToArray(); context.Response.StatusCode = 200; return context.Response.WriteAsJsonAsync(jobs); }); // Check the status of an encoding job by its ID app.MapGet("status/{jobId:guid}", (HttpContext context, Guid jobId) => { var encSrv = context.RequestServices.GetService(); if (encSrv == null) return Results.InternalServerError(); var job = encSrv.GetJobStatus(jobId); if (job == null) { return Results.NotFound(new { Message = "Job not found." }); } return Results.Ok(new { JobId = job.Id, Status = job.Status.ToString(), job.CreatedAt, job.CompletedAt }); }); app.MapGet("file/{jobId:guid}", (HttpContext context, Guid jobId) => { var encSrv = context.RequestServices.GetService(); if (encSrv == null) return Results.InternalServerError(); var job = encSrv.GetJobStatus(jobId); if (job == null) return Results.NotFound(new { Message = "Job not found." }); if (job.Status != JobStatus.Completed) return Results.BadRequest(new { Message = "Job is not completed yet." }); var filePath = job.EncodedFilePath; if (!File.Exists(filePath)) return Results.NotFound(new { Message = "Encoded file not found." }); var fileBytes = File.ReadAllBytes(filePath); return Results.File(fileBytes, "video/mp4", Path.GetFileName(filePath), enableRangeProcessing:true); }); app.MapOpenApi("openapi.json"); app.Run();