|
1 | 1 | using System.IO.Compression; |
| 2 | +using System.Text.Json; |
| 3 | +using System.Text.Json.Serialization; |
2 | 4 | using AssetStudio; |
3 | 5 | using MaiChartManager.Models; |
4 | 6 | using Microsoft.AspNetCore.Mvc; |
5 | 7 | using Microsoft.VisualBasic.FileIO; |
6 | 8 | using Sitreamai; |
7 | 9 | using Sitreamai.Models; |
8 | 10 | using Standart.Hash.xxHash; |
| 11 | +using Xabe.FFmpeg; |
9 | 12 |
|
10 | 13 | namespace MaiChartManager.Controllers; |
11 | 14 |
|
@@ -236,6 +239,112 @@ public void SetAudio(int id, [FromForm] float padding, IFormFile file, IFormFile |
236 | 239 | StaticSettings.AcbAwb[$"music{id:000000}.awb"] = targetAwbPath; |
237 | 240 | } |
238 | 241 |
|
| 242 | + public enum SetMovieEventType |
| 243 | + { |
| 244 | + Progress, |
| 245 | + Success, |
| 246 | + Error |
| 247 | + } |
| 248 | + |
| 249 | + [HttpPut] |
| 250 | + [DisableRequestSizeLimit] |
| 251 | + public async Task SetMovie(int id, [FromForm] float padding, IFormFile file) |
| 252 | + { |
| 253 | + Response.Headers.Append("Content-Type", "text/event-stream"); |
| 254 | + var tmpDir = Directory.CreateTempSubdirectory(); |
| 255 | + logger.LogInformation("Temp dir: {tmpDir}", tmpDir.FullName); |
| 256 | + // Convert vp9 |
| 257 | + string outVideoPath; |
| 258 | + try |
| 259 | + { |
| 260 | + var srcFilePath = Path.Combine(tmpDir.FullName, Path.GetFileName(file.FileName)); |
| 261 | + var srcFileStream = System.IO.File.OpenWrite(srcFilePath); |
| 262 | + await file.CopyToAsync(srcFileStream); |
| 263 | + await srcFileStream.DisposeAsync(); |
| 264 | + |
| 265 | + var srcMedia = await FFmpeg.GetMediaInfo(srcFilePath); |
| 266 | + var conversion = FFmpeg.Conversions.New() |
| 267 | + .UseMultiThread(true) |
| 268 | + .AddParameter("-cpu-used 5"); |
| 269 | + if (padding < 0) |
| 270 | + { |
| 271 | + conversion.SetSeek(TimeSpan.FromSeconds(-padding)); |
| 272 | + } |
| 273 | + else if (padding > 0) |
| 274 | + { |
| 275 | + var blankPath = Path.Combine(tmpDir.FullName, "blank.mp4"); |
| 276 | + var blank = FFmpeg.Conversions.New() |
| 277 | + .SetOutputTime(TimeSpan.FromSeconds(padding)) |
| 278 | + .SetInputFormat(Format.lavfi) |
| 279 | + .AddParameter("-i color=c=black:s=720x720:r=1") |
| 280 | + .UseMultiThread(true) |
| 281 | + .SetOutput(blankPath); |
| 282 | + logger.LogInformation("About to run FFMpeg with params: {params}", blank.Build()); |
| 283 | + await blank.Start(); |
| 284 | + var blankVideoInfo = await FFmpeg.GetMediaInfo(blankPath); |
| 285 | + conversion.AddStream(blankVideoInfo.VideoStreams.First().SetCodec(VideoCodec.vp9)); |
| 286 | + } |
| 287 | + |
| 288 | + outVideoPath = Path.Combine(tmpDir.FullName, "out.ivf"); |
| 289 | + conversion |
| 290 | + .AddStream(srcMedia.VideoStreams.First().SetCodec(VideoCodec.vp9)) |
| 291 | + .SetOutput(outVideoPath); |
| 292 | + logger.LogInformation("About to run FFMpeg with params: {params}", conversion.Build()); |
| 293 | + conversion.OnProgress += async (sender, args) => |
| 294 | + { |
| 295 | + logger.LogInformation("FFMpeg progress: {progress}", args.Percent); |
| 296 | + await Response.WriteAsync($"event: {SetMovieEventType.Progress}\ndata: {args.Percent}\n\n"); |
| 297 | + await Response.Body.FlushAsync(); |
| 298 | + }; |
| 299 | + await conversion.Start(); |
| 300 | + } |
| 301 | + catch (Exception e) |
| 302 | + { |
| 303 | + logger.LogError(e, "Failed to convert video"); |
| 304 | + SentrySdk.CaptureException(e); |
| 305 | + await Response.WriteAsync($"event: {SetMovieEventType.Error}\ndata: 视频转换为 VP9 失败:{e.Message}\n\n"); |
| 306 | + await Response.Body.FlushAsync(); |
| 307 | + return; |
| 308 | + } |
| 309 | + |
| 310 | + // Convert ivf to usm |
| 311 | + var outputFile = Path.Combine(tmpDir.FullName, "out.usm"); |
| 312 | + try |
| 313 | + { |
| 314 | + await WannaCRI.WannaCRI.CreateUsmAsync(outVideoPath); |
| 315 | + if (!System.IO.File.Exists(outputFile) || new FileInfo(outputFile).Length == 0) |
| 316 | + { |
| 317 | + throw new Exception("Output file not found or empty"); |
| 318 | + } |
| 319 | + } |
| 320 | + catch (Exception e) |
| 321 | + { |
| 322 | + logger.LogError(e, "Failed to convert ivf to usm"); |
| 323 | + SentrySdk.CaptureException(e); |
| 324 | + await Response.WriteAsync($"event: {SetMovieEventType.Error}\ndata: 视频转换为 USM 失败:{e.Message}\n\n"); |
| 325 | + await Response.Body.FlushAsync(); |
| 326 | + return; |
| 327 | + } |
| 328 | + |
| 329 | + try |
| 330 | + { |
| 331 | + var targetPath = Path.Combine(StaticSettings.StreamingAssets, settings.AssetDir, $@"MovieData\{id:000000}.dat"); |
| 332 | + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); |
| 333 | + FileSystem.CopyFile(outputFile, targetPath, true); |
| 334 | + |
| 335 | + StaticSettings.MovieDataMap[id] = targetPath; |
| 336 | + await Response.WriteAsync($"event: {SetMovieEventType.Success}\n\n"); |
| 337 | + await Response.Body.FlushAsync(); |
| 338 | + } |
| 339 | + catch (Exception e) |
| 340 | + { |
| 341 | + logger.LogError(e, "Failed to copy movie data"); |
| 342 | + SentrySdk.CaptureException(e); |
| 343 | + await Response.WriteAsync($"event: {SetMovieEventType.Error}\ndata: 复制文件失败:{e.Message}\n\n"); |
| 344 | + await Response.Body.FlushAsync(); |
| 345 | + } |
| 346 | + } |
| 347 | + |
239 | 348 | [HttpPost] |
240 | 349 | public void RequestCopyTo(int id) |
241 | 350 | { |
|
0 commit comments