diff --git a/calls/backend/Calls.Api.Tests/RecordingTests.cs b/calls/backend/Calls.Api.Tests/RecordingTests.cs index 181253c..979c94e 100644 --- a/calls/backend/Calls.Api.Tests/RecordingTests.cs +++ b/calls/backend/Calls.Api.Tests/RecordingTests.cs @@ -190,6 +190,8 @@ public sealed class ProcessorTests : IDisposable var c = _calls.Create("ws-room", "wsteam0000001"); _calls.RecordingStarted(c.Id, c.Id); _calls.Seen(c.Id, [new("Анна", false), new("Іван", true)]); + _time.Advance(TimeSpan.FromSeconds(duration)); // звонок шёл duration секунд + _calls.Seen(c.Id, [new("Анна", false), new("Іван", true)]); _calls.Ended(c.Id, _calls.Now, hadRecording: true); var dir = Path.Combine(_root, "rec", c.Id); Directory.CreateDirectory(Path.Combine(dir, "parts")); @@ -216,6 +218,7 @@ public sealed class ProcessorTests : IDisposable Assert.False(Directory.Exists(dir)); // проверка 3: файлов не осталось var up = Assert.Single(Uploads); Assert.Matches(@"^calls/2026/10/[0-9a-f]{24}/dzvinok-2026-10-15-1400\.mp3$", up); + Assert.Contains("1 хв 30 с", JsonDocument.Parse(Pages[0]).RootElement.GetProperty("markdown").GetString()); Assert.StartsWith("https://sb.test/storage/v1/object/public/files/calls/2026/10/", row.AudioUrl); Assert.Equal(["Дзвінки"], Folders); // папку создали var page = JsonDocument.Parse(Assert.Single(Pages)).RootElement; @@ -229,6 +232,17 @@ public sealed class ProcessorTests : IDisposable Assert.Contains(row.AudioUrl!, md); } + [Fact] + public async Task ShortCallByPresence_SkippedEvenIfAudioLonger() + { + // звонок 50 с, но запись 70 с (хвост после ухода всех) — по звонку он короткий + var (call, dir) = EndedCall(duration: 50); + File.WriteAllText(Path.Combine(dir, "meta.json"), File.ReadAllText(Path.Combine(dir, "meta.json")).Replace("\"duration\":50", "\"duration\":70")); + await Proc(Http(), Opt()).RunOnceAsync(default); + Assert.Equal("skipped", _calls.Get(call.Id)!.Status); + Assert.False(Directory.Exists(dir)); + } + [Fact] public async Task ShortCall_Skipped_NoPage_FilesDeleted() { diff --git a/calls/backend/Calls.Api/Recording/RecordingProcessor.cs b/calls/backend/Calls.Api/Recording/RecordingProcessor.cs index e3a09ec..e462506 100644 --- a/calls/backend/Calls.Api/Recording/RecordingProcessor.cs +++ b/calls/backend/Calls.Api/Recording/RecordingProcessor.cs @@ -64,11 +64,13 @@ public sealed class RecordingProcessor( return; } var meta = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(dir, "meta.json"), ct)).RootElement; - var duration = meta.GetProperty("duration").GetDouble(); + var audioSeconds = meta.GetProperty("duration").GetDouble(); + // Длительность звонка — по присутствию участников (запись идёт ещё EmptyGrace после ухода всех). + double duration = call.EndedAt is { } end && end > call.StartedAt ? end - call.StartedAt : audioSeconds; var audioFile = meta.GetProperty("audio").GetString()!; var mime = meta.GetProperty("mime").GetString()!; if (meta.TryGetProperty("quality", out var q)) - log.LogInformation("Дзвінок {C}: запис {D:0} с, {B} КБ, якість: {Q}", call.Id, duration, + log.LogInformation("Дзвінок {C}: тривалість {D:0} с, запис {A:0} с, {B} КБ, якість: {Q}", call.Id, duration, audioSeconds, meta.GetProperty("bytes").GetInt64() / 1024, q.GetRawText()); // Короткий звонок: без страницы, файлы удаляем. @@ -100,7 +102,7 @@ public sealed class RecordingProcessor( var parts = meta.GetProperty("parts").EnumerateArray() .Select(p => new AudioPart(p.GetProperty("file").GetString()!, p.GetProperty("duration").GetDouble(), p.GetProperty("bytes").GetInt64())) .ToList(); - if (parts.Count == 0) parts.Add(new AudioPart(audioFile, duration, meta.GetProperty("bytes").GetInt64())); + if (parts.Count == 0) parts.Add(new AudioPart(audioFile, audioSeconds, meta.GetProperty("bytes").GetInt64())); transcript = await whisper.TranscribeAsync(dir, parts, mime, ct); await File.WriteAllTextAsync(tPath + ".tmp", JsonSerializer.Serialize(transcript), ct); File.Move(tPath + ".tmp", tPath, overwrite: true);