Skip to content

feat: auto-split an element by silence detection - #2071

Closed
yuto-trd wants to merge 4 commits into
mainfrom
loop/silence-cut
Closed

feat: auto-split an element by silence detection#2071
yuto-trd wants to merge 4 commits into
mainfrom
loop/silence-cut

Conversation

@yuto-trd

@yuto-trd yuto-trd commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Adds silence-based auto-split for timeline elements:

  • SilenceDetector (new, Beutl.Engine.Audio) — pure chunk-based silence detection over WaveformChunk min/max pairs. Tuning goes through a SilenceDetectionOptions readonly 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 reuses ElementStructureService's now-internal SplitCore/DeleteCore so the whole batch folds into one undo entry. IElementStructureService is untouched — this is additive, not a breaking change (same dedicated-service pattern as IElementGapService feat: add gap close and gap navigation commands for timeline scenes #2066 and IElementSlipService feat(editor): add Slip / Roll / Slide trim modes with UI invocation #2067, per design review).
  • UI: Scene → Element menu gains "Auto Split by Silence" with a parameter prompt (threshold dB, min silence, padding, mode), wired through 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 (via SceneHistoryHarness).
  • Existing ElementStructureServiceTests stay green after the SplitCore/DeleteCore extraction.
  • Local gates: dotnet build Beutl.slnx 0 errors; 36 relevant tests green; dotnet format --verify-no-changes clean.

Review

  • Pre-PR design review (2-pass): pass 1 flagged the original breaking IElementStructureService.SplitBySilence addition; reworked into the dedicated ISilenceSplitService + SilenceDetectionOptions; pass 2 approved.

Manual verification

  • Linked-video sync-split and the real-time waveform preview highlight are UI surfaces verified manually (documented in the commit body).

Refs: Project #9 board item「無音検出による自動カット」

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Auto Split by Silence to the scene menu for the selected audio element, with configurable threshold (dB), minimum silence duration, padding, and mode (split only vs split and delete silence).
    • Performs automatic waveform-based silence detection and splits/deletes accordingly, with localized prompts and completion messaging.
  • Bug Fixes
    • Improved resilience for edge cases (missing/invalid selections, no silence detected, and cancellation), ensuring safe, consistent outcomes.
  • Tests
    • Added unit tests covering silence detection, waveform aggregation, split/delete behavior, boundary conditions, and single-commit history handling.

Copilot AI review requested due to automatic review settings July 7, 2026 16:24
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dabd94ae-4708-478d-b306-db3344f7ad50

📥 Commits

Reviewing files that changed from the base of the PR and between 66e6118 and d4cd772.

📒 Files selected for processing (1)
  • src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Auto Split by Silence

Layer / File(s) Summary
Silence detection
src/Beutl.Engine/Audio/SilenceDetector.cs, tests/Beutl.UnitTests/Engine/Audio/SilenceDetectorTests.cs
Detects silent waveform runs using thresholds, minimum durations, padding, missing-chunk handling, and validation rules.
Audio waveform analysis
src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs, tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs
Discovers enabled audio providers and aggregates waveform peaks with cancellation support.
Silence split contract and structure operations
src/Beutl.Editor/Services/ISilenceSplitService.cs, src/Beutl.Editor/Services/ElementStructureService.cs
Defines split modes and outcomes, and separates split/delete operations from history commits.
Silence split implementation
src/Beutl.Editor/Services/ElementSilenceSplitService.cs, tests/Beutl.UnitTests/Editor/Services/ElementSilenceSplitServiceTests.cs
Splits elements at scene-timeline silence boundaries, optionally deletes silent pieces, commits one history command, and tests boundary and coordinate behavior.
Timeline analysis entry point
src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs
Loads waveforms, detects silence, converts coordinates, invokes the split service, and handles cancellation, errors, and notifications.
Editor command and localization wiring
src/Beutl/ViewModels/EditViewModel.cs, src/Beutl/ViewModels/MenuBarViewModel.Scene.cs, src/Beutl/Views/MainView.axaml, src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs, src/Beutl.Language/*.resx
Registers the service, adds the scene command and configuration dialog, and adds English and Japanese labels and messages.

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
Loading

Possibly related PRs

  • b-editor/beutl#1956: Provides the scene-history test harness used by the silence-splitting service tests.

Suggested labels: pending-merge

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding silence-detection-based auto-splitting for elements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch loop/silence-cut

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/SilenceRegion to detect silence regions from WaveformChunk min/max pairs.
  • Added ISilenceSplitService + ElementSilenceSplitService to split elements at silence boundaries (optionally deleting silent pieces) with one history commit, reusing ElementStructureService core 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.

Comment thread src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs Outdated
Comment thread src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
Comment thread src/Beutl.Engine/Audio/SilenceDetector.cs Outdated
Comment thread src/Beutl.Editor/Services/ISilenceSplitService.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/Beutl.Engine/Audio/SilenceDetector.cs (1)

51-72: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: use a bool array instead of a Dictionary for silence lookup.

silentByIndex is keyed by chunk.Index, which is bounded to [0, chunkCount). A bool[chunkCount] initialized to true (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 via TryGetValue fallback.

♻️ 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 win

Duplicated ElementStructureService construction instead of reusing GetService.

The IElementStructureService branch (line 750) and this new ISilenceSplitService branch both construct new ElementStructureService(HistoryManager) independently. Other multi-service dependencies in this file (IElementMoveService, IElementClipboardService) instead call GetService(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

📥 Commits

Reviewing files that changed from the base of the PR and between e2dd0ab and ccfe3ad.

📒 Files selected for processing (16)
  • src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs
  • src/Beutl.Editor/Services/ElementSilenceSplitService.cs
  • src/Beutl.Editor/Services/ElementStructureService.cs
  • src/Beutl.Editor/Services/ISilenceSplitService.cs
  • src/Beutl.Engine/Audio/SilenceDetector.cs
  • src/Beutl.Language/CommandNames.ja.resx
  • src/Beutl.Language/CommandNames.resx
  • src/Beutl.Language/Strings.ja.resx
  • src/Beutl.Language/Strings.resx
  • src/Beutl/ViewModels/EditViewModel.cs
  • src/Beutl/ViewModels/MenuBarViewModel.Scene.cs
  • src/Beutl/Views/MainView.axaml
  • src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
  • tests/Beutl.UnitTests/Editor/Services/ElementSilenceSplitServiceTests.cs
  • tests/Beutl.UnitTests/Editor/Services/ElementStructureServiceTests.cs
  • tests/Beutl.UnitTests/Engine/Audio/SilenceDetectorTests.cs

Comment thread src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs Outdated
Comment thread src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
Comment thread src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
yuto-trd added a commit that referenced this pull request Jul 7, 2026
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 "無音検出による自動カット"

@drift-check drift-check Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@drift-check

drift-check Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review Bot

No comment/code divergences or documentation drift detected. Reviewed 19 file(s); skipped 0.

yuto-trd added 2 commits July 26, 2026 23:52
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 "無音検出による自動カット"
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The 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

Filename Overview
src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs Collects complete uncached waveforms from every enabled audio provider and conservatively combines their peaks by timeline chunk.
src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs Coordinates waveform analysis, silence detection, timeline-coordinate conversion, split-service invocation, and user notifications.
src/Beutl.Editor/Services/ElementSilenceSplitService.cs Splits elements at silence boundaries, optionally deletes silent pieces, and commits the batch as one history entry.
src/Beutl.Editor/Services/ElementStructureService.cs Extracts non-committing split and delete primitives for reuse within batched editor operations.
src/Beutl.Engine/Audio/SilenceDetector.cs Implements chunk-based silence detection with threshold, minimum-duration, padding, and input validation.
src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs Adds the parameter dialog and dispatches the configured silence-splitting operation.
tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs Covers complete uncached requests, enabled-provider discovery, and conservative multi-source peak aggregation.
tests/Beutl.UnitTests/Editor/Services/ElementSilenceSplitServiceTests.cs Covers split, deletion, coordinate, locking, grouping, no-op, and history behavior.
tests/Beutl.UnitTests/Engine/Audio/SilenceDetectorTests.cs Covers silence-region detection, thresholds, duration, padding, missing chunks, and invalid arguments.

Reviews (3): Last reviewed commit: "fix(ci): apply solution formatting" | Re-trigger Greptile

Comment thread src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs Outdated
Comment thread src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the ISilenceSplitService.SplitBySilence call at Lines 1507-1508. If the split/history-commit step throws, the user still sees AutoSplitBySilence_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 win

Consider 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/hasProviderPeak arrays before merging, this is safe to parallelize (e.g. Task.WhenAll over 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 win

Good coverage of the summation and cache-bypass behavior. Consider also covering the defensive branches in CollectConservativeChunksAsyncchunkCount <= 0 validation, out-of-range/duplicate chunk.Index handling, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 74fbe82 and 66e6118.

📒 Files selected for processing (3)
  • src/Beutl.Editor.Components/TimelineTab/Services/SilenceWaveformAnalysis.cs
  • src/Beutl.Editor.Components/TimelineTab/ViewModels/TimelineTabViewModel.cs
  • tests/Beutl.UnitTests/Editor/Services/SilenceWaveformAnalysisTests.cs

@github-actions

Copy link
Copy Markdown
Contributor

No TODO comments were found.

@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
Beutl 20% 12% 10892
Beutl.AgentToolkit 90% 75% 7414
Beutl.Api 24% 15% 1410
Beutl.Configuration 63% 40% 425
Beutl.Controls 35% 14% 5550
Beutl.Core 69% 60% 3108
Beutl.Editor 82% 79% 2921
Beutl.Editor.Components 20% 13% 9826
Beutl.Embedding.MediaFoundation 6% 8% 1374
Beutl.Engine 69% 58% 19262
Beutl.Engine.SourceGenerators 59% 44% 540
Beutl.ExceptionHandler 0% 0% 45
Beutl.Extensibility 68% 76% 166
Beutl.Extensions.AVFoundation 0% 2% 202
Beutl.Extensions.FFmpeg 28% 26% 738
Beutl.Extensions.FFmpeg.Core 49% 30% 323
Beutl.FFmpegIpc 27% 35% 858
Beutl.FFmpegWorker 4% 4% 898
Beutl.Language 46% 50% 1520
Beutl.NodeGraph 26% 17% 2477
Beutl.PackageTools.UI 0% 0% 674
Beutl.ProjectSystem 76% 62% 1266
Beutl.Testing.Headless 88% 92% 15
Beutl.Threading 100% 90% 137
Beutl.Utilities 94% 87% 358
Beutl.WaitingDialog 0% 0% 36
Iciclecreek.Avalonia.Terminal 33% 21% 1164
XTerm.NET 15% 7% 2009
Summary 46% (91291 / 198986) 37% (21625 / 58607) 75608

Minimum allowed line rate is 0%

@yuto-trd yuto-trd closed this Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants