TASK 18: manual rename modal for finished recordings
RenameRecordingDialog (themed, mirrors GoLiveWindow chrome): pre-fills the auto stem, Enter/OK saves, Esc/Cancel keeps the auto name, strips stray .mp4, rejects invalid filename chars. FinalizeRecordingAsync shows it before UniquePath rename. Naming logic still covered by RecordingFileTests; UI shell is pure. verify.sh gate: 0 warnings, 244/246 (2 known).
This commit is contained in:
+3
-2
@@ -70,6 +70,7 @@ docs/ytLlive-preview.jpg docs/ytLlive-preview.png(deleted)`
|
||||
|
||||
1. **Running-app verification** of TASK 18 (the one real gap): confirm rename-on-stop produces
|
||||
`ty-…-<len>.mp4` and dual stream+record output actually works live. Requires the creator to run the app.
|
||||
2. Manual rename dialog (planned modal for user-chosen name) — parked, needs a trigger (toast service has
|
||||
no actions).
|
||||
2. ~~Manual rename modal~~ **DONE** — `RenameRecordingDialog` (themed modal, pre-filled auto-stem,
|
||||
OK/Enter / Cancel/Esc, invalid-char + empty = keep-auto validation) shown at finalize in
|
||||
`FinalizeRecordingAsync`; reuses `YtTextBox`/`YtButton` styles.
|
||||
3. Continue queue: TASK 21 media source, TASK 22 audio sync offset.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<Window x:Class="ytLive.RenameRecordingDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Rename Recording" Height="230" Width="460"
|
||||
Icon="/Assets/llamacasty-icon.png"
|
||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
|
||||
ShowInTaskbar="False" Background="#1a1a2e">
|
||||
|
||||
<Grid Margin="24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Rename Recording" FontSize="18" FontWeight="Bold"
|
||||
Foreground="#e0e0e0" Margin="0,0,0,12"/>
|
||||
|
||||
<TextBlock Grid.Row="1" TextWrapping="Wrap" Foreground="#a0a0b0" FontSize="13"
|
||||
Margin="0,0,0,16">
|
||||
The recording is saved with an auto-generated name. Give it a clearer name —
|
||||
leave it empty to keep the auto name.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="2" Text="File name (without .mp4)" Style="{StaticResource YtLabel}"/>
|
||||
<TextBox Grid.Row="3" x:Name="NameBox" Style="{StaticResource YtTextBox}"
|
||||
VerticalAlignment="Top"/>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
Margin="0,20,0,0">
|
||||
<Button Content="Cancel" Click="Cancel_Click"
|
||||
Style="{StaticResource YtButtonSecondary}" Margin="0,0,8,0"/>
|
||||
<Button Content="Save" Click="Save_Click" Style="{StaticResource YtButton}" MinWidth="90"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace ytLive;
|
||||
|
||||
/// <summary>Modal that lets the creator rename an auto-named recording before it is
|
||||
/// finalized. Returns the chosen file stem (no extension) via <see cref="ResultFileName"/>,
|
||||
/// or <c>null</c> on Cancel / keep-auto-name.</summary>
|
||||
public partial class RenameRecordingDialog : Window
|
||||
{
|
||||
private readonly string _autoStem;
|
||||
|
||||
/// <summary>Stem (no extension) the creator chose, or <c>null</c> to keep auto-name.</summary>
|
||||
public string? ResultFileName { get; private set; }
|
||||
|
||||
public RenameRecordingDialog(string autoStem)
|
||||
{
|
||||
InitializeComponent();
|
||||
_autoStem = autoStem;
|
||||
NameBox.Text = autoStem;
|
||||
Loaded += (_, _) => { NameBox.Focus(); NameBox.SelectAll(); };
|
||||
NameBox.KeyDown += OnKeyDown;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter) { Save(); e.Handled = true; }
|
||||
else if (e.Key == Key.Escape) { DialogResult = false; e.Handled = true; }
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e) => DialogResult = false;
|
||||
|
||||
private void Save_Click(object sender, RoutedEventArgs e) => Save();
|
||||
|
||||
private void Save()
|
||||
{
|
||||
var name = NameBox.Text?.Trim();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
// Empty = keep the auto name.
|
||||
ResultFileName = null;
|
||||
DialogResult = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip any accidental ".mp4" so we return a clean stem.
|
||||
if (name.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase))
|
||||
name = Path.GetFileNameWithoutExtension(name);
|
||||
|
||||
// Reject characters that are illegal in Windows file names.
|
||||
if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
MessageBox.Show(this, "That name contains characters not allowed in file names.",
|
||||
"Invalid name", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
NameBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
ResultFileName = name;
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
@@ -951,7 +951,7 @@ click (volume sliders keep their manual `SetSliderValueFromClick`, harmless dupl
|
||||
|
||||
**Goal:** record the stream output to a local file, with or without simultaneously streaming.
|
||||
|
||||
### Status: ✅ Shipped `a9eb360` (2026-08-29) — code done, build 0 warnings, 244/246 tests; remaining = running-app verification + manual-rename modal
|
||||
### Status: ✅ Shipped `a9eb360` (2026-08-29) — code done (incl. manual-rename modal), build 0 warnings, 244/246 tests; remaining = running-app verification
|
||||
|
||||
1. ✅ `EncoderOptions` extended with `StreamEnabled` / `RecordEnabled` / `RecordPath` (independent intent flags)
|
||||
2. ✅ `FfmpegArgs.Build` reworked into per-output blocks (stream `-f flv`, record `-f mp4`) via `AddVideoTags`
|
||||
@@ -961,7 +961,7 @@ click (volume sliders keep their manual `SetSliderValueFromClick`, harmless dupl
|
||||
6. 🟡 Branding flash carries into local recordings — same frame path as streaming (verify in the running app)
|
||||
7. ✅ Output folder `%APPDATA%\ytLlive\recordings\` default; auto-name `ty-<yyyymmdd>-<hhmm start>-0000.mp4` at start, **rename-on-stop** to `ty-…-<hh2mm2 length>.mp4` (numeric suffix on collision)
|
||||
|
||||
Remaining: manual rename dialog for the user (toast actions unsupported — modal window was planned but parked), running-app verification of the rename + dual output.
|
||||
Remaining: running-app verification of the rename + dual output.
|
||||
|
||||
### Design decisions
|
||||
|
||||
|
||||
@@ -3380,9 +3380,17 @@ public class MainViewModel : ViewModelBase
|
||||
return;
|
||||
}
|
||||
_recordLength = _liveElapsed;
|
||||
var finalName = RecordingFile.BuildFinalName(_recordStartTime, _recordLength) + ".mp4";
|
||||
var autoStem = RecordingFile.BuildFinalName(_recordStartTime, _recordLength);
|
||||
var dir = Path.GetDirectoryName(startPath)!;
|
||||
var finalPath = UniquePath(Path.Combine(dir, finalName));
|
||||
|
||||
// Manual rename modal: give the creator a chance to name the recording;
|
||||
// null = keep the auto name (or the default recorded above).
|
||||
string stem = autoStem;
|
||||
var dialog = new RenameRecordingDialog(autoStem);
|
||||
if (dialog.ShowDialog() == true && !string.IsNullOrEmpty(dialog.ResultFileName))
|
||||
stem = dialog.ResultFileName;
|
||||
|
||||
var finalPath = UniquePath(Path.Combine(dir, stem + ".mp4"));
|
||||
File.Move(startPath, finalPath);
|
||||
AppLog.Write($"Recording saved: {finalPath}");
|
||||
_notifications.Info("Recording saved",
|
||||
|
||||
Reference in New Issue
Block a user