feat: auto-split an element by silence detection - #2071
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds auto-splitting of selected audio elements by detected silence, including waveform analysis, scene-element splitting and deletion, menu integration, localized UI text, notifications, and unit tests. ChangesAuto Split by Silence
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant TimelineTabViewModel
participant SilenceWaveformAnalysis
participant SilenceDetector
participant ISilenceSplitService
Editor->>TimelineTabViewModel: submit silence settings
TimelineTabViewModel->>SilenceWaveformAnalysis: collect audio waveform chunks
TimelineTabViewModel->>SilenceDetector: detect silence regions
TimelineTabViewModel->>ISilenceSplitService: split selected element
ISilenceSplitService-->>Editor: return split and deletion counts
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds a silence-detection driven “auto split” workflow for timeline elements, spanning the engine (silence detection over waveform chunks), editor services (batch split + optional delete in a single undo transaction), and UI wiring (menu command + parameter prompt).
Changes:
- Added
SilenceDetector+SilenceDetectionOptions/SilenceRegionto detect silence regions fromWaveformChunkmin/max pairs. - Added
ISilenceSplitService+ElementSilenceSplitServiceto split elements at silence boundaries (optionally deleting silent pieces) with one history commit, reusingElementStructureServicecore operations. - Added UI command (“Auto Split by Silence”) and localized strings, plus new unit tests for detector and split service.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Beutl.UnitTests/Engine/Audio/SilenceDetectorTests.cs | Adds unit coverage for silence detection edge cases and validation. |
| tests/Beutl.UnitTests/Editor/Services/ElementStructureServiceTests.cs | Minor formatting change after refactor of core split/delete helpers. |
| tests/Beutl.UnitTests/Editor/Services/ElementSilenceSplitServiceTests.cs | Adds tests for split-only vs split+delete behavior and commit/no-op behavior. |
| src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs | Hooks up menu command and shows a parameter dialog for silence split. |
| src/Beutl/Views/MainView.axaml | Adds the “Auto Split by Silence” menu item under Scene → Element. |
| src/Beutl/ViewModels/MenuBarViewModel.Scene.cs | Adds AutoSplitBySilence command to the scene command set. |
| src/Beutl/ViewModels/EditViewModel.cs | Registers ISilenceSplitService implementation in the editor service locator. |
| src/Beutl.Language/Strings.resx | Adds localized UI label for Auto Split by Silence (en). |
| src/Beutl.Language/Strings.ja.resx | Adds localized UI label for Auto Split by Silence (ja). |
| src/Beutl.Language/CommandNames.resx | Adds command name for history/command labeling (en). |
| src/Beutl.Language/CommandNames.ja.resx | Adds command name for history/command labeling (ja). |
| src/Beutl.Engine/Audio/SilenceDetector.cs | Introduces silence region detection utility and option types. |
| src/Beutl.Editor/Services/ISilenceSplitService.cs | Introduces new editor service contract for silence-based splitting. |
| src/Beutl.Editor/Services/ElementStructureService.cs | Refactors split/delete into non-committing cores for batch operations. |
| src/Beutl.Editor/Services/ElementSilenceSplitService.cs | Implements batch split/delete with a single history commit. |
| src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs | Adds async workflow to gather waveform chunks, detect silence, and invoke split service. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
src/Beutl.Engine/Audio/SilenceDetector.cs (1)
51-72: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueOptional: use a bool array instead of a Dictionary for silence lookup.
silentByIndexis keyed bychunk.Index, which is bounded to[0, chunkCount). Abool[chunkCount]initialized totrue(silent-by-default, matching the "missing = silent" semantics) would avoid dictionary hashing overhead and make the "missing chunk treated as silent" behavior explicit at allocation time rather than viaTryGetValuefallback.♻️ Sketch of the alternative
- var silentByIndex = new Dictionary<int, bool>(chunks.Count); + var silentByIndex = new bool[chunkCount]; + Array.Fill(silentByIndex, true); // missing indices default to silent foreach (WaveformChunk chunk in chunks) { float peak = Math.Max(Math.Abs(chunk.MinValue), Math.Abs(chunk.MaxValue)); - silentByIndex[chunk.Index] = peak <= thresholdLinear; + if (chunk.Index >= 0 && chunk.Index < chunkCount) + silentByIndex[chunk.Index] = peak <= thresholdLinear; } var regions = new List<SilenceRegion>(); int runStart = -1; for (int i = 0; i < chunkCount; i++) { - bool silent = !silentByIndex.TryGetValue(i, out bool isSilent) || isSilent; + bool silent = silentByIndex[i];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Beutl.Engine/Audio/SilenceDetector.cs` around lines 51 - 72, The silence lookup in SilenceDetector uses a Dictionary<int, bool> keyed by chunk.Index even though indices are bounded by chunkCount, so replace it with a bool[] in the silence-scanning logic. Initialize the array to represent “silent by default” so missing chunks still behave like silent, then update the loop that builds regions to read directly from the array instead of TryGetValue; keep the rest of the region detection flow in SilenceDetector and AddIfValid unchanged.src/Beutl/ViewModels/EditViewModel.cs (1)
749-756: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
ElementStructureServiceconstruction instead of reusingGetService.The
IElementStructureServicebranch (line 750) and this newISilenceSplitServicebranch both constructnew ElementStructureService(HistoryManager)independently. Other multi-service dependencies in this file (IElementMoveService,IElementClipboardService) instead callGetService(typeof(...))to reuse the existing branch's logic. Prefer the same pattern here to avoid the two constructions drifting apart.♻️ Proposed refactor
if (serviceType.IsAssignableTo(typeof(ISilenceSplitService))) return _elementSilenceSplitService ??= new ElementSilenceSplitService( HistoryManager, - _elementStructureService ??= new ElementStructureService(HistoryManager)); + (ElementStructureService)GetService(typeof(IElementStructureService))!);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Beutl/ViewModels/EditViewModel.cs` around lines 749 - 756, The `EditViewModel.GetService` logic is creating `ElementStructureService` twice, once in the `IElementStructureService` branch and again inside the `ISilenceSplitService` branch, instead of reusing the existing resolution path. Update the `ISilenceSplitService` construction to call `GetService(typeof(IElementStructureService))` (or equivalent reuse of the `IElementStructureService` branch) so both paths share the same cached instance and future changes stay centralized. Keep the existing lazy caching fields (`_elementStructureService`, `_elementSilenceSplitService`) intact while removing the duplicated `new ElementStructureService(HistoryManager)` creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs`:
- Around line 1129-1156: The silence-detection action in TimelineTabViewModel
exits silently when the selection is not an Element, no audio
IThumbnailsProvider is found, duration is zero, or SilenceDetector.Detect
returns no regions. Update the method that iterates element.Objects and calls
GetWaveformChunksAsync to surface a user-facing notification or warning on each
early-return path, following the same notification style used elsewhere in this
view model for failed actions.
- Around line 1124-1161: AutoSplitBySilenceAsync currently calls
GetWaveformChunksAsync without guarding external audio I/O or decode failures,
so exceptions can escape without diagnostics or user feedback. Wrap the waveform
collection and silence detection path in a try/catch consistent with PasteCore
and DuplicateSelectedElements in TimelineTabViewModel, log the exception and
surface it through NotificationService or the existing UI error handling, and
only proceed to SplitBySilence when the waveform chunks are successfully loaded.
In `@src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs`:
- Around line 210-218: The silence split action in the menu handler lacks the
same exception handling used by OnExportProject and OnImportProject. Wrap the
await of timeline.AutoSplitBySilenceAsync in a try/catch inside this async void
handler, log the exception through _logger, call ex.Handle(), and show a
user-facing error with NotificationService.ShowError so failures are handled
consistently and do not escape uncaught.
- Around line 177-199: The dialog text in the menu bar initializer is still
hardcoded English, so route the field labels and mode विकल्प text through
localization resources instead of string literals. Update the
`MainView.axaml.InitializeMenuBar` dialog setup to use `lang:Strings.*` entries
for the threshold/min silence/padding/mode labels and the `modeCombo` items,
matching how the dialog `Title` and button labels are already localized. Add or
reuse the corresponding resource keys in the resx files so the ComboBox and
labels display correctly in Japanese and other locales.
---
Nitpick comments:
In `@src/Beutl.Engine/Audio/SilenceDetector.cs`:
- Around line 51-72: The silence lookup in SilenceDetector uses a
Dictionary<int, bool> keyed by chunk.Index even though indices are bounded by
chunkCount, so replace it with a bool[] in the silence-scanning logic.
Initialize the array to represent “silent by default” so missing chunks still
behave like silent, then update the loop that builds regions to read directly
from the array instead of TryGetValue; keep the rest of the region detection
flow in SilenceDetector and AddIfValid unchanged.
In `@src/Beutl/ViewModels/EditViewModel.cs`:
- Around line 749-756: The `EditViewModel.GetService` logic is creating
`ElementStructureService` twice, once in the `IElementStructureService` branch
and again inside the `ISilenceSplitService` branch, instead of reusing the
existing resolution path. Update the `ISilenceSplitService` construction to call
`GetService(typeof(IElementStructureService))` (or equivalent reuse of the
`IElementStructureService` branch) so both paths share the same cached instance
and future changes stay centralized. Keep the existing lazy caching fields
(`_elementStructureService`, `_elementSilenceSplitService`) intact while
removing the duplicated `new ElementStructureService(HistoryManager)` creation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9beb6318-0a71-48c0-adcb-5be884267ea0
📒 Files selected for processing (16)
src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cssrc/Beutl.Editor/Services/ElementSilenceSplitService.cssrc/Beutl.Editor/Services/ElementStructureService.cssrc/Beutl.Editor/Services/ISilenceSplitService.cssrc/Beutl.Engine/Audio/SilenceDetector.cssrc/Beutl.Language/CommandNames.ja.resxsrc/Beutl.Language/CommandNames.resxsrc/Beutl.Language/Strings.ja.resxsrc/Beutl.Language/Strings.resxsrc/Beutl/ViewModels/EditViewModel.cssrc/Beutl/ViewModels/MenuBarViewModel.Scene.cssrc/Beutl/Views/MainView.axamlsrc/Beutl/Views/MainView.axaml.InitializeMenuBar.cstests/Beutl.UnitTests/Editor/Services/ElementSilenceSplitServiceTests.cstests/Beutl.UnitTests/Editor/Services/ElementStructureServiceTests.cstests/Beutl.UnitTests/Engine/Audio/SilenceDetectorTests.cs
Address PR #2071 bot review findings. Coordinate-system bug (A1): SilenceDetector returns element-local (0-based) region times, but ISilenceSplitService.SplitBySilence compares region boundaries against Element.Start/Length in scene-timeline coordinates. For any element not starting at 0 the boundaries landed at the wrong absolute time (or outside the element entirely). Fixed by offsetting each detected region by element.Start in the ViewModel before handing it to the split service. Added two regression tests pinning the scene-timeline contract for a non-zero-start element (correct split at absolute positions; element-local input is a no-op). Docs (A2/A3): SilenceRegion's comment now states the times are 0-based from the analyzed waveform (not TimeRange-relative); ISilenceSplitService documents that silenceRegions are scene-timeline coordinates. Localization (B4): the Auto Split dialog labels and the mode combo items are now resource strings (EN/JA) instead of hardcoded English. Error handling / feedback (C5/C6/C7): AutoSplitBySilenceAsync now wraps the waveform I/O in try/catch and reports every outcome through NotificationService (no audio element, no silence detected, analysis failed, or a success summary with cut/removed counts), matching the existing DuplicateSelectedElements/OnExportProject patterns. The menu handler also guards the await with a try/catch as OnExportProject does. Refs: Project #9 board item "無音検出による自動カット"
There was a problem hiding this comment.
Documentation drift
Code Review Bot flagged 2 possible documentation drift(s). These are advisory.
README.md(info):A new feature 'Auto Split by Silence' has been added to the software, but it is not mentioned in the Features section of the README.— suggested:Add 'Auto Split by Silence' to the Features section of the README, for example under a new 'Audio Editing' subsection or within the existing features.README.md(info):The code adds a new 'Auto Split by Silence' feature to the menu, but the README.md features section does not mention this new capability.— suggested:Add 'Auto Split by Silence' to the 'Rich Effects' or a new 'Editing Tools' section in the Features part of README.md.
Results are for commit 8045b2d. On newer commits, the bot's summary comment reflects the latest run.
Code Review BotNo comment/code divergences or documentation drift detected. Reviewed 19 file(s); skipped 0. |
Add a chunk-based silence detector (SilenceDetector) over WaveformChunk min/max pairs, taking its tuning through a SilenceDetectionOptions record struct (threshold dB, min silence, padding). A new ISilenceSplitService / ElementSilenceSplitService splits one or more elements at every silence-region boundary in a single history transaction, optionally deleting the pieces that fall inside a silence region; it reuses ElementStructureService's internal split/delete cores so the whole batch folds into one undo entry. The Scene -> Element menu gains "Auto Split by Silence" with a params prompt (threshold dB, min silence, padding, mode). Keeping the batch operation in its own service leaves IElementStructureService untouched, so this is additive rather than a breaking change. Mode 3 (split + delete + ripple-shift of the survivors) is deferred pending the dedicated Ripple feature (PR #2061); modes 1 (split-only) and 2 (split + delete silence) ship here. Linked-video sync-split and the real-time waveform preview highlight are UI surfaces documented for manual verification. The detector is pure logic (no UI / no I/O) and is unit-tested for threshold / min-duration / padding / empty / all-silent / missing-chunk edge cases; ElementSilenceSplitService is tested through the SceneHistoryHarness for split-only, split+delete, region-overlapping-start, whole-element-silent, and no-op commit contracts. The existing Split/Delete tests stay green after the SplitCore/DeleteCore extraction. Refs: Project #9 board item "無音検出による自動カット"
Address PR #2071 bot review findings. Coordinate-system bug (A1): SilenceDetector returns element-local (0-based) region times, but ISilenceSplitService.SplitBySilence compares region boundaries against Element.Start/Length in scene-timeline coordinates. For any element not starting at 0 the boundaries landed at the wrong absolute time (or outside the element entirely). Fixed by offsetting each detected region by element.Start in the ViewModel before handing it to the split service. Added two regression tests pinning the scene-timeline contract for a non-zero-start element (correct split at absolute positions; element-local input is a no-op). Docs (A2/A3): SilenceRegion's comment now states the times are 0-based from the analyzed waveform (not TimeRange-relative); ISilenceSplitService documents that silenceRegions are scene-timeline coordinates. Localization (B4): the Auto Split dialog labels and the mode combo items are now resource strings (EN/JA) instead of hardcoded English. Error handling / feedback (C5/C6/C7): AutoSplitBySilenceAsync now wraps the waveform I/O in try/catch and reports every outcome through NotificationService (no audio element, no silence detected, analysis failed, or a success summary with cut/removed counts), matching the existing DuplicateSelectedElements/OnExportProject patterns. The menu handler also guards the await with a try/catch as OnExportProject does. Refs: Project #9 board item "無音検出による自動カット"
8045b2d to
74fbe82
Compare
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the complete-span waveform request resolves partial-chunk sampling, and all enabled audio providers are analyzed over aligned element timeline chunks before silence regions are applied. Important Files Changed
Reviews (3): Last reviewed commit: "fix(ci): apply solution formatting" | Re-trigger Greptile |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs (1)
1483-1519: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win"AnalysisFailed" message also covers split/commit failures.
The single
catch (Exception ex)at Line 1514 wraps both the waveform analysis (CollectConservativeChunksAsync/SilenceDetector.Detect) and theISilenceSplitService.SplitBySilencecall at Lines 1507-1508. If the split/history-commit step throws, the user still seesAutoSplitBySilence_AnalysisFailed, which misattributes the failure to waveform analysis. Consider a distinct message for the split step, or a more generic "operation failed" message covering both.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs` around lines 1483 - 1519, The single exception handler around the auto-split workflow reports every failure as AutoSplitBySilence_AnalysisFailed, including ISilenceSplitService.SplitBySilence failures. Update the error handling in the surrounding TimelineTabViewModel method to distinguish analysis failures from split/commit failures, or use a generic operation-failed notification that accurately covers both while preserving cancellation behavior.
🧹 Nitpick comments (2)
src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs (1)
38-72: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider fetching per-provider waveforms concurrently.
Each provider is awaited sequentially, and per the comment at Lines 43-44 the cache is bypassed to get the complete span, meaning every call is a full un-cached decode. When an element has multiple enabled audio providers, this serializes several full decodes. Since each provider writes into its own
providerPeaks/hasProviderPeakarrays before merging, this is safe to parallelize (e.g.Task.WhenAllover per-provider decode+reduce, then merge sequentially).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs` around lines 38 - 72, Update the provider-processing loop in the silence waveform analysis method to decode and reduce all providers concurrently, such as by creating one task per provider and awaiting them together. Keep each provider’s peak arrays isolated during waveform fetching, then merge the completed results into combinedPeaks sequentially while preserving cancellation checks and existing filtering behavior.tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs (1)
32-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGood coverage of the summation and cache-bypass behavior. Consider also covering the defensive branches in
CollectConservativeChunksAsync—chunkCount <= 0validation, out-of-range/duplicatechunk.Indexhandling, and NaN/Infinity peak propagation — since those are the parts most likely to regress silently.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs` around lines 32 - 69, Extend the tests for SilenceWaveformAnalysis.CollectConservativeChunksAsync to cover chunkCount <= 0 validation, ignoring or safely handling out-of-range and duplicate WaveformChunk.Index values, and preserving NaN/Infinity peak values during aggregation. Add focused assertions for each defensive behavior without changing the existing summation and cache-bypass coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs`:
- Around line 1483-1519: The single exception handler around the auto-split
workflow reports every failure as AutoSplitBySilence_AnalysisFailed, including
ISilenceSplitService.SplitBySilence failures. Update the error handling in the
surrounding TimelineTabViewModel method to distinguish analysis failures from
split/commit failures, or use a generic operation-failed notification that
accurately covers both while preserving cancellation behavior.
---
Nitpick comments:
In `@src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs`:
- Around line 38-72: Update the provider-processing loop in the silence waveform
analysis method to decode and reduce all providers concurrently, such as by
creating one task per provider and awaiting them together. Keep each provider’s
peak arrays isolated during waveform fetching, then merge the completed results
into combinedPeaks sequentially while preserving cancellation checks and
existing filtering behavior.
In `@tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs`:
- Around line 32-69: Extend the tests for
SilenceWaveformAnalysis.CollectConservativeChunksAsync to cover chunkCount <= 0
validation, ignoring or safely handling out-of-range and duplicate
WaveformChunk.Index values, and preserving NaN/Infinity peak values during
aggregation. Add focused assertions for each defensive behavior without changing
the existing summation and cache-bypass coverage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1b96cea7-71d2-4470-939b-ea9d0305b9b5
📒 Files selected for processing (3)
src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cssrc/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cstests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs
|
No TODO comments were found. |
Minimum allowed line rate is |
Summary
Adds silence-based auto-split for timeline elements:
SilenceDetector(new,Beutl.Engine.Audio) — pure chunk-based silence detection overWaveformChunkmin/max pairs. Tuning goes through aSilenceDetectionOptionsreadonly record struct (ThresholdDb/MinSilenceDuration/Padding) so future knobs are non-breaking.ISilenceSplitService/ElementSilenceSplitService(new,Beutl.Editor) — splits one or more elements at every silence-region boundary in a single history transaction, optionally deleting the silent pieces. It reusesElementStructureService's now-internalSplitCore/DeleteCoreso the whole batch folds into one undo entry.IElementStructureServiceis untouched — this is additive, not a breaking change (same dedicated-service pattern asIElementGapServicefeat: add gap close and gap navigation commands for timeline scenes #2066 andIElementSlipServicefeat(editor): add Slip / Roll / Slide trim modes with UI invocation #2067, per design review).TimelineTabViewModel.AutoSplitBySilenceAsync.Mode 3 (split + delete + ripple-shift of survivors) is deferred pending the Ripple feature (#2061); modes 1 (split-only) and 2 (split + delete silence) ship here.
Tests
SilenceDetectorTests— 18+ cases: threshold / min-duration / padding / empty / all-silent / missing-chunk-as-silent / padding-collapse / argument validation.ElementSilenceSplitServiceTests— split-only, split+delete, region-overlapping-start, whole-element-silent, no-op-no-commit, constructor null guard (viaSceneHistoryHarness).ElementStructureServiceTestsstay green after theSplitCore/DeleteCoreextraction.dotnet build Beutl.slnx0 errors; 36 relevant tests green;dotnet format --verify-no-changesclean.Review
IElementStructureService.SplitBySilenceaddition; reworked into the dedicatedISilenceSplitService+SilenceDetectionOptions; pass 2 approved.Manual verification
Refs: Project #9 board item「無音検出による自動カット」
🤖 Generated with Claude Code
Summary by CodeRabbit