Snapshot all enabled participants in one click
Some checks failed
CI / build-and-test (push) Failing after 27s
Some checks failed
CI / build-and-test (push) Failing after 27s
Header button 'Snapshot all' fires SnapshotAllCommand which iterates every enabled participant, grabs the latest ProcessedFrame, encodes as PNG into a fresh timestamped subfolder under %USERPROFILE%\\Pictures\\TeamsISO\\snapshots-yyyyMMdd_HHmmss\\. One folder per click so back-to-back snapshot sessions don't comingle. Reuses the per-participant snapshot path established earlier — same WriteableBitmap(Bgra32) → PngBitmapEncoder pipeline. Reports saved + failed counts in the toast so the operator knows if anything was missed (typical failure: pipeline still warming up, no frame yet).
This commit is contained in:
parent
d6793d8d9c
commit
10a0826fb3
2 changed files with 91 additions and 0 deletions
|
|
@ -776,6 +776,26 @@
|
|||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource Wd.Button.Ghost}"
|
||||
Command="{Binding SnapshotAllCommand}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
ToolTip="Save a PNG of every enabled participant's current frame. One folder per click under %USERPROFILE%\Pictures\TeamsISO\snapshots-<timestamp>.">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Data="M 1,3 L 4,3 L 5,1 L 11,1 L 12,3 L 15,3 L 15,13 L 1,13 Z M 8,5 A 3,3 0 1 1 8,11 A 3,3 0 1 1 8,5"
|
||||
Stroke="{DynamicResource Wd.Text.Primary}"
|
||||
StrokeThickness="1.2"
|
||||
Fill="Transparent"
|
||||
Width="16" Height="14"
|
||||
Stretch="None"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock Text="Snapshot all"
|
||||
Style="{StaticResource Wd.Text.Body}"
|
||||
FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource Wd.Button.Ghost}"
|
||||
Click="OnPresetsClick"
|
||||
VerticalAlignment="Center"
|
||||
|
|
|
|||
|
|
@ -178,6 +178,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
/// <summary>Join a Teams meeting from a pasted URL — see <see cref="JoinMeetingUrl"/>.</summary>
|
||||
public RelayCommand JoinMeetingCommand { get; }
|
||||
|
||||
/// <summary>Save a PNG snapshot of every enabled participant's current frame.</summary>
|
||||
public RelayCommand SnapshotAllCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Two-way bound to the quick-join input. Whatever the operator pastes
|
||||
/// gets handed to <see cref="TeamsLauncher.TryJoinMeeting"/> when the
|
||||
|
|
@ -407,6 +410,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
notes.Show(); // non-modal so operators can stamp + read alongside the show
|
||||
});
|
||||
|
||||
SnapshotAllCommand = new RelayCommand(SnapshotAll, () => Participants.Any(p => p.IsEnabled));
|
||||
|
||||
JoinMeetingCommand = new RelayCommand(() =>
|
||||
{
|
||||
// Trim + handle the operator pasting whitespace around the URL.
|
||||
|
|
@ -587,6 +592,72 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
Toast.Show($"Stopped {enabled.Length} ISO(s)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a PNG of every currently-enabled participant's latest processed
|
||||
/// frame to a timestamped subdirectory under
|
||||
/// %USERPROFILE%\Pictures\TeamsISO\. One folder per Snapshot All click
|
||||
/// so back-to-back clicks don't comingle. Useful for end-of-meeting
|
||||
/// archives, recapping who showed up, etc.
|
||||
/// </summary>
|
||||
private void SnapshotAll()
|
||||
{
|
||||
var enabled = Participants.Where(p => p.IsEnabled).ToArray();
|
||||
if (enabled.Length == 0)
|
||||
{
|
||||
Toast.Warn("No enabled participants to snapshot");
|
||||
return;
|
||||
}
|
||||
|
||||
var rootDir = System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
|
||||
"TeamsISO",
|
||||
$"snapshots-{DateTimeOffset.Now:yyyyMMdd_HHmmss}");
|
||||
|
||||
try
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(rootDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Toast.Warn($"Couldn't create snapshot dir: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
var saved = 0;
|
||||
var failed = 0;
|
||||
foreach (var p in enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
var frame = _controller.GetLatestProcessedFrame(p.Id);
|
||||
if (frame is null || frame.Pixels.IsEmpty) { failed++; continue; }
|
||||
|
||||
var safeName = string.Join("_", p.DisplayName.Split(System.IO.Path.GetInvalidFileNameChars()));
|
||||
if (string.IsNullOrWhiteSpace(safeName)) safeName = "participant";
|
||||
var path = System.IO.Path.Combine(rootDir, $"{safeName}.png");
|
||||
|
||||
var stride = frame.Width * 4;
|
||||
var bmp = new System.Windows.Media.Imaging.WriteableBitmap(
|
||||
frame.Width, frame.Height, 96, 96,
|
||||
System.Windows.Media.PixelFormats.Bgra32, null);
|
||||
bmp.WritePixels(
|
||||
new System.Windows.Int32Rect(0, 0, frame.Width, frame.Height),
|
||||
frame.Pixels.ToArray(), stride, 0);
|
||||
|
||||
using var fs = new System.IO.FileStream(path, System.IO.FileMode.Create);
|
||||
var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
|
||||
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp));
|
||||
encoder.Save(fs);
|
||||
saved++;
|
||||
}
|
||||
catch { failed++; }
|
||||
}
|
||||
|
||||
Toast.Show(failed > 0
|
||||
? $"Saved {saved} snapshot(s) ({failed} failed) to Pictures\\TeamsISO\\{System.IO.Path.GetFileName(rootDir)}"
|
||||
: $"Saved {saved} snapshot(s) to Pictures\\TeamsISO\\{System.IO.Path.GetFileName(rootDir)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a byte count as a human-readable string with 0-1 decimal
|
||||
/// places — e.g. "8.4 GB", "245 GB", "1.2 TB". Optimized for footer
|
||||
|
|
|
|||
Loading…
Reference in a new issue